diff --git a/Cargo.lock b/Cargo.lock index 575b1eab..60bf4c03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,37 +19,154 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "astroport" +version = "2.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d102b618016b3c1f1ebb5750617a73dbd294a3c941e54b12deabc931d771bc6e" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "cw20", + "itertools", + "uint", +] + +[[package]] +name = "astroport" +version = "2.9.5" +source = "git+https://github.com/astroport-fi/astroport-core.git?tag=v2.9.5#700f66d677a173686cb15cb9cb8c7a4d20c84ad8" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "cw20", + "itertools", + "uint", +] + +[[package]] +name = "astroport-factory" +version = "1.5.1" +source = "git+https://github.com/astroport-fi/astroport-core.git?tag=v2.9.5#700f66d677a173686cb15cb9cb8c7a4d20c84ad8" +dependencies = [ + "astroport 2.9.5 (git+https://github.com/astroport-fi/astroport-core.git?tag=v2.9.5)", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw2 0.15.1", + "itertools", + "protobuf", + "thiserror", +] + +[[package]] +name = "astroport-native-coin-registry" +version = "1.0.1" +source = "git+https://github.com/astroport-fi/astroport-core.git?tag=v2.9.5#700f66d677a173686cb15cb9cb8c7a4d20c84ad8" +dependencies = [ + "astroport 2.9.5 (git+https://github.com/astroport-fi/astroport-core.git?tag=v2.9.5)", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus 0.15.1", + "cw2 0.15.1", + "thiserror", +] + +[[package]] +name = "astroport-pair" +version = "1.3.3" +source = "git+https://github.com/astroport-fi/astroport-core.git?tag=v2.9.5#700f66d677a173686cb15cb9cb8c7a4d20c84ad8" +dependencies = [ + "astroport 2.9.5 (git+https://github.com/astroport-fi/astroport-core.git?tag=v2.9.5)", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw2 0.15.1", + "cw20", + "integer-sqrt", + "protobuf", + "thiserror", +] + +[[package]] +name = "astroport-pair-stable" +version = "2.1.4" +source = "git+https://github.com/astroport-fi/astroport-core.git?tag=v2.9.5#700f66d677a173686cb15cb9cb8c7a4d20c84ad8" +dependencies = [ + "astroport 2.9.5 (git+https://github.com/astroport-fi/astroport-core.git?tag=v2.9.5)", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 1.0.3", + "cw2 0.15.1", + "cw20", + "itertools", + "thiserror", +] + +[[package]] +name = "astroport-token" +version = "1.1.1" +source = "git+https://github.com/astroport-fi/astroport-core.git?tag=v2.9.5#700f66d677a173686cb15cb9cb8c7a4d20c84ad8" +dependencies = [ + "astroport 2.9.5 (git+https://github.com/astroport-fi/astroport-core.git?tag=v2.9.5)", + "cosmwasm-schema", + "cosmwasm-std", + "cw2 0.15.1", + "cw20", + "cw20-base", + "snafu", +] + +[[package]] +name = "astroport-whitelist" +version = "1.0.1" +source = "git+https://github.com/astroport-fi/astroport-core.git?tag=v2.9.5#700f66d677a173686cb15cb9cb8c7a4d20c84ad8" +dependencies = [ + "astroport 2.9.5 (git+https://github.com/astroport-fi/astroport-core.git?tag=v2.9.5)", + "cosmwasm-schema", + "cosmwasm-std", + "cw1-whitelist", + "cw2 0.15.1", + "thiserror", +] + [[package]] name = "auction" -version = "0.1.0" +version = "1.2.0" dependencies = [ "anyhow", "auction-package", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus", - "cw-utils", - "cw2", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", "serde", "thiserror", ] [[package]] name = "auction-package" -version = "0.1.0" +version = "1.2.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", - "cw-utils", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", "serde", "thiserror", ] [[package]] name = "auctions-manager" -version = "0.1.0" +version = "1.2.0" dependencies = [ "anyhow", "auction", @@ -57,14 +174,20 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus", - "cw-utils", - "cw2", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", "price-oracle", "serde", "thiserror", ] +[[package]] +name = "autocfg" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" + [[package]] name = "base16ct" version = "0.1.1" @@ -235,6 +358,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cosmwasm-storage" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd2b4ae72a03e8f56c85df59d172d51d2d7dc9cec6e2bc811e3fb60c588032a4" +dependencies = [ + "cosmwasm-std", + "serde", +] + [[package]] name = "cpufeatures" version = "0.2.9" @@ -244,6 +377,12 @@ 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" @@ -299,8 +438,8 @@ checksum = "127c7bb95853b8e828bdab97065c81cb5ddc20f7339180b61b2300565aaa99d1" dependencies = [ "anyhow", "cosmwasm-std", - "cw-storage-plus", - "cw-utils", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", "derivative", "itertools", "k256 0.11.6", @@ -310,6 +449,17 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-storage-plus" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6cf70ef7686e2da9ad7b067c5942cd3e88dd9453f7af42f54557f8af300fb0" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "cw-storage-plus" version = "1.2.0" @@ -321,6 +471,21 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-utils" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae0b69fa7679de78825b4edeeec045066aa2b2c4b6e063d80042e565bb4da5c" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2 0.15.1", + "schemars", + "semver", + "serde", + "thiserror", +] + [[package]] name = "cw-utils" version = "1.0.3" @@ -329,13 +494,55 @@ checksum = "1c4a657e5caacc3a0d00ee96ca8618745d050b8f757c709babafb81208d4239c" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2", + "cw2 1.1.2", "schemars", "semver", "serde", "thiserror", ] +[[package]] +name = "cw1" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe0783ec4210ba4e0cdfed9874802f469c6db0880f742ad427cb950e940b21c" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw1-whitelist" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233dd13f61495f1336da57c8bdca0536fa9f8dd59c12d2bbfc59928ea580e478" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "cw1", + "cw2 0.15.1", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw2" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5abb8ecea72e09afff830252963cb60faf945ce6cef2c20a43814516082653da" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "schemars", + "serde", +] + [[package]] name = "cw2" version = "1.1.2" @@ -344,7 +551,38 @@ checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 1.2.0", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "cw20" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6025276fb6e603e974c21f3e4606982cdc646080e8fba3198816605505e1d9a" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 0.15.1", + "schemars", + "serde", +] + +[[package]] +name = "cw20-base" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0909c56d0c14601fbdc69382189799482799dcad87587926aec1f3aa321abc41" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "cw2 0.15.1", + "cw20", "schemars", "semver", "serde", @@ -403,6 +641,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "drawille" version = "0.3.0" @@ -621,6 +865,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + [[package]] name = "is-terminal" version = "0.4.9" @@ -691,6 +944,15 @@ version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -723,18 +985,25 @@ dependencies = [ "spki 0.7.2", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "price-oracle" -version = "0.1.0" +version = "1.2.0" dependencies = [ "anyhow", + "astroport 2.9.5 (registry+https://github.com/rust-lang/crates.io-index)", "auction-package", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus", - "cw-utils", - "cw2", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", "serde", "thiserror", ] @@ -771,6 +1040,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" +dependencies = [ + "bytes", +] + [[package]] name = "quote" version = "1.0.33" @@ -780,6 +1058,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" @@ -797,7 +1096,7 @@ dependencies = [ [[package]] name = "rebalancer" -version = "0.1.0" +version = "1.2.0" dependencies = [ "anyhow", "auction-package", @@ -805,8 +1104,9 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus", - "cw2", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", "serde", "thiserror", "valence-macros", @@ -973,14 +1273,14 @@ dependencies = [ [[package]] name = "services-manager" -version = "0.1.0" +version = "1.2.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus", - "cw2", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", "rebalancer", "serde", "thiserror", @@ -1032,6 +1332,27 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "snafu" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "spki" version = "0.6.0" @@ -1122,6 +1443,18 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[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 = "unicode-ident" version = "1.0.12" @@ -1130,14 +1463,14 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "valence-account" -version = "0.1.0" +version = "1.2.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus", - "cw2", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", "serde", "thiserror", "valence-macros", @@ -1146,7 +1479,7 @@ dependencies = [ [[package]] name = "valence-macros" -version = "0.1.0" +version = "1.2.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1157,13 +1490,13 @@ dependencies = [ [[package]] name = "valence-package" -version = "0.1.0" +version = "1.2.0" dependencies = [ "auction-package", "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", - "cw-utils", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", "serde", "thiserror", "valence-macros", @@ -1174,6 +1507,13 @@ name = "valence-tests" version = "0.0.1" dependencies = [ "anyhow", + "astroport 2.9.5 (git+https://github.com/astroport-fi/astroport-core.git?tag=v2.9.5)", + "astroport-factory", + "astroport-native-coin-registry", + "astroport-pair", + "astroport-pair-stable", + "astroport-token", + "astroport-whitelist", "auction", "auction-package", "auctions-manager", @@ -1181,9 +1521,10 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus", - "cw-utils", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", "price-oracle", + "rand", "rebalancer", "rgb", "services-manager", diff --git a/Cargo.toml b/Cargo.toml index babbd793..8c8be6d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "packages/*", "contracts/services/*", @@ -11,7 +12,7 @@ members = [ [workspace.package] edition = "2021" license = "BSL" -version = "0.1.0" +version = "1.2.0" repository = "https://github.com/timewave-computer/valence-services" rust-version = "1.66" @@ -43,14 +44,14 @@ valence-macros = { path = "packages/valence-macros" } valence-package = { path = "packages/valence-package" } auction-package = { path = "packages/auction-package" } -cosmwasm-schema = "1.5.0" -cosmwasm-std = { version = "1.5.0", features = ["ibc3"] } -cw-storage-plus = "1.2.0" -cw-utils = "1.0.3" -cw2 = "1.1.2" -serde = { version = "1.0.183", default-features = false, features = ["derive"] } -thiserror = "1.0.31" -schemars = "0.8.10" +cosmwasm-schema = "1.5.0" +cosmwasm-std = { version = "1.5.0", features = ["ibc3"] } +cw-storage-plus = "1.2.0" +cw-utils = "1.0.3" +cw2 = "1.1.2" +serde = { version = "1.0.183", default-features = false, features = ["derive"] } +thiserror = "1.0.31" +schemars = "0.8.10" # dev-dependencies cw-multi-test = "0.16.5" diff --git a/README.md b/README.md index 53439b93..5e7b57eb 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,44 @@ -# Neutron Contracts +# Neutron MAINNET Contracts [![Check Set-Up & Build](https://github.com/timewave-computer/valence-services/actions/workflows/check.yml/badge.svg)](https://github.com/timewave-computer/valence-services/actions/workflows/check.yml) ## Code ids -- auctions manager = `1762` -- auction = `1763` -- price oracle = `1764` -- rebalancer = `1765` -- services manager = `1766` -- account = `1767` +- auctions manager = `874` +- auction = `869` +- oracle = `883` +- rebalancer = `898` +- services manager = `872` +- account = `867` + +## Owner / Admin + +`neutron1phx0sz708k3t6xdnyc98hgkyhra4tp44et5s68` + +## Addresses + +- Services manager - `neutron1gantvpnat0la8kkkzrnj48d5d8wxdjllh5r2w4r2hcrpwy00s69quypupa` +- Auctions manager - `neutron13exc5wdc7y5qpqazc34djnu934lqvfw2dru30j52ahhjep6jzx8ssjxcyz` +- Rebalancer - `neutron1qs6mzpmcw3dvg5l8nyywetcj326scszdj7v4pfk55xwshd4prqnqfwc0z2` +- Account - `neutron1pkk88zqjd478x3maws3mv7qugylhsu0sjkejj3k2w02wwhp6fqgsl7m0js` +- Oracle - `neutron1s8uqyh0mmh8g66s2dectf56c08y6fvusp39undp8kf4v678ededsy6tstf` + +### Auctions + +- [uatom, untrn] - `neutron1l9zmckc8j7zhutx088g6ppd9dfs45jet6dyq3pypc0gt5h9ncsvs5m4tsz` +- [untrn, uatom] - `neutron13jppm4n77u8ud5wma9xe0dqnaz85ne9jem3r0scc009uemvh49qqxuuggf` + +- [uusdc, uatom] - `neutron1ku4zrr40u7w2265xustm3rj2ld5022p5u95e5q6sckekyrs59r8q9q0zdn` +- [uusdc, untrn] - `neutron18svf2f9eltzr4dm2p8q4jnxyu2sjejpggxhcvaspeq8vaj4gdtuqdg2z2f` +- [uatom, uusdc] - `neutron1uf23w2ejztrz0sz92x26tnavdatyxwq4axt96zqaxc7sshalx4nqxj89sd` +- [untrn, uusdc] - `neutron1r7ytd0m9j5t668wg7e9u287f9kzfxqulwuslexjeqjvwas0qzxjs67kzq6` + +- [newt, uatom] - `neutron1dz6kyp6sh5myulmmna6wt62kc65xkccrp7f5sqfqyv4vdkte885s00zm3p` +- [newt, untrn] - `neutron1dajsjk985c29tv5v985gvd55vzllx97aaw0ekurty62xwf3l53usrhcf3t` +- [newt, uusdc] - `neutron1qdp2qhtt2jyefn0dqxsl7ffah9xmm8jxg3z462u42rp424eecrms2rshxg` +- [uatom, newt] - `neutron1fyk77ttx2j3wxjj26g3d8csjzp005cxdacstfxcrdexpn8nsz79qhjhpsd` +- [untrn, newt] - `neutron1zvw9l8c82hnvwsntpuy89p86ztfmmudd9usfmnpa2tnqws74zsxq56sczm` +- [uusdc, newt] - `neutron1vu04szc78ae0nplwpuxjr6j592hn2d60zqtuts7w3ah6kajtxd2q2vfv59` # Juno Contracts @@ -57,14 +86,3 @@ junod keys add valence-owner --recover - (ujunox, vuusdcx) - `` - (vuusdcx, ujunox) - `` - -# FIGMA - flow - -1. init a services manager -2. init a auctions manager -3. init services (rebalancer) -4. add service to manager -5. init account -6. init auctions -7. init price oracle -8. set initial prices for auctions diff --git a/contracts/account/schema/valence-account.json b/contracts/account/schema/valence-account.json index b580ff1b..ef6ec3b9 100644 --- a/contracts/account/schema/valence-account.json +++ b/contracts/account/schema/valence-account.json @@ -1,6 +1,6 @@ { "contract_name": "valence-account", - "contract_version": "0.1.0", + "contract_version": "1.2.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -22,13 +22,6 @@ "title": "ExecuteMsg", "description": "This is base account execute msgs, it implements messages to be called on the service manager as well as messages to be called by services (valence_account_execute)", "oneOf": [ - { - "type": "string", - "enum": [ - "cancel_admin_change", - "approve_admin_change" - ] - }, { "description": "Register account to a service.", "type": "object", @@ -122,6 +115,12 @@ "service_name" ], "properties": { + "reason": { + "type": [ + "string", + "null" + ] + }, "service_name": { "$ref": "#/definitions/ValenceServices" } @@ -260,6 +259,32 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "cancel_admin_change" + ], + "properties": { + "cancel_admin_change": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "approve_admin_change" + ], + "properties": { + "approve_admin_change": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { @@ -365,30 +390,6 @@ }, "additionalProperties": false }, - { - "type": "object", - "required": [ - "staking" - ], - "properties": { - "staking": { - "$ref": "#/definitions/StakingMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "distribution" - ], - "properties": { - "distribution": { - "$ref": "#/definitions/DistributionMsg" - } - }, - "additionalProperties": false - }, { "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", "type": "object", @@ -452,55 +453,6 @@ } ] }, - "DistributionMsg": { - "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", - "oneOf": [ - { - "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "set_withdraw_address" - ], - "properties": { - "set_withdraw_address": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "description": "The `withdraw_address`", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "withdraw_delegator_reward" - ], - "properties": { - "withdraw_delegator_reward": { - "type": "object", - "required": [ - "validator" - ], - "properties": { - "validator": { - "description": "The `validator_address`", - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, "Empty": { "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", "type": "object" @@ -742,90 +694,6 @@ } } }, - "StakingMsg": { - "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", - "oneOf": [ - { - "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "delegate" - ], - "properties": { - "delegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "undelegate" - ], - "properties": { - "undelegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "redelegate" - ], - "properties": { - "redelegate": { - "type": "object", - "required": [ - "amount", - "dst_validator", - "src_validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "dst_validator": { - "type": "string" - }, - "src_validator": { - "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); ```", "allOf": [ diff --git a/contracts/account/src/contract.rs b/contracts/account/src/contract.rs index a823d7ab..4b7035ca 100644 --- a/contracts/account/src/contract.rs +++ b/contracts/account/src/contract.rs @@ -1,19 +1,20 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, BankMsg, Binary, CosmosMsg, Deps, DepsMut, Env, IbcMsg, MessageInfo, Reply, - Response, StdResult, SubMsg, WasmMsg, + to_json_binary, BankMsg, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, IbcMsg, MessageInfo, + Reply, Response, StdResult, SubMsg, WasmMsg, }; use cw2::set_contract_version; use valence_package::helpers::{ - approve_admin_change, cancel_admin_change, forward_to_services_manager, sender_is_a_service, - start_admin_change, verify_admin, + approve_admin_change, cancel_admin_change, forward_to_services_manager, + forward_to_services_manager_with_funds, sender_is_a_service, start_admin_change, verify_admin, }; use valence_package::msgs::core_execute::{AccountBaseExecuteMsg, ServicesManagerExecuteMsg}; +use valence_package::msgs::core_query::ServicesManagerQueryMsg; use valence_package::states::{ADMIN, SERVICES_MANAGER}; use crate::error::ContractError; -use crate::msg::{InstantiateMsg, QueryMsg}; +use crate::msg::{InstantiateMsg, MigrateMsg, QueryMsg}; const CONTRACT_NAME: &str = "crates.io:valence-account"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -52,10 +53,27 @@ pub fn execute( AccountBaseExecuteMsg::RegisterToService { service_name, data } => { verify_admin(deps.as_ref(), &info)?; let services_manager_addr = SERVICES_MANAGER.load(deps.storage)?; - Ok(forward_to_services_manager( - services_manager_addr.to_string(), - ServicesManagerExecuteMsg::RegisterToService { service_name, data }, - )?) + // Query service fee + Ok( + match deps.querier.query_wasm_smart::>( + services_manager_addr.clone(), + &ServicesManagerQueryMsg::GetServiceFee { + account: env.contract.address.to_string(), + service: service_name.clone(), + action: valence_package::states::QueryFeeAction::Register, + }, + )? { + Some(fee) => forward_to_services_manager_with_funds( + services_manager_addr.to_string(), + ServicesManagerExecuteMsg::RegisterToService { service_name, data }, + vec![fee], + )?, + None => forward_to_services_manager( + services_manager_addr.to_string(), + ServicesManagerExecuteMsg::RegisterToService { service_name, data }, + )?, + }, + ) } // unregister from a service AccountBaseExecuteMsg::DeregisterFromService { service_name } => { @@ -76,7 +94,10 @@ pub fn execute( )?) } // Pause the service - AccountBaseExecuteMsg::PauseService { service_name } => { + AccountBaseExecuteMsg::PauseService { + service_name, + reason, + } => { verify_admin(deps.as_ref(), &info)?; let services_manager_addr = SERVICES_MANAGER.load(deps.storage)?; Ok(forward_to_services_manager( @@ -84,6 +105,7 @@ pub fn execute( ServicesManagerExecuteMsg::PauseService { service_name, pause_for: env.contract.address.to_string(), + reason, }, )?) } @@ -91,13 +113,33 @@ pub fn execute( AccountBaseExecuteMsg::ResumeService { service_name } => { verify_admin(deps.as_ref(), &info)?; let services_manager_addr = SERVICES_MANAGER.load(deps.storage)?; - Ok(forward_to_services_manager( - services_manager_addr.to_string(), - ServicesManagerExecuteMsg::ResumeService { - service_name, - resume_for: env.contract.address.to_string(), + + Ok( + match deps.querier.query_wasm_smart::>( + services_manager_addr.clone(), + &ServicesManagerQueryMsg::GetServiceFee { + account: env.contract.address.to_string(), + service: service_name.clone(), + action: valence_package::states::QueryFeeAction::Resume, + }, + )? { + Some(fee) => forward_to_services_manager_with_funds( + services_manager_addr.to_string(), + ServicesManagerExecuteMsg::ResumeService { + service_name, + resume_for: env.contract.address.to_string(), + }, + vec![fee], + )?, + None => forward_to_services_manager( + services_manager_addr.to_string(), + ServicesManagerExecuteMsg::ResumeService { + service_name, + resume_for: env.contract.address.to_string(), + }, + )?, }, - )?) + ) } // Messages to be executed by the service, with sending funds. AccountBaseExecuteMsg::SendFundsByService { msgs, atomic } => { @@ -130,8 +172,10 @@ pub fn execute( AccountBaseExecuteMsg::StartAdminChange { addr, expiration } => { Ok(start_admin_change(deps, &info, &addr, expiration)?) } - AccountBaseExecuteMsg::CancelAdminChange => Ok(cancel_admin_change(deps, &info)?), - AccountBaseExecuteMsg::ApproveAdminChange => Ok(approve_admin_change(deps, &env, &info)?), + AccountBaseExecuteMsg::CancelAdminChange {} => Ok(cancel_admin_change(deps, &info)?), + AccountBaseExecuteMsg::ApproveAdminChange {} => { + Ok(approve_admin_change(deps, &env, &info)?) + } } } @@ -213,3 +257,10 @@ pub fn reply(_deps: DepsMut, _env: Env, msg: Reply) -> Result Result { + match msg { + MigrateMsg::NoStateChange {} => Ok(Response::default()), + } +} diff --git a/contracts/account/src/msg.rs b/contracts/account/src/msg.rs index 814c2944..b36ae947 100644 --- a/contracts/account/src/msg.rs +++ b/contracts/account/src/msg.rs @@ -15,4 +15,6 @@ pub enum QueryMsg { } #[cw_serde] -pub enum MigrateMsg {} +pub enum MigrateMsg { + NoStateChange {}, +} diff --git a/contracts/auction/auction/schema/auction.json b/contracts/auction/auction/schema/auction.json index f724bca0..948223d2 100644 --- a/contracts/auction/auction/schema/auction.json +++ b/contracts/auction/auction/schema/auction.json @@ -1,6 +1,6 @@ { "contract_name": "auction", - "contract_version": "0.1.0", + "contract_version": "1.2.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -155,10 +155,17 @@ }, { "description": "Send funds to be auctioned on the next auction", - "type": "string", - "enum": [ + "type": "object", + "required": [ "auction_funds" - ] + ], + "properties": { + "auction_funds": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false }, { "description": "Withdraw funds from future auction, can only be called by the admin/auctions manager", @@ -184,17 +191,31 @@ }, { "description": "Withdraw funds from future auction", - "type": "string", - "enum": [ + "type": "object", + "required": [ "withdraw_funds" - ] + ], + "properties": { + "withdraw_funds": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false }, { "description": "Bid on the current auction", - "type": "string", - "enum": [ + "type": "object", + "required": [ "bid" - ] + ], + "properties": { + "bid": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false }, { "description": "Finish the current auction and send funds to the funds provider Send pair.1 according to the weight of the funds provider from the total amount If we have unsold pair.0, send to funds provider according to provided weight", @@ -222,10 +243,17 @@ }, { "description": "Message to clean finished auction unneeded storage", - "type": "string", - "enum": [ + "type": "object", + "required": [ "clean_after_auction" - ] + ], + "properties": { + "clean_after_auction": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false }, { "description": "Admin messages that can only be called by the auctions manager", @@ -615,7 +643,7 @@ ] }, { - "description": "The auction is finished, if param is 0, we resolved everything, else it holds the last resovled height", + "description": "The auction is finished, waiting for the funds to be resolved", "type": "string", "enum": [ "finished" @@ -887,7 +915,7 @@ ] }, { - "description": "The auction is finished, if param is 0, we resolved everything, else it holds the last resovled height", + "description": "The auction is finished, waiting for the funds to be resolved", "type": "string", "enum": [ "finished" diff --git a/contracts/auction/auction/src/contract.rs b/contracts/auction/auction/src/contract.rs index c6848383..3ca13412 100644 --- a/contracts/auction/auction/src/contract.rs +++ b/contracts/auction/auction/src/contract.rs @@ -1,7 +1,7 @@ use std::collections::VecDeque; use auction_package::helpers::{verify_admin, AuctionConfig, GetPriceResponse}; -use auction_package::states::{ADMIN, TWAP_PRICES}; +use auction_package::states::{ADMIN, MIN_AUCTION_AMOUNT, TWAP_PRICES}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; @@ -14,7 +14,8 @@ use crate::error::ContractError; use crate::execute; use crate::helpers::calc_price; use crate::msg::{ - ExecuteMsg, GetFundsAmountResponse, GetMmResponse, InstantiateMsg, NewAuctionParams, QueryMsg, + ExecuteMsg, GetFundsAmountResponse, GetMmResponse, InstantiateMsg, MigrateMsg, + NewAuctionParams, QueryMsg, }; use crate::state::{ ActiveAuction, ActiveAuctionStatus, AuctionIds, ACTIVE_AUCTION, AUCTION_CONFIG, AUCTION_FUNDS, @@ -107,17 +108,18 @@ pub fn execute( verify_admin(deps.as_ref(), &info)?; execute::withdraw_funds(deps, sender) } - ExecuteMsg::AuctionFunds => execute::auction_funds(deps, &info, info.sender.clone()), - ExecuteMsg::WithdrawFunds => execute::withdraw_funds(deps, info.sender), - ExecuteMsg::Admin(admin_msg) => admin::handle_msg(deps, env, info, admin_msg), - ExecuteMsg::Bid => execute::do_bid(deps, &info, &env), + ExecuteMsg::AuctionFunds {} => execute::auction_funds(deps, &info, info.sender.clone()), + ExecuteMsg::WithdrawFunds {} => execute::withdraw_funds(deps, info.sender), + ExecuteMsg::Admin(admin_msg) => admin::handle_msg(deps, env, info, *admin_msg), + ExecuteMsg::Bid {} => execute::do_bid(deps, &info, &env), ExecuteMsg::FinishAuction { limit } => execute::finish_auction(deps, &env, limit), - ExecuteMsg::CleanAfterAuction => execute::clean_auction(deps), + ExecuteMsg::CleanAfterAuction {} => execute::clean_auction(deps), } } mod admin { use auction_package::helpers::GetPriceResponse; + use cosmwasm_std::{coin, BankMsg, Event}; use crate::msg::AdminMsgs; @@ -215,6 +217,19 @@ mod admin { return Err(ContractError::NoFundsForAuction); } + // Verify the amount of funds we have to auction, is more then the start auction min amount + let manager_addr = ADMIN.load(deps.storage)?; + let min_start_acution = MIN_AUCTION_AMOUNT + .query(&deps.querier, manager_addr, config.pair.0.clone())? + .unwrap_or_default() + .start_auction; + + // if its less, refund the funds to the users + if total_funds < min_start_acution { + return do_refund(deps, auction_ids.next, config.pair.0.clone(), total_funds); + } + + // get the starting and closing price of the auction let (start_price, end_price) = get_strategy_prices(deps.as_ref(), &config, env)?; // Add leftovers from previous auction @@ -240,9 +255,45 @@ mod admin { AUCTION_IDS.save(deps.storage, &auction_ids)?; ACTIVE_AUCTION.save(deps.storage, &new_active_auction)?; - Ok(Response::default()) + Ok(Response::default().add_event( + Event::new("new-auction") + .add_attribute("start_block", new_active_auction.start_block.to_string()) + .add_attribute("end_block", new_active_auction.end_block.to_string()) + .add_attribute("start_price", new_active_auction.start_price.to_string()) + .add_attribute("end_price", new_active_auction.end_price.to_string()) + .add_attribute("total_funds", new_active_auction.total_amount.to_string()), + )) } + /// Currently we only use this function for refunding when there is not enough funds to start an auction + /// so we can safely assume the AUCTION_FUNDS map will not hold a lot of entries ( max_entries = start_auction_minimum / send_minimum) + /// and because of this we do not need to paginate the map + fn do_refund( + deps: DepsMut, + auction_id: u64, + denom: String, + total_funds: Uint128, + ) -> Result { + let bank_msgs = AUCTION_FUNDS + .prefix(auction_id) + .range(deps.storage, None, None, cosmwasm_std::Order::Ascending) + .map(|fund| { + let (addr, amount) = fund.unwrap(); + Ok(BankMsg::Send { + to_address: addr.to_string(), + amount: vec![coin(amount.into(), denom.clone())], + }) + }) + .collect::>>()?; + + AUCTION_FUNDS_SUM.save(deps.storage, auction_id, &Uint128::zero())?; + AUCTION_FUNDS.clear(deps.storage); + + Ok(Response::new() + .add_messages(bank_msgs) + .add_attribute("method", "refund") + .add_attribute("total_funds", total_funds.to_string())) + } /// Helper functions to get the starting and ending prices /// Factors in freshness of the price from the oracle /// as well as the strategy percentage @@ -345,6 +396,15 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { } } +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + match msg { + MigrateMsg::NoStateChange {} => Ok(Response::default()), + } +} + #[cfg(test)] mod test { use std::str::FromStr; diff --git a/contracts/auction/auction/src/execute.rs b/contracts/auction/auction/src/execute.rs index 3abf65be..288f9e80 100644 --- a/contracts/auction/auction/src/execute.rs +++ b/contracts/auction/auction/src/execute.rs @@ -3,7 +3,8 @@ use auction_package::{ Price, CLOSEST_TO_ONE_POSSIBLE, }; use cosmwasm_std::{ - coin, Addr, BankMsg, Coin, CosmosMsg, Decimal, DepsMut, Env, MessageInfo, Response, Uint128, + coin, Addr, BankMsg, Coin, CosmosMsg, Decimal, DepsMut, Env, Event, MessageInfo, Response, + Uint128, }; use cw_storage_plus::Bound; use cw_utils::must_pay; @@ -32,7 +33,7 @@ pub(crate) fn auction_funds( let funds = must_pay(info, &config.pair.0)?; let min_amount = match MIN_AUCTION_AMOUNT.query(&deps.querier, admin, config.pair.0)? { - Some(amount) => Ok(amount), + Some(amount) => Ok(amount.send), None => Err(ContractError::NoTokenMinAmount), }?; @@ -97,10 +98,12 @@ pub fn withdraw_funds(deps: DepsMut, sender: Addr) -> Result Result { @@ -220,10 +223,10 @@ pub fn finish_auction(deps: DepsMut, env: &Env, limit: u64) -> Result { Ok((addr, total_sent_sold_token, total_sent_bought_token)) } - ActiveAuctionStatus::AuctionClosed => Err(ContractError::AuctionClosed), ActiveAuctionStatus::Finished | ActiveAuctionStatus::Started => { Ok((None, Uint128::zero(), Uint128::zero())) } + ActiveAuctionStatus::AuctionClosed => Err(ContractError::AuctionClosed), }?; let config = AUCTION_CONFIG.load(deps.storage)?; @@ -293,7 +296,7 @@ pub fn finish_auction(deps: DepsMut, env: &Env, limit: u64) -> Result Result Result Result { diff --git a/contracts/auction/auction/src/msg.rs b/contracts/auction/auction/src/msg.rs index 482d281b..83b4b04e 100644 --- a/contracts/auction/auction/src/msg.rs +++ b/contracts/auction/auction/src/msg.rs @@ -20,21 +20,21 @@ pub enum ExecuteMsg { /// Send funds to be auctioned on the next auction, can only be called by the admin/auctions manager AuctionFundsManager { sender: Addr }, /// Send funds to be auctioned on the next auction - AuctionFunds, + AuctionFunds {}, /// Withdraw funds from future auction, can only be called by the admin/auctions manager WithdrawFundsManager { sender: Addr }, /// Withdraw funds from future auction - WithdrawFunds, + WithdrawFunds {}, /// Bid on the current auction - Bid, + Bid {}, /// Finish the current auction and send funds to the funds provider /// Send pair.1 according to the weight of the funds provider from the total amount /// If we have unsold pair.0, send to funds provider according to provided weight FinishAuction { limit: u64 }, /// Message to clean finished auction unneeded storage - CleanAfterAuction, + CleanAfterAuction {}, /// Admin messages that can only be called by the auctions manager - Admin(AdminMsgs), + Admin(Box), } #[cw_serde] @@ -93,7 +93,9 @@ pub enum QueryMsg { } #[cw_serde] -pub enum MigrateMsg {} +pub enum MigrateMsg { + NoStateChange {}, +} #[cw_serde] pub struct GetFundsAmountResponse { diff --git a/contracts/auction/auction/src/state.rs b/contracts/auction/auction/src/state.rs index c7d54026..1c5dfd51 100644 --- a/contracts/auction/auction/src/state.rs +++ b/contracts/auction/auction/src/state.rs @@ -45,8 +45,7 @@ pub struct ActiveAuction { pub enum ActiveAuctionStatus { /// The auction started, and last resolved block height is (u64) Started, - /// The auction is finished, if param is 0, we resolved everything, - /// else it holds the last resovled height + /// The auction is finished, waiting for the funds to be resolved Finished, /// Handle closing auction, addr of the last funds provider we resolved /// and the total amounts of the pair we sent already diff --git a/contracts/auction/auctions_manager/schema/auctions-manager.json b/contracts/auction/auctions_manager/schema/auctions-manager.json index fd4947af..d031e4bb 100644 --- a/contracts/auction/auctions_manager/schema/auctions-manager.json +++ b/contracts/auction/auctions_manager/schema/auctions-manager.json @@ -1,6 +1,6 @@ { "contract_name": "auctions-manager", - "contract_version": "0.1.0", + "contract_version": "1.2.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -25,7 +25,7 @@ "type": "string" }, { - "$ref": "#/definitions/Uint128" + "$ref": "#/definitions/MinAmount" } ], "maxItems": 2, @@ -35,6 +35,32 @@ }, "additionalProperties": false, "definitions": { + "MinAmount": { + "type": "object", + "required": [ + "send", + "start_auction" + ], + "properties": { + "send": { + "description": "Minimum amount that is allowed to send to the auction", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "start_auction": { + "description": "Minimum amount that auction can start from\n\nIf auction amount is below this amount, it will not start the auction and will refund sent funds back to the sender", + "allOf": [ + { + "$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" @@ -45,12 +71,6 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", "oneOf": [ - { - "type": "string", - "enum": [ - "approve_admin_change" - ] - }, { "type": "object", "required": [ @@ -120,6 +140,19 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "approve_admin_change" + ], + "properties": { + "approve_admin_change": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -151,13 +184,17 @@ "new_auction": { "type": "object", "required": [ + "label", "msg" ], "properties": { + "label": { + "type": "string" + }, "min_amount": { "anyOf": [ { - "$ref": "#/definitions/Uint128" + "$ref": "#/definitions/MinAmount" }, { "type": "null" @@ -284,6 +321,31 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "update_min_amount" + ], + "properties": { + "update_min_amount": { + "type": "object", + "required": [ + "denom", + "min_amount" + ], + "properties": { + "denom": { + "type": "string" + }, + "min_amount": { + "$ref": "#/definitions/MinAmount" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -359,6 +421,37 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "migrate_auction" + ], + "properties": { + "migrate_auction": { + "type": "object", + "required": [ + "code_id", + "msg", + "pair" + ], + "properties": { + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "msg": { + "$ref": "#/definitions/MigrateMsg" + }, + "pair": { + "$ref": "#/definitions/Pair" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -505,6 +598,49 @@ }, "additionalProperties": false }, + "MigrateMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "no_state_change" + ], + "properties": { + "no_state_change": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MinAmount": { + "type": "object", + "required": [ + "send", + "start_auction" + ], + "properties": { + "send": { + "description": "Minimum amount that is allowed to send to the auction", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "start_auction": { + "description": "Minimum amount that auction can start from\n\nIf auction amount is below this amount, it will not start the auction and will refund sent funds back to the sender", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + }, "NewAuctionParams": { "type": "object", "required": [ @@ -606,6 +742,40 @@ "get_admin" ] }, + { + "description": "Get the price of a specific pair", + "type": "object", + "required": [ + "get_pairs" + ], + "properties": { + "get_pairs": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "anyOf": [ + { + "$ref": "#/definitions/Pair" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Get the price of a specific pair", "type": "object", @@ -833,9 +1003,37 @@ }, "get_min_limit": { "$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" + "title": "MinAmount", + "type": "object", + "required": [ + "send", + "start_auction" + ], + "properties": { + "send": { + "description": "Minimum amount that is allowed to send to the auction", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "start_auction": { + "description": "Minimum amount that auction can start from\n\nIf auction amount is below this amount, it will not start the auction and will refund sent funds back to the sender", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } }, "get_oracle_addr": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -849,6 +1047,43 @@ "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" }, + "get_pairs": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Tuple_of_Pair_and_Addr", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/Pair" + }, + { + "$ref": "#/definitions/Addr" + } + ], + "maxItems": 2, + "minItems": 2 + }, + "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" + }, + "Pair": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, "get_price": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "GetPriceResponse", diff --git a/contracts/auction/auctions_manager/src/contract.rs b/contracts/auction/auctions_manager/src/contract.rs index efef53f9..8f303a5a 100644 --- a/contracts/auction/auctions_manager/src/contract.rs +++ b/contracts/auction/auctions_manager/src/contract.rs @@ -1,16 +1,20 @@ use auction_package::helpers::{approve_admin_change, GetPriceResponse}; use auction_package::msgs::AuctionsManagerQueryMsg; -use auction_package::states::{ADMIN, MIN_AUCTION_AMOUNT, ORACLE_ADDR, PAIRS}; +use auction_package::states::{ + MinAmount, ADMIN, MIN_AUCTION_AMOUNT, MIN_AUCTION_AMOUNT_V0, ORACLE_ADDR, PAIRS, +}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, WasmMsg, + to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Order, Reply, Response, StdResult, + WasmMsg, }; use cw2::set_contract_version; +use cw_storage_plus::Bound; use cw_utils::{nonpayable, parse_reply_instantiate_data}; use crate::error::ContractError; -use crate::msg::{ExecuteMsg, InstantiateMsg}; +use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg}; use crate::state::AUCTION_CODE_ID; const CONTRACT_NAME: &str = "crates.io:auctions-manager"; @@ -87,8 +91,8 @@ pub fn execute( Ok(Response::default().add_message(msg)) } - ExecuteMsg::Admin(admin_msg) => admin::handle_msg(deps, env, info, admin_msg), - ExecuteMsg::ApproveAdminChange => Ok(approve_admin_change(deps, &env, &info)?), + ExecuteMsg::Admin(admin_msg) => admin::handle_msg(deps, env, info, *admin_msg), + ExecuteMsg::ApproveAdminChange {} => Ok(approve_admin_change(deps, &env, &info)?), } } @@ -110,7 +114,11 @@ mod admin { verify_admin(deps.as_ref(), &info)?; match msg { - AdminMsgs::NewAuction { msg, min_amount } => { + AdminMsgs::NewAuction { + msg, + label, + min_amount, + } => { msg.pair.verify()?; // Make sure we either set min_amount, or have it in storage @@ -135,7 +143,7 @@ mod admin { code_id: AUCTION_CODE_ID.load(deps.storage)?, msg: to_json_binary(&msg)?, funds: vec![], - label: format!("auction-{}-{}", msg.pair.0, msg.pair.1), + label, }, INSTANTIATE_AUCTION_REPLY_ID, ); @@ -146,9 +154,9 @@ mod admin { let pair_addr = PAIRS.load(deps.storage, pair)?; let msg = WasmMsg::Execute { contract_addr: pair_addr.to_string(), - msg: to_json_binary(&auction::msg::ExecuteMsg::Admin( + msg: to_json_binary(&auction::msg::ExecuteMsg::Admin(Box::new( auction::msg::AdminMsgs::PauseAuction, - ))?, + )))?, funds: vec![], }; @@ -158,9 +166,9 @@ mod admin { let pair_addr = PAIRS.load(deps.storage, pair)?; let msg = WasmMsg::Execute { contract_addr: pair_addr.to_string(), - msg: to_json_binary(&auction::msg::ExecuteMsg::Admin( + msg: to_json_binary(&auction::msg::ExecuteMsg::Admin(Box::new( auction::msg::AdminMsgs::ResumeAuction, - ))?, + )))?, funds: vec![], }; @@ -170,9 +178,9 @@ mod admin { let pair_addr = PAIRS.load(deps.storage, pair)?; let msg = WasmMsg::Execute { contract_addr: pair_addr.to_string(), - msg: to_json_binary(&auction::msg::ExecuteMsg::Admin( + msg: to_json_binary(&auction::msg::ExecuteMsg::Admin(Box::new( auction::msg::AdminMsgs::StartAuction(params), - ))?, + )))?, funds: vec![], }; @@ -192,9 +200,9 @@ mod admin { let pair_addr = PAIRS.load(deps.storage, pair)?; let msg = WasmMsg::Execute { contract_addr: pair_addr.to_string(), - msg: to_json_binary(&auction::msg::ExecuteMsg::Admin( + msg: to_json_binary(&auction::msg::ExecuteMsg::Admin(Box::new( auction::msg::AdminMsgs::UpdateStrategy { strategy }, - ))?, + )))?, funds: vec![], }; @@ -204,9 +212,9 @@ mod admin { let pair_addr = PAIRS.load(deps.storage, pair)?; let msg = WasmMsg::Execute { contract_addr: pair_addr.to_string(), - msg: to_json_binary(&auction::msg::ExecuteMsg::Admin( + msg: to_json_binary(&auction::msg::ExecuteMsg::Admin(Box::new( auction::msg::AdminMsgs::UpdateChainHaltConfig(halt_config), - ))?, + )))?, funds: vec![], }; @@ -216,14 +224,30 @@ mod admin { let pair_addr = PAIRS.load(deps.storage, pair)?; let msg = WasmMsg::Execute { contract_addr: pair_addr.to_string(), - msg: to_json_binary(&auction::msg::ExecuteMsg::Admin( + msg: to_json_binary(&auction::msg::ExecuteMsg::Admin(Box::new( auction::msg::AdminMsgs::UpdatePriceFreshnessStrategy(strategy), - ))?, + )))?, funds: vec![], }; Ok(Response::default().add_message(msg)) } + AdminMsgs::MigrateAuction { pair, code_id, msg } => { + let pair_addr = PAIRS.load(deps.storage, pair)?; + + let migrate_msg = WasmMsg::Migrate { + contract_addr: pair_addr.to_string(), + msg: to_json_binary(&msg)?, + new_code_id: code_id, + }; + + Ok(Response::default().add_message(migrate_msg)) + } + AdminMsgs::UpdateMinAmount { denom, min_amount } => { + MIN_AUCTION_AMOUNT.save(deps.storage, denom, &min_amount)?; + + Ok(Response::default()) + } AdminMsgs::StartAdminChange { addr, expiration } => { Ok(start_admin_change(deps, &info, &addr, expiration)?) } @@ -235,6 +259,15 @@ mod admin { #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: AuctionsManagerQueryMsg) -> StdResult { match msg { + AuctionsManagerQueryMsg::GetPairs { start_after, limit } => { + let start_after = start_after.map(Bound::exclusive); + let pairs = PAIRS + .range(deps.storage, start_after, None, Order::Ascending) + .take(limit.unwrap_or(50) as usize) + .collect::>>()?; + + to_json_binary(&pairs) + } AuctionsManagerQueryMsg::GetPairAddr { pair } => { to_json_binary(&PAIRS.load(deps.storage, pair)?) } @@ -279,3 +312,32 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result Err(ContractError::UnknownReplyId(msg.id)), } } + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + match msg { + MigrateMsg::NoStateChange {} => Ok(Response::default()), + MigrateMsg::ToV1 {} => { + let mins = MIN_AUCTION_AMOUNT_V0 + .range(deps.storage, None, None, cosmwasm_std::Order::Ascending) + .collect::>>()?; + + mins.iter().for_each(|(denom, amount)| { + MIN_AUCTION_AMOUNT + .save( + deps.storage, + denom.to_string(), + &MinAmount { + send: *amount, + start_auction: *amount, + }, + ) + .unwrap(); + }); + + Ok(Response::default()) + } + } +} diff --git a/contracts/auction/auctions_manager/src/msg.rs b/contracts/auction/auctions_manager/src/msg.rs index d114a9d5..54fe0429 100644 --- a/contracts/auction/auctions_manager/src/msg.rs +++ b/contracts/auction/auctions_manager/src/msg.rs @@ -1,13 +1,14 @@ use auction::msg::NewAuctionParams; -use auction_package::{helpers::ChainHaltConfig, AuctionStrategy, Pair, PriceFreshnessStrategy}; +use auction_package::{ + helpers::ChainHaltConfig, states::MinAmount, AuctionStrategy, Pair, PriceFreshnessStrategy, +}; use cosmwasm_schema::cw_serde; -use cosmwasm_std::Uint128; use cw_utils::Expiration; #[cw_serde] pub struct InstantiateMsg { pub auction_code_id: u64, - pub min_auction_amount: Vec<(String, Uint128)>, + pub min_auction_amount: Vec<(String, MinAmount)>, } #[cw_serde] @@ -15,18 +16,22 @@ pub enum ExecuteMsg { AuctionFunds { pair: Pair }, WithdrawFunds { pair: Pair }, FinishAuction { pair: Pair, limit: u64 }, - ApproveAdminChange, - Admin(AdminMsgs), + ApproveAdminChange {}, + Admin(Box), } #[cw_serde] -pub enum MigrateMsg {} +pub enum MigrateMsg { + NoStateChange {}, + ToV1 {}, +} #[cw_serde] pub enum AdminMsgs { NewAuction { msg: auction::msg::InstantiateMsg, - min_amount: Option, + label: String, + min_amount: Option, }, OpenAuction { pair: Pair, @@ -44,6 +49,10 @@ pub enum AdminMsgs { UpdateOracle { oracle_addr: String, }, + UpdateMinAmount { + denom: String, + min_amount: MinAmount, + }, UpdateStrategy { pair: Pair, strategy: AuctionStrategy, @@ -56,6 +65,11 @@ pub enum AdminMsgs { pair: Pair, strategy: PriceFreshnessStrategy, }, + MigrateAuction { + pair: Pair, + code_id: u64, + msg: auction::msg::MigrateMsg, + }, StartAdminChange { addr: String, expiration: Expiration, diff --git a/contracts/auction/price_oracle/Cargo.toml b/contracts/auction/price_oracle/Cargo.toml index f4a20b62..05ae7083 100644 --- a/contracts/auction/price_oracle/Cargo.toml +++ b/contracts/auction/price_oracle/Cargo.toml @@ -26,6 +26,8 @@ thiserror = { workspace = true } cw-utils = { workspace = true } auction-package = { workspace = true } +astroport = "2.9.5" + [dev-dependencies] cw-multi-test = { workspace = true } anyhow = { workspace = true } diff --git a/contracts/auction/price_oracle/schema/price-oracle.json b/contracts/auction/price_oracle/schema/price-oracle.json index 00ad52a5..98097078 100644 --- a/contracts/auction/price_oracle/schema/price-oracle.json +++ b/contracts/auction/price_oracle/schema/price-oracle.json @@ -1,17 +1,29 @@ { "contract_name": "price-oracle", - "contract_version": "0.1.0", + "contract_version": "1.2.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "InstantiateMsg", "type": "object", "required": [ - "auctions_manager_addr" + "auctions_manager_addr", + "seconds_allow_manual_change", + "seconds_auction_prices_fresh" ], "properties": { "auctions_manager_addr": { "type": "string" + }, + "seconds_allow_manual_change": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "seconds_auction_prices_fresh": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 } }, "additionalProperties": false @@ -21,11 +33,29 @@ "title": "ExecuteMsg", "oneOf": [ { - "type": "string", - "enum": [ - "cancel_admin_change", - "approve_admin_change" - ] + "type": "object", + "required": [ + "manual_price_update" + ], + "properties": { + "manual_price_update": { + "type": "object", + "required": [ + "pair", + "price" + ], + "properties": { + "pair": { + "$ref": "#/definitions/Pair" + }, + "price": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false }, { "type": "object", @@ -38,19 +68,102 @@ "required": [ "pair" ], + "properties": { + "pair": { + "$ref": "#/definitions/Pair" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "add_astro_path" + ], + "properties": { + "add_astro_path": { + "type": "object", + "required": [ + "pair", + "path" + ], "properties": { "pair": { "$ref": "#/definitions/Pair" }, - "price": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } + "path": { + "type": "array", + "items": { + "$ref": "#/definitions/PriceStep" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_astro_path" + ], + "properties": { + "update_astro_path": { + "type": "object", + "required": [ + "pair", + "path" + ], + "properties": { + "pair": { + "$ref": "#/definitions/Pair" + }, + "path": { + "type": "array", + "items": { + "$ref": "#/definitions/PriceStep" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "auction_manager_addr": { + "type": [ + "string", + "null" ] + }, + "seconds_allow_manual_change": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "seconds_auction_prices_fresh": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 } }, "additionalProperties": false @@ -82,9 +195,39 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "cancel_admin_change" + ], + "properties": { + "cancel_admin_change": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "approve_admin_change" + ], + "properties": { + "approve_admin_change": { + "type": "object", + "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" + }, "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" @@ -149,6 +292,26 @@ "maxItems": 2, "minItems": 2 }, + "PriceStep": { + "type": "object", + "required": [ + "denom1", + "denom2", + "pool_address" + ], + "properties": { + "denom1": { + "type": "string" + }, + "denom2": { + "type": "string" + }, + "pool_address": { + "$ref": "#/definitions/Addr" + } + }, + "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": [ @@ -227,11 +390,30 @@ "title": "Config", "type": "object", "required": [ - "auction_manager_addr" + "auction_manager_addr", + "seconds_allow_manual_change", + "seconds_auction_prices_fresh" ], "properties": { "auction_manager_addr": { - "$ref": "#/definitions/Addr" + "description": "The address of the auctions manager contract", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "seconds_allow_manual_change": { + "description": "If the price wasn't changed for this amount of time, the admin can change the price manually", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "seconds_auction_prices_fresh": { + "description": "The amount of seconds we use auctions as our price source If last auction ran more than this amount of seconds, we do not use the auction as the source of price", + "type": "integer", + "format": "uint64", + "minimum": 0.0 } }, "additionalProperties": false, diff --git a/contracts/auction/price_oracle/src/contract.rs b/contracts/auction/price_oracle/src/contract.rs index 83205fa8..f0324add 100644 --- a/contracts/auction/price_oracle/src/contract.rs +++ b/contracts/auction/price_oracle/src/contract.rs @@ -1,3 +1,5 @@ +use std::collections::VecDeque; + use auction_package::helpers::{ approve_admin_change, cancel_admin_change, start_admin_change, verify_admin, }; @@ -5,14 +7,12 @@ use auction_package::states::{ADMIN, PAIRS, PRICES, TWAP_PRICES}; use auction_package::Price; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{ - to_json_binary, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Response, Timestamp, -}; +use cosmwasm_std::{to_json_binary, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Response}; use cw2::set_contract_version; use crate::error::ContractError; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::{Config, CONFIG}; +use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use crate::state::{Config, PriceStep, ASTRO_PRICE_PATHS, CONFIG}; const CONTRACT_NAME: &str = "crates.io:oracle"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -33,6 +33,8 @@ pub fn instantiate( deps.storage, &Config { auction_manager_addr: deps.api.addr_validate(&msg.auctions_manager_addr)?, + seconds_allow_manual_change: msg.seconds_allow_manual_change, + seconds_auction_prices_fresh: msg.seconds_auction_prices_fresh, }, )?; @@ -47,71 +49,228 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::UpdatePrice { pair, price } => { + ExecuteMsg::UpdatePrice { pair } => { pair.verify()?; let config = CONFIG.load(deps.storage)?; - verify_admin(deps.as_ref(), &info)?; let auction_addr = PAIRS - .query(&deps.querier, config.auction_manager_addr, pair.clone())? + .query( + &deps.querier, + config.auction_manager_addr.clone(), + pair.clone(), + )? .ok_or(ContractError::PairAuctionNotFound)?; let twap_prices = TWAP_PRICES.query(&deps.querier, auction_addr)?; - let (price, time) = match price { - // We have a price, so set that as the price of the pair - Some(price) => { - if price.is_zero() { - return Err(ContractError::PriceIsZero); - } + let price = if can_update_price_from_auction(&config, &env, &twap_prices) { + get_avg_price(twap_prices) + } else { + let steps = ASTRO_PRICE_PATHS + .load(deps.storage, pair.clone()) + .map_err(|_| ContractError::NoAstroPath(pair.clone()))?; + get_price_from_astroport(deps.as_ref(), &env, steps)? + }; - if twap_prices.len() < 4 - || twap_prices[0].time.seconds() - < env.block.time.seconds() - (60 * 60 * 24 * 2) - { - Ok::<(Decimal, Timestamp), ContractError>((price, env.block.time)) - } else { - return Err(ContractError::NoTermsForManualUpdate); - } - } - // We don't have a price, so we are trying to look for the price in the auction - None => { - if twap_prices.len() < 3 { - return Err(ContractError::NotEnoughTwaps); - } + // Save price + PRICES.save(deps.storage, pair, &price)?; - // Check if we had an auction in the last 3 days and 6 hours - // 6 hours is a little buffer in case our auction end time doesn't match exactly our update time - if twap_prices[0].time.seconds() - < env.block.time.seconds() - (60 * 60 * 24 * 3 + (60 * 60 * 6)) - { - return Err(ContractError::NoAuctionInLast3Days); - } + Ok(Response::default().add_attribute("price", price.to_string())) + } + ExecuteMsg::ManualPriceUpdate { pair, price } => { + let config = CONFIG.load(deps.storage)?; + verify_admin(deps.as_ref(), &info)?; + + pair.verify()?; - let (total_count, prices_sum) = twap_prices.iter().fold( - (Decimal::zero(), Decimal::zero()), - |(total_count, prices_sum), price| { - (total_count + Decimal::one(), prices_sum + price.price) - }, - ); + // sanity check + if price.is_zero() { + return Err(ContractError::PriceIsZero); + } - Ok((prices_sum / total_count, twap_prices[0].time)) + // Get the time last update happened + match PRICES.load(deps.storage, pair.clone()) { + Ok(Price { + time: last_updated, .. + }) => { + // Verify enough time has passed since last update to allow manual update + // 'enough time' is defined in the config + if env.block.time.seconds() + < last_updated.seconds() + config.seconds_allow_manual_change + { + Err(ContractError::NoTermsForManualUpdate) + } else { + Ok(()) + } } + Err(_) => Ok(()), }?; // Save price - PRICES.save(deps.storage, pair, &Price { price, time })?; + PRICES.save( + deps.storage, + pair, + &Price { + price, + time: env.block.time, + }, + )?; Ok(Response::default().add_attribute("price", price.to_string())) } + ExecuteMsg::AddAstroPath { pair, path } => { + verify_admin(deps.as_ref(), &info)?; + + pair.verify()?; + + if ASTRO_PRICE_PATHS.has(deps.storage, pair.clone()) { + return Err(ContractError::PricePathAlreadyExists); + } + + if path.is_empty() { + return Err(ContractError::PricePathIsEmpty); + } + + if path[0].denom1 != pair.0 || path[path.len() - 1].denom2 != pair.1 { + return Err(ContractError::PricePathIsWrong); + } + + ASTRO_PRICE_PATHS.save(deps.storage, pair, &path)?; + + Ok(Response::default()) + } + ExecuteMsg::UpdateAstroPath { pair, path } => { + verify_admin(deps.as_ref(), &info)?; + + pair.verify()?; + + if !ASTRO_PRICE_PATHS.has(deps.storage, pair.clone()) { + return Err(ContractError::PricePathNotFound); + } + + if path.is_empty() { + return Err(ContractError::PricePathIsEmpty); + } + + if path[0].denom1 != pair.0 || path[path.len() - 1].denom2 != pair.1 { + return Err(ContractError::PricePathIsWrong); + } + + ASTRO_PRICE_PATHS.save(deps.storage, pair, &path)?; + + Ok(Response::default()) + } + ExecuteMsg::UpdateConfig { + auction_manager_addr, + seconds_allow_manual_change, + seconds_auction_prices_fresh, + } => { + verify_admin(deps.as_ref(), &info)?; + + let mut config = CONFIG.load(deps.storage)?; + + if let Some(auction_manager_addr) = auction_manager_addr { + config.auction_manager_addr = deps.api.addr_validate(&auction_manager_addr)?; + } + + if let Some(seconds_allow_manual_change) = seconds_allow_manual_change { + config.seconds_allow_manual_change = seconds_allow_manual_change; + } + + if let Some(seconds_auction_prices_fresh) = seconds_auction_prices_fresh { + config.seconds_auction_prices_fresh = seconds_auction_prices_fresh; + } + + CONFIG.save(deps.storage, &config)?; + + Ok(Response::default()) + } ExecuteMsg::StartAdminChange { addr, expiration } => { Ok(start_admin_change(deps, &info, &addr, expiration)?) } - ExecuteMsg::CancelAdminChange => Ok(cancel_admin_change(deps, &info)?), - ExecuteMsg::ApproveAdminChange => Ok(approve_admin_change(deps, &env, &info)?), + ExecuteMsg::CancelAdminChange {} => Ok(cancel_admin_change(deps, &info)?), + ExecuteMsg::ApproveAdminChange {} => Ok(approve_admin_change(deps, &env, &info)?), } } +fn can_update_price_from_auction( + config: &Config, + env: &Env, + auction_prices: &VecDeque, +) -> bool { + if auction_prices.len() < 3 { + return false; + } + + // Make sure last auction ran in the acceptable time frame + // else we consider the auction prices not up to date + if auction_prices[0].time.seconds() + config.seconds_auction_prices_fresh + < env.block.time.seconds() + { + return false; + } + + true +} + +fn get_avg_price(vec: VecDeque) -> Price { + let (total_count, prices_sum) = vec.iter().fold( + (Decimal::zero(), Decimal::zero()), + |(total_count, prices_sum), price| (total_count + Decimal::one(), prices_sum + price.price), + ); + + Price { + price: prices_sum / total_count, + time: vec[0].time, + } +} + +fn get_price_from_astroport( + deps: Deps, + env: &Env, + steps: Vec, +) -> Result { + let final_denom_amount = steps.iter().fold( + Decimal::from_atomics(1000000_u128, 0).map_err(ContractError::DecimalRangeExceeded), + |amount, step| { + // Build the asset + let offer_asset = astroport::asset::Asset { + info: astroport::asset::AssetInfo::NativeToken { + denom: step.denom1.clone(), + }, + amount: amount?.to_uint_floor(), + }; + + let res = astroport::querier::simulate( + &deps.querier, + step.pool_address.clone(), + &offer_asset, + )?; + + let price = Decimal::from_atomics( + res.return_amount + .checked_add(res.commission_amount)? + .checked_add(res.spread_amount)?, + 0, + )?; + deps.api.debug(format!("res: {:?}", res).as_str()); + deps.api.debug(format!("Price step: {:?}", price).as_str()); + + Ok(price) + }, + )?; + + let _price = final_denom_amount.checked_div(Decimal::from_atomics(1000000_u128, 0)?)?; + deps.api.debug(format!("Price: {:?}", _price).as_str()); + + let price = Price { + price: final_denom_amount.checked_div(Decimal::from_atomics(1000000_u128, 0)?)?, + time: env.block.time, + }; + + Ok(price) +} + #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { match msg { @@ -127,3 +286,12 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result Ok(to_json_binary(&ADMIN.load(deps.storage)?)?), } } + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + match msg { + MigrateMsg::NoStateChange {} => Ok(Response::default()), + } +} diff --git a/contracts/auction/price_oracle/src/error.rs b/contracts/auction/price_oracle/src/error.rs index 1c0b4eab..a0dd0d61 100644 --- a/contracts/auction/price_oracle/src/error.rs +++ b/contracts/auction/price_oracle/src/error.rs @@ -1,5 +1,5 @@ -use auction_package::error::AuctionError; -use cosmwasm_std::StdError; +use auction_package::{error::AuctionError, Pair}; +use cosmwasm_std::{CheckedFromRatioError, DecimalRangeExceeded, OverflowError, StdError}; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -10,6 +10,15 @@ pub enum ContractError { #[error(transparent)] AuctionError(#[from] AuctionError), + #[error(transparent)] + DecimalRangeExceeded(#[from] DecimalRangeExceeded), + + #[error(transparent)] + OverflowError(#[from] OverflowError), + + #[error(transparent)] + CheckedFromRatioError(#[from] CheckedFromRatioError), + #[error("Sender is not admin")] NotAdmin, @@ -25,6 +34,21 @@ pub enum ContractError { #[error("Set price cannot be zero")] PriceIsZero, - #[error("Can't manually update price")] + #[error("Can't manually update price, terms are not met for manual update")] NoTermsForManualUpdate, + + #[error("Path for this pair already exists")] + PricePathAlreadyExists, + + #[error("Path for this pair doesn't exists yet")] + PricePathNotFound, + + #[error("Path must not be empty")] + PricePathIsEmpty, + + #[error("Path doesn't match pair, denom1 in first step must be the same as pair.0, and last step denom2 must match pair.1")] + PricePathIsWrong, + + #[error("No astroport path found for pair: {0}")] + NoAstroPath(Pair), } diff --git a/contracts/auction/price_oracle/src/msg.rs b/contracts/auction/price_oracle/src/msg.rs index b32bec8d..4e15ef2c 100644 --- a/contracts/auction/price_oracle/src/msg.rs +++ b/contracts/auction/price_oracle/src/msg.rs @@ -3,25 +3,43 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Decimal}; use cw_utils::Expiration; -use crate::state::Config; +use crate::state::{Config, PriceStep}; #[cw_serde] pub struct InstantiateMsg { pub auctions_manager_addr: String, + pub seconds_allow_manual_change: u64, + pub seconds_auction_prices_fresh: u64, } #[cw_serde] pub enum ExecuteMsg { + ManualPriceUpdate { + pair: Pair, + price: Decimal, + }, UpdatePrice { pair: Pair, - price: Option, + }, + AddAstroPath { + pair: Pair, + path: Vec, + }, + UpdateAstroPath { + pair: Pair, + path: Vec, + }, + UpdateConfig { + auction_manager_addr: Option, + seconds_allow_manual_change: Option, + seconds_auction_prices_fresh: Option, }, StartAdminChange { addr: String, expiration: Expiration, }, - CancelAdminChange, - ApproveAdminChange, + CancelAdminChange {}, + ApproveAdminChange {}, } #[cw_serde] @@ -37,4 +55,6 @@ pub enum QueryMsg { } #[cw_serde] -pub enum MigrateMsg {} +pub enum MigrateMsg { + NoStateChange {}, +} diff --git a/contracts/auction/price_oracle/src/state.rs b/contracts/auction/price_oracle/src/state.rs index 78be502e..fa328535 100644 --- a/contracts/auction/price_oracle/src/state.rs +++ b/contracts/auction/price_oracle/src/state.rs @@ -1,10 +1,25 @@ +use auction_package::Pair; use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; -use cw_storage_plus::Item; +use cw_storage_plus::{Item, Map}; pub const CONFIG: Item = Item::new("config"); +pub const ASTRO_PRICE_PATHS: Map> = Map::new("astro_price_paths"); #[cw_serde] pub struct Config { + /// The address of the auctions manager contract pub auction_manager_addr: Addr, + /// If the price wasn't changed for this amount of time, the admin can change the price manually + pub seconds_allow_manual_change: u64, + /// The amount of seconds we use auctions as our price source + /// If last auction ran more than this amount of seconds, we do not use the auction as the source of price + pub seconds_auction_prices_fresh: u64, +} + +#[cw_serde] +pub struct PriceStep { + pub denom1: String, + pub denom2: String, + pub pool_address: Addr, } diff --git a/contracts/services/rebalancer/Cargo.toml b/contracts/services/rebalancer/Cargo.toml index f39c64f2..c46e0f88 100644 --- a/contracts/services/rebalancer/Cargo.toml +++ b/contracts/services/rebalancer/Cargo.toml @@ -17,15 +17,16 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -cw2 = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } -valence-macros = { workspace = true } -valence-package = { workspace = true } -auction-package = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +cw2 = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +valence-macros = { workspace = true } +valence-package = { workspace = true } +auction-package = { workspace = true } auctions-manager = { workspace = true } [dev-dependencies] diff --git a/contracts/services/rebalancer/README.md b/contracts/services/rebalancer/README.md index a4c96e9c..feb1d8fb 100644 --- a/contracts/services/rebalancer/README.md +++ b/contracts/services/rebalancer/README.md @@ -94,7 +94,9 @@ pub enum TargetOverrideStrategy { Example: A user has set the following rebalancing targets for tokens with denominations A, B, and C: -Target A - 25%, no min_balance Target B - 25%, no min_balance Target C - 50%, must maintain min_balance of 100 tokens. +Target A - 25%, no min_balance +Target B - 25%, no min_balance +Target C - 50%, must maintain min_balance of 100 tokens. Now suppose at one particular rebalancing interval, 100 tokens of the C denomination make up 60% of the total portfolio valuation. This a problem because the user has also required the Rebalancer to maintain 50% cumulatively in token A and B, with 25% of the total portfolio value in denomination A and another 25% in denomination B. Given that it can only maintain a total of 40% of the total value in A and B, what strategy should the Rebalancer pursue to reassign weights in A and B? diff --git a/contracts/services/rebalancer/schema/rebalancer.json b/contracts/services/rebalancer/schema/rebalancer.json index a18b8a93..2c7d5653 100644 --- a/contracts/services/rebalancer/schema/rebalancer.json +++ b/contracts/services/rebalancer/schema/rebalancer.json @@ -1,6 +1,6 @@ { "contract_name": "rebalancer", - "contract_version": "0.1.0", + "contract_version": "1.2.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -11,6 +11,7 @@ "base_denom_whitelist", "cycle_start", "denom_whitelist", + "fees", "services_manager_addr" ], "properties": { @@ -40,6 +41,9 @@ "type": "string" } }, + "fees": { + "$ref": "#/definitions/ServiceFeeConfig" + }, "services_manager_addr": { "type": "string" } @@ -62,6 +66,26 @@ }, "additionalProperties": false }, + "ServiceFeeConfig": { + "type": "object", + "required": [ + "denom", + "register_fee", + "resume_fee" + ], + "properties": { + "denom": { + "type": "string" + }, + "register_fee": { + "$ref": "#/definitions/Uint128" + }, + "resume_fee": { + "$ref": "#/definitions/Uint128" + } + }, + "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": [ @@ -223,6 +247,12 @@ "pause_for": { "type": "string" }, + "reason": { + "type": [ + "string", + "null" + ] + }, "sender": { "type": "string" } @@ -354,6 +384,31 @@ } ] }, + "OptionalField_for_uint64": { + "description": "An optional helper for Option, for when we need to update an optional field in storage. Ex: We want to update an optional field in storage: `sample: Option` but we also want to have it optional on the update message: `sample: Option>`\n\nThis allows us to have 3 options: 1. None: Do nothing, keep storage as is. 2. Some(OptionalField::Clear): Clear the field in storage and set it to None. 3. Some(OptionalField::Set(value)): Set the field in storage to Some(value)", + "oneOf": [ + { + "type": "string", + "enum": [ + "clear" + ] + }, + { + "type": "object", + "required": [ + "set" + ], + "properties": { + "set": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, "PID": { "description": "The PID parameters we use to calculate the rebalance amounts", "type": "object", @@ -544,6 +599,27 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "update_fess" + ], + "properties": { + "update_fess": { + "type": "object", + "required": [ + "fees" + ], + "properties": { + "fees": { + "$ref": "#/definitions/ServiceFeeConfig" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -639,13 +715,15 @@ "null" ] }, - "max_limit": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 + "max_limit_bps": { + "anyOf": [ + { + "$ref": "#/definitions/OptionalField_for_uint64" + }, + { + "type": "null" + } + ] }, "pid": { "anyOf": [ @@ -687,6 +765,26 @@ }, "additionalProperties": false }, + "ServiceFeeConfig": { + "type": "object", + "required": [ + "denom", + "register_fee", + "resume_fee" + ], + "properties": { + "denom": { + "type": "string" + }, + "register_fee": { + "$ref": "#/definitions/Uint128" + }, + "resume_fee": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, "SystemRebalanceStatus": { "oneOf": [ { @@ -840,6 +938,7 @@ { "type": "string", "enum": [ + "get_system_status", "get_white_lists", "get_managers_addrs", "get_admin" @@ -869,17 +968,89 @@ { "type": "object", "required": [ - "get_system_status" + "get_all_configs" + ], + "properties": { + "get_all_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": [ + "get_paused_config" + ], + "properties": { + "get_paused_config": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get the service fee of a service", + "type": "object", + "required": [ + "get_service_fee" ], "properties": { - "get_system_status": { + "get_service_fee": { "type": "object", + "required": [ + "account", + "action" + ], + "properties": { + "account": { + "type": "string" + }, + "action": { + "$ref": "#/definitions/QueryFeeAction" + } + }, "additionalProperties": false } }, "additionalProperties": false } - ] + ], + "definitions": { + "QueryFeeAction": { + "type": "string", + "enum": [ + "register", + "resume" + ] + } + } }, "migrate": null, "sudo": null, @@ -890,81 +1061,23 @@ "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" }, - "get_config": { + "get_all_configs": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "RebalancerConfig", - "type": "object", - "required": [ - "base_denom", - "has_min_balance", - "last_rebalance", - "max_limit", - "pid", - "target_override_strategy", - "targets" - ], - "properties": { - "base_denom": { - "description": "The base denom we will be calculating everything based on", - "type": "string" - }, - "has_min_balance": { - "type": "boolean" - }, - "is_paused": { - "description": "Is_paused holds the pauser if it is paused, None if its not paused", - "anyOf": [ - { - "$ref": "#/definitions/Addr" - }, - { - "type": "null" - } - ] - }, - "last_rebalance": { - "description": "When the last rebalance happened.", - "allOf": [ - { - "$ref": "#/definitions/Timestamp" - } - ] - }, - "max_limit": { - "description": "Percentage from the total balance that we are allowed to sell in 1 rebalance cycle.", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "pid": { - "description": "The PID parameters the account want to rebalance with", - "allOf": [ - { - "$ref": "#/definitions/ParsedPID" - } - ] - }, - "target_override_strategy": { - "$ref": "#/definitions/TargetOverrideStrategy" - }, - "targets": { - "description": "A vector of targets to rebalance for this account", - "type": "array", - "items": { - "$ref": "#/definitions/ParsedTarget" + "title": "Array_of_Tuple_of_Addr_and_RebalancerConfig", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/Addr" + }, + { + "$ref": "#/definitions/RebalancerConfig" } - }, - "trustee": { - "description": "the address that can pause and resume the service", - "type": [ - "string", - "null" - ] - } + ], + "maxItems": 2, + "minItems": 2 }, - "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.", @@ -1048,37 +1161,295 @@ }, "additionalProperties": false }, - "SignedDecimal": { - "description": "A helper struct to have a signed decimal This allows us to keep track if the number is positive or negative\n\nOur usecse / example: In the rebalancer, when we are doing the rebalance calculation, we can either have a positive number or a negetive number. positive number means we need to buy the asset, while negetive menas we need to sell.\n\nThis struct makes it easier for us to do the calculation, and act upon the sign only after the calculation is done, in order to know if we should buy or sell.", - "type": "array", - "items": [ - { - "$ref": "#/definitions/Decimal" + "RebalancerConfig": { + "type": "object", + "required": [ + "base_denom", + "has_min_balance", + "last_rebalance", + "max_limit", + "pid", + "target_override_strategy", + "targets" + ], + "properties": { + "base_denom": { + "description": "The base denom we will be calculating everything based on", + "type": "string" }, - { + "has_min_balance": { "type": "boolean" - } - ], - "maxItems": 2, - "minItems": 2 - }, - "TargetOverrideStrategy": { - "description": "The strategy we will use when overriding targets", - "type": "string", - "enum": [ - "proportional", - "priority" - ] - }, - "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": { + }, + "last_rebalance": { + "description": "When the last rebalance happened.", + "allOf": [ + { + "$ref": "#/definitions/Timestamp" + } + ] + }, + "max_limit": { + "description": "Percentage from the total balance that we are allowed to sell in 1 rebalance cycle.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "pid": { + "description": "The PID parameters the account want to rebalance with", + "allOf": [ + { + "$ref": "#/definitions/ParsedPID" + } + ] + }, + "target_override_strategy": { + "$ref": "#/definitions/TargetOverrideStrategy" + }, + "targets": { + "description": "A vector of targets to rebalance for this account", + "type": "array", + "items": { + "$ref": "#/definitions/ParsedTarget" + } + }, + "trustee": { + "description": "the address that can pause and resume the service", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "SignedDecimal": { + "description": "A helper struct to have a signed decimal This allows us to keep track if the number is positive or negative\n\nOur usecse / example: In the rebalancer, when we are doing the rebalance calculation, we can either have a positive number or a negetive number. positive number means we need to buy the asset, while negetive menas we need to sell.\n\nThis struct makes it easier for us to do the calculation, and act upon the sign only after the calculation is done, in order to know if we should buy or sell.", + "type": "array", + "items": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "boolean" + } + ], + "maxItems": 2, + "minItems": 2 + }, + "TargetOverrideStrategy": { + "description": "The strategy we will use when overriding targets", + "type": "string", + "enum": [ + "proportional", + "priority" + ] + }, + "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" + } + } + }, + "get_config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RebalancerConfig", + "type": "object", + "required": [ + "base_denom", + "has_min_balance", + "last_rebalance", + "max_limit", + "pid", + "target_override_strategy", + "targets" + ], + "properties": { + "base_denom": { + "description": "The base denom we will be calculating everything based on", + "type": "string" + }, + "has_min_balance": { + "type": "boolean" + }, + "last_rebalance": { + "description": "When the last rebalance happened.", + "allOf": [ + { + "$ref": "#/definitions/Timestamp" + } + ] + }, + "max_limit": { + "description": "Percentage from the total balance that we are allowed to sell in 1 rebalance cycle.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "pid": { + "description": "The PID parameters the account want to rebalance with", + "allOf": [ + { + "$ref": "#/definitions/ParsedPID" + } + ] + }, + "target_override_strategy": { + "$ref": "#/definitions/TargetOverrideStrategy" + }, + "targets": { + "description": "A vector of targets to rebalance for this account", + "type": "array", + "items": { + "$ref": "#/definitions/ParsedTarget" + } + }, + "trustee": { + "description": "the address that can pause and resume the service", + "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" + }, + "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" + }, + "ParsedPID": { + "type": "object", + "required": [ + "d", + "i", + "p" + ], + "properties": { + "d": { + "$ref": "#/definitions/Decimal" + }, + "i": { + "$ref": "#/definitions/Decimal" + }, + "p": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "ParsedTarget": { + "description": "A parsed target struct that contains all info about a single denom target", + "type": "object", + "required": [ + "denom", + "last_i", + "percentage" + ], + "properties": { + "denom": { + "description": "The name of the denom", + "type": "string" + }, + "last_i": { + "description": "The last I value we got from the last rebalance PID calculation.", + "allOf": [ + { + "$ref": "#/definitions/SignedDecimal" + } + ] + }, + "last_input": { + "description": "The input we got from the last rebalance.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "min_balance": { + "description": "The minimum balance the account should hold for this denom. Can only be a single one for an account", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "percentage": { + "description": "The percentage of the total balance we want to have in this denom", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "SignedDecimal": { + "description": "A helper struct to have a signed decimal This allows us to keep track if the number is positive or negative\n\nOur usecse / example: In the rebalancer, when we are doing the rebalance calculation, we can either have a positive number or a negetive number. positive number means we need to buy the asset, while negetive menas we need to sell.\n\nThis struct makes it easier for us to do the calculation, and act upon the sign only after the calculation is done, in order to know if we should buy or sell.", + "type": "array", + "items": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "boolean" + } + ], + "maxItems": 2, + "minItems": 2 + }, + "TargetOverrideStrategy": { + "description": "The strategy we will use when overriding targets", + "type": "string", + "enum": [ + "proportional", + "priority" + ] + }, + "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" }, @@ -1112,6 +1483,274 @@ } } }, + "get_paused_config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PauseData", + "type": "object", + "required": [ + "config", + "pauser", + "reason" + ], + "properties": { + "config": { + "$ref": "#/definitions/RebalancerConfig" + }, + "pauser": { + "$ref": "#/definitions/Addr" + }, + "reason": { + "$ref": "#/definitions/PauseReason" + } + }, + "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" + }, + "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" + }, + "ParsedPID": { + "type": "object", + "required": [ + "d", + "i", + "p" + ], + "properties": { + "d": { + "$ref": "#/definitions/Decimal" + }, + "i": { + "$ref": "#/definitions/Decimal" + }, + "p": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "ParsedTarget": { + "description": "A parsed target struct that contains all info about a single denom target", + "type": "object", + "required": [ + "denom", + "last_i", + "percentage" + ], + "properties": { + "denom": { + "description": "The name of the denom", + "type": "string" + }, + "last_i": { + "description": "The last I value we got from the last rebalance PID calculation.", + "allOf": [ + { + "$ref": "#/definitions/SignedDecimal" + } + ] + }, + "last_input": { + "description": "The input we got from the last rebalance.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "min_balance": { + "description": "The minimum balance the account should hold for this denom. Can only be a single one for an account", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "percentage": { + "description": "The percentage of the total balance we want to have in this denom", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "PauseReason": { + "oneOf": [ + { + "description": "This reason can only be called if the rebalancer is pausing the account because it has an empty balance.", + "type": "string", + "enum": [ + "empty_balance" + ] + }, + { + "description": "This reason is given by the user/account, he might forget why he paused the account this will remind him of it.", + "type": "object", + "required": [ + "account_reason" + ], + "properties": { + "account_reason": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "RebalancerConfig": { + "type": "object", + "required": [ + "base_denom", + "has_min_balance", + "last_rebalance", + "max_limit", + "pid", + "target_override_strategy", + "targets" + ], + "properties": { + "base_denom": { + "description": "The base denom we will be calculating everything based on", + "type": "string" + }, + "has_min_balance": { + "type": "boolean" + }, + "last_rebalance": { + "description": "When the last rebalance happened.", + "allOf": [ + { + "$ref": "#/definitions/Timestamp" + } + ] + }, + "max_limit": { + "description": "Percentage from the total balance that we are allowed to sell in 1 rebalance cycle.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "pid": { + "description": "The PID parameters the account want to rebalance with", + "allOf": [ + { + "$ref": "#/definitions/ParsedPID" + } + ] + }, + "target_override_strategy": { + "$ref": "#/definitions/TargetOverrideStrategy" + }, + "targets": { + "description": "A vector of targets to rebalance for this account", + "type": "array", + "items": { + "$ref": "#/definitions/ParsedTarget" + } + }, + "trustee": { + "description": "the address that can pause and resume the service", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "SignedDecimal": { + "description": "A helper struct to have a signed decimal This allows us to keep track if the number is positive or negative\n\nOur usecse / example: In the rebalancer, when we are doing the rebalance calculation, we can either have a positive number or a negetive number. positive number means we need to buy the asset, while negetive menas we need to sell.\n\nThis struct makes it easier for us to do the calculation, and act upon the sign only after the calculation is done, in order to know if we should buy or sell.", + "type": "array", + "items": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "boolean" + } + ], + "maxItems": 2, + "minItems": 2 + }, + "TargetOverrideStrategy": { + "description": "The strategy we will use when overriding targets", + "type": "string", + "enum": [ + "proportional", + "priority" + ] + }, + "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" + } + } + }, + "get_service_fee": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_Coin", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ], + "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" + } + } + }, "get_system_status": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "SystemRebalanceStatus", @@ -1250,13 +1889,15 @@ "type": "array", "items": { "$ref": "#/definitions/BaseDenom" - } + }, + "uniqueItems": true }, "denom_whitelist": { "type": "array", "items": { "type": "string" - } + }, + "uniqueItems": true } }, "additionalProperties": false, diff --git a/contracts/services/rebalancer/src/contract.rs b/contracts/services/rebalancer/src/contract.rs index f777ebe6..ec13175c 100644 --- a/contracts/services/rebalancer/src/contract.rs +++ b/contracts/services/rebalancer/src/contract.rs @@ -1,29 +1,33 @@ +use std::collections::HashSet; + use auction_package::helpers::GetPriceResponse; use auction_package::Pair; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, - Uint128, + to_json_binary, Addr, Binary, Coin, Decimal, Deps, DepsMut, Env, Event, MessageInfo, Reply, + Response, StdError, StdResult, Uint128, }; use cw2::set_contract_version; +use cw_storage_plus::Bound; use valence_package::error::ValenceError; use valence_package::helpers::{approve_admin_change, verify_services_manager, OptionalField}; -use valence_package::services::rebalancer::{RebalancerExecuteMsg, SystemRebalanceStatus}; -use valence_package::states::{ADMIN, SERVICES_MANAGER}; +use valence_package::services::rebalancer::{ + PauseData, RebalancerExecuteMsg, SystemRebalanceStatus, +}; +use valence_package::states::{QueryFeeAction, ADMIN, SERVICES_MANAGER, SERVICE_FEE_CONFIG}; use crate::error::ContractError; -use crate::msg::{InstantiateMsg, ManagersAddrsResponse, QueryMsg, WhitelistsResponse}; +use crate::msg::{InstantiateMsg, ManagersAddrsResponse, MigrateMsg, QueryMsg, WhitelistsResponse}; use crate::rebalance::execute_system_rebalance; use crate::state::{ AUCTIONS_MANAGER_ADDR, BASE_DENOM_WHITELIST, CONFIGS, CYCLE_PERIOD, DENOM_WHITELIST, - SYSTEM_REBALANCE_STATUS, + PAUSED_CONFIGS, SYSTEM_REBALANCE_STATUS, }; const CONTRACT_NAME: &str = "crates.io:rebalancer"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -// TODO: Make cycle period configurable pub const DEFAULT_CYCLE_PERIOD: u64 = 60 * 60 * 24; // 24 hours /// The default limit of how many accounts we loop over in a single message /// If wasn't specified in the message @@ -63,8 +67,8 @@ pub fn instantiate( )?; // Set our whitelist - DENOM_WHITELIST.save(deps.storage, &msg.denom_whitelist)?; - BASE_DENOM_WHITELIST.save(deps.storage, &msg.base_denom_whitelist)?; + DENOM_WHITELIST.save(deps.storage, &HashSet::from_iter(msg.denom_whitelist))?; + BASE_DENOM_WHITELIST.save(deps.storage, &HashSet::from_iter(msg.base_denom_whitelist))?; // save auction addr AUCTIONS_MANAGER_ADDR.save( @@ -78,6 +82,9 @@ pub fn instantiate( &msg.cycle_period.unwrap_or(DEFAULT_CYCLE_PERIOD), )?; + // store the fees + SERVICE_FEE_CONFIG.save(deps.storage, &msg.fees)?; + Ok(Response::default().add_attribute("method", "instantiate")) } @@ -92,15 +99,19 @@ pub fn execute( RebalancerExecuteMsg::Admin(admin_msg) => admin::handle_msg(deps, env, info, admin_msg), RebalancerExecuteMsg::ApproveAdminChange => Ok(approve_admin_change(deps, &env, &info)?), RebalancerExecuteMsg::Register { register_for, data } => { - verify_services_manager(deps.as_ref(), &info)?; + let manager_addr = verify_services_manager(deps.as_ref(), &info)?; let data = data.ok_or(ContractError::MustProvideRebalancerData)?; let registree = deps.api.addr_validate(®ister_for)?; - let auctions_manager_addr = AUCTIONS_MANAGER_ADDR.load(deps.storage)?; if CONFIGS.has(deps.storage, registree.clone()) { return Err(ContractError::AccountAlreadyRegistered); } + // Verify user paid the registration fee + let fee_msg = SERVICE_FEE_CONFIG + .load(deps.storage)? + .handle_registration_fee(&info, &manager_addr)?; + // Find base denom in our whitelist let base_denom_whitelist = BASE_DENOM_WHITELIST .load(deps.storage)? @@ -120,6 +131,7 @@ pub fn execute( return Err(ContractError::TwoTargetsMinimum); } + let auctions_manager_addr = AUCTIONS_MANAGER_ADDR.load(deps.storage)?; // check target denoms are whitelisted let denom_whitelist = DENOM_WHITELIST.load(deps.storage)?; let mut total_bps: u64 = 0; @@ -198,13 +210,16 @@ pub fn execute( } // save config - CONFIGS.save(deps.storage, registree, &data.to_config()?)?; + CONFIGS.save(deps.storage, registree, &data.to_config(deps.api)?)?; - Ok(Response::default()) + Ok(Response::default().add_messages(fee_msg)) } RebalancerExecuteMsg::Deregister { deregister_for } => { verify_services_manager(deps.as_ref(), &info)?; - CONFIGS.remove(deps.storage, deps.api.addr_validate(&deregister_for)?); + let account = deps.api.addr_validate(&deregister_for)?; + + CONFIGS.remove(deps.storage, account.clone()); + PAUSED_CONFIGS.remove(deps.storage, account); Ok(Response::default()) } @@ -253,9 +268,7 @@ pub fn execute( if let Some(trustee) = data.trustee { config.trustee = match trustee { - OptionalField::Set(trustee) => { - Some(deps.api.addr_validate(&trustee)?.to_string()) - } + OptionalField::Set(trustee) => Some(deps.api.addr_validate(&trustee)?), OptionalField::Clear => None, }; } @@ -275,12 +288,16 @@ pub fn execute( config.pid = pid.into_parsed()?; } - if let Some(max_limit) = data.max_limit { - if !(1..=10000).contains(&max_limit) { - return Err(ValenceError::InvalidMaxLimitRange.into()); - } - - config.max_limit = Decimal::bps(max_limit); + if let Some(max_limit_option) = data.max_limit_bps { + config.max_limit = match max_limit_option { + OptionalField::Set(max_limit) => { + if !(1..=10000).contains(&max_limit) { + return Err(ValenceError::InvalidMaxLimitRange.into()); + } + Decimal::bps(max_limit) + } + OptionalField::Clear => Decimal::one(), + }; } if let Some(target_override_strategy) = data.target_override_strategy { @@ -291,77 +308,112 @@ pub fn execute( Ok(Response::default()) } - RebalancerExecuteMsg::Pause { pause_for, sender } => { + RebalancerExecuteMsg::Pause { + pause_for, + sender, + reason, + } => { verify_services_manager(deps.as_ref(), &info)?; let account = deps.api.addr_validate(&pause_for)?; let sender = deps.api.addr_validate(&sender)?; - let mut config = CONFIGS.load(deps.storage, account.clone())?; - let trustee = config - .trustee - .clone() - .map(|a| deps.api.addr_validate(&a)) - .transpose()?; - - if let Some(pauser) = config.is_paused { - if let Some(trustee) = trustee { - // If we have trustee, and its the pauser, and the sender is the account, we change the pauser to the account - // else it means that the pauser is the account, so we error because rebalancer already paused. - if pauser == trustee && sender == account { - config.is_paused = Some(account.clone()); - } else { - return Err(ContractError::AccountAlreadyPaused); - } - } else { - // If we reach here, it means we don't have a trustee, but the rebalancer is paused - // which can only mean that the pauser is the account, so we error because rebalancer already paused. + if let Some(mut paused_data) = PAUSED_CONFIGS.may_load(deps.storage, account.clone())? { + // If the sender already paused it before, just error out. + if sender == paused_data.pauser || account == paused_data.pauser { return Err(ContractError::AccountAlreadyPaused); } - } else { - // If we reached here it means the rebalancer is not paused so we check if the sender is valid - // sender can either be the trustee or the account. + + // If the sender is the account, we set it as the pauser to override other possible pausers if sender == account { - // If we don't have a trustee, and the sender is the account, then we set him as the pauser - config.is_paused = Some(account.clone()); - } else if let Some(trustee) = trustee { - // If we have a trustee, and its the sender, then we set him as the pauser - if trustee == sender { - config.is_paused = Some(trustee); - } else { - // The sender is not the trustee, so we error - return Err(ContractError::NotAuthorizedToPause); + paused_data.pauser = account.clone(); + + PAUSED_CONFIGS.save(deps.storage, account, &paused_data)?; + return Ok(Response::default()); + } + + // If we have trustee, and he is the sender we set him as the pauser + if let Some(trustee) = paused_data.config.trustee.clone() { + if sender == trustee { + paused_data.pauser = account.clone(); + + PAUSED_CONFIGS.save(deps.storage, account, &paused_data)?; + return Ok(Response::default()); } - } else { - // If we reach here, it means we don't have a trustee, and the sender is not the account - // so we error because only the account can pause the rebalancer. - return Err(ContractError::NotAuthorizedToPause); } + + return Err(ContractError::NotAuthorizedToPause); } - CONFIGS.save(deps.storage, account, &config)?; + let config = CONFIGS.load(deps.storage, account.clone())?; + + let mut move_config_to_paused = |pauser: Addr| -> Result<(), StdError> { + CONFIGS.remove(deps.storage, account.clone()); + PAUSED_CONFIGS.save( + deps.storage, + account.clone(), + &PauseData::new(pauser, reason.clone().unwrap_or_default(), &config), + )?; + Ok(()) + }; + + if sender == account { + move_config_to_paused(account.clone())?; + return Ok(Response::default()); + }; + + if let Some(trustee) = config.trustee.clone() { + if trustee == sender { + move_config_to_paused(trustee.clone())?; + return Ok(Response::default()); + } + } - Ok(Response::default()) + Err(ContractError::NotAuthorizedToPause) } RebalancerExecuteMsg::Resume { resume_for, sender } => { - verify_services_manager(deps.as_ref(), &info)?; + let manager_addr = verify_services_manager(deps.as_ref(), &info)?; let account = deps.api.addr_validate(&resume_for)?; let sender = deps.api.addr_validate(&sender)?; - let mut config = CONFIGS.load(deps.storage, account.clone())?; + let paused_data = PAUSED_CONFIGS + .load(deps.storage, account.clone()) + .map_err(|_| ContractError::NotPaused)?; let auctions_manager_addr = AUCTIONS_MANAGER_ADDR.load(deps.storage)?; + // Verify user paid the resume fee if its needed + let fee_msg = SERVICE_FEE_CONFIG.load(deps.storage)?.handle_resume_fee( + &info, + &manager_addr, + paused_data.reason, + )?; + + // Verify sender is autorized to resume + (|| { + if sender == account { + return Ok(()); + } + + if let Some(trustee) = paused_data.config.trustee.clone() { + if sender == trustee && paused_data.pauser != account { + return Ok(()); + } + } + + Err(ContractError::NotAuthorizedToResume) + })()?; + // verify minimum balance is met let base_denom = BASE_DENOM_WHITELIST .load(deps.storage)? .iter() - .find(|bd| bd.denom == config.base_denom) + .find(|bd| bd.denom == paused_data.config.base_denom) .expect("Base denom not found in whitelist") .clone(); let mut total_value = Uint128::zero(); let mut min_value_met = false; - for target in &config.targets { + for target in &paused_data.config.targets { let target_balance = deps.querier.query_balance(&account, &target.denom)?; let value = if target.denom == base_denom.denom { @@ -405,37 +457,10 @@ pub fn execute( )); } - let trustee = config - .trustee - .clone() - .map(|a| deps.api.addr_validate(&a)) - .transpose()?; - - // If config is paused - if let Some(resumer) = config.is_paused { - // If the sender is the account, we resume - if sender == account { - config.is_paused = None; - } else if let Some(trustee) = trustee { - // If we have a trustee, and its the sender, we resume - if sender == trustee && resumer == trustee { - config.is_paused = None; - } else { - // We error because only the account or the trustee can resume - return Err(ContractError::NotAuthorizedToResume); - } - } else { - // If we don't have a trustee and sender is not account, we error - return Err(ContractError::NotAuthorizedToResume); - } - } else { - // config is not paused, so error out - return Err(ContractError::NotPaused); - } + CONFIGS.save(deps.storage, account.clone(), &paused_data.config)?; + PAUSED_CONFIGS.remove(deps.storage, account); - CONFIGS.save(deps.storage, account, &config)?; - - Ok(Response::default()) + Ok(Response::default().add_messages(fee_msg)) } RebalancerExecuteMsg::SystemRebalance { limit } => { execute_system_rebalance(deps, &env, limit) @@ -447,8 +472,8 @@ mod admin { use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; use valence_package::{ helpers::{cancel_admin_change, start_admin_change, verify_admin}, - services::rebalancer::{RebalancerAdminMsg, SystemRebalanceStatus}, - states::SERVICES_MANAGER, + services::rebalancer::{BaseDenom, RebalancerAdminMsg, SystemRebalanceStatus}, + states::{SERVICES_MANAGER, SERVICE_FEE_CONFIG}, }; use crate::{ @@ -486,17 +511,13 @@ mod admin { // first remove denoms for denom in to_remove { - if let Some(index) = denoms.iter().position(|d| d == &denom) { - denoms.remove(index); + if !denoms.remove(&denom) { + return Err(ContractError::CannotRemoveDenom(denom)); } } // add new denoms - for denom in to_add { - if !denoms.contains(&denom) { - denoms.push(denom); - } - } + denoms.extend(to_add); DENOM_WHITELIST.save(deps.storage, &denoms)?; @@ -507,19 +528,14 @@ mod admin { // first remove denoms for denom in to_remove { - if let Some(index) = base_denoms.iter().position(|d| d.denom == denom) { - base_denoms.remove(index); + let base_denom = BaseDenom::new_empty(&denom); + if !base_denoms.remove(&base_denom) { + return Err(ContractError::CannotRemoveBaseDenom(denom)); } } // add new denoms - for denom in to_add { - if let Some(index) = base_denoms.iter().position(|d| d.denom == denom.denom) { - base_denoms[index] = denom; - } else { - base_denoms.push(denom); - } - } + base_denoms.extend(to_add); BASE_DENOM_WHITELIST.save(deps.storage, &base_denoms)?; @@ -544,6 +560,11 @@ mod admin { Ok(Response::default()) } + RebalancerAdminMsg::UpdateFess { fees } => { + SERVICE_FEE_CONFIG.save(deps.storage, &fees)?; + + Ok(Response::default()) + } RebalancerAdminMsg::StartAdminChange { addr, expiration } => { Ok(start_admin_change(deps, &info, &addr, expiration)?) } @@ -558,6 +579,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::GetConfig { addr } => { to_json_binary(&CONFIGS.load(deps.storage, deps.api.addr_validate(&addr)?)?) } + QueryMsg::GetPausedConfig { addr } => { + to_json_binary(&PAUSED_CONFIGS.load(deps.storage, deps.api.addr_validate(&addr)?)?) + } QueryMsg::GetSystemStatus {} => { to_json_binary(&SYSTEM_REBALANCE_STATUS.load(deps.storage)?) } @@ -577,20 +601,71 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { to_json_binary(&ManagersAddrsResponse { services, auctions }) } QueryMsg::GetAdmin => to_json_binary(&ADMIN.load(deps.storage)?), + QueryMsg::GetServiceFee { account, action } => { + let fees = SERVICE_FEE_CONFIG.load(deps.storage)?; + let fee_amount = match action { + QueryFeeAction::Register => fees.register_fee, + QueryFeeAction::Resume => { + let Ok(paused_config) = + PAUSED_CONFIGS.load(deps.storage, deps.api.addr_validate(&account)?) + else { + return to_json_binary::>(&None); + }; + + match paused_config.reason { + valence_package::services::rebalancer::PauseReason::EmptyBalance => { + fees.resume_fee + } + valence_package::services::rebalancer::PauseReason::AccountReason(_) => { + Uint128::zero() + } + } + } + }; + + if !fee_amount.is_zero() { + return to_json_binary(&Some(Coin { + denom: fees.denom, + amount: fee_amount, + })); + } + + to_json_binary::>(&None) + } + QueryMsg::GetAllConfigs { start_after, limit } => { + let start_after = + start_after.map(|addr| Bound::inclusive(deps.api.addr_validate(&addr).unwrap())); + + let configs = CONFIGS + .range( + deps.storage, + start_after, + None, + cosmwasm_std::Order::Ascending, + ) + .take(limit.unwrap_or(50) as usize) + .collect::, StdError>>()?; + + to_json_binary(&configs) + } } } #[cfg_attr(not(feature = "library"), entry_point)] pub fn reply(_deps: DepsMut, _env: Env, msg: Reply) -> Result { - // Tick messages are dispatched with reply ID 0 and reply on - // error. If an error occurs, we ignore it but stop the parent - // message from failing, so the state change which moved the tick - // receiver to the end of the message queue gets committed. This - // prevents an erroring tick receiver from locking the clock. match msg.id { - REPLY_DEFAULT_REBALANCE => Ok(Response::default() - .add_attribute("method", "reply_on_error") - .add_attribute("error", msg.result.unwrap_err())), + REPLY_DEFAULT_REBALANCE => Ok(Response::default().add_event( + Event::new("fail-rebalance").add_attribute("error", msg.result.unwrap_err()), + )), _ => Err(ContractError::UnexpectedReplyId(msg.id)), } } + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + match msg { + MigrateMsg::NoStateChange {} => Ok(Response::default()), + } +} diff --git a/contracts/services/rebalancer/src/error.rs b/contracts/services/rebalancer/src/error.rs index be81aabe..297393f7 100644 --- a/contracts/services/rebalancer/src/error.rs +++ b/contracts/services/rebalancer/src/error.rs @@ -1,4 +1,5 @@ use cosmwasm_std::{CheckedFromRatioError, DecimalRangeExceeded, OverflowError, StdError}; +use cw_utils::PaymentError; use thiserror::Error; use valence_package::error::ValenceError; @@ -19,6 +20,9 @@ pub enum ContractError { #[error(transparent)] DecimalRangeExceeded(#[from] DecimalRangeExceeded), + #[error(transparent)] + PaymentError(#[from] PaymentError), + #[error("Account is already registered")] AccountAlreadyRegistered, @@ -92,4 +96,10 @@ pub enum ContractError { "Account balance doesn't meet the minimum balance requirement: Current: {0}, Minimum: {1}" )] InvalidAccountMinValue(String, String), + + #[error("Cannot remove base denom that doesn't exist: {0}")] + CannotRemoveBaseDenom(String), + + #[error("Cannot remove denom that doesn't exist: {0}")] + CannotRemoveDenom(String), } diff --git a/contracts/services/rebalancer/src/helpers.rs b/contracts/services/rebalancer/src/helpers.rs index d4b52d41..166f67a2 100644 --- a/contracts/services/rebalancer/src/helpers.rs +++ b/contracts/services/rebalancer/src/helpers.rs @@ -1,5 +1,5 @@ -use cosmwasm_std::{Decimal, Uint128}; -use valence_package::services::rebalancer::ParsedTarget; +use cosmwasm_std::{Decimal, Event, SubMsg, Uint128}; +use valence_package::services::rebalancer::{ParsedTarget, RebalancerConfig}; pub const TRADE_HARD_LIMIT: Decimal = Decimal::raw(5_u128); @@ -24,3 +24,26 @@ pub struct TargetHelper { /// The minimum value we can send to the auction pub auction_min_amount: Decimal, } + +pub struct RebalanceResponse { + pub config: RebalancerConfig, + pub msg: Option, + pub event: Event, + pub should_pause: bool, +} + +impl RebalanceResponse { + pub fn new( + config: RebalancerConfig, + msg: Option, + event: Event, + should_pause: bool, + ) -> Self { + Self { + config, + msg, + event, + should_pause, + } + } +} diff --git a/contracts/services/rebalancer/src/msg.rs b/contracts/services/rebalancer/src/msg.rs index cda5676b..678d2a4f 100644 --- a/contracts/services/rebalancer/src/msg.rs +++ b/contracts/services/rebalancer/src/msg.rs @@ -1,6 +1,14 @@ +use std::collections::HashSet; + use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Timestamp}; -use valence_package::services::rebalancer::{BaseDenom, RebalancerConfig, SystemRebalanceStatus}; +use cosmwasm_std::{Addr, Coin, Timestamp}; +use valence_macros::valence_service_query_msgs; +use valence_package::{ + services::rebalancer::{ + BaseDenom, PauseData, RebalancerConfig, ServiceFeeConfig, SystemRebalanceStatus, + }, + states::QueryFeeAction, +}; #[cw_serde] pub struct InstantiateMsg { @@ -10,17 +18,24 @@ pub struct InstantiateMsg { pub cycle_start: Timestamp, pub auctions_manager_addr: String, pub cycle_period: Option, + pub fees: ServiceFeeConfig, } +#[valence_service_query_msgs] #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - // /// Returns true if `address` is in the queue, and false - // /// otherwise. #[returns(RebalancerConfig)] GetConfig { addr: String }, + #[returns(Vec<(Addr, RebalancerConfig)>)] + GetAllConfigs { + start_after: Option, + limit: Option, + }, + #[returns(PauseData)] + GetPausedConfig { addr: String }, #[returns(SystemRebalanceStatus)] - GetSystemStatus {}, + GetSystemStatus, #[returns(WhitelistsResponse)] GetWhiteLists, #[returns(ManagersAddrsResponse)] @@ -30,12 +45,14 @@ pub enum QueryMsg { } #[cw_serde] -pub enum MigrateMsg {} +pub enum MigrateMsg { + NoStateChange {}, +} #[cw_serde] pub struct WhitelistsResponse { - pub denom_whitelist: Vec, - pub base_denom_whitelist: Vec, + pub denom_whitelist: HashSet, + pub base_denom_whitelist: HashSet, } #[cw_serde] diff --git a/contracts/services/rebalancer/src/rebalance.rs b/contracts/services/rebalancer/src/rebalance.rs index 56970096..bedc10e0 100644 --- a/contracts/services/rebalancer/src/rebalance.rs +++ b/contracts/services/rebalancer/src/rebalance.rs @@ -1,15 +1,19 @@ use std::{borrow::BorrowMut, collections::HashMap, str::FromStr}; -use auction_package::{helpers::GetPriceResponse, states::MIN_AUCTION_AMOUNT, Pair}; +use auction_package::{ + helpers::GetPriceResponse, + states::{MinAmount, MIN_AUCTION_AMOUNT}, + Pair, +}; use cosmwasm_std::{ - coin, to_json_binary, Addr, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, Order, Response, - StdError, SubMsg, Uint128, WasmMsg, + coin, to_json_binary, Addr, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, Event, Order, + Response, StdError, SubMsg, Uint128, WasmMsg, }; use cw_storage_plus::Bound; use valence_package::{ helpers::start_of_cycle, services::rebalancer::{ - ParsedPID, RebalancerConfig, SystemRebalanceStatus, TargetOverrideStrategy, + ParsedPID, PauseData, RebalancerConfig, SystemRebalanceStatus, TargetOverrideStrategy, }, signed_decimal::SignedDecimal, CLOSEST_TO_ONE_POSSIBLE, @@ -18,10 +22,10 @@ use valence_package::{ use crate::{ contract::{DEFAULT_SYSTEM_LIMIT, REPLY_DEFAULT_REBALANCE}, error::ContractError, - helpers::{TargetHelper, TradesTuple}, + helpers::{RebalanceResponse, TargetHelper, TradesTuple}, state::{ AUCTIONS_MANAGER_ADDR, BASE_DENOM_WHITELIST, CONFIGS, CYCLE_PERIOD, DENOM_WHITELIST, - SYSTEM_REBALANCE_STATUS, + PAUSED_CONFIGS, SYSTEM_REBALANCE_STATUS, }, }; @@ -105,6 +109,7 @@ pub fn execute_system_rebalance( let mut min_amount_limits: Vec<(String, Uint128)> = vec![]; let mut msgs: Vec = vec![]; + let mut events: Vec = vec![]; for res in configs { let Ok((account, config)) = res else { @@ -113,11 +118,6 @@ pub fn execute_system_rebalance( last_addr = Some(account.clone()); - // If account paused rebalancing, we continue - if config.is_paused.is_some() { - continue; - }; - // Do rebalance for the account, and construct the msg let rebalance_res = do_rebalance( deps.as_ref(), @@ -130,13 +130,34 @@ pub fn execute_system_rebalance( &prices, cycle_period, ); - let Ok((config, msg)) = rebalance_res else { + let Ok(RebalanceResponse { + config, + msg, + event, + should_pause, + }) = rebalance_res + else { continue; }; - // Rebalacing does edit some config fields that are needed for future rebalancing - // so we save the config here - CONFIGS.save(deps.branch().storage, account, &config)?; + // check if we should pause the account or not. + if should_pause { + // Save to the paused config + PAUSED_CONFIGS.save( + deps.storage, + account.clone(), + &PauseData::new_empty_balance(env, &config), + )?; + // remove from active configs + CONFIGS.remove(deps.storage, account); + } else { + // Rebalacing modify the config to include the latest data available to us + // as well as some rebalancing data we need for the next rebalance cycle + CONFIGS.save(deps.branch().storage, account, &config)?; + } + + // Add event to all events + events.push(event); if let Some(msg) = msg { msgs.push(msg); @@ -159,7 +180,14 @@ pub fn execute_system_rebalance( SYSTEM_REBALANCE_STATUS.save(deps.storage, &status)?; - Ok(Response::default().add_submessages(msgs)) + Ok(Response::default() + .add_submessages(msgs) + .add_events(events) + .add_event( + Event::new("rebalance_cycle") + .add_attribute("limit", limit.to_string()) + .add_attribute("cycled_over", configs_len.to_string()), + )) } /// Make sure the balance of the account is not zero and is above our minimum value @@ -190,7 +218,10 @@ pub fn do_rebalance( min_values: &HashMap, prices: &[(Pair, Decimal)], cycle_period: u64, -) -> Result<(RebalancerConfig, Option), ContractError> { +) -> Result { + // Create new event to show important data about the rebalance + let mut event = Event::new("rebalance-account").add_attribute("account", account.to_string()); + // get a vec of inputs for our calculations let (total_value, mut target_helpers) = get_inputs(deps, account, &config, prices)?; @@ -200,11 +231,14 @@ pub fn do_rebalance( .unwrap_or(&Uint128::zero()); if verify_account_balance(total_value.to_uint_floor(), min_value).is_err() { + event = event.add_attribute("pausing", true.to_string()); + // We pause the account if the account balance doesn't meet the minimum requirements - config.is_paused = Some(account.clone()); - return Ok((config, None)); + return Ok(RebalanceResponse::new(config, None, event, true)); }; + event = event.add_attribute("total_account_value", total_value.to_string()); + // Verify the targets, if we have a min_balance we need to do some extra steps // to make sure min_balance is accounted for in our calculations if config.has_min_balance { @@ -228,7 +262,15 @@ pub fn do_rebalance( set_auction_min_amounts(deps, auction_manager, &mut to_sell, min_amount_limits)?; // Generate the trades msgs, how much funds to send to what auction. - let msgs = generate_trades_msgs(deps, to_sell, to_buy, auction_manager, &config, total_value); + let (msgs, event) = generate_trades_msgs( + deps, + to_sell, + to_buy, + auction_manager, + &config, + total_value, + event, + ); // Construct the msg we need to execute on the account // Notice the atomic false, it means each trade msg (sending funds to specific pair auction) @@ -252,7 +294,7 @@ pub fn do_rebalance( // We edit config to save data for the next rebalance calculation config.last_rebalance = env.block.time; - Ok((config, Some(msg))) + Ok(RebalanceResponse::new(config, Some(msg), event, false)) } /// Set the min amount an auction is willing to accept for a specific token @@ -279,10 +321,13 @@ pub(crate) fn set_auction_min_amounts( auction_manager.clone(), sell_token.target.denom.clone(), )? { - Some(min_amount) => { + Some(MinAmount { + send: min_send_amount, + .. + }) => { sell_token.auction_min_amount = - Decimal::from_atomics(min_amount, 0)? / sell_token.price; - min_amount_limits.push((sell_token.target.denom.clone(), min_amount)); + Decimal::from_atomics(min_send_amount, 0)? / sell_token.price; + min_amount_limits.push((sell_token.target.denom.clone(), min_send_amount)); Ok(()) } None => Err(ContractError::NoMinAuctionAmountFound), @@ -352,7 +397,7 @@ fn get_inputs( } else { prices .iter() - .find(|(pair, _)| pair.1 == target.denom) + .find(|(pair, _)| pair.0 == config.base_denom && pair.1 == target.denom) // we can safely unwrap here as we are 100% sure we have all prices for the whitelisted targets .unwrap() .1 @@ -546,7 +591,8 @@ fn generate_trades_msgs( auction_manager: &Addr, config: &RebalancerConfig, total_value: Decimal, -) -> Vec { + mut event: Event, +) -> (Vec, Event) { let mut msgs: Vec = vec![]; // Get max tokens to sell as a value and not amount @@ -663,7 +709,7 @@ fn generate_trades_msgs( if token_sell.value_to_trade >= token_buy.value_to_trade { token_sell.value_to_trade -= token_buy.value_to_trade; - let coin = coin( + let token = coin( (token_buy.value_to_trade * token_sell.price) .to_uint_ceil() .u128(), @@ -672,14 +718,18 @@ fn generate_trades_msgs( token_buy.value_to_trade = Decimal::zero(); - let Ok(msg) = construct_msg(deps, auction_manager, pair, coin) else { + event = event + .clone() + .add_attribute("trade", format!("Coin: {token} | Pair: {pair:?}")); + + let Ok(msg) = construct_msg(deps, auction_manager, pair, token) else { return; }; msgs.push(msg); } else { token_buy.value_to_trade -= token_sell.value_to_trade; - let coin = coin( + let token = coin( (token_sell.value_to_trade * token_sell.price) .to_uint_ceil() .u128(), @@ -688,7 +738,11 @@ fn generate_trades_msgs( token_sell.value_to_trade = Decimal::zero(); - let Ok(msg) = construct_msg(deps, auction_manager, pair, coin) else { + event = event + .clone() + .add_attribute("trade", format!("Coin: {token} | Pair: {pair:?}")); + + let Ok(msg) = construct_msg(deps, auction_manager, pair, token) else { return; }; @@ -697,5 +751,5 @@ fn generate_trades_msgs( }); }); - msgs + (msgs, event) } diff --git a/contracts/services/rebalancer/src/state.rs b/contracts/services/rebalancer/src/state.rs index e60d3b8d..4a685391 100644 --- a/contracts/services/rebalancer/src/state.rs +++ b/contracts/services/rebalancer/src/state.rs @@ -1,11 +1,15 @@ +use std::collections::HashSet; + use cosmwasm_std::Addr; use cw_storage_plus::{Item, Map}; -use valence_package::services::rebalancer::{BaseDenom, RebalancerConfig, SystemRebalanceStatus}; +use valence_package::services::rebalancer::{ + BaseDenom, PauseData, RebalancerConfig, SystemRebalanceStatus, +}; /// All available denom to target (denom whitelist) -pub(crate) const DENOM_WHITELIST: Item> = Item::new("token_whitelist"); +pub(crate) const DENOM_WHITELIST: Item> = Item::new("token_whitelist"); /// Base denom whitelist -pub(crate) const BASE_DENOM_WHITELIST: Item> = Item::new("base_token_whitelist"); +pub(crate) const BASE_DENOM_WHITELIST: Item> = Item::new("base_token_whitelist"); /// Storage to keep all configs of all registered accounts pub(crate) const CONFIGS: Map = Map::new("configs"); /// Storage to keep the current status of the system rebalance @@ -15,3 +19,5 @@ pub(crate) const SYSTEM_REBALANCE_STATUS: Item = pub(crate) const AUCTIONS_MANAGER_ADDR: Item = Item::new("auctions_manager_addr"); pub(crate) const CYCLE_PERIOD: Item = Item::new("cycle_period"); + +pub const PAUSED_CONFIGS: Map = Map::new("paused_configs"); diff --git a/contracts/services_manager/schema/services-manager.json b/contracts/services_manager/schema/services-manager.json index 180c55f2..87b7aa6c 100644 --- a/contracts/services_manager/schema/services-manager.json +++ b/contracts/services_manager/schema/services-manager.json @@ -1,6 +1,6 @@ { "contract_name": "services-manager", - "contract_version": "0.1.0", + "contract_version": "1.2.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -123,6 +123,12 @@ "pause_for": { "type": "string" }, + "reason": { + "type": [ + "string", + "null" + ] + }, "service_name": { "$ref": "#/definitions/ValenceServices" } @@ -160,10 +166,17 @@ }, { "description": "Message to aprprove the admin change if you are the new admin", - "type": "string", - "enum": [ + "type": "object", + "required": [ "approve_admin_change" - ] + ], + "properties": { + "approve_admin_change": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false }, { "description": "Add admin messages", @@ -267,7 +280,7 @@ "additionalProperties": false }, { - "description": "Update a service address", + "description": "Update a service name to address data", "type": "object", "required": [ "update_service" @@ -373,6 +386,27 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw" + ], + "properties": { + "withdraw": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, @@ -508,9 +542,46 @@ } }, "additionalProperties": false + }, + { + "description": "Get the service fee of a service", + "type": "object", + "required": [ + "get_service_fee" + ], + "properties": { + "get_service_fee": { + "type": "object", + "required": [ + "account", + "action", + "service" + ], + "properties": { + "account": { + "type": "string" + }, + "action": { + "$ref": "#/definitions/QueryFeeAction" + }, + "service": { + "$ref": "#/definitions/ValenceServices" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { + "QueryFeeAction": { + "type": "string", + "enum": [ + "register", + "resume" + ] + }, "ValenceServices": { "description": "An enum that represent all services that available for valence accounts", "oneOf": [ @@ -570,6 +641,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" }, + "get_service_fee": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_Coin", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ], + "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" + } + } + }, "is_service": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ValenceServices", diff --git a/contracts/services_manager/src/contract.rs b/contracts/services_manager/src/contract.rs index e6e44a58..63aabfe5 100644 --- a/contracts/services_manager/src/contract.rs +++ b/contracts/services_manager/src/contract.rs @@ -3,7 +3,8 @@ use std::collections::HashSet; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, + to_json_binary, Addr, BankMsg, Binary, Coin, Deps, DepsMut, Env, MessageInfo, Response, + StdError, StdResult, }; use cw2::set_contract_version; use cw_storage_plus::Bound; @@ -15,7 +16,7 @@ use valence_package::states::ADMIN; use crate::error::ContractError; use crate::helpers::{get_service_addr, save_service}; -use crate::msg::InstantiateMsg; +use crate::msg::{InstantiateMsg, MigrateMsg}; use crate::state::{ACCOUNT_WHITELISTED_CODE_IDS, ADDR_TO_SERVICES, SERVICES_TO_ADDR}; const CONTRACT_NAME: &str = "crates.io:services-manager"; @@ -51,7 +52,7 @@ pub fn execute( ServicesManagerExecuteMsg::Admin(admin_msg) => { admin::handle_msg(deps, env, info, admin_msg) } - ServicesManagerExecuteMsg::ApproveAdminChange => { + ServicesManagerExecuteMsg::ApproveAdminChange {} => { Ok(approve_admin_change(deps, &env, &info)?) } ServicesManagerExecuteMsg::RegisterToService { service_name, data } => { @@ -67,37 +68,32 @@ pub fn execute( let service_addr = get_service_addr(deps.as_ref(), service_name.to_string())?; - let msg = - service_name.get_register_msg(info.sender.as_ref(), service_addr.as_ref(), data)?; + let msg = service_name.get_register_msg(&info, service_addr.as_ref(), data)?; Ok(Response::default().add_message(msg)) } ServicesManagerExecuteMsg::DeregisterFromService { service_name } => { let service_addr = get_service_addr(deps.as_ref(), service_name.to_string())?; - let msg = - service_name.get_deregister_msg(info.sender.as_ref(), service_addr.as_ref())?; + let msg = service_name.get_deregister_msg(&info, service_addr.as_ref())?; Ok(Response::default().add_message(msg)) } ServicesManagerExecuteMsg::UpdateService { service_name, data } => { let service_addr = get_service_addr(deps.as_ref(), service_name.to_string())?; - let msg = - service_name.get_update_msg(info.sender.as_ref(), service_addr.as_ref(), data)?; + let msg = service_name.get_update_msg(&info, service_addr.as_ref(), data)?; Ok(Response::default().add_message(msg)) } ServicesManagerExecuteMsg::PauseService { service_name, pause_for, + reason, } => { let service_addr = get_service_addr(deps.as_ref(), service_name.to_string())?; - let msg = service_name.get_pause_msg( - pause_for, - info.sender.as_ref(), - service_addr.as_ref(), - )?; + let msg = + service_name.get_pause_msg(pause_for, &info, service_addr.as_ref(), reason)?; Ok(Response::default().add_message(msg)) } @@ -106,11 +102,7 @@ pub fn execute( resume_for, } => { let service_addr = get_service_addr(deps.as_ref(), service_name.to_string())?; - let msg = service_name.get_resume_msg( - resume_for, - info.sender.as_ref(), - service_addr.as_ref(), - )?; + let msg = service_name.get_resume_msg(resume_for, &info, service_addr.as_ref())?; Ok(Response::default().add_message(msg)) } @@ -129,7 +121,7 @@ mod admin { pub fn handle_msg( deps: DepsMut, - _env: Env, + env: Env, info: MessageInfo, msg: ServicesManagerAdminMsg, ) -> Result { @@ -188,6 +180,16 @@ mod admin { Ok(start_admin_change(deps, &info, &addr, expiration)?) } ServicesManagerAdminMsg::CancelAdminChange => Ok(cancel_admin_change(deps, &info)?), + ServicesManagerAdminMsg::Withdraw { denom } => { + let amount = deps.querier.query_balance(env.contract.address, denom)?; + + let msg = BankMsg::Send { + to_address: info.sender.to_string(), // sender must be admin + amount: vec![amount], + }; + + Ok(Response::default().add_message(msg)) + } } } } @@ -221,6 +223,20 @@ pub fn query(deps: Deps, _env: Env, msg: ServicesManagerQueryMsg) -> StdResult { + let service_addr = SERVICES_TO_ADDR.load(deps.storage, service.to_string())?; + + let fee = deps.querier.query_wasm_smart::>( + service_addr, + &rebalancer::msg::QueryMsg::GetServiceFee { account, action }, + )?; + + to_json_binary(&fee) + } ServicesManagerQueryMsg::GetRebalancerConfig { account } => { let service_addr = SERVICES_TO_ADDR.load(deps.storage, "rebalancer".to_string())?; let config = deps.querier.query_wasm_smart::( @@ -232,3 +248,12 @@ pub fn query(deps: Deps, _env: Env, msg: ServicesManagerQueryMsg) -> StdResult Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + match msg { + MigrateMsg::NoStateChange {} => Ok(Response::default()), + } +} diff --git a/contracts/services_manager/src/msg.rs b/contracts/services_manager/src/msg.rs index 08467375..c5b92c79 100644 --- a/contracts/services_manager/src/msg.rs +++ b/contracts/services_manager/src/msg.rs @@ -6,4 +6,6 @@ pub struct InstantiateMsg { } #[cw_serde] -pub enum MigrateMsg {} +pub enum MigrateMsg { + NoStateChange {}, +} diff --git a/devtools/optimize.sh b/devtools/optimize.sh index 0ad7438c..f5e3ab5b 100755 --- a/devtools/optimize.sh +++ b/devtools/optimize.sh @@ -5,11 +5,11 @@ if [[ $(uname -m) =~ "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.14.0 + cosmwasm/optimizer-arm64:0.15.0 else docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/workspace-optimizer:0.14.0 + cosmwasm/optimizer:0.15.0 fi diff --git a/devtools/schema.sh b/devtools/schema.sh new file mode 100755 index 00000000..63c53553 --- /dev/null +++ b/devtools/schema.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +CONTRACTS_DIR=$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )/../contracts +echo $CONTRACTS_DIR + +# Schema for account +CONTRACT="account" +cd "$CONTRACTS_DIR/$CONTRACT" || { echo "No $CONTRACT dir" ; exit 1; } +cargo schema || { echo "Failed doing schema $CONTRACT" ; exit 1; } +rm -r schema/raw + +# Schema for rebalancer +CONTRACT="rebalancer" +cd "$CONTRACTS_DIR/services/$CONTRACT" || { echo "No $CONTRACT dir" ; exit 1; } +cargo schema || { echo "Failed doing schema $CONTRACT" ; exit 1; } +rm -r schema/raw + +# Schema for rebalancer +CONTRACT="services_manager" +cd "$CONTRACTS_DIR/$CONTRACT" || { echo "No $CONTRACT dir" ; exit 1; } +cargo schema || { echo "Failed doing schema $CONTRACT" ; exit 1; } +rm -r schema/raw + +# Schema for auction +CONTRACT="auction" +cd "$CONTRACTS_DIR/auction/$CONTRACT" || { echo "No $CONTRACT dir" ; exit 1; } +cargo schema || { echo "Failed doing schema $CONTRACT" ; exit 1; } +rm -r schema/raw + +# Schema for auction +CONTRACT="auctions_manager" +cd "$CONTRACTS_DIR/auction/$CONTRACT" || { echo "No $CONTRACT dir" ; exit 1; } +cargo schema || { echo "Failed doing schema $CONTRACT" ; exit 1; } +rm -r schema/raw + +# Schema for auction +CONTRACT="price_oracle" +cd "$CONTRACTS_DIR/auction/$CONTRACT" || { echo "No $CONTRACT dir" ; exit 1; } +cargo schema || { echo "Failed doing schema $CONTRACT" ; exit 1; } +rm -r schema/raw \ No newline at end of file diff --git a/packages/auction-package/src/lib.rs b/packages/auction-package/src/lib.rs index de54f71e..6b5ed9f3 100644 --- a/packages/auction-package/src/lib.rs +++ b/packages/auction-package/src/lib.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use cosmwasm_schema::cw_serde; use cosmwasm_std::{Decimal, Timestamp}; @@ -14,8 +16,8 @@ pub const CLOSEST_TO_ONE_POSSIBLE: u64 = 9999; #[cw_serde] pub struct AuctionStrategy { - pub start_price_perc: u64, // BPS - pub end_price_perc: u64, // BPS + pub start_price_perc: u64, // BPS // 1.5 + pub end_price_perc: u64, // BPS // 0.01 } impl AuctionStrategy { @@ -52,3 +54,9 @@ pub struct Price { pub price: Decimal, pub time: Timestamp, } + +impl Display for Price { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Price: {}, Time: {}", self.price, self.time) + } +} diff --git a/packages/auction-package/src/msgs.rs b/packages/auction-package/src/msgs.rs index 3f6588e2..eeda1781 100644 --- a/packages/auction-package/src/msgs.rs +++ b/packages/auction-package/src/msgs.rs @@ -1,14 +1,22 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Uint128}; +use cosmwasm_std::Addr; use crate::{ helpers::{AuctionConfig, GetPriceResponse}, + states::MinAmount, Pair, }; #[cw_serde] #[derive(QueryResponses)] pub enum AuctionsManagerQueryMsg { + /// Get the price of a specific pair + #[returns(Vec<(Pair, Addr)>)] + GetPairs { + start_after: Option, + limit: Option, + }, + /// Get the price of a specific pair #[returns(GetPriceResponse)] GetPrice { pair: Pair }, @@ -25,7 +33,7 @@ pub enum AuctionsManagerQueryMsg { #[returns(Addr)] GetOracleAddr, - #[returns(Uint128)] + #[returns(MinAmount)] GetMinLimit { denom: String }, #[returns(Addr)] diff --git a/packages/auction-package/src/pair.rs b/packages/auction-package/src/pair.rs index 9a89f613..fcaed35b 100644 --- a/packages/auction-package/src/pair.rs +++ b/packages/auction-package/src/pair.rs @@ -1,6 +1,7 @@ -use std::fmt; +use std::fmt::{self, Display}; -use cw_storage_plus::PrimaryKey; +use cosmwasm_std::{from_json, StdError, StdResult}; +use cw_storage_plus::{KeyDeserialize, PrimaryKey}; use serde::{ de, ser::{self, SerializeSeq}, @@ -18,6 +19,12 @@ use crate::error::AuctionError; #[schemars(crate = "::cosmwasm_schema::schemars")] pub struct Pair(pub String, pub String); +impl Display for Pair { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}/{}", self.0, self.1) + } +} + impl<'a> PrimaryKey<'a> for Pair { type Prefix = >::Prefix; type SubPrefix = >::SubPrefix; @@ -52,6 +59,12 @@ impl From for (String, String) { } } +impl From> for Pair { + fn from(value: Vec) -> Self { + from_json(value).expect("couldn't parse Pair from Vec") + } +} + /// Serializes as a decimal string impl Serialize for Pair { fn serialize(&self, serializer: S) -> Result @@ -98,6 +111,27 @@ impl<'de> de::Visitor<'de> for PairVisitor { } } +fn parse_length(value: &[u8]) -> StdResult { + Ok(u16::from_be_bytes( + value + .try_into() + .map_err(|_| StdError::generic_err("Could not read 2 byte length"))?, + ) + .into()) +} + +impl KeyDeserialize for Pair { + type Output = Pair; + + fn from_vec(mut value: Vec) -> cosmwasm_std::StdResult { + let mut tu = value.split_off(2); + let t_len = parse_length(&value)?; + let u = tu.split_off(t_len); + + Ok((String::from_vec(tu)?, String::from_vec(u)?).into()) + } +} + #[cfg(test)] mod test { #[test] diff --git a/packages/auction-package/src/states.rs b/packages/auction-package/src/states.rs index fe7e53e0..22be5a65 100644 --- a/packages/auction-package/src/states.rs +++ b/packages/auction-package/src/states.rs @@ -21,7 +21,9 @@ pub const TWAP_PRICES: Item> = Item::new("twap_prices"); /// Chain halt config pub const CHAIN_HALT_CONFIG: Item = Item::new("ch_config"); /// The min amount allowed to send to auction per token -pub const MIN_AUCTION_AMOUNT: Map = Map::new("min_auction_amount"); +/// Denom -> MinAmount +pub const MIN_AUCTION_AMOUNT_V0: Map = Map::new("min_auction_amount"); +pub const MIN_AUCTION_AMOUNT: Map = Map::new("min_auction_amount_v1"); pub const ADMIN_CHANGE: Item = Item::new("admin_change"); @@ -30,3 +32,23 @@ pub struct AdminChange { pub addr: Addr, pub expiration: Expiration, } + +#[cw_serde] +pub struct MinAmount { + /// Minimum amount that is allowed to send to the auction + pub send: Uint128, + /// Minimum amount that auction can start from + /// + /// If auction amount is below this amount, it will not start the auction + /// and will refund sent funds back to the sender + pub start_auction: Uint128, +} + +impl Default for MinAmount { + fn default() -> Self { + Self { + send: Uint128::zero(), + start_auction: Uint128::zero(), + } + } +} diff --git a/packages/valence-macros/src/lib.rs b/packages/valence-macros/src/lib.rs index f4910091..77c85d15 100644 --- a/packages/valence-macros/src/lib.rs +++ b/packages/valence-macros/src/lib.rs @@ -33,7 +33,8 @@ pub fn valence_service_manager_execute_msgs( /// Only callable by the account or the trustee PauseService { service_name: ValenceServices, - pause_for: String + pause_for: String, + reason: Option }, /// Resume service for the pause_for address /// Only callable by the account or the trustee @@ -42,7 +43,7 @@ pub fn valence_service_manager_execute_msgs( resume_for: String }, /// Message to aprprove the admin change if you are the new admin - ApproveAdminChange, + ApproveAdminChange {}, } }; @@ -71,6 +72,7 @@ pub fn valence_account_execute_msgs(metadata: TokenStream, input: TokenStream) - /// Pause service PauseService { service_name: ValenceServices, + reason: Option }, /// Resume service ResumeService { @@ -96,8 +98,8 @@ pub fn valence_account_execute_msgs(metadata: TokenStream, input: TokenStream) - addr: String, expiration: Expiration, }, - CancelAdminChange, - ApproveAdminChange, + CancelAdminChange {}, + ApproveAdminChange {}, } }; @@ -127,6 +129,7 @@ pub fn valence_service_execute_msgs(metadata: TokenStream, input: TokenStream) - Pause { pause_for: String, sender: String, + reason: Option }, /// Resume the service Resume { @@ -164,7 +167,7 @@ pub fn valence_services_manager_query_msgs( input: TokenStream, ) -> TokenStream { let quote = quote! { - enum ServiceQueryMsg { + enum ServicesManagerQueryMsg { /// Check if address is of a service #[returns(ValenceServices)] IsService { @@ -184,6 +187,30 @@ pub fn valence_services_manager_query_msgs( start_from: Option, limit: Option }, + /// Get the service fee of a service + #[returns(Option)] + GetServiceFee { + account: String, + service: ValenceServices, + action: QueryFeeAction, + }, + } + }; + + merge_variants(metadata, input, quote.into()) +} + +/// Macro to add queries to services manager +#[proc_macro_attribute] +pub fn valence_service_query_msgs(metadata: TokenStream, input: TokenStream) -> TokenStream { + let quote = quote! { + enum ServiceQueryMsg { + /// Get the service fee of a service + #[returns(Option)] + GetServiceFee { + account: String, + action: QueryFeeAction, + }, } }; diff --git a/packages/valence-package/src/error.rs b/packages/valence-package/src/error.rs index b3b202fc..44656802 100644 --- a/packages/valence-package/src/error.rs +++ b/packages/valence-package/src/error.rs @@ -1,4 +1,5 @@ use cosmwasm_std::{DecimalRangeExceeded, StdError}; +use cw_utils::PaymentError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -9,6 +10,9 @@ pub enum ValenceError { #[error(transparent)] DecimalRangeExceeded(#[from] DecimalRangeExceeded), + #[error(transparent)] + PaymentError(#[from] PaymentError), + #[error("Sender is not a service!")] UnauthorizedService, @@ -24,8 +28,8 @@ pub enum ValenceError { #[error("This services expects data on register: {0}")] MissingRegisterData(String), - #[error("Couldn't parse binary into: {0}")] - RegisterDataParseError(String), + #[error("Service: {service}, Couldn't parse binary into: {ty}")] + DataParseError { service: String, ty: String }, #[error("PID values cannot be more then 1")] PIDErrorOver, @@ -41,4 +45,7 @@ pub enum ValenceError { #[error("Change admin is expired")] AdminChangeExpired, + + #[error("Must pay the registration fee of: {0}{1}")] + MustPayRegistrationFee(String, String), } diff --git a/packages/valence-package/src/helpers.rs b/packages/valence-package/src/helpers.rs index fa3380e7..01ebf40b 100644 --- a/packages/valence-package/src/helpers.rs +++ b/packages/valence-package/src/helpers.rs @@ -1,6 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - to_json_binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, Timestamp, WasmMsg, + to_json_binary, Addr, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, Timestamp, + WasmMsg, }; use cw_utils::Expiration; @@ -29,11 +30,20 @@ pub enum OptionalField { pub fn forward_to_services_manager( manager_addr: String, msg: ServicesManagerExecuteMsg, +) -> Result { + forward_to_services_manager_with_funds(manager_addr, msg, vec![]) +} + +/// Forward the message to the services manager contract with funds +pub fn forward_to_services_manager_with_funds( + manager_addr: String, + msg: ServicesManagerExecuteMsg, + funds: Vec, ) -> Result { let msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: manager_addr, msg: to_json_binary(&msg)?, - funds: vec![], + funds, }); Ok(Response::default().add_message(msg)) } @@ -65,16 +75,17 @@ pub fn verify_admin(deps: Deps, info: &MessageInfo) -> Result<(), ValenceError> } /// Verify the sender is the services manager -pub fn verify_services_manager(deps: Deps, info: &MessageInfo) -> Result<(), ValenceError> { - if SERVICES_MANAGER.load(deps.storage)? != info.sender { +pub fn verify_services_manager(deps: Deps, info: &MessageInfo) -> Result { + let manager_addr = SERVICES_MANAGER.load(deps.storage)?; + if manager_addr != info.sender { return Err(ValenceError::NotServicesManager {}); } - Ok(()) + Ok(manager_addr) } -/// Get the timestomt of the start of the day (00:00 midnight) +/// Get the timestamp of the start of the cycle (if cycle is a day - 00:00 midnight) pub fn start_of_cycle(time: Timestamp, cycle: u64) -> Timestamp { - let leftover = time.seconds() % cycle; // How much leftover from the start of the day (mid night UTC) + let leftover = time.seconds() % cycle; // How much leftover from the start of the cycle (if cycle is a day - mid night UTC) time.minus_seconds(leftover) } diff --git a/packages/valence-package/src/msgs/core_execute.rs b/packages/valence-package/src/msgs/core_execute.rs index 258e963e..7ebe1896 100644 --- a/packages/valence-package/src/msgs/core_execute.rs +++ b/packages/valence-package/src/msgs/core_execute.rs @@ -31,7 +31,7 @@ pub enum ServicesManagerAdminMsg { name: ValenceServices, addr: String, }, - /// Update a service address + /// Update a service name to address data UpdateService { name: ValenceServices, addr: String, @@ -49,4 +49,7 @@ pub enum ServicesManagerAdminMsg { expiration: Expiration, }, CancelAdminChange, + Withdraw { + denom: String, + }, } diff --git a/packages/valence-package/src/msgs/core_query.rs b/packages/valence-package/src/msgs/core_query.rs index 1d3e15d0..a8745192 100644 --- a/packages/valence-package/src/msgs/core_query.rs +++ b/packages/valence-package/src/msgs/core_query.rs @@ -1,6 +1,7 @@ use crate::services::ValenceServices; +use crate::states::QueryFeeAction; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Addr; +use cosmwasm_std::{Addr, Coin}; use valence_macros::valence_services_manager_query_msgs; /// Services manager query messages diff --git a/packages/valence-package/src/services/mod.rs b/packages/valence-package/src/services/mod.rs index 56a5029a..c1b30717 100644 --- a/packages/valence-package/src/services/mod.rs +++ b/packages/valence-package/src/services/mod.rs @@ -1,9 +1,9 @@ pub mod rebalancer; -use std::{fmt, str::FromStr}; +use std::{any::type_name, fmt, str::FromStr}; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{from_json, to_json_binary, Binary, CosmosMsg, Empty, WasmMsg}; +use cosmwasm_std::{from_json, to_json_binary, Binary, CosmosMsg, Empty, MessageInfo, WasmMsg}; use valence_macros::valence_service_execute_msgs; use crate::error::ValenceError; @@ -38,8 +38,9 @@ impl ValenceServices { data: Binary, service_name: &str, ) -> Result { - from_json::(&data).map_err(|_| { - ValenceError::RegisterDataParseError(format!("{service_name} register msg",)) + from_json::(&data).map_err(|_| ValenceError::DataParseError { + service: service_name.to_string(), + ty: type_name::().to_string(), }) } @@ -48,7 +49,7 @@ impl ValenceServices { /// Return `Some(...)` with the required register message if the service requires it pub fn get_register_msg( &self, - sender: &str, + info: &MessageInfo, contract_addr: &str, data: Option, ) -> Result { @@ -62,11 +63,11 @@ impl ValenceServices { contract_addr: contract_addr.to_string(), msg: to_json_binary( &GeneralServiceExecuteMsg::::Register { - register_for: sender.to_string(), + register_for: info.sender.to_string(), data: Some(register), }, )?, - funds: vec![], + funds: info.funds.clone(), }; Ok(msg.into()) @@ -91,7 +92,7 @@ impl ValenceServices { /// Return `Some(...)` with the required register message if the service requires it pub fn get_update_msg( &self, - sender: &str, + info: &MessageInfo, contract_addr: &str, data: Binary, ) -> Result { @@ -103,11 +104,11 @@ impl ValenceServices { contract_addr: contract_addr.to_string(), msg: to_json_binary( &GeneralServiceExecuteMsg::::Update { - update_for: sender.to_string(), + update_for: info.sender.to_string(), data: update, }, )?, - funds: vec![], + funds: info.funds.clone(), }; Ok(msg.into()) @@ -132,15 +133,15 @@ impl ValenceServices { /// can be switched to work indevidually if needed. pub fn get_deregister_msg( &self, - sender: &str, + info: &MessageInfo, contract_addr: &str, ) -> Result { Ok(WasmMsg::Execute { contract_addr: contract_addr.to_string(), msg: to_json_binary(&GeneralServiceExecuteMsg::::Deregister { - deregister_for: sender.to_string(), + deregister_for: info.sender.to_string(), })?, - funds: vec![], + funds: info.funds.clone(), } .into()) } @@ -148,16 +149,18 @@ impl ValenceServices { pub fn get_pause_msg( &self, pause_for: String, - sender: &str, + info: &MessageInfo, contract_addr: &str, + reason: Option, ) -> Result { Ok(WasmMsg::Execute { contract_addr: contract_addr.to_string(), msg: to_json_binary(&GeneralServiceExecuteMsg::::Pause { pause_for, - sender: sender.to_string(), + sender: info.sender.to_string(), + reason, })?, - funds: vec![], + funds: info.funds.clone(), } .into()) } @@ -165,16 +168,16 @@ impl ValenceServices { pub fn get_resume_msg( &self, resume_for: String, - sender: &str, + info: &MessageInfo, contract_addr: &str, ) -> Result { Ok(WasmMsg::Execute { contract_addr: contract_addr.to_string(), msg: to_json_binary(&GeneralServiceExecuteMsg::::Resume { resume_for, - sender: sender.to_string(), + sender: info.sender.to_string(), })?, - funds: vec![], + funds: info.funds.clone(), } .into()) } diff --git a/packages/valence-package/src/services/rebalancer.rs b/packages/valence-package/src/services/rebalancer.rs index d43010c3..af92370d 100644 --- a/packages/valence-package/src/services/rebalancer.rs +++ b/packages/valence-package/src/services/rebalancer.rs @@ -1,7 +1,9 @@ use auction_package::Pair; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Decimal, Timestamp, Uint128}; -use cw_utils::Expiration; +use cosmwasm_std::{ + coins, Addr, Api, BankMsg, CosmosMsg, Decimal, Env, MessageInfo, Timestamp, Uint128, +}; +use cw_utils::{must_pay, Expiration}; use std::borrow::Borrow; use std::hash::Hash; use std::{collections::HashSet, hash::Hasher, str::FromStr}; @@ -40,6 +42,9 @@ pub enum RebalancerAdminMsg { UpdateCyclePeriod { period: u64, }, + UpdateFess { + fees: ServiceFeeConfig, + }, StartAdminChange { addr: String, expiration: Expiration, @@ -69,12 +74,12 @@ pub struct RebalancerUpdateData { pub base_denom: Option, pub targets: HashSet, pub pid: Option, - pub max_limit: Option, // BPS + pub max_limit_bps: Option>, // BPS pub target_override_strategy: Option, } impl RebalancerData { - pub fn to_config(self) -> Result { + pub fn to_config(self, api: &dyn Api) -> Result { let max_limit = if let Some(max_limit) = self.max_limit_bps { // Suggested by clippy to check for a range of 1-10000 if !(1..=10000).contains(&max_limit) { @@ -87,10 +92,10 @@ impl RebalancerData { }; let has_min_balance = self.targets.iter().any(|t| t.min_balance.is_some()); + let trustee = self.trustee.map(|a| api.addr_validate(&a)).transpose()?; Ok(RebalancerConfig { - is_paused: None, - trustee: self.trustee, + trustee, base_denom: self.base_denom, targets: self.targets.into_iter().map(|t| t.into()).collect(), pid: self.pid.into_parsed()?, @@ -104,10 +109,8 @@ impl RebalancerData { #[cw_serde] pub struct RebalancerConfig { - /// Is_paused holds the pauser if it is paused, None if its not paused - pub is_paused: Option, /// the address that can pause and resume the service - pub trustee: Option, + pub trustee: Option, /// The base denom we will be calculating everything based on pub base_denom: String, /// A vector of targets to rebalance for this account @@ -122,6 +125,47 @@ pub struct RebalancerConfig { pub target_override_strategy: TargetOverrideStrategy, } +#[cw_serde] +pub struct PauseData { + pub pauser: Addr, + pub reason: PauseReason, + pub config: RebalancerConfig, +} + +impl PauseData { + pub fn new(pauser: Addr, reason: String, config: &RebalancerConfig) -> Self { + Self { + pauser, + reason: PauseReason::AccountReason(reason), + config: config.clone(), + } + } + + pub fn new_empty_balance(env: &Env, config: &RebalancerConfig) -> Self { + Self { + pauser: env.contract.address.clone(), + reason: PauseReason::EmptyBalance, + config: config.clone(), + } + } +} + +#[cw_serde] +pub enum PauseReason { + /// This reason can only be called if the rebalancer is pausing the account because it + /// has an empty balance. + EmptyBalance, + /// This reason is given by the user/account, he might forget why he paused the account + /// this will remind him of it. + AccountReason(String), +} + +impl PauseReason { + pub fn is_empty_balance(&self) -> bool { + matches!(self, PauseReason::EmptyBalance) + } +} + /// The strategy we will use when overriding targets #[cw_serde] pub enum TargetOverrideStrategy { @@ -248,12 +292,128 @@ impl ParsedPID { } } -#[cw_serde] +#[derive( + ::cosmwasm_schema::serde::Serialize, + ::cosmwasm_schema::serde::Deserialize, + ::std::clone::Clone, + ::std::fmt::Debug, + ::cosmwasm_schema::schemars::JsonSchema, +)] +#[allow(clippy::derive_partial_eq_without_eq)] // Allow users of `#[cw_serde]` to not implement Eq without clippy complaining +#[serde(deny_unknown_fields, crate = "::cosmwasm_schema::serde")] +#[schemars(crate = "::cosmwasm_schema::schemars")] +#[derive(Eq)] pub struct BaseDenom { pub denom: String, pub min_balance_limit: Uint128, } +impl BaseDenom { + pub fn new_empty(denom: impl Into) -> Self { + Self { + denom: denom.into(), + min_balance_limit: Uint128::zero(), + } + } +} + +impl PartialEq for BaseDenom { + fn eq(&self, other: &BaseDenom) -> bool { + self.denom == other.denom + } +} + +impl Hash for BaseDenom { + fn hash(&self, state: &mut H) { + self.denom.hash(state); + } +} + +impl Borrow for BaseDenom { + fn borrow(&self) -> &String { + &self.denom + } +} + +#[cw_serde] +pub struct ServiceFeeConfig { + pub denom: String, + pub register_fee: Uint128, + pub resume_fee: Uint128, +} + +impl ServiceFeeConfig { + /// We verify the registration fee is paid and generate msg to send it to the manager + pub fn handle_registration_fee( + self, + info: &MessageInfo, + manager_addr: &Addr, + ) -> Result, ValenceError> { + let mut msgs: Vec = Vec::with_capacity(1); + + if !self.register_fee.is_zero() { + let paid = must_pay(info, &self.denom).map_err(|_| { + ValenceError::MustPayRegistrationFee( + self.register_fee.to_string(), + self.denom.clone(), + ) + })?; + + if self.register_fee != paid { + return Err(ValenceError::MustPayRegistrationFee( + self.register_fee.to_string(), + self.denom.clone(), + )); + } + + msgs.push(self.generate_transfer_msg(paid, manager_addr).into()); + } + + Ok(msgs) + } + + /// We verify the resume fee is paid if needed and generate msg to send it to the manager + pub fn handle_resume_fee( + self, + info: &MessageInfo, + manager_addr: &Addr, + reason: PauseReason, + ) -> Result, ValenceError> { + let mut msgs: Vec = Vec::with_capacity(1); + + if !self.resume_fee.is_zero() { + if !reason.is_empty_balance() { + return Ok(msgs); + } + + let paid = must_pay(info, &self.denom).map_err(|_| { + ValenceError::MustPayRegistrationFee( + self.resume_fee.to_string(), + self.denom.clone(), + ) + })?; + + if self.resume_fee != paid { + return Err(ValenceError::MustPayRegistrationFee( + self.resume_fee.to_string(), + self.denom.clone(), + )); + } + + msgs.push(self.generate_transfer_msg(paid, manager_addr).into()); + } + + Ok(msgs) + } + + fn generate_transfer_msg(self, amount: Uint128, manager_addr: &Addr) -> BankMsg { + BankMsg::Send { + to_address: manager_addr.to_string(), + amount: coins(amount.u128(), self.denom), + } + } +} + #[cfg(test)] mod test { use crate::error::ValenceError; diff --git a/packages/valence-package/src/states.rs b/packages/valence-package/src/states.rs index 4f7652c9..84624fa6 100644 --- a/packages/valence-package/src/states.rs +++ b/packages/valence-package/src/states.rs @@ -3,6 +3,8 @@ use cosmwasm_std::Addr; use cw_storage_plus::Item; use cw_utils::Expiration; +use crate::services::rebalancer::ServiceFeeConfig; + /// State to store the address of the services manager contract. pub const SERVICES_MANAGER: Item = Item::new("services_manager"); @@ -17,3 +19,11 @@ pub struct AdminChange { pub addr: Addr, pub expiration: Expiration, } + +pub const SERVICE_FEE_CONFIG: Item = Item::new("fee_config"); + +#[cw_serde] +pub enum QueryFeeAction { + Register, + Resume, +} diff --git a/scripts/README.md b/scripts/README.md index 14b47086..c45af36f 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -85,6 +85,3 @@ Example to add oracle address: ```sh ./update_oracle_addr.sh juno juno14vgs85az6xlfzkczzq06agk2tv8zkdxqdue4gs08h0f60smu3jjqfryaj2 ``` - -# TODO -Change from fixed fees to gas auto once juno testnet is fixed. diff --git a/scripts/add_astro_path_to_oracle.sh b/scripts/add_astro_path_to_oracle.sh new file mode 100755 index 00000000..dbf4173e --- /dev/null +++ b/scripts/add_astro_path_to_oracle.sh @@ -0,0 +1,127 @@ +#!/bin/bash + +# DENOMS +ATOM="ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9" +NTRN="untrn" +USDC="ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81" +USDC_AXL="ibc/F082B65C88E4B6D5EF1DB243CDA1D331D002759E938A0F5CD3FFDC5D53B3E349" +NEWT="factory/neutron1p8d89wvxyjcnawmgw72klknr3lg9gwwl6ypxda/newt" + +# POOLS +ATOM_NTRN_POOL="neutron1e22zh5p8meddxjclevuhjmfj69jxfsa8uu3jvht72rv9d8lkhves6t8veq" # ATOM <-> NTRN +NTRN_USDC_AXL_POOL="neutron1l3gtxnwjuy65rzk63k352d52ad0f2sh89kgrqwczgt56jc8nmc3qh5kag3" # NTRN <-> USDC.AXL +USDC_AXL_USDC_POOL="neutron1ns2tcunrlrk5yk62fpl74ycazanceyfmmq7dlj6sq8n0rnkuvk7szstkyx" # USDC.AXL <-> USDC +NTRN_NEWT_POOL="neutron14798daa6d6ysq28fjzpg7jkykaseq7tlz6euleeegj02e0a8gkksltdm9l" # NTRN <-> NEWT + +# PATHS +## ATOM / NTRN +ATOM_NTRN_PATH='[{"denom1": "'$ATOM'", "denom2": "'$NTRN'","pool_address": "'$ATOM_NTRN_POOL'"}]' +NTRN_ATOM_PATH='[{"denom1": "'$NTRN'", "denom2": "'$ATOM'","pool_address": "'$ATOM_NTRN_POOL'"}]' + +## USDC +ATOM_USDC_PATH='[{"denom1": "'$ATOM'", "denom2": "'$NTRN'","pool_address": "'$ATOM_NTRN_POOL'"}, + {"denom1": "'$NTRN'", "denom2": "'$USDC_AXL'","pool_address": "'$NTRN_USDC_AXL_POOL'"}, + {"denom1": "'$USDC_AXL'", "denom2": "'$USDC'","pool_address": "'$USDC_AXL_USDC_POOL'"}]' + +NTRN_USDC_PATH='[{"denom1": "'$NTRN'", "denom2": "'$USDC_AXL'","pool_address": "'$NTRN_USDC_AXL_POOL'"}, + {"denom1": "'$USDC_AXL'", "denom2": "'$USDC'","pool_address": "'$USDC_AXL_USDC_POOL'"}]' + +USDC_ATOM_PATH='[{"denom1": "'$USDC'", "denom2": "'$USDC_AXL'","pool_address": "'$USDC_AXL_USDC_POOL'"}, + {"denom1": "'$USDC_AXL'", "denom2": "'$NTRN'","pool_address": "'$NTRN_USDC_AXL_POOL'"}, + {"denom1": "'$NTRN'", "denom2": "'$ATOM'","pool_address": "'$ATOM_NTRN_POOL'"}]' + +USDC_NTRN_PATH='[{"denom1": "'$USDC'", "denom2": "'$USDC_AXL'","pool_address": "'$USDC_AXL_USDC_POOL'"}, + {"denom1": "'$USDC_AXL'", "denom2": "'$NTRN'","pool_address": "'$NTRN_USDC_AXL_POOL'"}]' + +## NEWT +ATOM_NEWT_PATH='[{"denom1": "'$ATOM'", "denom2": "'$NTRN'","pool_address": "'$ATOM_NTRN_POOL'"}, + {"denom1": "'$NTRN'", "denom2": "'$NEWT'","pool_address": "'$NTRN_NEWT_POOL'"}]' + +NTRN_NEWT_PATH='[{"denom1": "'$NTRN'", "denom2": "'$NEWT'","pool_address": "'$NTRN_NEWT_POOL'"}]' + +USDC_NEWT_PATH='[{"denom1": "'$USDC'", "denom2": "'$USDC_AXL'","pool_address": "'$USDC_AXL_USDC_POOL'"}, + {"denom1": "'$USDC_AXL'", "denom2": "'$NTRN'","pool_address": "'$NTRN_USDC_AXL_POOL'"}, + {"denom1": "'$NTRN'", "denom2": "'$NEWT'","pool_address": "'$NTRN_NEWT_POOL'"}]' + +NEWT_ATOM_PATH='[{"denom1": "'$NEWT'", "denom2": "'$NTRN'","pool_address": "'$NTRN_NEWT_POOL'"}, + {"denom1": "'$NTRN'", "denom2": "'$ATOM'","pool_address": "'$ATOM_NTRN_POOL'"}]' + +NEWT_NTRN_PATH='[{"denom1": "'$NEWT'", "denom2": "'$NTRN'","pool_address": "'$NTRN_NEWT_POOL'"}]' + +NEWT_USDC_PATH='[{"denom1": "'$NEWT'", "denom2": "'$NTRN'","pool_address": "'$NTRN_NEWT_POOL'"}, + {"denom1": "'$NTRN'", "denom2": "'$USDC_AXL'","pool_address": "'$NTRN_USDC_AXL_POOL'"}, + {"denom1": "'$USDC_AXL'", "denom2": "'$USDC'","pool_address": "'$USDC_AXL_USDC_POOL'"}]' + +# MSGS +## ATOM / NTRN +ATOM_NTRN_MSG=$(jq -n \ + --argjson pair '["'$ATOM'","'$NTRN'"]' \ + --argjson path "$ATOM_NTRN_PATH" \ + '{pair: $pair, path: $path}') + +NTRN_ATOM_MSG=$(jq -n \ + --argjson pair '["'$NTRN'","'$ATOM'"]' \ + --argjson path "$NTRN_ATOM_PATH" \ + '{pair: $pair, path: $path}') + +## USDC +ATOM_USDC_MSG=$(jq -n \ + --argjson pair '["'$ATOM'","'$USDC'"]' \ + --argjson path "$ATOM_USDC_PATH" \ + '{pair: $pair, path: $path}') + +NTRN_USDC_MSG=$(jq -n \ + --argjson pair '["'$NTRN'","'$USDC'"]' \ + --argjson path "$NTRN_USDC_PATH" \ + '{pair: $pair, path: $path}') + +USDC_ATOM_MSG=$(jq -n \ + --argjson pair '["'$USDC'","'$ATOM'"]' \ + --argjson path "$USDC_ATOM_PATH" \ + '{pair: $pair, path: $path}') + +USDC_NTRN_MSG=$(jq -n \ + --argjson pair '["'$USDC'","'$NTRN'"]' \ + --argjson path "$USDC_NTRN_PATH" \ + '{pair: $pair, path: $path}') + +## NEWT +ATOM_NEWT_MSG=$(jq -n \ + --argjson pair '["'$ATOM'","'$NEWT'"]' \ + --argjson path "$ATOM_NEWT_PATH" \ + '{pair: $pair, path: $path}') + +NTRN_NEWT_MSG=$(jq -n \ + --argjson pair '["'$NTRN'","'$NEWT'"]' \ + --argjson path "$NTRN_NEWT_PATH" \ + '{pair: $pair, path: $path}') + +USDC_NEWT_MSG=$(jq -n \ + --argjson pair '["'$USDC'","'$NEWT'"]' \ + --argjson path "$USDC_NEWT_PATH" \ + '{pair: $pair, path: $path}') + +NEWT_ATOM_MSG=$(jq -n \ + --argjson pair '["'$NEWT'","'$ATOM'"]' \ + --argjson path "$NEWT_ATOM_PATH" \ + '{pair: $pair, path: $path}') + +NEWT_NTRN_MSG=$(jq -n \ + --argjson pair '["'$NEWT'","'$NTRN'"]' \ + --argjson path "$NEWT_NTRN_PATH" \ + '{pair: $pair, path: $path}') + +NEWT_USDC_MSG=$(jq -n \ + --argjson pair '["'$NEWT'","'$USDC'"]' \ + --argjson path "$NEWT_USDC_PATH" \ + '{pair: $pair, path: $path}') + +# TODO: Modify the path here +execute_msg=$(jq -n \ + --argjson msg "$NEWT_USDC_MSG" \ + '{add_astro_path: $msg}') + +ORACLE_ADDR="neutron1s8uqyh0mmh8g66s2dectf56c08y6fvusp39undp8kf4v678ededsy6tstf" +EXECUTE_FLAGS="--gas-prices 0.075untrn --gas auto --gas-adjustment 1.4 --output json -y" + +neutrond tx wasm execute $ORACLE_ADDR "$execute_msg" --from neutron1phx0sz708k3t6xdnyc98hgkyhra4tp44et5s68 $EXECUTE_FLAGS diff --git a/scripts/add_service_to_manager.sh b/scripts/add_service_to_manager.sh index 90074771..68df4ed6 100755 --- a/scripts/add_service_to_manager.sh +++ b/scripts/add_service_to_manager.sh @@ -16,17 +16,17 @@ if [[ "$CHAIN" == 'juno' ]]; then ADDR_SERVICES_MANAGER="juno1wh5gyyd3hhaeq6jgnawcecvgear7k8c94celuqqxcrz65sglemlql37ple" elif [[ "$CHAIN" == 'neutron' || "$CHAIN" == 'ntrn' ]]; then BINARY="neutrond" - GAS_PRICES="0.025ntrn" - OWNER_ADDR="neutron17s47ltx2hth9w5hntncv70kvyygvg0qr4ug32g" + GAS_PRICES="0.075untrn" + OWNER_ADDR="neutron1phx0sz708k3t6xdnyc98hgkyhra4tp44et5s68" FEES="1000untrn" - # ADDR_SERVICES_MANAGER="" + ADDR_SERVICES_MANAGER="neutron1gantvpnat0la8kkkzrnj48d5d8wxdjllh5r2w4r2hcrpwy00s69quypupa" else echo "Unknown chain" fi -# EXECUTE_FLAGS="--gas-prices $GAS_PRICES --gas auto --gas-adjustment 1.4 -y" -EXECUTE_FLAGS="--fees $FEES --gas auto --gas-adjustment 1.4 -y" +EXECUTE_FLAGS="--gas-prices $GAS_PRICES --gas auto --gas-adjustment 1.4 -y" +# EXECUTE_FLAGS="--fees $FEES --gas auto --gas-adjustment 1.4 -y" execute_msg=$(jq -n \ --arg service_name "$SERVICE_NAME" \ diff --git a/scripts/general_commands.txt b/scripts/general_commands.txt new file mode 100644 index 00000000..865a0133 --- /dev/null +++ b/scripts/general_commands.txt @@ -0,0 +1,27 @@ +// Update minimum amount for a denom on the auction manager +neutrond tx wasm execute neutron13exc5wdc7y5qpqazc34djnu934lqvfw2dru30j52ahhjep6jzx8ssjxcyz '{"admin":{"update_min_amount": {"denom":"ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9", "min_amount": {"send": "100000", "start_auction": "100000"}}}}' --from ntrn-main-tester --gas-prices 0.075untrn --gas auto --gas-adjustment 1.4 --output json -y + +// migrate auction from auction manager (because auction manager is the admin of the auctions) +neutrond tx wasm execute neutron13exc5wdc7y5qpqazc34djnu934lqvfw2dru30j52ahhjep6jzx8ssjxcyz '{"admin":{"migrate_auction": {"pair": ["ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9", "untrn"], "code_id": 869, "msg": {"no_state_change": {}}}}}' --from ntrn-main-tester --gas-prices 0.075untrn --gas auto --gas-adjustment 1.4 --output json -y + +// Migrate contract without any state changes +neutrond tx wasm migrate neutron1gantvpnat0la8kkkzrnj48d5d8wxdjllh5r2w4r2hcrpwy00s69quypupa 872 '{"no_state_change":{}}' --from ntrn-main-tester --gas-prices 0.075untrn --gas auto --gas-adjustment 1.4 --output json -y + +// Whitelist a denom on the rebalancer contract +neutrond tx wasm execute neutron1qs6mzpmcw3dvg5l8nyywetcj326scszdj7v4pfk55xwshd4prqnqfwc0z2 '{"admin":{"update_denom_whitelist": {"to_add": ["ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81", "factory/neutron1p8d89wvxyjcnawmgw72klknr3lg9gwwl6ypxda/newt"], "to_remove":[]}}}' --from ntrn-main-tester --gas-prices 0.075untrn --gas auto --gas-adjustment 1.4 --output json -y + +// Whitelist base denom +neutrond tx wasm execute neutron1qs6mzpmcw3dvg5l8nyywetcj326scszdj7v4pfk55xwshd4prqnqfwc0z2 '{"admin":{"update_base_denom_whitelist": {"to_add": [{"denom": "ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81", "min_balance_limit": "10000000"}], "to_remove":[]}}}' --from ntrn-main-tester --gas-prices 0.075untrn --gas auto --gas-adjustment 1.4 --output json -y + +// update auction code id in auctions manager +neutrond tx wasm execute neutron13exc5wdc7y5qpqazc34djnu934lqvfw2dru30j52ahhjep6jzx8ssjxcyz '{"admin": {"update_auction_id":"869"}}' --from ntrn-main-tester --gas-prices 0.075untrn --gas auto --gas-adjustment 1.4 --output json -y + +// Send funds to the auction manually (outside of the rebalancer) +neutrond tx wasm execute neutron13jppm4n77u8ud5wma9xe0dqnaz85ne9jem3r0scc009uemvh49qqxuuggf '{"auction_funds": {}}' --amount 1500000untrn --from ntrn-main-tester --gas-prices 0.075untrn --gas auto --gas-adjustment 1.4 --output json -y + +// Open auction manually (start_block doesn't needed because it takes current block by default) +neutrond tx wasm execute neutron13exc5wdc7y5qpqazc34djnu934lqvfw2dru30j52ahhjep6jzx8ssjxcyz '{"admin": {"open_auction": {"pair": ["untrn", "ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81"], "params": {"end_block": 8796000}}}}' --from ntrn-main-tester --gas-prices 0.075untrn --gas auto --gas-adjustment 1.4 --output json -y + +// Update price in the oracle for a pair +neutrond tx wasm execute neutron1s8uqyh0mmh8g66s2dectf56c08y6fvusp39undp8kf4v678ededsy6tstf '{"update_price": {"pair": ["ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9", "factory/neutron1p8d89wvxyjcnawmgw72klknr3lg9gwwl6ypxda/newt"]}}' --from ntrn-main-tester --gas-prices 0.075untrn --gas auto --gas-adjustment 1.4 --output json -y + diff --git a/scripts/init.sh b/scripts/init.sh index ecf4d63c..69515b51 100755 --- a/scripts/init.sh +++ b/scripts/init.sh @@ -26,23 +26,23 @@ if [[ "$CHAIN" == 'juno' ]]; then WHITELISTED_BASE_DENOMS='[\"ujunox\", \"factory/juno17s47ltx2hth9w5hntncv70kvyygvg0qr83zghn/vuusdcx\"]' elif [[ "$CHAIN" == 'neutron' || "$CHAIN" == 'ntrn' ]]; then BINARY="neutrond" - GAS_PRICES="0.025ntrn" - OWNER_ADDR="neutron17s47ltx2hth9w5hntncv70kvyygvg0qr4ug32g" + GAS_PRICES="0.075untrn" + OWNER_ADDR="neutron1phx0sz708k3t6xdnyc98hgkyhra4tp44et5s68" - CODE_ID_ACCOUNT=1767 - CODE_ID_SERVICES_MANAGER=1766 - CODE_ID_REBALANCER=1765 - CODE_ID_ORACLE=1764 - CODE_ID_AUCTION=1763 - CODE_ID_AUCTIONS_MANAGER=1762 + CODE_ID_ACCOUNT=750 + CODE_ID_SERVICES_MANAGER=754 + CODE_ID_REBALANCER=755 + CODE_ID_ORACLE=753 + CODE_ID_AUCTION=751 + CODE_ID_AUCTIONS_MANAGER=752 # Contracts addresses for init below - # ADDR_SERVICES_MANAGER="" - # ADDR_AUCTIONS_MANAGER="" + ADDR_SERVICES_MANAGER="neutron1gantvpnat0la8kkkzrnj48d5d8wxdjllh5r2w4r2hcrpwy00s69quypupa" + ADDR_AUCTIONS_MANAGER="neutron13exc5wdc7y5qpqazc34djnu934lqvfw2dru30j52ahhjep6jzx8ssjxcyz" # General data per chain - WHITELISTED_DENOMS='[\"untrn\"]' - WHITELISTED_BASE_DENOMS='[\"untrn\"]' + WHITELISTED_DENOMS='["untrn", "ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9"]' + WHITELISTED_BASE_DENOMS='[{"denom": "untrn", "min_balance_limit": "10000000"}, {"denom": "ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9", "min_balance_limit": "10000000"}]' else echo "Unknown chain" fi @@ -69,7 +69,8 @@ if [[ "$COMMAND" == 'account' ]]; then ################################################ elif [[ "$COMMAND" == 'services-manager' ]]; then init_msg=$(jq -n \ - '{}') + --argjson account_code_id $CODE_ID_ACCOUNT \ + '{"whitelisted_code_ids": [$account_code_id]}') $BINARY tx wasm init $CODE_ID_SERVICES_MANAGER "$init_msg" --label "Valence services manager" \ --admin $OWNER_ADDR --from $OWNER_ADDR $EXECUTE_FLAGS @@ -81,7 +82,7 @@ elif [[ "$COMMAND" == 'auctions-manager' ]]; then init_msg=$(jq -n \ --argjson auction_code_id $CODE_ID_AUCTION \ '{ auction_code_id: $auction_code_id, - min_auction_amount: [["ujunox", "2000"], ["factory/juno17s47ltx2hth9w5hntncv70kvyygvg0qr83zghn/vuusdcx", "1000"]] + min_auction_amount: [["untrn", "20000"], ["ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9", "10000"]] }') $BINARY tx wasm init $CODE_ID_AUCTIONS_MANAGER "$init_msg" --label "Valence auctions manager" \ @@ -98,13 +99,19 @@ elif [[ "$COMMAND" == 'rebalancer' ]]; then init_msg=$(jq -n \ --arg services_manager_addr "$ADDR_SERVICES_MANAGER" \ --arg auctions_manager_addr "$ADDR_AUCTIONS_MANAGER" \ - --arg whitelist_denom "$WHITELISTED_DENOMS" \ - --arg whitelist_base_denom "$WHITELISTED_BASE_DENOMS" \ + --argjson whitelist_denom "$WHITELISTED_DENOMS" \ + --argjson whitelist_base_denom "$WHITELISTED_BASE_DENOMS" \ '{services_manager_addr: $services_manager_addr, auctions_manager_addr: $auctions_manager_addr, cycle_start: "0", - denom_whitelist: [$whitelist_denom], - base_denom_whitelist: [$whitelist_base_denom] + denom_whitelist: $whitelist_denom, + base_denom_whitelist: $whitelist_base_denom, + cycle_period: 60, + fees: { + denom: "untrn", + register_fee: "1000000", + resume_fee: "1000000" + }, }') $BINARY tx wasm init $CODE_ID_REBALANCER "$init_msg" --label "Valence rebalancer" \ diff --git a/scripts/new_auction.sh b/scripts/new_auction.sh index 6accc0ef..fff6083c 100755 --- a/scripts/new_auction.sh +++ b/scripts/new_auction.sh @@ -7,26 +7,25 @@ if [[ "$CHAIN" == 'juno' ]]; then BINARY="junod" GAS_PRICES="0.025ujunox" OWNER_ADDR="juno17s47ltx2hth9w5hntncv70kvyygvg0qr83zghn" - FEES="10000ujunox" ADDR_AUCTIONS_MANAGER="juno1tp2n8fa9848355hfd98lufhm84sudlvnzwvsdsqtlahtsrdtl6astvrz9j" elif [[ "$CHAIN" == 'neutron' || "$CHAIN" == 'ntrn' ]]; then BINARY="neutrond" - GAS_PRICES="0.025ntrn" - OWNER_ADDR="neutron17s47ltx2hth9w5hntncv70kvyygvg0qr4ug32g" - FEES="1000untrn" + GAS_PRICES="0.075untrn" + OWNER_ADDR="neutron1phx0sz708k3t6xdnyc98hgkyhra4tp44et5s68" - # ADDR_AUCTIONS_MANAGER="" + ADDR_AUCTIONS_MANAGER="neutron13exc5wdc7y5qpqazc34djnu934lqvfw2dru30j52ahhjep6jzx8ssjxcyz" else echo "Unknown chain" fi -# EXECUTE_FLAGS="--gas-prices $GAS_PRICES --gas auto --gas-adjustment 1.4 -y" -EXECUTE_FLAGS="--fees $FEES --gas auto --gas-adjustment 1.4 -y" +EXECUTE_FLAGS="--gas-prices $GAS_PRICES --gas auto --gas-adjustment 1.4 -y" +# EXECUTE_FLAGS="--fees $FEES --gas auto --gas-adjustment 1.4 -y" ## You can change value manually and uncomment it here -PAIR='["factory/juno17s47ltx2hth9w5hntncv70kvyygvg0qr83zghn/vuusdcx", "ujunox"]' -AUCTION_STRATEGY='{ "start_price_perc": 2000, "end_price_perc": 2000 }' +PAIR='["ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81", "factory/neutron1p8d89wvxyjcnawmgw72klknr3lg9gwwl6ypxda/newt"]' +LABEL="auction USDC/NEWT" +AUCTION_STRATEGY='{ "start_price_perc": 5000, "end_price_perc": 5000 }' CHAIN_HALT='{ "cap": "14400", "block_avg": "3" }' PRICE_FRESHNESS='{ "limit": "3", "multipliers": [["2", "2"], ["1", "1.5"]] }' @@ -58,6 +57,7 @@ done execute_msg=$(jq -n \ --argjson pair "$PAIR" \ + --arg label "$LABEL" \ --argjson auction_strategy "$AUCTION_STRATEGY" \ --argjson chain_halt_config "$CHAIN_HALT" \ --argjson price_freshness_strategy "$PRICE_FRESHNESS" \ @@ -69,6 +69,7 @@ execute_msg=$(jq -n \ chain_halt_config: $chain_halt_config, price_freshness_strategy: $price_freshness_strategy }, + label: "$label", } }}') diff --git a/scripts/register_to_service.sh b/scripts/register_to_service.sh new file mode 100755 index 00000000..e52b3b95 --- /dev/null +++ b/scripts/register_to_service.sh @@ -0,0 +1,14 @@ +ACCOUNT_ADDR=$1 +shift + +execute_msg=$(jq -n \ + '{ + "register_to_service": { + "service_name": "rebalancer", + "data": "ewoiYmFzZV9kZW5vbSI6ICJpYmMvQzRDRkY0NkZENkRFMzVDQTRDRjRDRTAzMUU2NDNDOEZEQzlCQTRCOTlBRTU5OEU5QjBFRDk4RkUzQTIzMTlGOSIsCiJwaWQiOiB7ICJwIjogIjAuMSIsICJpIjogIjAiLCAiZCI6ICIwIiB9LAoidGFyZ2V0X292ZXJyaWRlX3N0cmF0ZWd5IjogInByb3BvcnRpb25hbCIsCiJ0YXJnZXRzIjogWwp7CiJkZW5vbSI6ICJpYmMvQzRDRkY0NkZENkRFMzVDQTRDRjRDRTAzMUU2NDNDOEZEQzlCQTRCOTlBRTU5OEU5QjBFRDk4RkUzQTIzMTlGOSIsCiJicHMiOiA1MDAwCn0sCnsgImRlbm9tIjogInVudHJuIiwgImJwcyI6IDUwMDAgfQpdCn0=" + } +}') + +EXECUTE_FLAGS="--gas-prices 0.075untrn --gas auto --gas-adjustment 1.4 --output json -y" + +neutrond tx wasm execute $ACCOUNT_ADDR "$execute_msg" --from neutron1phx0sz708k3t6xdnyc98hgkyhra4tp44et5s68 $EXECUTE_FLAGS diff --git a/scripts/system.sh b/scripts/system.sh index 52eb3fdf..002e6b7b 100755 --- a/scripts/system.sh +++ b/scripts/system.sh @@ -16,22 +16,22 @@ if [[ "$CHAIN" == 'juno' ]]; then elif [[ "$CHAIN" == 'neutron' || "$CHAIN" == 'ntrn' ]]; then BINARY="neutrond" - GAS_PRICES="0.025ntrn" - OWNER_ADDR="neutron17s47ltx2hth9w5hntncv70kvyygvg0qr4ug32g" + GAS_PRICES="0.075untrn" + OWNER_ADDR="neutron1phx0sz708k3t6xdnyc98hgkyhra4tp44et5s68" FEES="1000untrn" -# REBALANCER_ADDR="" -# AUCTIONS_MANAGER="" + REBALANCER_ADDR="neutron1qs6mzpmcw3dvg5l8nyywetcj326scszdj7v4pfk55xwshd4prqnqfwc0z2" + AUCTIONS_MANAGER="neutron13exc5wdc7y5qpqazc34djnu934lqvfw2dru30j52ahhjep6jzx8ssjxcyz" else echo "Unknown chain" fi -# EXECUTE_FLAGS="--gas-prices $GAS_PRICES --gas auto --gas-adjustment 1.4 -y" -EXECUTE_FLAGS="--fees $FEES --gas auto --gas-adjustment 1.4 -y" +EXECUTE_FLAGS="--gas-prices $GAS_PRICES --gas auto --gas-adjustment 1.4 -y" +# EXECUTE_FLAGS="--fees $FEES --gas auto --gas-adjustment 1.4 -y" -declare -A pair1=([pair1]="ujunox" [pair2]="factory/juno17s47ltx2hth9w5hntncv70kvyygvg0qr83zghn/vuusdcx") -declare -A pair2=([pair1]="factory/juno17s47ltx2hth9w5hntncv70kvyygvg0qr83zghn/vuusdcx" [pair2]="ujunox") +declare -A pair1=([pair1]="untrn" [pair2]="ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9") +declare -A pair2=([pair1]="ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9" [pair2]="untrn") declare -a pairs=( pair1 @@ -40,8 +40,8 @@ declare -a pairs=( if [[ "$COMMAND" == 'update-prices' ]]; then - declare -A price1=([pair1]="ujunox" [pair2]="factory/juno17s47ltx2hth9w5hntncv70kvyygvg0qr83zghn/vuusdcx" [price]="0.5") - declare -A price2=([pair1]="factory/juno17s47ltx2hth9w5hntncv70kvyygvg0qr83zghn/vuusdcx" [pair2]="ujunox" [price]="2.0") + declare -A price2=([pair1]="ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9" [pair2]="untrn" [price]="10.59551") + declare -A price1=([pair1]="untrn" [pair2]="ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9" [price]="0.09380") declare -a prices=( price1 @@ -57,14 +57,14 @@ if [[ "$COMMAND" == 'update-prices' ]]; then ./update_price.sh $CHAIN ${!pair1} ${!pair2} ${!price} - sleep 3 + sleep 6 done elif [[ "$COMMAND" == 'rebalance' ]]; then LIMIT=$1 shift - if [ -z "$PRICE" ]; then + if [ -z "$LIMIT" ]; then execute_msg=$(jq -n \ '{system_rebalance: {}}') @@ -95,7 +95,7 @@ elif [[ "$COMMAND" == 'open-auctions' ]]; then execute_msg=$(jq -n \ --arg pair1 "${!pair1}" \ --arg pair2 "${!pair2}" \ - --arg end_block "$END_BLOCK" \ + --arg end_block $END_BLOCK \ '{admin: { open_auction: { pair: [$pair1, $pair2], @@ -109,8 +109,8 @@ elif [[ "$COMMAND" == 'open-auctions' ]]; then execute_msg=$(jq -n \ --arg pair1 "${!pair1}" \ --arg pair2 "${!pair2}" \ - --arg end_block "$END_BLOCK" \ - --arg start_block "$START_BLOCK" \ + --arg end_block $END_BLOCK \ + --arg start_block $START_BLOCK \ '{admin: { open_auction: { pair: [$pair1, $pair2], @@ -125,7 +125,7 @@ elif [[ "$COMMAND" == 'open-auctions' ]]; then $BINARY tx wasm execute $AUCTIONS_MANAGER "$execute_msg" --from $OWNER_ADDR $EXECUTE_FLAGS - sleep 3 + sleep 9 done elif [[ "$COMMAND" == 'close-auctions' ]]; then @@ -148,9 +148,17 @@ elif [[ "$COMMAND" == 'close-auctions' ]]; then pair: [$pair1, $pair2], limit: $limit, }}') + + $BINARY tx wasm execute $AUCTIONS_MANAGER "$execute_msg" --from $OWNER_ADDR $EXECUTE_FLAGS done - echo $BINARY tx wasm execute $AUCTIONS_MANAGER "$execute_msg" --from $OWNER_ADDR $EXECUTE_FLAGS +elif [[ "$COMMAND" == 'do-bid' ]]; then + AMOUNT=$1 + shift + AUCTION_ADDR=$1 + shift + + $BINARY tx wasm execute $AUCTION_ADDR '"bid"' --amount $AMOUNT --from $OWNER_ADDR $EXECUTE_FLAGS else echo "Unknown command" fi diff --git a/scripts/temp.sh b/scripts/temp.sh new file mode 100755 index 00000000..8f311235 --- /dev/null +++ b/scripts/temp.sh @@ -0,0 +1,12 @@ +WHITELISTED_BASE_DENOMS='[\"untrn\", \"ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9\"]' + +execute_msg=$(jq -n \ + --arg whitelist_denom "$WHITELISTED_BASE_DENOMS" \ + '{admin: {update_base_denom_whitelist: { + to_add: [], + to_remove: [$whitelist_denom], + }}}') + +EXECUTE_FLAGS="--gas-prices 0.015untrn --gas auto --gas-adjustment 1.4 --output json -y" + +echo neutrond tx wasm execute neutron1jreurhf7g43l0zdxu26fa8aahnjxyng8sjh5vvwjpn4lucwq8tsq7jxl5t "$execute_msg" --from neutron1phx0sz708k3t6xdnyc98hgkyhra4tp44et5s68 $EXECUTE_FLAGS diff --git a/scripts/update_oracle_addr.sh b/scripts/update_oracle_addr.sh index 46dd4e17..1edef92b 100755 --- a/scripts/update_oracle_addr.sh +++ b/scripts/update_oracle_addr.sh @@ -15,17 +15,17 @@ if [[ "$CHAIN" == 'juno' ]]; then ADDR_AUCTIONS_MANAGER="juno1tp2n8fa9848355hfd98lufhm84sudlvnzwvsdsqtlahtsrdtl6astvrz9j" elif [[ "$CHAIN" == 'neutron' || "$CHAIN" == 'ntrn' ]]; then BINARY="neutrond" - GAS_PRICES="0.025ntrn" - OWNER_ADDR="neutron17s47ltx2hth9w5hntncv70kvyygvg0qr4ug32g" + GAS_PRICES="0.075untrn" + OWNER_ADDR="neutron1phx0sz708k3t6xdnyc98hgkyhra4tp44et5s68" FEES="1000untrn" - # ADDR_AUCTIONS_MANAGER="" + ADDR_AUCTIONS_MANAGER="neutron13exc5wdc7y5qpqazc34djnu934lqvfw2dru30j52ahhjep6jzx8ssjxcyz" else echo "Unknown chain" fi -# EXECUTE_FLAGS="--gas-prices $GAS_PRICES --gas auto --gas-adjustment 1.4 -y" -EXECUTE_FLAGS="--fees $FEES --gas auto --gas-adjustment 1.4 -y" +EXECUTE_FLAGS="--gas-prices $GAS_PRICES --gas auto --gas-adjustment 1.4 -y" +# EXECUTE_FLAGS="--fees $FEES --gas auto --gas-adjustment 1.4 -y" execute_msg=$(jq -n \ --arg oracle_Addr "$ORACLE_ADDR" \ diff --git a/scripts/update_price.sh b/scripts/update_price.sh index 52712dc4..8e293865 100755 --- a/scripts/update_price.sh +++ b/scripts/update_price.sh @@ -18,17 +18,17 @@ if [[ "$CHAIN" == 'juno' ]]; then ORACLE_ADDR="juno14vgs85az6xlfzkczzq06agk2tv8zkdxqdue4gs08h0f60smu3jjqfryaj2" elif [[ "$CHAIN" == 'neutron' || "$CHAIN" == 'ntrn' ]]; then BINARY="neutrond" - GAS_PRICES="0.025ntrn" - OWNER_ADDR="neutron17s47ltx2hth9w5hntncv70kvyygvg0qr4ug32g" + GAS_PRICES="0.075untrn" + OWNER_ADDR="neutron1phx0sz708k3t6xdnyc98hgkyhra4tp44et5s68" FEES="1000untrn" -# ORACLE_ADDR="" + ORACLE_ADDR="neutron1s8uqyh0mmh8g66s2dectf56c08y6fvusp39undp8kf4v678ededsy6tstf" else echo "Unknown chain" fi -# EXECUTE_FLAGS="--gas-prices $GAS_PRICES --gas auto --gas-adjustment 1.4 --output json -y" -EXECUTE_FLAGS="--fees $FEES --gas auto --gas-adjustment 1.4 -y" +EXECUTE_FLAGS="--gas-prices $GAS_PRICES --gas auto --gas-adjustment 1.4 --output json -y" +# EXECUTE_FLAGS="--fees $FEES --gas auto --gas-adjustment 1.4 -y" if [ -z "$ORACLE_ADDR" ]; then echo "[ERROR] Oracle address is missing for $CHAIN" && exit 1; fi diff --git a/scripts/upload.sh b/scripts/upload.sh index 89bf08a2..c649d99c 100755 --- a/scripts/upload.sh +++ b/scripts/upload.sh @@ -4,6 +4,8 @@ CHAIN=$1 shift COMMAND=$1 shift +INIT_BY=$1 +shift if [[ "$CHAIN" == 'juno' ]]; then BINARY="junod" @@ -12,13 +14,21 @@ if [[ "$CHAIN" == 'juno' ]]; then elif [[ "$CHAIN" == 'neutron' || "$CHAIN" == 'ntrn' ]]; then BINARY="neutrond" - GAS_PRICES="0.025ntrn" - OWNER_ADDR="neutron17s47ltx2hth9w5hntncv70kvyygvg0qr4ug32g" + GAS_PRICES="0.075untrn" + OWNER_ADDR="neutron1phx0sz708k3t6xdnyc98hgkyhra4tp44et5s68" + else echo "Unknown chain" fi -EXECUTE_FLAGS="--gas-prices $GAS_PRICES --gas auto --gas-adjustment 1.4 --output json -y" +if [ -z "$INIT_BY" ]; then + ADDRESSES="$OWNER_ADDR" +else + ADDRESSES="$OWNER_ADDR,$INIT_BY" +fi + +EXECUTE_FLAGS="--gas-prices $GAS_PRICES --gas auto --gas-adjustment 1.4 --output json --instantiate-anyof-addresses $ADDRESSES -y" +ACCOUNT_EXECUTE_FLAGS="--gas-prices $GAS_PRICES --gas auto --gas-adjustment 1.4 --output json -y" ARTIFACTS_PATH="../artifacts" # File names @@ -30,8 +40,9 @@ SERVICES_MANAGER_FILE_NAME="$ARTIFACTS_PATH/services_manager.wasm" REBALANCER_FILE_NAME="$ARTIFACTS_PATH/rebalancer.wasm" if [[ "$COMMAND" == 'account' ]]; then - $BINARY tx wasm s $ACCOUNT_FILE_NAME --from $OWNER_ADDR $EXECUTE_FLAGS + $BINARY tx wasm s $ACCOUNT_FILE_NAME --from $OWNER_ADDR $ACCOUNT_EXECUTE_FLAGS elif [[ "$COMMAND" == 'auction' ]]; then + # Auction needs to be instantiated by the manager, so need to change the --instantiate-anyof-addresses $BINARY tx wasm s $AUCTION_FILE_NAME --from $OWNER_ADDR $EXECUTE_FLAGS elif [[ "$COMMAND" == 'auctions-manager' ]]; then $BINARY tx wasm s $AUCTIONS_MANAGER_FILE_NAME --from $OWNER_ADDR $EXECUTE_FLAGS diff --git a/test.json b/test.json new file mode 100644 index 00000000..e69de29b diff --git a/tests/rust-tests/Cargo.toml b/tests/rust-tests/Cargo.toml index ba29941e..16f40487 100644 --- a/tests/rust-tests/Cargo.toml +++ b/tests/rust-tests/Cargo.toml @@ -29,3 +29,13 @@ auction-package = { workspace = true } auctions-manager = { workspace = true } auction = { workspace = true } price-oracle = { workspace = true } + +# Astro +astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.9.5" } +astroport-token = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.9.5" } +astroport-whitelist = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.9.5" } +astroport-factory = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.9.5" } +astroport-native-coin-registry = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.9.5" } +astroport-pair-stable = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.9.5" } +astroport-pair = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.9.5" } +rand = "0.8.5" \ No newline at end of file diff --git a/tests/rust-tests/src/suite/builder_astro.rs b/tests/rust-tests/src/suite/builder_astro.rs new file mode 100644 index 00000000..d5b2bbfd --- /dev/null +++ b/tests/rust-tests/src/suite/builder_astro.rs @@ -0,0 +1,257 @@ +use std::collections::HashMap; + +use cosmwasm_schema::serde; +use cosmwasm_std::{to_json_binary, Addr, Coin, StdError, Uint128}; +use cw_multi_test::{App, Executor}; + +use super::{ + instantiates::{AstroFactoryInstantiate, AstroRegisteryInstantiate}, + suite::{ATOM, NTRN, OSMO}, + suite_builder::SuiteBuilder, +}; + +impl SuiteBuilder { + fn init_astro_factory( + &mut self, + app: &mut App, + init_msg: astroport::factory::InstantiateMsg, + ) -> Addr { + app.instantiate_contract( + self.astro_factory_code_id, + self.admin.clone(), + &init_msg, + &[], + "astro_factor", + Some(self.admin.to_string()), + ) + .unwrap() + } + + fn init_astro_registery( + &mut self, + app: &mut App, + init_msg: astroport::native_coin_registry::InstantiateMsg, + ) -> Addr { + app.instantiate_contract( + self.astro_registery_code_id, + self.admin.clone(), + &init_msg, + &[], + "astro_registery", + Some(self.admin.to_string()), + ) + .unwrap() + } + + // fn init_astro_whitelist( + // &mut self, + // app: &mut App, + // init_msg: cw1_whitelist::msg::InstantiateMsg, + // ) -> Addr { + // app.instantiate_contract( + // self.astro_whitelist_code_id, + // self.admin.clone(), + // &init_msg, + // &[], + // "astro_whitelist", + // Some(self.admin.to_string()), + // ) + // .unwrap() + // } + + fn init_pair_pool( + &mut self, + app: &mut App, + factory_addr: &Addr, + pair_type: astroport::factory::PairType, + asset_infos: Vec, + init_params: T, + ) -> Addr { + let init_atom_ntrn_msg = astroport::factory::ExecuteMsg::CreatePair { + pair_type, + asset_infos: asset_infos.clone(), + init_params: Some(to_json_binary::(&init_params).unwrap()), + }; + + app.execute_contract( + self.admin.clone(), + factory_addr.clone(), + &init_atom_ntrn_msg, + &[], + ) + .unwrap(); + + let pair_info: astroport::asset::PairInfo = app + .wrap() + .query_wasm_smart( + factory_addr, + &astroport::factory::QueryMsg::Pair { asset_infos }, + ) + .unwrap(); + + pair_info.contract_addr + } + + // Returns (factory_Addr, registery_Addr, pools) + pub fn init_astro(&mut self, app: &mut App) -> HashMap<(String, String), Addr> { + let registery_init_msg = AstroRegisteryInstantiate::default(); + let registery_addr = self.init_astro_registery(app, registery_init_msg.into()); + + // Add pairs to registery + app.execute_contract( + self.admin.clone(), + registery_addr.clone(), + &astroport::native_coin_registry::ExecuteMsg::Add { + native_coins: vec![ + (ATOM.to_string(), 6), + (NTRN.to_string(), 6), + (OSMO.to_string(), 6), + ], + }, + &[], + ) + .unwrap(); + + let factory_init_msg = AstroFactoryInstantiate::default( + self.astro_token_code_id, + registery_addr.as_str(), + self.astro_pair_code_id, + self.astro_stable_pair_code_id, + ); + let factory_addr = self.init_astro_factory(app, factory_init_msg.into()); + + let mut pools: HashMap<(String, String), Addr> = HashMap::new(); + + // Init atom ntrn as a stable pair + let asset_infos = vec![ + astroport::asset::AssetInfo::NativeToken { + denom: ATOM.to_string(), + }, + astroport::asset::AssetInfo::NativeToken { + denom: NTRN.to_string(), + }, + ]; + let pair_type = astroport::factory::PairType::Stable {}; + let init_params = astroport::pair::StablePoolParams { + owner: Some(self.admin.to_string()), + amp: 1, + }; + + let atom_ntrn_pool_addr = + self.init_pair_pool(app, &factory_addr, pair_type, asset_infos, init_params); + pools.insert( + (ATOM.to_string(), NTRN.to_string()), + atom_ntrn_pool_addr.clone(), + ); + pools.insert( + (NTRN.to_string(), ATOM.to_string()), + atom_ntrn_pool_addr.clone(), + ); + + let assets = vec![ + astroport::asset::Asset { + info: astroport::asset::AssetInfo::NativeToken { + denom: ATOM.to_string(), + }, + amount: Uint128::from(1_000_000_000_u128), + }, + astroport::asset::Asset { + info: astroport::asset::AssetInfo::NativeToken { + denom: NTRN.to_string(), + }, + amount: Uint128::from(1_000_000_000_u128), + }, + ]; + + self.astro_provide_liquidity(app, self.admin.clone(), atom_ntrn_pool_addr, assets); + + // Init NTRN OSMO pair as XYK pool + let asset_infos = vec![ + astroport::asset::AssetInfo::NativeToken { + denom: NTRN.to_string(), + }, + astroport::asset::AssetInfo::NativeToken { + denom: OSMO.to_string(), + }, + ]; + let pair_type = astroport::factory::PairType::Xyk {}; + let init_params = astroport::pair::XYKPoolParams { + track_asset_balances: None, + }; + + let ntrn_osmo_pool_addr = + self.init_pair_pool(app, &factory_addr, pair_type, asset_infos, init_params); + pools.insert( + (NTRN.to_string(), OSMO.to_string()), + ntrn_osmo_pool_addr.clone(), + ); + pools.insert( + (OSMO.to_string(), NTRN.to_string()), + ntrn_osmo_pool_addr.clone(), + ); + + let assets = vec![ + astroport::asset::Asset { + info: astroport::asset::AssetInfo::NativeToken { + denom: NTRN.to_string(), + }, + amount: Uint128::from(1_000_000_000_u128), + }, + astroport::asset::Asset { + info: astroport::asset::AssetInfo::NativeToken { + denom: OSMO.to_string(), + }, + amount: Uint128::from(2_000_000_000_u128), + }, + ]; + + self.astro_provide_liquidity(app, self.admin.clone(), ntrn_osmo_pool_addr, assets); + + pools + } +} + +impl SuiteBuilder { + pub fn astro_provide_liquidity( + &mut self, + app: &mut App, + from: Addr, + pool_addr: Addr, + assets: Vec, + ) -> &mut Self { + let balances = assets + .iter() + .map(|asset| { + let denom = match asset.info.clone() { + astroport::asset::AssetInfo::Token { .. } => Err(StdError::generic_err( + "we do not support tokens, only native", + )), + astroport::asset::AssetInfo::NativeToken { denom } => Ok(denom), + } + .unwrap(); + + Coin { + denom, + amount: asset.amount, + } + }) + .collect::>(); + + let provide_liquidity_msg = astroport::pair::ExecuteMsg::ProvideLiquidity { + assets, + slippage_tolerance: None, + auto_stake: Some(false), + receiver: Some(from.to_string()), + }; + + app.execute_contract( + from, + Addr::unchecked(pool_addr), + &provide_liquidity_msg, + &balances, + ) + .unwrap(); + + self + } +} diff --git a/tests/rust-tests/src/suite/contracts.rs b/tests/rust-tests/src/suite/contracts.rs index 0da76299..6bcabf0f 100644 --- a/tests/rust-tests/src/suite/contracts.rs +++ b/tests/rust-tests/src/suite/contracts.rs @@ -57,3 +57,61 @@ pub fn oracle_contract() -> Box> { ); Box::new(contract) } + +pub fn astro_token_contract() -> Box> { + Box::new( + ContractWrapper::new( + astroport_token::contract::execute, + astroport_token::contract::instantiate, + astroport_token::contract::query, + ) + .with_migrate(astroport_token::contract::migrate), + ) +} + +pub fn astro_factory_contract() -> Box> { + Box::new( + ContractWrapper::new( + astroport_factory::contract::execute, + astroport_factory::contract::instantiate, + astroport_factory::contract::query, + ) + .with_migrate(astroport_factory::contract::migrate) + .with_reply(astroport_factory::contract::reply), + ) +} + +pub fn astro_pair_contract() -> Box> { + Box::new( + ContractWrapper::new( + astroport_pair::contract::execute, + astroport_pair::contract::instantiate, + astroport_pair::contract::query, + ) + .with_reply(astroport_pair::contract::reply) + .with_migrate(astroport_pair::contract::migrate), + ) +} + +pub fn astro_pair_stable_contract() -> Box> { + Box::new( + ContractWrapper::new( + astroport_pair_stable::contract::execute, + astroport_pair_stable::contract::instantiate, + astroport_pair_stable::contract::query, + ) + .with_reply(astroport_pair_stable::contract::reply) + .with_migrate(astroport_pair_stable::contract::migrate), + ) +} + +pub fn astro_coin_registry_contract() -> Box> { + let registry_contract = ContractWrapper::new( + astroport_native_coin_registry::contract::execute, + astroport_native_coin_registry::contract::instantiate, + astroport_native_coin_registry::contract::query, + ) + .with_migrate(astroport_native_coin_registry::contract::migrate); + + Box::new(registry_contract) +} diff --git a/tests/rust-tests/src/suite/instantiates/astro_factory.rs b/tests/rust-tests/src/suite/instantiates/astro_factory.rs new file mode 100644 index 00000000..a02bddd3 --- /dev/null +++ b/tests/rust-tests/src/suite/instantiates/astro_factory.rs @@ -0,0 +1,110 @@ +use crate::suite::suite::{ADMIN, FEE}; + +pub struct AstroFactoryInstantiate { + pub msg: astroport::factory::InstantiateMsg, +} + +impl From for astroport::factory::InstantiateMsg { + fn from(value: AstroFactoryInstantiate) -> Self { + value.msg + } +} + +impl AstroFactoryInstantiate { + pub fn into_init(self) -> astroport::factory::InstantiateMsg { + self.msg + } + + pub fn default( + token_code_id: u64, + coin_registry_address: &str, + pair_code_id: u64, + pair_stable_code_id: u64, + ) -> Self { + Self::new( + ADMIN.to_string(), + Some(FEE.to_string()), + vec![ + astroport::factory::PairConfig { + code_id: pair_code_id, + pair_type: astroport::factory::PairType::Xyk {}, + total_fee_bps: 100, + maker_fee_bps: 10, + is_disabled: false, + is_generator_disabled: true, + }, + astroport::factory::PairConfig { + code_id: pair_stable_code_id, + pair_type: astroport::factory::PairType::Stable {}, + total_fee_bps: 100, + maker_fee_bps: 10, + is_disabled: false, + is_generator_disabled: true, + }, + ], + token_code_id, + None, + coin_registry_address.to_string(), + ) + } + + pub fn new( + owner: String, + fee_address: Option, + pair_configs: Vec, + token_code_id: u64, + generator_address: Option, + coin_registry_address: String, + ) -> Self { + Self { + msg: astroport::factory::InstantiateMsg { + owner, + fee_address, + pair_configs, + token_code_id, + whitelist_code_id: 10000, + generator_address, + coin_registry_address, + }, + } + } + + /* Change functions */ + pub fn change_pair_configs( + &mut self, + pair_configs: Vec, + ) -> &mut Self { + self.msg.pair_configs = pair_configs; + self + } + + pub fn change_owner(&mut self, owner: &str) -> &mut Self { + self.msg.owner = owner.to_string(); + self + } + + pub fn change_fee_address(&mut self, fee_address: Option<&str>) -> &mut Self { + self.msg.fee_address = fee_address.map(|f| f.to_string()); + self + } + + pub fn change_token_code_id(&mut self, token_code_id: u64) -> &mut Self { + self.msg.token_code_id = token_code_id; + self + } + + pub fn change_whitelist_code_id(&mut self, whitelist_code_id: u64) -> &mut Self { + self.msg.whitelist_code_id = whitelist_code_id; + self + } + + pub fn change_generator_address(&mut self, generator_address: Option<&str>) -> &mut Self { + self.msg.generator_address = generator_address.map(|f| f.to_string()); + self + } + + pub fn change_coin_registry_address(&mut self, coin_registry_address: &str) -> &mut Self { + self.msg.coin_registry_address = coin_registry_address.to_string(); + self + } +} diff --git a/tests/rust-tests/src/suite/instantiates/astro_pair.rs b/tests/rust-tests/src/suite/instantiates/astro_pair.rs new file mode 100644 index 00000000..cbbb63d6 --- /dev/null +++ b/tests/rust-tests/src/suite/instantiates/astro_pair.rs @@ -0,0 +1,91 @@ +use cosmwasm_std::Binary; + +use crate::suite::suite::{ATOM, NTRN, OSMO}; + +pub struct AstroPairInstantiate { + pub msg: astroport::pair::InstantiateMsg, +} + +impl From for astroport::pair::InstantiateMsg { + fn from(value: AstroPairInstantiate) -> Self { + value.msg + } +} + +impl AstroPairInstantiate { + pub fn into_init(self) -> astroport::pair::InstantiateMsg { + self.msg + } + + pub fn default_atom_ntrn(token_code_id: u64, factory_addr: &str) -> Self { + Self::new( + token_code_id, + factory_addr, + vec![ + astroport::asset::AssetInfo::NativeToken { + denom: ATOM.to_string(), + }, + astroport::asset::AssetInfo::NativeToken { + denom: NTRN.to_string(), + }, + ], + None, + ) + } + + pub fn default_ntrn_osmo(token_code_id: u64, factory_addr: &str) -> Self { + Self::new( + token_code_id, + factory_addr, + vec![ + astroport::asset::AssetInfo::NativeToken { + denom: NTRN.to_string(), + }, + astroport::asset::AssetInfo::NativeToken { + denom: OSMO.to_string(), + }, + ], + None, + ) + } + + pub fn new( + token_code_id: u64, + factory_addr: &str, + asset_infos: Vec, + init_params: Option, + ) -> Self { + Self { + msg: astroport::pair::InstantiateMsg { + asset_infos, + token_code_id, + factory_addr: factory_addr.to_string(), + init_params, + }, + } + } + + /* Change functions */ + pub fn change_token_code_id(&mut self, token_code_id: u64) -> &mut Self { + self.msg.token_code_id = token_code_id; + self + } + + pub fn change_factory_addr(&mut self, factory_addr: &str) -> &mut Self { + self.msg.factory_addr = factory_addr.to_string(); + self + } + + pub fn change_asset_infos( + &mut self, + asset_infos: Vec, + ) -> &mut Self { + self.msg.asset_infos = asset_infos; + self + } + + pub fn change_init_params(&mut self, init_params: Option) -> &mut Self { + self.msg.init_params = init_params; + self + } +} diff --git a/tests/rust-tests/src/suite/instantiates/astro_registery.rs b/tests/rust-tests/src/suite/instantiates/astro_registery.rs new file mode 100644 index 00000000..6e322a0d --- /dev/null +++ b/tests/rust-tests/src/suite/instantiates/astro_registery.rs @@ -0,0 +1,37 @@ +use crate::suite::suite::ADMIN; + +pub struct AstroRegisteryInstantiate { + pub msg: astroport::native_coin_registry::InstantiateMsg, +} + +impl From for astroport::native_coin_registry::InstantiateMsg { + fn from(value: AstroRegisteryInstantiate) -> Self { + value.msg + } +} + +impl Default for AstroRegisteryInstantiate { + fn default() -> Self { + Self::new(ADMIN) + } +} + +impl AstroRegisteryInstantiate { + pub fn into_init(self) -> astroport::native_coin_registry::InstantiateMsg { + self.msg + } + + pub fn new(owner: &str) -> Self { + Self { + msg: astroport::native_coin_registry::InstantiateMsg { + owner: owner.to_string(), + }, + } + } + + /* Change functions */ + pub fn change_owner(&mut self, owner: &str) -> &mut Self { + self.msg.owner = owner.to_string(); + self + } +} diff --git a/tests/rust-tests/src/suite/instantiates/auctions_manager.rs b/tests/rust-tests/src/suite/instantiates/auctions_manager.rs index 6fe826b0..85244e20 100644 --- a/tests/rust-tests/src/suite/instantiates/auctions_manager.rs +++ b/tests/rust-tests/src/suite/instantiates/auctions_manager.rs @@ -1,3 +1,4 @@ +use auction_package::states::MinAmount; use cosmwasm_std::Uint128; use crate::suite::suite::{ATOM, NTRN, OSMO}; @@ -19,9 +20,27 @@ impl AuctionsManagerInstantiate { pub fn new(auction_code_id: u64) -> Self { let min_auction_amount = vec![ - (ATOM.to_string(), Uint128::new(5)), - (NTRN.to_string(), Uint128::new(10)), - (OSMO.to_string(), Uint128::new(10)), + ( + ATOM.to_string(), + MinAmount { + send: Uint128::new(5), + start_auction: Uint128::new(5), + }, + ), + ( + NTRN.to_string(), + MinAmount { + send: Uint128::new(10), + start_auction: Uint128::new(10), + }, + ), + ( + OSMO.to_string(), + MinAmount { + send: Uint128::new(10), + start_auction: Uint128::new(10), + }, + ), ]; Self { diff --git a/tests/rust-tests/src/suite/instantiates/mod.rs b/tests/rust-tests/src/suite/instantiates/mod.rs index 51f83e79..c6fdee56 100644 --- a/tests/rust-tests/src/suite/instantiates/mod.rs +++ b/tests/rust-tests/src/suite/instantiates/mod.rs @@ -1,4 +1,7 @@ mod account; +mod astro_factory; +mod astro_pair; +mod astro_registery; mod auction; mod auctions_manager; mod oracle; @@ -6,6 +9,8 @@ mod rebalancer; mod services_manager; pub use self::account::AccountInstantiate; +pub use self::astro_factory::AstroFactoryInstantiate; +pub use self::astro_registery::AstroRegisteryInstantiate; pub use self::auction::AuctionInstantiate; pub use self::auctions_manager::AuctionsManagerInstantiate; pub use self::oracle::OracleInstantiate; diff --git a/tests/rust-tests/src/suite/instantiates/oracle.rs b/tests/rust-tests/src/suite/instantiates/oracle.rs index a84c2c5d..6d6349d5 100644 --- a/tests/rust-tests/src/suite/instantiates/oracle.rs +++ b/tests/rust-tests/src/suite/instantiates/oracle.rs @@ -19,6 +19,8 @@ impl OracleInstantiate { Self { msg: price_oracle::msg::InstantiateMsg { auctions_manager_addr: auctions_manager_addr.to_string(), + seconds_allow_manual_change: 60 * 60 * 24 * 2, // 2 days + seconds_auction_prices_fresh: 60 * 60 * 24 * 3, // 3 days }, } } diff --git a/tests/rust-tests/src/suite/instantiates/rebalancer.rs b/tests/rust-tests/src/suite/instantiates/rebalancer.rs index a596c3b6..97b24e9f 100644 --- a/tests/rust-tests/src/suite/instantiates/rebalancer.rs +++ b/tests/rust-tests/src/suite/instantiates/rebalancer.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{testing::mock_env, Timestamp, Uint128}; -use valence_package::services::rebalancer::BaseDenom; +use valence_package::services::rebalancer::{BaseDenom, ServiceFeeConfig}; use crate::suite::suite::{ATOM, NTRN, OSMO}; @@ -33,6 +33,11 @@ impl RebalancerInstantiate { cycle_start: mock_env().block.time, auctions_manager_addr: auctions_manager.to_string(), // to modify cycle_period: None, + fees: ServiceFeeConfig { + denom: NTRN.to_string(), + register_fee: Uint128::zero(), + resume_fee: Uint128::zero(), + }, }, } } @@ -44,6 +49,7 @@ impl RebalancerInstantiate { services_manager: &str, auctions_manager: &str, cycle_period: Option, + fees: ServiceFeeConfig, ) -> Self { Self { msg: rebalancer::msg::InstantiateMsg { @@ -53,6 +59,7 @@ impl RebalancerInstantiate { cycle_start, auctions_manager_addr: auctions_manager.to_string(), // to modify cycle_period, + fees, }, } } @@ -85,8 +92,18 @@ impl RebalancerInstantiate { self.msg.auctions_manager_addr = auctions_manager.to_string(); self } + pub fn change_cycle_period(mut self, cycle_period: Option) -> Self { self.msg.cycle_period = cycle_period; self } + + pub fn change_fees(&mut self, fee: impl Into + Copy) -> &mut Self { + self.msg.fees = ServiceFeeConfig { + denom: NTRN.to_string(), + register_fee: fee.into(), + resume_fee: fee.into(), + }; + self + } } diff --git a/tests/rust-tests/src/suite/mod.rs b/tests/rust-tests/src/suite/mod.rs index f1aee4c5..4342be1a 100644 --- a/tests/rust-tests/src/suite/mod.rs +++ b/tests/rust-tests/src/suite/mod.rs @@ -1,3 +1,4 @@ +pub mod builder_astro; pub mod contracts; pub mod instantiates; #[allow(clippy::module_inception)] diff --git a/tests/rust-tests/src/suite/suite.rs b/tests/rust-tests/src/suite/suite.rs index 7741a16b..71beb825 100644 --- a/tests/rust-tests/src/suite/suite.rs +++ b/tests/rust-tests/src/suite/suite.rs @@ -2,27 +2,28 @@ use std::collections::HashMap; use auction_package::Pair; use cosmwasm_schema::{cw_serde, serde, QueryResponses}; -use cosmwasm_std::{to_json_binary, Addr, Coin, Empty, StdError}; +use cosmwasm_std::{to_json_binary, Addr, Coin, Empty, StdError, Uint128}; use cw_multi_test::{App, AppResponse, Executor}; use rebalancer::{ contract::DEFAULT_CYCLE_PERIOD, msg::{ManagersAddrsResponse, WhitelistsResponse}, }; use valence_package::services::{ - rebalancer::{BaseDenom, RebalancerConfig, SystemRebalanceStatus}, + rebalancer::{BaseDenom, PauseData, RebalancerConfig, ServiceFeeConfig, SystemRebalanceStatus}, ValenceServices, }; use super::{instantiates::AccountInstantiate, suite_builder::SuiteBuilder}; pub const ATOM: &str = "uatom"; -pub const NTRN: &str = "untrn"; +pub const NTRN: &str = "ibc/untrn"; pub const OSMO: &str = "uosmo"; pub const ADMIN: &str = "admin"; pub const ACC_OWNER: &str = "owner"; pub const TRUSTEE: &str = "trustee"; pub const MM: &str = "market_maker"; +pub const FEE: &str = "fee_addr"; // PID defaults pub const DEFAULT_P: &str = "0.5"; @@ -36,6 +37,8 @@ pub const HALF_DAY: u64 = DAY / 2; pub const DEFAULT_NTRN_PRICE_BPS: u64 = 15000; pub const DEFAULT_OSMO_PRICE_BPS: u64 = 25000; +pub const DEFAULT_BALANCE_AMOUNT: Uint128 = Uint128::new(1_000_000_000_000); + pub(crate) struct Suite { pub app: App, pub admin: Addr, @@ -53,6 +56,9 @@ pub(crate) struct Suite { // code ids for future use pub account_code_id: u64, + + // astro + pub astro_pools: HashMap<(String, String), Addr>, } impl Default for Suite { @@ -71,6 +77,7 @@ impl Suite { self } + /// Updates block by 1 cycle (24 hours) pub fn update_block_cycle(&mut self) -> &mut Self { self.update_block(DEFAULT_CYCLE_PERIOD / DEFAULT_BLOCK_TIME) } @@ -184,6 +191,17 @@ impl Suite { ) } + pub fn register_to_rebalancer_fee_err( + &mut self, + account_position: u64, + register_data: &D, + ) -> StdError { + self.register_to_rebalancer(account_position, register_data) + .unwrap_err() + .downcast() + .unwrap() + } + pub fn register_to_rebalancer_err( &mut self, account_position: u64, @@ -318,6 +336,20 @@ impl Suite { &[], ) } + + pub fn update_rebalancer_fees( + &mut self, + fees: ServiceFeeConfig, + ) -> Result { + self.app.execute_contract( + self.admin.clone(), + self.rebalancer_addr.clone(), + &valence_package::services::rebalancer::RebalancerExecuteMsg::::Admin( + valence_package::services::rebalancer::RebalancerAdminMsg::UpdateFess { fees }, + ), + &[], + ) + } } // Execute service management @@ -446,6 +478,7 @@ impl Suite { account_addr, &valence_package::msgs::core_execute::AccountBaseExecuteMsg::PauseService { service_name, + reason: Some("Some reason".to_string()), }, &[], ) @@ -464,6 +497,7 @@ impl Suite { &valence_package::msgs::core_execute::ServicesManagerExecuteMsg::PauseService { service_name, pause_for: account_addr.to_string(), + reason: Some("Some reason".to_string()), }, &[], ) @@ -513,6 +547,22 @@ impl Suite { &[], ) } + + pub fn withdraw_fees_from_manager( + &mut self, + denom: impl Into, + ) -> Result { + self.app.execute_contract( + self.admin.clone(), + self.manager_addr.clone(), + &valence_package::msgs::core_execute::ServicesManagerExecuteMsg::Admin( + valence_package::msgs::core_execute::ServicesManagerAdminMsg::Withdraw { + denom: denom.into(), + }, + ), + &[], + ) + } } // Queries @@ -526,6 +576,15 @@ impl Suite { ) } + pub fn query_rebalancer_paused_config(&self, account: Addr) -> Result { + self.app.wrap().query_wasm_smart( + self.rebalancer_addr.clone(), + &rebalancer::msg::QueryMsg::GetPausedConfig { + addr: account.to_string(), + }, + ) + } + pub fn query_service_addr_from_manager( &self, service: ValenceServices, @@ -589,7 +648,6 @@ impl Suite { assert!(query_config.targets.contains(target)); } - assert_eq!(query_config.is_paused, config.is_paused); assert_eq!(query_config.trustee, config.trustee); assert_eq!(query_config.base_denom, config.base_denom); assert_eq!(query_config.pid, config.pid); @@ -601,10 +659,4 @@ impl Suite { config.target_override_strategy ); } - - pub fn assert_rebalancer_is_paused(&self, account_position: u64, is_paused: Option) { - let account_addr = self.get_account_addr(account_position); - let query_config = self.query_rebalancer_config(account_addr).unwrap(); - assert_eq!(query_config.is_paused, is_paused) - } } diff --git a/tests/rust-tests/src/suite/suite_auction.rs b/tests/rust-tests/src/suite/suite_auction.rs index 19897af7..ebca2a50 100644 --- a/tests/rust-tests/src/suite/suite_auction.rs +++ b/tests/rust-tests/src/suite/suite_auction.rs @@ -4,10 +4,14 @@ use auction::{ }; use auction_package::{ helpers::{ChainHaltConfig, GetPriceResponse}, + msgs::AuctionsManagerQueryMsg, + states::MinAmount, AuctionStrategy, Pair, PriceFreshnessStrategy, }; use cosmwasm_std::{coin, coins, Addr, Coin, Decimal, Uint128}; use cw_multi_test::{AppResponse, Executor}; +use price_oracle::state::PriceStep; +use rand::{rngs::ThreadRng, Rng}; use valence_package::signed_decimal::SignedDecimal; use super::suite::{Suite, ATOM, DAY, DEFAULT_BLOCK_TIME, HALF_DAY, NTRN}; @@ -18,18 +22,19 @@ impl Suite { &mut self, pair: Pair, init_msg: auction::msg::InstantiateMsg, - min_amount: Option, + min_amount: Option, ) -> &mut Self { self.app .execute_contract( self.admin.clone(), self.auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::NewAuction { msg: init_msg.clone(), + label: "label".to_string(), min_amount, }, - ), + )), &[], ) .unwrap(); @@ -52,18 +57,19 @@ impl Suite { pub fn init_auction_err( &mut self, init_msg: auction::msg::InstantiateMsg, - min_amount: Option, + min_amount: Option, ) -> anyhow::Error { self.app .execute_contract( self.admin.clone(), self.auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::NewAuction { msg: init_msg, + label: "label".to_string(), min_amount, }, - ), + )), &[], ) .unwrap_err() @@ -74,7 +80,7 @@ impl Suite { .execute_contract( user, auction_addr, - &auction::msg::ExecuteMsg::AuctionFunds, + &auction::msg::ExecuteMsg::AuctionFunds {}, amount, ) .unwrap(); @@ -92,7 +98,7 @@ impl Suite { .execute_contract( user, auction_addr, - &auction::msg::ExecuteMsg::AuctionFunds, + &auction::msg::ExecuteMsg::AuctionFunds {}, amount, ) .unwrap_err() @@ -109,7 +115,7 @@ impl Suite { self.app.execute_contract( self.admin.clone(), self.auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::OpenAuction { pair, params: NewAuctionParams { @@ -117,7 +123,7 @@ impl Suite { end_block, }, }, - ), + )), &[], ) } @@ -154,7 +160,7 @@ impl Suite { self.app.execute_contract( self.mm.clone(), auction_addr, - &auction::msg::ExecuteMsg::Bid, + &auction::msg::ExecuteMsg::Bid {}, &[amount], ) } @@ -173,7 +179,7 @@ impl Suite { .execute_contract( self.mm.clone(), auction_addr, - &auction::msg::ExecuteMsg::Bid, + &auction::msg::ExecuteMsg::Bid {}, &[amount], ) .unwrap_err() @@ -231,25 +237,39 @@ impl Suite { self.rebalance(None).unwrap(); - // Its find if we can't update price yet - let _ = self.update_price(pair1.clone(), None); - let _ = self.update_price(pair2.clone(), None); + // Its fine if we can't update price yet + let _ = self.update_price(pair1.clone()); + let _ = self.update_price(pair2.clone()); + let _ = self.start_auction( + pair1.clone(), + None, + self.app.block_info().height + (DAY / DEFAULT_BLOCK_TIME), + ); let auction1_started = self - .start_auction( - pair1.clone(), - None, - self.app.block_info().height + (DAY / DEFAULT_BLOCK_TIME), + .query_auction_details( + self.auction_addrs + .get(&pair1.clone().into()) + .unwrap() + .clone(), ) - .is_ok(); + .status + == auction::state::ActiveAuctionStatus::Started; + let _ = self.start_auction( + pair2.clone(), + None, + self.app.block_info().height + (DAY / DEFAULT_BLOCK_TIME), + ); let auction2_started = self - .start_auction( - pair2.clone(), - None, - self.app.block_info().height + (DAY / DEFAULT_BLOCK_TIME), + .query_auction_details( + self.auction_addrs + .get(&pair2.clone().into()) + .unwrap() + .clone(), ) - .is_ok(); + .status + == auction::state::ActiveAuctionStatus::Started; self.update_block(HALF_DAY / DEFAULT_BLOCK_TIME); @@ -303,28 +323,42 @@ impl Suite { price - price * price_change.value() }; - self.update_price(pair, Some(new_price)).unwrap(); + self.manual_update_price(pair, new_price).unwrap(); } - pub fn update_price( + // Permissionless price udpate method + pub fn update_price(&mut self, pair: Pair) -> Result { + self.app.execute_contract( + self.admin.clone(), + self.oracle_addr.clone(), + &price_oracle::msg::ExecuteMsg::UpdatePrice { pair }, + &[], + ) + } + + pub fn manual_update_price( &mut self, pair: Pair, - price: Option, + price: Decimal, ) -> Result { self.app.execute_contract( self.admin.clone(), self.oracle_addr.clone(), - &price_oracle::msg::ExecuteMsg::UpdatePrice { pair, price }, + &price_oracle::msg::ExecuteMsg::ManualPriceUpdate { pair, price }, &[], ) } - pub fn update_price_err( + pub fn update_price_err(&mut self, pair: Pair) -> price_oracle::error::ContractError { + self.update_price(pair).unwrap_err().downcast().unwrap() + } + + pub fn manual_update_price_err( &mut self, pair: Pair, - price: Option, + price: Decimal, ) -> price_oracle::error::ContractError { - self.update_price(pair, price) + self.manual_update_price(pair, price) .unwrap_err() .downcast() .unwrap() @@ -340,7 +374,7 @@ impl Suite { .execute_contract( self.mm.clone(), self.get_default_auction_addr(), - &auction::msg::ExecuteMsg::Bid, + &auction::msg::ExecuteMsg::Bid {}, &coins(amount.u128(), self.pair.1.clone()), ) .unwrap() @@ -351,9 +385,9 @@ impl Suite { .execute_contract( self.admin.clone(), self.auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::PauseAuction { pair }, - ), + )), &[], ) .unwrap(); @@ -364,9 +398,9 @@ impl Suite { self.app.execute_contract( self.admin.clone(), self.auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::ResumeAuction { pair }, - ), + )), &[], ) } @@ -376,7 +410,7 @@ impl Suite { .execute_contract( self.auctions_manager_addr.clone(), auction_addr, - &auction::msg::ExecuteMsg::CleanAfterAuction, + &auction::msg::ExecuteMsg::CleanAfterAuction {}, &[], ) .unwrap(); @@ -389,7 +423,7 @@ impl Suite { .execute_contract( self.auctions_manager_addr.clone(), auction_addr, - &auction::msg::ExecuteMsg::CleanAfterAuction, + &auction::msg::ExecuteMsg::CleanAfterAuction {}, &[], ) .unwrap_err() @@ -402,9 +436,9 @@ impl Suite { .execute_contract( self.admin.clone(), self.auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::UpdateStrategy { pair, strategy }, - ), + )), &[], ) .unwrap(); @@ -417,11 +451,11 @@ impl Suite { .execute_contract( self.admin.clone(), self.auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::UpdateOracle { oracle_addr: oracle_addr.to_string(), }, - ), + )), &[], ) .unwrap(); @@ -450,9 +484,9 @@ impl Suite { .execute_contract( self.admin.clone(), self.auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::UpdateChainHaltConfig { pair, halt_config }, - ), + )), &[], ) .unwrap(); @@ -469,12 +503,12 @@ impl Suite { .execute_contract( self.admin.clone(), self.auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::UpdatePriceFreshnessStrategy { pair, strategy, }, - ), + )), &[], ) .unwrap(); @@ -490,7 +524,7 @@ impl Suite { self.app.execute_contract( user, auction_addr, - &auction::msg::ExecuteMsg::WithdrawFunds, + &auction::msg::ExecuteMsg::WithdrawFunds {}, &[], ) } @@ -518,6 +552,80 @@ impl Suite { self } + + pub fn add_astro_path_to_oracle( + &mut self, + pair: Pair, + path: Vec, + ) -> Result { + self.app.execute_contract( + self.admin.clone(), + self.oracle_addr.clone(), + &price_oracle::msg::ExecuteMsg::AddAstroPath { pair, path }, + &[], + ) + } + + pub fn add_astro_path_to_oracle_err( + &mut self, + pair: Pair, + path: Vec, + ) -> price_oracle::error::ContractError { + self.add_astro_path_to_oracle(pair, path) + .unwrap_err() + .downcast() + .unwrap() + } + + pub fn astro_swap(&mut self, pool_addr: Addr, coin: Coin) -> &mut Self { + let offer_asset = astroport::asset::Asset { + info: astroport::asset::AssetInfo::NativeToken { + denom: coin.denom.clone(), + }, + amount: coin.amount, + }; + + let swap_msg = astroport::pair::ExecuteMsg::Swap { + offer_asset, + ask_asset_info: None, + belief_price: None, + max_spread: Some(Decimal::bps(5000)), + to: None, + }; + + self.app + .execute_contract(self.admin.clone(), pool_addr, &swap_msg, &[coin]) + .unwrap(); + + self + } + + pub fn do_random_swap( + &mut self, + rng: &mut ThreadRng, + pair: Pair, + min_limit: u128, + max_limit: u128, + ) -> &mut Self { + let pool_addr = self.astro_pools.get(&pair.clone().into()).unwrap().clone(); + let swap_amount = rng.gen_range(min_limit..max_limit); + let denom_index = rng.gen_range(0..2); + + let coin = match denom_index { + 0 => Coin { + denom: pair.0, + amount: Uint128::from(swap_amount), + }, + _ => Coin { + denom: pair.1, + amount: Uint128::from(swap_amount), + }, + }; + self.update_block_cycle(); + // println!("Swap amount: {}", coin); + + self.astro_swap(pool_addr, coin) + } } // Queries @@ -540,16 +648,17 @@ impl Suite { .price } - pub fn get_min_limit(&mut self, denom: &str) -> Uint128 { + pub fn get_send_min_limit(&mut self, denom: &str) -> Uint128 { self.app .wrap() - .query_wasm_smart( + .query_wasm_smart::( self.auctions_manager_addr.clone(), &auction_package::msgs::AuctionsManagerQueryMsg::GetMinLimit { denom: denom.to_string(), }, ) .unwrap() + .send } pub fn query_auction_details(&self, auction_addr: Addr) -> ActiveAuction { @@ -624,6 +733,44 @@ impl Suite { ) .unwrap() } + + pub fn query_auctions_manager_all_pairs(&self) -> Vec<(Pair, Addr)> { + self.app + .wrap() + .query_wasm_smart( + self.auctions_manager_addr.clone(), + &AuctionsManagerQueryMsg::GetPairs { + start_after: None, + limit: None, + }, + ) + .unwrap() + } + + pub fn query_astro_pool_price(&self, pool_addr: Addr, pair: Pair) -> Decimal { + let multiply = 1_000_000_u128; + let res: astroport::pair::SimulationResponse = self + .app + .wrap() + .query_wasm_smart( + pool_addr, + &astroport::pair::QueryMsg::Simulation { + offer_asset: astroport::asset::Asset { + info: astroport::asset::AssetInfo::NativeToken { denom: pair.0 }, + amount: multiply.into(), + }, + ask_asset_info: None, + }, + ) + .unwrap(); + + let total_got = Decimal::from_atomics( + res.return_amount + res.commission_amount + res.spread_amount, + 0, + ) + .unwrap(); + total_got / Decimal::from_atomics(multiply, 0).unwrap() + } } // Helpers diff --git a/tests/rust-tests/src/suite/suite_builder.rs b/tests/rust-tests/src/suite/suite_builder.rs index 373fcb5f..37e0e9c3 100644 --- a/tests/rust-tests/src/suite/suite_builder.rs +++ b/tests/rust-tests/src/suite/suite_builder.rs @@ -3,7 +3,7 @@ use std::{ collections::{HashMap, HashSet}, }; -use auction_package::Pair; +use auction_package::{states::MinAmount, Pair}; use cosmwasm_schema::serde; use cosmwasm_std::{coin, coins, from_json, Addr, Decimal, Uint128}; use cw_multi_test::{App, AppBuilder, Executor}; @@ -15,16 +15,17 @@ use valence_package::services::{ use super::{ contracts::{ - account_contract, auction_contract, auctions_manager_contract, oracle_contract, - rebalancer_contract, services_manager_contract, + account_contract, astro_coin_registry_contract, astro_factory_contract, + astro_pair_contract, astro_pair_stable_contract, astro_token_contract, auction_contract, + auctions_manager_contract, oracle_contract, rebalancer_contract, services_manager_contract, }, instantiates::{ AccountInstantiate, AuctionInstantiate, AuctionsManagerInstantiate, OracleInstantiate, RebalancerInstantiate, ServicesManagerInstantiate, }, suite::{ - Suite, ACC_OWNER, ADMIN, ATOM, DEFAULT_D, DEFAULT_I, DEFAULT_NTRN_PRICE_BPS, - DEFAULT_OSMO_PRICE_BPS, DEFAULT_P, MM, NTRN, OSMO, TRUSTEE, + Suite, ACC_OWNER, ADMIN, ATOM, DEFAULT_BALANCE_AMOUNT, DEFAULT_D, DEFAULT_I, + DEFAULT_NTRN_PRICE_BPS, DEFAULT_OSMO_PRICE_BPS, DEFAULT_P, MM, NTRN, OSMO, TRUSTEE, }, }; @@ -52,6 +53,13 @@ pub(crate) struct SuiteBuilder { // Flags for adding stuff to builds for tests pub add_oracle_addr: bool, + + // astro code id + pub astro_factory_code_id: u64, + pub astro_registery_code_id: u64, + pub astro_token_code_id: u64, + pub astro_pair_code_id: u64, + pub astro_stable_pair_code_id: u64, } impl Default for SuiteBuilder { @@ -76,6 +84,11 @@ impl Default for SuiteBuilder { oracle_code_id: 100000, custom_rebalancer_init: None, add_oracle_addr: true, + astro_factory_code_id: 100000, + astro_registery_code_id: 100000, + astro_token_code_id: 100000, + astro_pair_code_id: 100000, + astro_stable_pair_code_id: 100000, } } } @@ -172,6 +185,11 @@ impl SuiteBuilder { self.auction_code_id = app.store_code(auction_contract()); self.auctions_manager_code_id = app.store_code(auctions_manager_contract()); self.oracle_code_id = app.store_code(oracle_contract()); + self.astro_factory_code_id = app.store_code(astro_factory_contract()); + self.astro_registery_code_id = app.store_code(astro_coin_registry_contract()); + self.astro_token_code_id = app.store_code(astro_token_contract()); + self.astro_pair_code_id = app.store_code(astro_pair_contract()); + self.astro_stable_pair_code_id = app.store_code(astro_pair_stable_contract()); } pub fn init_auctions_manager( @@ -206,7 +224,11 @@ impl SuiteBuilder { .unwrap() } - fn init_auctions(&mut self, app: &mut App) -> (Addr, Addr, HashMap<(String, String), Addr>) { + fn init_auctions( + &mut self, + app: &mut App, + with_prices: bool, + ) -> (Addr, Addr, HashMap<(String, String), Addr>) { // init Auctions manager contract let auctions_manager_addr = self.init_auctions_manager( app, @@ -225,11 +247,11 @@ impl SuiteBuilder { app.execute_contract( self.admin.clone(), auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::UpdateOracle { oracle_addr: price_oracle_addr.to_string(), }, - ), + )), &[], ) .unwrap(); @@ -240,12 +262,16 @@ impl SuiteBuilder { app.execute_contract( self.admin.clone(), auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::NewAuction { msg: AuctionInstantiate::atom_ntrn().into(), - min_amount: Some(Uint128::new(5)), + label: "atom-ntrn".to_string(), + min_amount: Some(MinAmount { + send: Uint128::new(5), + start_auction: Uint128::new(10), + }), }, - ), + )), &[], ) .unwrap(); @@ -254,12 +280,16 @@ impl SuiteBuilder { app.execute_contract( self.admin.clone(), auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::NewAuction { msg: AuctionInstantiate::atom_osmo().into(), - min_amount: Some(Uint128::new(5)), + label: "atom-osmo".to_string(), + min_amount: Some(MinAmount { + send: Uint128::new(5), + start_auction: Uint128::new(10), + }), }, - ), + )), &[], ) .unwrap(); @@ -268,12 +298,16 @@ impl SuiteBuilder { app.execute_contract( self.admin.clone(), auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::NewAuction { msg: AuctionInstantiate::ntrn_atom().into(), - min_amount: Some(Uint128::new(10)), + label: "ntrn-atom".to_string(), + min_amount: Some(MinAmount { + send: Uint128::new(5), + start_auction: Uint128::new(10), + }), }, - ), + )), &[], ) .unwrap(); @@ -282,12 +316,16 @@ impl SuiteBuilder { app.execute_contract( self.admin.clone(), auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::NewAuction { msg: AuctionInstantiate::ntrn_osmo().into(), - min_amount: Some(Uint128::new(10)), + label: "ntrn-osmo".to_string(), + min_amount: Some(MinAmount { + send: Uint128::new(5), + start_auction: Uint128::new(10), + }), }, - ), + )), &[], ) .unwrap(); @@ -296,12 +334,16 @@ impl SuiteBuilder { app.execute_contract( self.admin.clone(), auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::NewAuction { msg: AuctionInstantiate::osmo_atom().into(), - min_amount: Some(Uint128::new(10)), + label: "osmo-atom".to_string(), + min_amount: Some(MinAmount { + send: Uint128::new(5), + start_auction: Uint128::new(10), + }), }, - ), + )), &[], ) .unwrap(); @@ -310,12 +352,16 @@ impl SuiteBuilder { app.execute_contract( self.admin.clone(), auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::NewAuction { msg: AuctionInstantiate::osmo_ntrn().into(), - min_amount: Some(Uint128::new(10)), + label: "osmo_ntrn".to_string(), + min_amount: Some(MinAmount { + send: Uint128::new(5), + start_auction: Uint128::new(10), + }), }, - ), + )), &[], ) .unwrap(); @@ -350,17 +396,19 @@ impl SuiteBuilder { let mut auctions = HashMap::<(String, String), Addr>::new(); for (pair, price) in pairs { - // update price - app.execute_contract( - self.admin.clone(), - price_oracle_addr.clone(), - &price_oracle::msg::ExecuteMsg::UpdatePrice { - pair: pair.clone(), - price: Some(price), - }, - &[], - ) - .unwrap(); + if with_prices { + // update price + app.execute_contract( + self.admin.clone(), + price_oracle_addr.clone(), + &price_oracle::msg::ExecuteMsg::ManualPriceUpdate { + pair: pair.clone(), + price, + }, + &[], + ) + .unwrap(); + } let auction_addr: Addr = app .wrap() @@ -451,9 +499,9 @@ impl SuiteBuilder { impl SuiteBuilder { pub fn set_app(&mut self) -> App { let balances = vec![ - coin(1000000000_u128, ATOM.to_string()), - coin(1000000000_u128, NTRN.to_string()), - coin(1000000000_u128, OSMO.to_string()), + coin(DEFAULT_BALANCE_AMOUNT.u128(), ATOM.to_string()), + coin(DEFAULT_BALANCE_AMOUNT.u128(), NTRN.to_string()), + coin(DEFAULT_BALANCE_AMOUNT.u128(), OSMO.to_string()), ]; AppBuilder::new().build(|router, _, storage| { @@ -476,7 +524,7 @@ impl SuiteBuilder { }) } /// build a basic suite that upload all contracts and init contracts - pub fn build_basic(&mut self) -> Suite { + pub fn build_basic(&mut self, with_price: bool) -> Suite { let mut app = self.set_app(); // upload contracts @@ -484,7 +532,7 @@ impl SuiteBuilder { // Init auction let (auctions_manager_addr, oracle_addr, auction_addrs) = - self.init_auctions(app.borrow_mut()); + self.init_auctions(app.borrow_mut(), with_price); // Init services manager let manager_addr = self.init_manager(app.borrow_mut()); @@ -499,6 +547,8 @@ impl SuiteBuilder { // Init accounts based on the amount is set let account_addrs = self.init_accounts(app.borrow_mut(), manager_addr.clone()); + let pools = self.init_astro(app.borrow_mut()); + Suite { app, admin: self.admin.clone(), @@ -513,12 +563,13 @@ impl SuiteBuilder { auction_addrs, account_code_id: self.account_code_id, pair: Pair::from((ATOM.to_string(), NTRN.to_string())), + astro_pools: pools, } } /// Does a basic build but also add service to manager and register accounts to the services pub fn build_default(&mut self) -> Suite { - let mut suite = self.build_basic(); + let mut suite = self.build_basic(true); // Add the rebalancer to the services manager suite diff --git a/tests/rust-tests/src/test_service_management.rs b/tests/rust-tests/src/test_service_management.rs index 9c71269f..53c5fdc5 100644 --- a/tests/rust-tests/src/test_service_management.rs +++ b/tests/rust-tests/src/test_service_management.rs @@ -21,7 +21,7 @@ use crate::suite::{ #[test] fn test_add_service() { - let mut suite = SuiteBuilder::default().build_basic(); + let mut suite = SuiteBuilder::default().build_basic(true); suite .add_service_to_manager( @@ -73,7 +73,7 @@ fn test_add_service() { #[test] fn test_register() { - let mut suite = SuiteBuilder::default().with_accounts(2).build_basic(); + let mut suite = SuiteBuilder::default().with_accounts(2).build_basic(true); // Because we have a basic setup here, we need to register the service to the manager suite @@ -97,7 +97,6 @@ fn test_register() { suite.assert_rebalancer_config( 0, RebalancerConfig { - is_paused: None, trustee: None, base_denom: ATOM.to_string(), targets: vec![ @@ -144,8 +143,7 @@ fn test_register() { suite.assert_rebalancer_config( 1, RebalancerConfig { - is_paused: None, - trustee: register_data_1.trustee, + trustee: Some(suite.trustee.clone()), base_denom: ATOM.to_string(), targets: vec![ ParsedTarget { @@ -226,18 +224,25 @@ fn test_pause() { /* Try to pause as someone random (trustee that isn't set as trustee) */ let err: rebalancer::error::ContractError = suite - .pause_service_with_sender(suite.trustee.clone(), 0, ValenceServices::Rebalancer) + .pause_service_with_sender( + Addr::unchecked("random_sender"), + 0, + ValenceServices::Rebalancer, + ) .unwrap_err() .downcast() .unwrap(); assert_eq!(err, rebalancer::error::ContractError::NotAuthorizedToPause); suite.pause_service(0, ValenceServices::Rebalancer).unwrap(); - suite.assert_rebalancer_is_paused(0, Some(account_addr_0)); + let paused_config = suite + .query_rebalancer_paused_config(account_addr_0.clone()) + .unwrap(); + assert_eq!(paused_config.pauser, account_addr_0); // If account paused try to pause again, it should fail let err: rebalancer::error::ContractError = suite - .pause_service_with_sender(suite.owner.clone(), 0, ValenceServices::Rebalancer) + .pause_service(0, ValenceServices::Rebalancer) .unwrap_err() .downcast() .unwrap(); @@ -245,7 +250,10 @@ fn test_pause() { /* Pause as the account owner */ suite.pause_service(1, ValenceServices::Rebalancer).unwrap(); - suite.assert_rebalancer_is_paused(1, Some(account_addr_1)); + let paused_config = suite + .query_rebalancer_paused_config(account_addr_1.clone()) + .unwrap(); + assert_eq!(paused_config.pauser, account_addr_1); // Trustee can't pause after main account paused let err: rebalancer::error::ContractError = suite @@ -270,11 +278,17 @@ fn test_pause() { suite .pause_service_with_sender(suite.trustee.clone(), 2, ValenceServices::Rebalancer) .unwrap(); - suite.assert_rebalancer_is_paused(2, Some(suite.trustee.clone())); + let paused_config = suite + .query_rebalancer_paused_config(account_addr_2.clone()) + .unwrap(); + assert_eq!(paused_config.pauser, suite.trustee); // try pausing as the owner after trustee paused suite.pause_service(2, ValenceServices::Rebalancer).unwrap(); - suite.assert_rebalancer_is_paused(2, Some(account_addr_2)); + let paused_config = suite + .query_rebalancer_paused_config(account_addr_2.clone()) + .unwrap(); + assert_eq!(paused_config.pauser, account_addr_2); } #[test] @@ -294,7 +308,9 @@ fn test_resume() { suite .resume_service(0, ValenceServices::Rebalancer) .unwrap(); - suite.assert_rebalancer_is_paused(0, None); + suite + .query_rebalancer_paused_config(suite.get_account_addr(0)) + .unwrap_err(); /* Pause as account owner, but try to resume as trustee (can't because owner paused it) */ suite.pause_service(1, ValenceServices::Rebalancer).unwrap(); @@ -376,7 +392,7 @@ fn test_update() { i: "0.5".to_string(), d: "0.5".to_string(), }), - max_limit: Some(5000), + max_limit_bps: Some(valence_package::helpers::OptionalField::Set(5000)), target_override_strategy: Some(TargetOverrideStrategy::Priority), }, ) @@ -385,8 +401,7 @@ fn test_update() { suite.assert_rebalancer_config( 0, RebalancerConfig { - is_paused: None, - trustee: Some("random_addr".to_string()), + trustee: Some(Addr::unchecked("random_addr")), base_denom: NTRN.to_string(), targets: vec![ ParsedTarget { @@ -499,7 +514,7 @@ fn test_update_admin_start() { .execute_contract( new_admin.clone(), suite.manager_addr.clone(), - &price_oracle::msg::ExecuteMsg::ApproveAdminChange, + &price_oracle::msg::ExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap_err(); @@ -524,7 +539,7 @@ fn test_update_admin_start() { .execute_contract( new_admin.clone(), suite.manager_addr.clone(), - &valence_package::msgs::core_execute::ServicesManagerExecuteMsg::ApproveAdminChange, + &valence_package::msgs::core_execute::ServicesManagerExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap(); @@ -571,7 +586,7 @@ fn test_update_admin_cancel() { .execute_contract( new_admin, suite.manager_addr.clone(), - &valence_package::msgs::core_execute::ServicesManagerExecuteMsg::ApproveAdminChange, + &valence_package::msgs::core_execute::ServicesManagerExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap_err(); @@ -604,7 +619,7 @@ fn test_update_admin_fails() { .execute_contract( random_addr, suite.manager_addr.clone(), - &valence_package::msgs::core_execute::ServicesManagerExecuteMsg::ApproveAdminChange, + &valence_package::msgs::core_execute::ServicesManagerExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap_err(); @@ -617,7 +632,7 @@ fn test_update_admin_fails() { .execute_contract( new_admin, suite.manager_addr.clone(), - &valence_package::msgs::core_execute::ServicesManagerExecuteMsg::ApproveAdminChange, + &valence_package::msgs::core_execute::ServicesManagerExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap_err(); diff --git a/tests/rust-tests/src/tests_account/test_account.rs b/tests/rust-tests/src/tests_account/test_account.rs index 6246417e..a349e2b8 100644 --- a/tests/rust-tests/src/tests_account/test_account.rs +++ b/tests/rust-tests/src/tests_account/test_account.rs @@ -155,7 +155,7 @@ fn test_update_admin_start() { .execute_contract( new_admin.clone(), account_addr.clone(), - &price_oracle::msg::ExecuteMsg::ApproveAdminChange, + &price_oracle::msg::ExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap_err(); @@ -178,7 +178,7 @@ fn test_update_admin_start() { .execute_contract( new_admin.clone(), account_addr.clone(), - &price_oracle::msg::ExecuteMsg::ApproveAdminChange, + &price_oracle::msg::ExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap(); @@ -211,7 +211,7 @@ fn test_update_admin_cancel() { .execute_contract( suite.owner.clone(), account_addr.clone(), - &price_oracle::msg::ExecuteMsg::CancelAdminChange, + &price_oracle::msg::ExecuteMsg::CancelAdminChange {}, &[], ) .unwrap(); @@ -222,7 +222,7 @@ fn test_update_admin_cancel() { .execute_contract( new_admin, account_addr, - &price_oracle::msg::ExecuteMsg::ApproveAdminChange, + &price_oracle::msg::ExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap_err(); @@ -254,7 +254,7 @@ fn test_update_admin_fails() { .execute_contract( random_addr, account_addr.clone(), - &price_oracle::msg::ExecuteMsg::ApproveAdminChange, + &price_oracle::msg::ExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap_err(); @@ -267,7 +267,7 @@ fn test_update_admin_fails() { .execute_contract( new_admin, account_addr, - &price_oracle::msg::ExecuteMsg::ApproveAdminChange, + &price_oracle::msg::ExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap_err(); diff --git a/tests/rust-tests/src/tests_auctions/mod.rs b/tests/rust-tests/src/tests_auctions/mod.rs index 6aba223c..3995bce0 100644 --- a/tests/rust-tests/src/tests_auctions/mod.rs +++ b/tests/rust-tests/src/tests_auctions/mod.rs @@ -1,7 +1,7 @@ +mod test_astro; mod test_auction; mod test_auctions_manager; mod test_full_auctions; mod test_instantiates; mod test_oracle; -#[cfg(test)] mod test_withdraw; diff --git a/tests/rust-tests/src/tests_auctions/test_astro.rs b/tests/rust-tests/src/tests_auctions/test_astro.rs new file mode 100644 index 00000000..716ca177 --- /dev/null +++ b/tests/rust-tests/src/tests_auctions/test_astro.rs @@ -0,0 +1,185 @@ +use std::borrow::BorrowMut; + +use auction_package::Pair; +use price_oracle::state::PriceStep; + +use crate::suite::{ + suite::{ATOM, NTRN, OSMO}, + suite_builder::SuiteBuilder, +}; + +#[test] +fn test_add_path_for_pair() { + let mut suite = SuiteBuilder::default().build_basic(true); + + // denom1 is the 2nd denom, so its wrong and should error + let path = vec![PriceStep { + denom1: suite.pair.1.to_string(), + denom2: suite.pair.0.to_string(), + pool_address: suite + .astro_pools + .get(&suite.pair.clone().into()) + .unwrap() + .clone(), + }]; + let err = suite.add_astro_path_to_oracle_err(suite.pair.clone(), path); + assert_eq!(err, price_oracle::error::ContractError::PricePathIsWrong); + + // Should be successful + let path = vec![PriceStep { + denom1: suite.pair.0.to_string(), + denom2: suite.pair.1.to_string(), + pool_address: suite + .astro_pools + .get(&suite.pair.clone().into()) + .unwrap() + .clone(), + }]; + suite + .add_astro_path_to_oracle(suite.pair.clone(), path) + .unwrap(); +} + +// ATOM-NTRN is stable, so the price should be 1-ish +#[test] +fn test_basic_astro_default() { + let mut suite = SuiteBuilder::default().build_basic(true); + + let old_oracle_price = suite.query_oracle_price(suite.pair.clone()); + // This should error because we don't have astro path for the price yet + // and no auction ran so far + let err = suite.update_price_err(suite.pair.clone()); + assert_eq!( + err, + price_oracle::error::ContractError::NoAstroPath(suite.pair.clone()) + ); + + // Register the astro path + let path = vec![PriceStep { + denom1: suite.pair.0.to_string(), + denom2: suite.pair.1.to_string(), + pool_address: suite + .astro_pools + .get(&suite.pair.clone().into()) + .unwrap() + .clone(), + }]; + suite + .add_astro_path_to_oracle(suite.pair.clone(), path) + .unwrap(); + + // Randomize the pool a little to get a "nice" price + let mut rng = rand::thread_rng(); + + for _ in 0..10 { + suite.do_random_swap( + rng.borrow_mut(), + suite.pair.clone(), + 100_000_u128, + 1_000_000_u128, + ); + } + + let pool_price = suite.query_astro_pool_price( + suite + .astro_pools + .get(&suite.pair.clone().into()) + .unwrap() + .clone(), + suite.pair.clone(), + ); + + // Make sure the pool price is not the same as the old price + // To confirm the price actually changed later + assert_ne!(pool_price, old_oracle_price.price); + + // Try to update again + suite.update_price(suite.pair.clone()).unwrap(); + + // Verify we do get an acceptable price (query price from pool) + let oracle_price = suite.query_oracle_price(suite.pair.clone()); + println!("pool_price: {:?}", pool_price); + println!("oracle_price: {:?}", oracle_price); + assert_eq!(oracle_price.price, pool_price); +} + +// Test for a pair that doesn't have a direct pool, so the astro path is 2 pools +#[test] +fn test_complex_astro_default() { + let mut suite = SuiteBuilder::default().build_basic(true); + let complex_pair = Pair::from((ATOM.to_string(), OSMO.to_string())); + + let old_oracle_price = suite.query_oracle_price(complex_pair.clone()); + // Register the astro path + let path = vec![ + PriceStep { + denom1: ATOM.to_string(), + denom2: NTRN.to_string(), + pool_address: suite + .astro_pools + .get(&suite.pair.clone().into()) + .unwrap() + .clone(), + }, + PriceStep { + denom1: NTRN.to_string(), + denom2: OSMO.to_string(), + pool_address: suite + .astro_pools + .get(&(NTRN.to_string(), OSMO.to_string())) + .unwrap() + .clone(), + }, + ]; + suite + .add_astro_path_to_oracle(complex_pair.clone(), path) + .unwrap(); + + // Randomize the pool a little to get a "nice" price + let mut rng = rand::thread_rng(); + + for _ in 0..100 { + suite.do_random_swap( + rng.borrow_mut(), + Pair::from((ATOM.to_string(), NTRN.to_string())), + 100_000_u128, + 1_000_000_u128, + ); + + suite.do_random_swap( + rng.borrow_mut(), + Pair::from((NTRN.to_string(), OSMO.to_string())), + 100_000_u128, + 1_000_000_u128, + ); + } + + // Try to update again + suite.update_price(complex_pair.clone()).unwrap(); + + let oracle_price = suite.query_oracle_price(complex_pair); + // let pool_atom_ntrn_price = suite.query_astro_pool_price( + // suite + // .astro_pools + // .get(&suite.pair.clone().into()) + // .unwrap() + // .clone(), + // suite.pair.clone(), + // ); + // let pool_ntrn_osmo_price = suite.query_astro_pool_price( + // suite + // .astro_pools + // .get(&(NTRN.to_string(), OSMO.to_string())) + // .unwrap() + // .clone(), + // (NTRN.to_string(), OSMO.to_string()).into(), + // ); + + println!("old_oracle_price: {:?}", old_oracle_price); + println!("oracle_price: {:?}", oracle_price); + // println!("pool_atom_ntrn_price: {:?}", pool_atom_ntrn_price); + // println!("pool_ntrn_osmo_price: {:?}", pool_ntrn_osmo_price); + + // make sure the old price is not the same as new price + assert_ne!(oracle_price.price, old_oracle_price.price); +} diff --git a/tests/rust-tests/src/tests_auctions/test_auction.rs b/tests/rust-tests/src/tests_auctions/test_auction.rs index 0a5a9d79..7bbf90f7 100644 --- a/tests/rust-tests/src/tests_auctions/test_auction.rs +++ b/tests/rust-tests/src/tests_auctions/test_auction.rs @@ -2,8 +2,11 @@ use auction::state::{ActiveAuction, ActiveAuctionStatus}; use auction_package::{error::AuctionError, states::TWAP_PRICES}; use cosmwasm_std::{coin, coins, testing::mock_env, Addr, Decimal, Timestamp, Uint128}; use cw_multi_test::Executor; +use price_oracle::state::PriceStep; -use crate::suite::suite::{Suite, DAY, DEFAULT_BLOCK_TIME, DEFAULT_NTRN_PRICE_BPS}; +use crate::suite::suite::{ + Suite, DAY, DEFAULT_BALANCE_AMOUNT, DEFAULT_BLOCK_TIME, DEFAULT_NTRN_PRICE_BPS, +}; #[test] fn test_open_auction() { @@ -160,7 +163,7 @@ fn test_bid() { .query_balance(suite.mm.clone(), suite.pair.1.clone()) .unwrap(); assert_eq!( - Uint128::from(1000000000_u128) - mm_balance.amount, + DEFAULT_BALANCE_AMOUNT - mm_balance.amount, active_auction.resolved_amount ); @@ -188,7 +191,7 @@ fn test_bid() { .query_balance(suite.mm.clone(), suite.pair.1.clone()) .unwrap(); assert_eq!( - Uint128::from(1000000000_u128) - mm_balance.amount, + DEFAULT_BALANCE_AMOUNT - mm_balance.amount, active_auction.resolved_amount ); @@ -213,7 +216,7 @@ fn test_bid() { .query_balance(suite.mm.clone(), suite.pair.1.clone()) .unwrap(); assert_eq!( - Uint128::from(1000000000_u128) - mm_balance.amount, + DEFAULT_BALANCE_AMOUNT - mm_balance.amount, active_auction.resolved_amount ); assert_eq!(active_auction.status, ActiveAuctionStatus::Finished) @@ -264,10 +267,7 @@ fn test_exact_bid() { .wrap() .query_balance(suite.mm.clone(), suite.pair.1.clone()) .unwrap(); - assert_eq!( - Uint128::from(1000000000_u128) - mm_balance.amount, - ntrn_to_send - ); + assert_eq!(DEFAULT_BALANCE_AMOUNT - mm_balance.amount, ntrn_to_send); assert_eq!(active_auction.status, ActiveAuctionStatus::Finished) } @@ -318,10 +318,7 @@ fn test_overflow_bid() { .wrap() .query_balance(suite.mm.clone(), suite.pair.1.clone()) .unwrap(); - assert_eq!( - Uint128::from(1000000000_u128) - mm_balance.amount, - ntrn_to_buy_all - ); + assert_eq!(DEFAULT_BALANCE_AMOUNT - mm_balance.amount, ntrn_to_buy_all); assert_eq!(active_auction.status, ActiveAuctionStatus::Finished) } @@ -407,7 +404,7 @@ fn test_not_admin() { .execute_contract( Addr::unchecked("not_admin"), suite.get_default_auction_addr(), - &auction::msg::ExecuteMsg::Admin(auction::msg::AdminMsgs::PauseAuction), + &auction::msg::ExecuteMsg::Admin(Box::new(auction::msg::AdminMsgs::PauseAuction)), &[], ) .unwrap_err(); @@ -622,16 +619,25 @@ fn test_saving_10_twap_prices() { let mut suite = Suite::default(); let funds = coins(10_u128, suite.pair.0.clone()); + // Add astroport path to oracle + let path = vec![PriceStep { + denom1: suite.pair.0.to_string(), + denom2: suite.pair.1.to_string(), + pool_address: suite + .astro_pools + .get(&suite.pair.clone().into()) + .unwrap() + .clone(), + }]; + suite + .add_astro_path_to_oracle(suite.pair.clone(), path) + .unwrap(); + // Do 11 auctions - for i in 0..11 { + for _ in 0..11 { suite.finalize_auction(&funds); - if i < 3 { - suite - .update_price(suite.pair.clone(), Some(Decimal::one())) - .unwrap(); - } else { - suite.update_price(suite.pair.clone(), None).unwrap(); - } + + suite.update_price(suite.pair.clone()).unwrap(); } let prices = TWAP_PRICES @@ -726,7 +732,7 @@ fn test_auction_modified_strategy_for_price_freshness() { suite.finalize_auction(&funds); suite.finalize_auction(&funds); - suite.update_price(suite.pair.clone(), None).unwrap(); + suite.update_price(suite.pair.clone()).unwrap(); suite.update_block_cycle(); suite.update_block_cycle(); @@ -742,13 +748,13 @@ fn test_auction_modified_strategy_for_price_freshness() { ); assert_eq!(active_auction.end_price, price - price * Decimal::bps(3000)); - suite.update_price(suite.pair.clone(), None).unwrap(); + suite.update_price(suite.pair.clone()).unwrap(); suite.update_block_cycle(); suite.update_block_cycle(); suite.update_block_cycle(); - suite.update_price(suite.pair.clone(), None).unwrap(); + suite.update_price(suite.pair.clone()).unwrap(); suite.finalize_auction(&funds); @@ -761,7 +767,7 @@ fn test_auction_modified_strategy_for_price_freshness() { ); assert_eq!(active_auction.end_price, price - price * Decimal::bps(4000)); - suite.update_price(suite.pair.clone(), None).unwrap(); + suite.update_price(suite.pair.clone()).unwrap(); suite.finalize_auction(&funds); @@ -884,3 +890,80 @@ fn test_open_auction_bid_after_end_block_passed() { assert_eq!(err, auction::error::ContractError::AuctionFinished) } + +#[test] +fn test_minimum_amounts() { + let mut suite = Suite::default(); + + // Try to send 1 denom to the auction (minimum is 5) + let err = suite.auction_funds_err( + suite.get_account_addr(0), + suite + .auction_addrs + .get(&suite.pair.clone().into()) + .unwrap() + .clone(), + &coins(1_u128, suite.pair.0.clone()), + ); + + // should error telling us minimum is 5 + assert_eq!( + err, + auction::error::ContractError::AuctionAmountTooLow(5_u128.into()) + ); + + // get the old balance of the account + let old_balance = suite.get_balance(0, suite.pair.0.as_str()); + + // send 5 denom to the auction + suite.auction_funds( + suite.get_account_addr(0), + suite + .auction_addrs + .get(&suite.pair.clone().into()) + .unwrap() + .clone(), + &coins(5_u128, suite.pair.0.clone()), + ); + + // we try to star start the auction, it doesn't fail, but doesn't start and refunds to the account the sent amount + suite.start_auction_day(suite.pair.clone()).unwrap(); + + // get new balance of the account + let new_balance = suite.get_balance(0, suite.pair.0.as_str()); + assert_eq!(old_balance.amount, new_balance.amount); + + // the auction status should be still AuctionClosed + let active_auction = suite.query_auction_details(suite.get_default_auction_addr()); + assert_eq!(active_auction.status, ActiveAuctionStatus::AuctionClosed); + + // send 10 denom + suite.auction_funds( + suite.get_account_addr(0), + suite + .auction_addrs + .get(&suite.pair.clone().into()) + .unwrap() + .clone(), + &coins(10_u128, suite.pair.0.clone()), + ); + // start auction as usual + suite.start_auction_day(suite.pair.clone()).unwrap(); + + let curr_balance = suite.get_balance(0, suite.pair.0.as_str()); + assert_eq!(old_balance.amount - Uint128::new(10), curr_balance.amount); + + // The auction status should be started, and avilable funds should be 10 + let active_auction = suite.query_auction_details(suite.get_default_auction_addr()); + assert_eq!(active_auction.status, ActiveAuctionStatus::Started); + assert_eq!(active_auction.available_amount, Uint128::new(10)); +} + +#[test] +fn test_all_pairs_query() { + let suite = Suite::default(); + + // Should be successful query and not empty + let pairs = suite.query_auctions_manager_all_pairs(); + assert!(!pairs.is_empty()) +} diff --git a/tests/rust-tests/src/tests_auctions/test_auctions_manager.rs b/tests/rust-tests/src/tests_auctions/test_auctions_manager.rs index 19194220..00829015 100644 --- a/tests/rust-tests/src/tests_auctions/test_auctions_manager.rs +++ b/tests/rust-tests/src/tests_auctions/test_auctions_manager.rs @@ -69,9 +69,9 @@ fn test_not_admin() { .execute_contract( Addr::unchecked("not_admin"), suite.auctions_manager_addr, - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::PauseAuction { pair: suite.pair }, - ), + )), &[], ) .unwrap_err(); @@ -82,7 +82,9 @@ fn test_not_admin() { #[test] fn test_no_oracle_addr() { - let mut suite = SuiteBuilder::default().without_oracle_addr().build_basic(); + let mut suite = SuiteBuilder::default() + .without_oracle_addr() + .build_basic(true); let pair = Pair::from(("random".to_string(), "random2".to_string())); suite.init_auction(pair.clone(), AuctionInstantiate::default().into(), None); @@ -182,7 +184,7 @@ fn test_update_admin_start() { .execute_contract( new_admin.clone(), suite.auctions_manager_addr.clone(), - &price_oracle::msg::ExecuteMsg::ApproveAdminChange, + &price_oracle::msg::ExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap_err(); @@ -192,12 +194,12 @@ fn test_update_admin_start() { .execute_contract( suite.admin.clone(), suite.auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::StartAdminChange { addr: new_admin.to_string(), expiration: Expiration::Never {}, }, - ), + )), &[], ) .unwrap(); @@ -207,7 +209,7 @@ fn test_update_admin_start() { .execute_contract( new_admin.clone(), suite.auctions_manager_addr.clone(), - &price_oracle::msg::ExecuteMsg::ApproveAdminChange, + &price_oracle::msg::ExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap(); @@ -226,12 +228,12 @@ fn test_update_admin_cancel() { .execute_contract( suite.admin.clone(), suite.auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::StartAdminChange { addr: new_admin.to_string(), expiration: Expiration::Never {}, }, - ), + )), &[], ) .unwrap(); @@ -241,9 +243,9 @@ fn test_update_admin_cancel() { .execute_contract( suite.admin.clone(), suite.auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::CancelAdminChange, - ), + )), &[], ) .unwrap(); @@ -254,7 +256,7 @@ fn test_update_admin_cancel() { .execute_contract( new_admin, suite.auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::ApproveAdminChange, + &auctions_manager::msg::ExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap_err(); @@ -271,12 +273,12 @@ fn test_update_admin_fails() { .execute_contract( suite.admin.clone(), suite.auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::Admin( + &auctions_manager::msg::ExecuteMsg::Admin(Box::new( auctions_manager::msg::AdminMsgs::StartAdminChange { addr: new_admin.to_string(), expiration: Expiration::AtHeight(suite.app.block_info().height + 5), }, - ), + )), &[], ) .unwrap(); @@ -287,7 +289,7 @@ fn test_update_admin_fails() { .execute_contract( random_addr, suite.auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::ApproveAdminChange, + &auctions_manager::msg::ExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap_err(); @@ -300,7 +302,7 @@ fn test_update_admin_fails() { .execute_contract( new_admin, suite.auctions_manager_addr.clone(), - &auctions_manager::msg::ExecuteMsg::ApproveAdminChange, + &auctions_manager::msg::ExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap_err(); diff --git a/tests/rust-tests/src/tests_auctions/test_full_auctions.rs b/tests/rust-tests/src/tests_auctions/test_full_auctions.rs index d967e708..5e5c9b7c 100644 --- a/tests/rust-tests/src/tests_auctions/test_full_auctions.rs +++ b/tests/rust-tests/src/tests_auctions/test_full_auctions.rs @@ -1,7 +1,10 @@ use auction::state::ActiveAuctionStatus; use cosmwasm_std::{coins, testing::mock_env, Uint128}; -use crate::suite::{suite::Suite, suite_builder::SuiteBuilder}; +use crate::suite::{ + suite::{Suite, DEFAULT_BALANCE_AMOUNT}, + suite_builder::SuiteBuilder, +}; #[test] fn test_close_auction() { @@ -41,7 +44,10 @@ fn test_close_auction() { .wrap() .query_balance(suite.mm.clone(), suite.pair.0.clone()) .unwrap(); - assert_eq!(mm_balance.amount, Uint128::from(1000001000_u128)); + assert_eq!( + mm_balance.amount, + DEFAULT_BALANCE_AMOUNT + Uint128::from(1000_u128) + ); } #[test] @@ -81,7 +87,10 @@ fn test_close_auction_time() { .wrap() .query_balance(suite.mm.clone(), suite.pair.0.clone()) .unwrap(); - assert_eq!(mm_balance.amount, Uint128::from(1000000500_u128)); + assert_eq!( + mm_balance.amount, + DEFAULT_BALANCE_AMOUNT + Uint128::from(500_u128) + ); } #[test] @@ -118,7 +127,7 @@ fn test_close_auction_no_bids() { .wrap() .query_balance(suite.mm.clone(), suite.pair.0.clone()) .unwrap(); - assert_eq!(mm_balance.amount, Uint128::from(1000000000_u128)); + assert_eq!(mm_balance.amount, DEFAULT_BALANCE_AMOUNT); } #[test] @@ -195,7 +204,10 @@ fn test_auction_multiple_providers() { .wrap() .query_balance(suite.mm.clone(), suite.pair.0.clone()) .unwrap(); - assert_eq!(mm_balance.amount.u128(), 1000003000_u128); + assert_eq!( + mm_balance.amount.u128(), + DEFAULT_BALANCE_AMOUNT.u128() + 3000_u128 + ); } #[test] @@ -263,7 +275,10 @@ fn test_auction_multiple_providers_rounding() { .wrap() .query_balance(suite.mm.clone(), suite.pair.0.clone()) .unwrap(); - assert_eq!(mm_balance.amount.u128(), 1000001269_u128); + assert_eq!( + mm_balance.amount.u128(), + DEFAULT_BALANCE_AMOUNT.u128() + 1269_u128 + ); } #[test] diff --git a/tests/rust-tests/src/tests_auctions/test_instantiates.rs b/tests/rust-tests/src/tests_auctions/test_instantiates.rs index d17be656..713c267e 100644 --- a/tests/rust-tests/src/tests_auctions/test_instantiates.rs +++ b/tests/rust-tests/src/tests_auctions/test_instantiates.rs @@ -52,13 +52,15 @@ fn test_instantiate_oracle() { config, price_oracle::state::Config { auction_manager_addr: manager_addr, + seconds_allow_manual_change: 60 * 60 * 24 * 2, + seconds_auction_prices_fresh: 60 * 60 * 24 * 3, } ) } #[test] fn test_instantiate_auction() { - let mut suite = SuiteBuilder::default().build_basic(); + let mut suite = SuiteBuilder::default().build_basic(true); let init_msg: auction::msg::InstantiateMsg = AuctionInstantiate::default().into(); suite.init_auction(suite.pair.clone(), init_msg.clone(), None); @@ -92,7 +94,7 @@ fn test_instantiate_auction() { #[test] fn test_instantiate_auction_err() { - let mut suite = SuiteBuilder::default().build_basic(); + let mut suite = SuiteBuilder::default().build_basic(true); let mut init_msg = AuctionInstantiate::default(); @@ -129,7 +131,7 @@ fn test_instantiate_auction_err() { #[test] fn test_auction_strategy() { - let mut suite = SuiteBuilder::default().build_basic(); + let mut suite = SuiteBuilder::default().build_basic(true); // Try with start price 0 let mut init_msg: auction::msg::InstantiateMsg = AuctionInstantiate::default().into(); diff --git a/tests/rust-tests/src/tests_auctions/test_oracle.rs b/tests/rust-tests/src/tests_auctions/test_oracle.rs index ff12667f..5ef4e056 100644 --- a/tests/rust-tests/src/tests_auctions/test_oracle.rs +++ b/tests/rust-tests/src/tests_auctions/test_oracle.rs @@ -9,10 +9,12 @@ use crate::suite::{ #[test] fn test_update_price_manually() { - let mut suite = SuiteBuilder::default().build_basic(); + let mut suite = SuiteBuilder::default().build_basic(false); let price = Decimal::bps(5000); - suite.update_price(suite.pair.clone(), Some(price)).unwrap(); + suite + .manual_update_price(suite.pair.clone(), price) + .unwrap(); let price_res = suite.query_oracle_price(suite.pair.clone()); assert_eq!(price_res.price, price); @@ -29,7 +31,7 @@ fn test_update_price_from_auctions() { suite.finalize_auction(&funds); // Update the price from twap - suite.update_price(suite.pair.clone(), None).unwrap(); + suite.update_price(suite.pair.clone()).unwrap(); // Get the price which should be an average of 1.5 let price_res = suite.query_oracle_price(suite.pair.clone()); @@ -38,6 +40,7 @@ fn test_update_price_from_auctions() { assert_eq!(rounded_price.u128(), 150_u128); // 150 / 100 = 1.50 } +// TODO: Should fallback to astroport and not error, remove once astroport test is added #[test] fn test_twap_less_then_3_auctions() { let mut suite = Suite::default(); @@ -46,10 +49,11 @@ fn test_twap_less_then_3_auctions() { // do auction suite.finalize_auction(&funds); - let err = suite.update_price_err(suite.pair.clone(), None); - assert_eq!(err, price_oracle::error::ContractError::NotEnoughTwaps) + let _ = suite.update_price_err(suite.pair.clone()); + // assert_eq!(err, price_oracle::error::ContractError::NotEnoughTwaps) } +// NOTE: This doesn't actually test #[test] fn test_twap_no_recent_auction() { let mut suite = Suite::default(); @@ -63,10 +67,10 @@ fn test_twap_no_recent_auction() { // Move chain 6 days ahead suite.update_block(DAY * 4 / DEFAULT_BLOCK_TIME); - let err = suite.update_price_err(suite.pair.clone(), None); + let err = suite.update_price_err(suite.pair.clone()); assert_eq!( err, - price_oracle::error::ContractError::NoAuctionInLast3Days + price_oracle::error::ContractError::NoAstroPath(suite.pair.clone()) ) } @@ -86,9 +90,9 @@ fn test_not_admin() { .execute_contract( Addr::unchecked("not_admin"), suite.oracle_addr, - &price_oracle::msg::ExecuteMsg::UpdatePrice { + &price_oracle::msg::ExecuteMsg::ManualPriceUpdate { pair: suite.pair, - price: None, + price: Decimal::one(), }, &[], ) @@ -103,16 +107,18 @@ fn test_config() { config, price_oracle::state::Config { auction_manager_addr: suite.auctions_manager_addr, + seconds_allow_manual_change: 60 * 60 * 24 * 2, + seconds_auction_prices_fresh: 60 * 60 * 24 * 3, } ) } #[test] fn test_update_price_0() { - let mut suite = SuiteBuilder::default().build_basic(); + let mut suite = SuiteBuilder::default().build_basic(true); let price: Decimal = Decimal::zero(); - let err = suite.update_price_err(suite.pair.clone(), Some(price)); + let err = suite.manual_update_price_err(suite.pair.clone(), price); assert_eq!(err, price_oracle::error::ContractError::PriceIsZero) } @@ -129,7 +135,7 @@ fn test_update_admin_start() { .execute_contract( new_admin.clone(), suite.oracle_addr.clone(), - &price_oracle::msg::ExecuteMsg::ApproveAdminChange, + &price_oracle::msg::ExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap_err(); @@ -152,7 +158,7 @@ fn test_update_admin_start() { .execute_contract( new_admin.clone(), suite.oracle_addr.clone(), - &price_oracle::msg::ExecuteMsg::ApproveAdminChange, + &price_oracle::msg::ExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap(); @@ -184,7 +190,7 @@ fn test_update_admin_cancel() { .execute_contract( suite.admin.clone(), suite.oracle_addr.clone(), - &price_oracle::msg::ExecuteMsg::CancelAdminChange, + &price_oracle::msg::ExecuteMsg::CancelAdminChange {}, &[], ) .unwrap(); @@ -195,7 +201,7 @@ fn test_update_admin_cancel() { .execute_contract( new_admin, suite.oracle_addr.clone(), - &price_oracle::msg::ExecuteMsg::ApproveAdminChange, + &price_oracle::msg::ExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap_err(); @@ -226,7 +232,7 @@ fn test_update_admin_fails() { .execute_contract( random_addr, suite.oracle_addr.clone(), - &price_oracle::msg::ExecuteMsg::ApproveAdminChange, + &price_oracle::msg::ExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap_err(); @@ -239,7 +245,7 @@ fn test_update_admin_fails() { .execute_contract( new_admin, suite.oracle_addr.clone(), - &price_oracle::msg::ExecuteMsg::ApproveAdminChange, + &price_oracle::msg::ExecuteMsg::ApproveAdminChange {}, &[], ) .unwrap_err(); @@ -247,12 +253,12 @@ fn test_update_admin_fails() { #[test] fn test_manual_price_update() { - let mut suite = Suite::default(); + let mut suite = SuiteBuilder::default().build_basic(false); let funds = coins(10_u128, suite.pair.0.clone()); // no auctions yet, so should be able to update suite - .update_price(suite.pair.clone(), Some(Decimal::one())) + .manual_update_price(suite.pair.clone(), Decimal::one()) .unwrap(); // 4 auctions passed we should not be able to update price now. @@ -261,7 +267,9 @@ fn test_manual_price_update() { suite.finalize_auction(&funds); suite.finalize_auction(&funds); - let err = suite.update_price_err(suite.pair.clone(), Some(Decimal::one())); + suite.update_price(suite.pair.clone()).unwrap(); + + let err = suite.manual_update_price_err(suite.pair.clone(), Decimal::one()); assert_eq!( err, price_oracle::error::ContractError::NoTermsForManualUpdate @@ -273,15 +281,45 @@ fn test_manual_price_update() { suite.update_block_cycle(); suite - .update_price(suite.pair.clone(), Some(Decimal::one())) + .manual_update_price(suite.pair.clone(), Decimal::one()) .unwrap(); // an auction happened, we should not be able to update price now. suite.finalize_auction(&funds); - let err = suite.update_price_err(suite.pair.clone(), Some(Decimal::one())); + let err = suite.manual_update_price_err(suite.pair.clone(), Decimal::one()); assert_eq!( err, price_oracle::error::ContractError::NoTermsForManualUpdate ); } + +#[test] +fn test_update_config() { + let mut suite = Suite::default(); + + let oracle_config = suite.query_oracle_config(); + + let new_addr = "some_addr"; + + suite + .app + .execute_contract( + suite.admin.clone(), + suite.oracle_addr.clone(), + &price_oracle::msg::ExecuteMsg::UpdateConfig { + auction_manager_addr: Some(new_addr.to_string()), + seconds_allow_manual_change: Some(12), + seconds_auction_prices_fresh: Some(455), + }, + &[], + ) + .unwrap(); + + let new_oracle_config = suite.query_oracle_config(); + + assert_ne!(new_oracle_config, oracle_config); + assert_eq!(new_oracle_config.auction_manager_addr, new_addr.to_string()); + assert_eq!(new_oracle_config.seconds_allow_manual_change, 12); + assert_eq!(new_oracle_config.seconds_auction_prices_fresh, 455); +} diff --git a/tests/rust-tests/src/tests_rebalancer/pid_tunning.rs b/tests/rust-tests/src/tests_rebalancer/pid_tunning.rs index 913a37ba..1c05e8ff 100644 --- a/tests/rust-tests/src/tests_rebalancer/pid_tunning.rs +++ b/tests/rust-tests/src/tests_rebalancer/pid_tunning.rs @@ -188,7 +188,7 @@ fn terminal_play() { let new_price = info[1]; suite - .update_price(pair.clone(), Some(Decimal::from_str(new_price).unwrap())) + .manual_update_price(pair.clone(), Decimal::from_str(new_price).unwrap()) .unwrap(); } else { println!("Command wasn't recognized"); diff --git a/tests/rust-tests/src/tests_rebalancer/test_limits.rs b/tests/rust-tests/src/tests_rebalancer/test_limits.rs index cd61c550..7ccf8cd6 100644 --- a/tests/rust-tests/src/tests_rebalancer/test_limits.rs +++ b/tests/rust-tests/src/tests_rebalancer/test_limits.rs @@ -29,9 +29,9 @@ fn test_min_balance_more_than_balance_with_coins() { // Send some ntrn to the account let amount = (Decimal::bps(DEFAULT_NTRN_PRICE_BPS) - * Decimal::from_atomics(1000_u128, 0).unwrap()) + * Decimal::from_atomics(2000_u128, 0).unwrap()) .to_uint_floor() - + Uint128::new(50); + + Uint128::new(100); suite .app .execute( @@ -51,6 +51,7 @@ fn test_min_balance_more_than_balance_with_coins() { let balance_atom = suite.get_balance(0, ATOM); // Balance should be equal or greater then our set minimum assert!(balance_atom.amount >= Uint128::new(2000)); + println!("{}", balance_atom.amount); } #[test] diff --git a/tests/rust-tests/src/tests_rebalancer/test_management.rs b/tests/rust-tests/src/tests_rebalancer/test_management.rs index bb7fbf4f..7141aaa0 100644 --- a/tests/rust-tests/src/tests_rebalancer/test_management.rs +++ b/tests/rust-tests/src/tests_rebalancer/test_management.rs @@ -1,9 +1,14 @@ use std::collections::HashSet; -use cosmwasm_std::{coins, testing::mock_env, to_json_binary, Addr, Timestamp, Uint128}; +use cosmwasm_std::{ + coin, coins, testing::mock_env, to_json_binary, Addr, OverflowError, StdError, Timestamp, + Uint128, +}; use cw_multi_test::Executor; use valence_package::services::{ - rebalancer::{BaseDenom, RebalancerUpdateData, SystemRebalanceStatus}, + rebalancer::{ + BaseDenom, PauseReason, RebalancerUpdateData, ServiceFeeConfig, SystemRebalanceStatus, + }, ValenceServices, }; @@ -23,7 +28,7 @@ fn test_remove_trustee() { let config = suite .query_rebalancer_config(suite.account_addrs[0].clone()) .unwrap(); - assert_eq!(config.trustee, Some(TRUSTEE.to_string())); + assert_eq!(config.trustee, Some(Addr::unchecked(TRUSTEE))); suite .update_config( @@ -35,7 +40,7 @@ fn test_remove_trustee() { base_denom: None, targets: HashSet::new(), pid: None, - max_limit: None, + max_limit_bps: None, target_override_strategy: None, }, ) @@ -70,7 +75,7 @@ fn test_not_whitelisted_base_denom() { base_denom: Some(not_whitelisted_base_denom.clone()), targets: HashSet::new(), pid: None, - max_limit: None, + max_limit_bps: None, target_override_strategy: None, }, ) @@ -107,7 +112,7 @@ fn test_multiple_min_balance_on_update() { base_denom: None, targets: data.targets, pid: None, - max_limit: None, + max_limit_bps: None, target_override_strategy: None, }, ) @@ -144,7 +149,7 @@ fn test_not_whitelisted_denom_on_update() { base_denom: None, targets: data.targets, pid: None, - max_limit: None, + max_limit_bps: None, target_override_strategy: None, }, ) @@ -181,7 +186,7 @@ fn test_invalid_targets_perc_on_update() { base_denom: None, targets: data.targets, pid: None, - max_limit: None, + max_limit_bps: None, target_override_strategy: None, }, ) @@ -416,7 +421,7 @@ fn test_update_config_not_whitelsited_denom() { base_denom: None, targets: HashSet::new(), pid: None, - max_limit: None, + max_limit_bps: None, target_override_strategy: None, }, ) @@ -479,29 +484,25 @@ fn test_account_balance_limit() { .unwrap(); // make sure account 2 and 3 are not paused - let config_2 = suite + suite .query_rebalancer_config(account_addr_2.clone()) .unwrap(); - assert!(config_2.is_paused.is_none()); - let config_3 = suite + suite .query_rebalancer_config(account_addr_3.clone()) .unwrap(); - assert!(config_3.is_paused.is_none()); // do a rebalance with an account with no balance suite.rebalance_with_update_block(None).unwrap(); // Account 2 and 3 should be paused now. - let config_2 = suite - .query_rebalancer_config(account_addr_2.clone()) + suite + .query_rebalancer_paused_config(account_addr_2.clone()) .unwrap(); - assert!(config_2.is_paused.is_some()); - let config_3 = suite - .query_rebalancer_config(account_addr_3.clone()) + suite + .query_rebalancer_paused_config(account_addr_3.clone()) .unwrap(); - assert!(config_3.is_paused.is_some()); // Try to resuem without enough balance let err = suite.resume_service_err(account_position_2, ValenceServices::Rebalancer); @@ -549,3 +550,208 @@ fn test_account_balance_limit() { .resume_service(account_position_3, ValenceServices::Rebalancer) .unwrap(); } + +#[test] +fn test_with_fee() { + let mut suite = Suite::default(); + + suite + .update_rebalancer_fees(ServiceFeeConfig { + denom: NTRN.to_string(), + register_fee: 100_u128.into(), + resume_fee: 100_u128.into(), + }) + .unwrap(); + + let (account_position, _) = suite.create_temp_account(&coins(1000, ATOM.to_string())); + let register_data = SuiteBuilder::get_default_rebalancer_register_data(); + + // Register account without enough fee token should fail. + let err = suite.register_to_rebalancer_fee_err(account_position, ®ister_data); + assert_eq!( + err, + StdError::overflow(OverflowError::new( + cosmwasm_std::OverflowOperation::Sub, + "0", + "100" + )) + ); + + // set balance of account to ntrn + suite.set_balance(account_position, coin(1000, NTRN.to_string())); + + // Should successfully register + suite + .register_to_rebalancer(account_position, ®ister_data) + .unwrap(); + + // Account balance should be - 900 ntrn + let balance = suite + .app + .wrap() + .query_balance(suite.get_account_addr(account_position), NTRN.to_string()) + .unwrap(); + assert!(balance.amount == Uint128::new(900_u128)) +} + +#[test] +fn test_manual_resume_without_fee() { + let mut suite = Suite::default(); + + suite + .update_rebalancer_fees(ServiceFeeConfig { + denom: NTRN.to_string(), + register_fee: 100_u128.into(), + resume_fee: 100_u128.into(), + }) + .unwrap(); + + let (account_position, _) = suite.create_temp_account(&coins(1000, NTRN.to_string())); + let register_data = SuiteBuilder::get_default_rebalancer_register_data(); + suite + .register_to_rebalancer(account_position, ®ister_data) + .unwrap(); + + let balance = suite + .app + .wrap() + .query_balance(suite.get_account_addr(account_position), NTRN.to_string()) + .unwrap(); + assert_eq!(balance.amount, Uint128::new(900_u128)); + + // account pause the rebalancer + suite + .pause_service(account_position, ValenceServices::Rebalancer) + .unwrap(); + + let paused_config = suite + .query_rebalancer_paused_config(suite.get_account_addr(account_position)) + .unwrap(); + assert_eq!( + paused_config.reason, + PauseReason::AccountReason("Some reason".to_string()) + ); + + suite + .resume_service(account_position, ValenceServices::Rebalancer) + .unwrap(); + + suite + .query_rebalancer_config(suite.get_account_addr(account_position)) + .unwrap(); + + // Verify fee was not taken from the account, (still 900 NTRN) + let balance = suite + .app + .wrap() + .query_balance(suite.get_account_addr(account_position), NTRN.to_string()) + .unwrap(); + assert_eq!(balance.amount, Uint128::new(900_u128)); +} + +#[test] +fn test_resume_with_fee() { + let mut suite = Suite::default(); + + suite + .update_rebalancer_fees(ServiceFeeConfig { + denom: NTRN.to_string(), + register_fee: 100_u128.into(), + resume_fee: 100_u128.into(), + }) + .unwrap(); + + let (account_position, _) = suite.create_temp_account(&coins(1000, NTRN.to_string())); + let register_data = SuiteBuilder::get_default_rebalancer_register_data(); + suite + .register_to_rebalancer(account_position, ®ister_data) + .unwrap(); + + suite.set_balance(account_position, coin(0, NTRN.to_string())); + + suite.rebalance(None).unwrap(); + + // Account should be paused because of empty balance + let paused_config = suite + .query_rebalancer_paused_config(suite.get_account_addr(account_position)) + .unwrap(); + assert_eq!(paused_config.reason, PauseReason::EmptyBalance); + + suite.set_balance(account_position, coin(1000, NTRN.to_string())); + + suite + .resume_service(account_position, ValenceServices::Rebalancer) + .unwrap(); + + // Verify the account is resumed (if we get config, it means it's not paused) + suite + .query_rebalancer_config(suite.get_account_addr(account_position)) + .unwrap(); + + // Verify balance is 900 because we paid 100 for resume fee + let balance = suite + .app + .wrap() + .query_balance(suite.get_account_addr(account_position), NTRN.to_string()) + .unwrap(); + assert_eq!(balance.amount, Uint128::new(900_u128)); +} + +#[test] +fn test_fee_withdraw() { + let mut suite = Suite::default(); + + suite + .update_rebalancer_fees(ServiceFeeConfig { + denom: NTRN.to_string(), + register_fee: 100_u128.into(), + resume_fee: 100_u128.into(), + }) + .unwrap(); + + let (account_position, _) = suite.create_temp_account(&coins(1000, NTRN.to_string())); + let register_data = SuiteBuilder::get_default_rebalancer_register_data(); + + // Register account without enough fee token should fail. + suite + .register_to_rebalancer(account_position, ®ister_data) + .unwrap(); + + // account balance should be 900 (1000 - 100) + let balance = suite + .app + .wrap() + .query_balance(suite.get_account_addr(account_position), NTRN.to_string()) + .unwrap(); + assert_eq!(balance.amount, Uint128::new(900_u128)); + + // manager should have the 100 NTRN fee + let manager_balance = suite + .app + .wrap() + .query_balance(suite.manager_addr.clone(), NTRN.to_string()) + .unwrap(); + assert_eq!(manager_balance.amount, Uint128::new(100_u128)); + + // Get initial admin balance of NTRN + let admin_initial_balance = suite + .app + .wrap() + .query_balance(suite.admin.clone(), NTRN.to_string()) + .unwrap(); + + // Do the withdraw from naanger (should send it to the admin) + suite.withdraw_fees_from_manager(NTRN).unwrap(); + + // Get balance of the admin after withdraw, should be extra 100 NTRN + let admin_new_balance = suite + .app + .wrap() + .query_balance(suite.admin.clone(), NTRN.to_string()) + .unwrap(); + + assert_eq!( + admin_new_balance.amount, + admin_initial_balance.amount + Uint128::new(100_u128) + ); +} diff --git a/tests/rust-tests/src/tests_rebalancer/test_rebalancing.rs b/tests/rust-tests/src/tests_rebalancer/test_rebalancing.rs index 4692c266..a1d733d7 100644 --- a/tests/rust-tests/src/tests_rebalancer/test_rebalancing.rs +++ b/tests/rust-tests/src/tests_rebalancer/test_rebalancing.rs @@ -34,10 +34,10 @@ fn test_basic_p_controller() { .build_default(); let p_perc = Decimal::from_str(&config.pid.p).unwrap(); - let atom_limit = suite.get_min_limit(ATOM); + let atom_limit = suite.get_send_min_limit(ATOM); - // we check 5 here because on the 7th we already reach the limit - for _ in 0..5 { + // we check 4 here because on the 4th we already reach the limit + for _ in 0..4 { let mut balance = suite.get_balance(0, ATOM); let ntrn_balance = suite.get_balance(0, NTRN); let price = suite.get_price(Pair::from((ATOM.to_string(), NTRN.to_string()))); diff --git a/tests/rust-tests/src/tests_rebalancer/test_system.rs b/tests/rust-tests/src/tests_rebalancer/test_system.rs index f1d00884..c055f07a 100644 --- a/tests/rust-tests/src/tests_rebalancer/test_system.rs +++ b/tests/rust-tests/src/tests_rebalancer/test_system.rs @@ -39,32 +39,35 @@ fn test_rebalancer_system() { suite.rebalance_with_update_block(Some(1)).unwrap(); // check status matches what we expect - let status = suite.query_rebalancer_system_status().unwrap(); + let SystemRebalanceStatus::Processing { + cycle_started, + start_from, + prices, + } = suite.query_rebalancer_system_status().unwrap() + else { + panic!("System status is not processing but something else") + }; assert_eq!( - status, - SystemRebalanceStatus::Processing { - cycle_started: start_of_cycle(suite.app.block_info().time, DEFAULT_CYCLE_PERIOD), - start_from: suite.get_account_addr(0), - prices: vec![ - ( - Pair::from((ATOM.to_string(), NTRN.to_string())), - Decimal::bps(DEFAULT_NTRN_PRICE_BPS) - ), - ( - Pair::from((ATOM.to_string(), OSMO.to_string())), - Decimal::bps(DEFAULT_OSMO_PRICE_BPS) - ), - ( - Pair::from((NTRN.to_string(), ATOM.to_string())), - Decimal::one() / Decimal::bps(DEFAULT_NTRN_PRICE_BPS) - ), - ( - Pair::from((NTRN.to_string(), OSMO.to_string())), - Decimal::bps(DEFAULT_OSMO_PRICE_BPS) / Decimal::bps(DEFAULT_NTRN_PRICE_BPS) - ) - ] - } + cycle_started, + start_of_cycle(suite.app.block_info().time, DEFAULT_CYCLE_PERIOD) ); + assert_eq!(start_from, suite.get_account_addr(0)); + assert!(prices.contains(&( + Pair::from((ATOM.to_string(), NTRN.to_string())), + Decimal::bps(DEFAULT_NTRN_PRICE_BPS) + ))); + assert!(prices.contains(&( + Pair::from((ATOM.to_string(), OSMO.to_string())), + Decimal::bps(DEFAULT_OSMO_PRICE_BPS) + ))); + assert!(prices.contains(&( + Pair::from((NTRN.to_string(), ATOM.to_string())), + Decimal::one() / Decimal::bps(DEFAULT_NTRN_PRICE_BPS) + ))); + assert!(prices.contains(&( + Pair::from((NTRN.to_string(), OSMO.to_string())), + Decimal::bps(DEFAULT_OSMO_PRICE_BPS) / Decimal::bps(DEFAULT_NTRN_PRICE_BPS) + ))); // confirm our rebalancer ran over the first account only let config_1 = suite @@ -131,7 +134,7 @@ fn test_rebalancer_system() { #[test] fn test_register() { - let mut suite = SuiteBuilder::default().with_accounts(2).build_basic(); + let mut suite = SuiteBuilder::default().with_accounts(2).build_basic(true); // Because we have a basic setup here, we need to register the service to the manager suite @@ -226,7 +229,7 @@ fn test_register() { #[test] fn test_dup_targets() { - let mut suite = SuiteBuilder::default().with_accounts(1).build_basic(); + let mut suite = SuiteBuilder::default().with_accounts(1).build_basic(true); suite .add_service_to_manager( @@ -264,7 +267,7 @@ fn test_dup_targets() { #[test] fn test_set_2_min_balance() { - let mut suite = SuiteBuilder::default().build_basic(); + let mut suite = SuiteBuilder::default().build_basic(true); suite .add_service_to_manager( @@ -353,7 +356,7 @@ fn test_rebalancer_cycle_next_day_while_processing() { #[test] fn test_invalid_max_limit_range() { - let mut suite = SuiteBuilder::default().with_accounts(2).build_basic(); + let mut suite = SuiteBuilder::default().with_accounts(2).build_basic(true); // Because we have a basic setup here, we need to register the service to the manager suite diff --git a/tests/rust-tests/src/tests_rebalancer/tests_unit.rs b/tests/rust-tests/src/tests_rebalancer/tests_unit.rs index 5b9cfc31..a8dcff19 100644 --- a/tests/rust-tests/src/tests_rebalancer/tests_unit.rs +++ b/tests/rust-tests/src/tests_rebalancer/tests_unit.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use cosmwasm_std::{Decimal, Uint128}; +use cosmwasm_std::{testing::mock_dependencies, Decimal, Uint128}; use rebalancer::{helpers::TargetHelper, rebalance::verify_targets}; use valence_package::{ services::rebalancer::{ParsedTarget, TargetOverrideStrategy}, @@ -14,8 +14,9 @@ use crate::suite::{ #[test] fn test_verify_target_2_denoms() { + let deps = mock_dependencies(); let config = SuiteBuilder::get_default_rebalancer_register_data() - .to_config() + .to_config(&deps.api) .unwrap(); let mut target_helpers = vec![ TargetHelper { @@ -96,8 +97,9 @@ fn test_verify_target_2_denoms() { #[test] fn test_verify_target_3_denoms() { + let deps = mock_dependencies(); let config = SuiteBuilder::get_default_rebalancer_register_data() - .to_config() + .to_config(&deps.api) .unwrap(); let mut target_helpers = vec![ TargetHelper { @@ -208,8 +210,9 @@ fn test_verify_target_3_denoms() { #[test] fn test_verify_target_leftover_strategy() { + let deps = mock_dependencies(); let mut config = SuiteBuilder::get_default_rebalancer_register_data() - .to_config() + .to_config(&deps.api) .unwrap(); let mut target_helpers = vec![ TargetHelper { @@ -317,8 +320,9 @@ fn test_verify_target_leftover_strategy() { #[test] fn test_verify_target_min_balance_over_balance() { + let deps = mock_dependencies(); let config = SuiteBuilder::get_default_rebalancer_register_data() - .to_config() + .to_config(&deps.api) .unwrap(); let target_helpers = vec![ TargetHelper { @@ -373,8 +377,9 @@ fn test_verify_target_min_balance_over_balance() { #[test] fn test_verify_target_priority() { + let deps = mock_dependencies(); let mut config = SuiteBuilder::get_default_rebalancer_register_data() - .to_config() + .to_config(&deps.api) .unwrap(); let mut target_helpers = vec![ TargetHelper {