diff --git a/Cargo.lock b/Cargo.lock index 8f19ec41..053c799b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ahash" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", @@ -15,14 +15,14 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "astroport" version = "2.0.0" -source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main#c73552b5ed09c2a74e5854cb1d5c3c4c9fe599af" +source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main#2dc53b4f011bd8c0de838f9e76345a6766620464" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -35,7 +35,20 @@ dependencies = [ [[package]] name = "astroport" version = "2.0.0" -source = "git+https://github.com/neutron-org/neutron-tge-contracts.git#c73552b5ed09c2a74e5854cb1d5c3c4c9fe599af" +source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?rev=e306308dd23d567399c15d899f295a910ede945b#e306308dd23d567399c15d899f295a910ede945b" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw20 0.15.1", + "itertools 0.10.5", + "uint", +] + +[[package]] +name = "astroport" +version = "2.0.0" +source = "git+https://github.com/neutron-org/neutron-tge-contracts.git#2dc53b4f011bd8c0de838f9e76345a6766620464" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -59,10 +72,56 @@ dependencies = [ "uint", ] +[[package]] +name = "astroport" +version = "2.8.0" +source = "git+https://github.com/astroport-fi/astroport-core.git?tag=v2.8.0#3b44a4044b823a145730f66ffaf7ae4205b2cd35" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "cw20 0.15.1", + "itertools 0.10.5", + "uint", +] + +[[package]] +name = "astroport-factory" +version = "1.5.1" +source = "git+https://github.com/astroport-fi/astroport-core.git?tag=v2.8.0#3b44a4044b823a145730f66ffaf7ae4205b2cd35" +dependencies = [ + "astroport 2.8.0", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw2 0.15.1", + "itertools 0.10.5", + "protobuf 2.28.0", + "thiserror", +] + +[[package]] +name = "astroport-pair-concentrated" +version = "1.2.0" +source = "git+https://github.com/astroport-fi/astroport-core.git?tag=v2.8.0#3b44a4044b823a145730f66ffaf7ae4205b2cd35" +dependencies = [ + "astroport 2.8.0", + "astroport-factory", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "cw2 0.15.1", + "cw20 0.15.1", + "itertools 0.10.5", + "thiserror", +] + [[package]] name = "astroport-periphery" version = "1.1.0" -source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main#c73552b5ed09c2a74e5854cb1d5c3c4c9fe599af" +source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main#2dc53b4f011bd8c0de838f9e76345a6766620464" dependencies = [ "astroport 2.5.0", "cosmwasm-schema", @@ -74,10 +133,40 @@ dependencies = [ ] [[package]] -name = "autocfg" +name = "astroport-periphery" version = "1.1.0" +source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?rev=e306308dd23d567399c15d899f295a910ede945b#e306308dd23d567399c15d899f295a910ede945b" +dependencies = [ + "astroport 2.5.0", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw20 0.13.4", + "schemars", + "serde", + "terraswap", +] + +[[package]] +name = "astroport-xastro-token" +version = "1.0.2" +source = "git+https://github.com/astroport-fi/astroport-core.git?tag=v2.8.0#3b44a4044b823a145730f66ffaf7ae4205b2cd35" +dependencies = [ + "astroport 2.8.0", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw2 0.15.1", + "cw20 0.15.1", + "cw20-base 0.15.1", + "snafu", +] + +[[package]] +name = "autocfg" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "base16ct" @@ -93,9 +182,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64ct" @@ -129,9 +218,9 @@ dependencies = [ [[package]] name = "bnum" -version = "0.8.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9008b6bb9fc80b5277f2fe481c09e828743d9151203e804583eb4c9e15b31d" +checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" [[package]] name = "byteorder" @@ -141,9 +230,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" dependencies = [ "serde", ] @@ -173,32 +262,32 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.5.0" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bb3c77c3b7ce472056968c745eb501c440fbc07be5004eba02782c35bfbbe3" +checksum = "9934c79e58d9676edfd592557dee765d2a6ef54c09d5aa2edb06156b00148966" dependencies = [ "digest 0.10.7", "ecdsa 0.16.9", "ed25519-zebra", - "k256 0.13.2", + "k256 0.13.1", "rand_core 0.6.4", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "1.5.0" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea73e9162e6efde00018d55ed0061e93a108b5d6ec4548b4f8ce3c706249687" +checksum = "bc5e72e330bd3bdab11c52b5ecbdeb6a8697a004c57964caeb5d876f0b088b3c" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.5.0" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df41ea55f2946b6b43579659eec048cc2f66e8c8e2e3652fc5e5e476f673856" +checksum = "ac3e3a2136e2a60e8b6582f5dffca5d1a683ed77bf38537d330bc1dfccd69010" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -209,9 +298,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.5.0" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43609e92ce1b9368aa951b334dd354a2d0dd4d484931a5f83ae10e12a26c8ba9" +checksum = "f5d803bea6bd9ed61bd1ee0b4a2eb09ee20dbb539cc6e0b8795614d20952ebb1" dependencies = [ "proc-macro2", "quote", @@ -220,9 +309,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.5.0" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04d6864742e3a7662d024b51a94ea81c9af21db6faea2f9a6d2232bb97c6e53e" +checksum = "ef8666e572a3a2519010dde88c04d16e9339ae751b56b2bb35081fe3f7d6be74" dependencies = [ "base64", "bech32", @@ -242,9 +331,9 @@ dependencies = [ [[package]] name = "cosmwasm-storage" -version = "1.5.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd2b4ae72a03e8f56c85df59d172d51d2d7dc9cec6e2bc811e3fb60c588032a4" +checksum = "66de2ab9db04757bcedef2b5984fbe536903ada4a8a9766717a4a71197ef34f6" dependencies = [ "cosmwasm-std", "serde", @@ -252,9 +341,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -408,7 +497,7 @@ dependencies = [ "cosmwasm-std", "cw-multi-test", "cw20 1.1.2", - "cw20-base", + "cw20-base 1.1.2", "schemars", "serde", "thiserror", @@ -697,6 +786,24 @@ dependencies = [ "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 0.15.1", + "schemars", + "semver", + "serde", + "thiserror", +] + [[package]] name = "cw20-base" version = "1.1.2" @@ -948,7 +1055,7 @@ dependencies = [ "cw-utils 1.0.3", "cw2 1.1.2", "cw20 1.1.2", - "cw20-base", + "cw20-base 1.1.2", "cw4-group", "cwd-core", "cwd-interface", @@ -997,7 +1104,7 @@ dependencies = [ "cw-utils 1.0.3", "cw2 1.1.2", "cw20 1.1.2", - "cw20-base", + "cw20-base 1.1.2", "cw4-group", "cwd-core", "cwd-interface", @@ -1034,7 +1141,7 @@ dependencies = [ "cw-utils 1.0.3", "cw2 1.1.2", "cw20 1.1.2", - "cw20-base", + "cw20-base 1.1.2", "cw3 1.1.2", "cw4", "cw4-group", @@ -1074,7 +1181,7 @@ dependencies = [ "cw-utils 1.0.3", "cw2 1.1.2", "cw20 1.1.2", - "cw20-base", + "cw20-base 1.1.2", "cw3 1.1.2", "cw4", "cw4-group", @@ -1186,7 +1293,7 @@ dependencies = [ "cw-utils 1.0.3", "cw2 1.1.2", "cw20 1.1.2", - "cw20-base", + "cw20-base 1.1.2", "cw3 1.1.2", "cw4", "cw4-group", @@ -1249,7 +1356,7 @@ dependencies = [ "cw-utils 1.0.3", "cw2 1.1.2", "cw20 1.1.2", - "cw20-base", + "cw20-base 1.1.2", "cw4", "cw4-group", "cw721-base 0.18.0", @@ -1318,9 +1425,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] @@ -1357,11 +1464,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "dyn-clone" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "ecdsa" @@ -1406,9 +1519,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "elliptic-curve" @@ -1507,9 +1620,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -1624,9 +1737,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "k256" @@ -1642,9 +1755,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" dependencies = [ "cfg-if", "ecdsa 0.16.9", @@ -1656,9 +1769,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.151" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "lockdrop-vault" @@ -1666,7 +1779,7 @@ version = "0.1.1" dependencies = [ "anyhow", "astroport 2.0.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main)", - "astroport-periphery", + "astroport-periphery 1.1.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main)", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", @@ -1682,6 +1795,28 @@ dependencies = [ "thiserror", ] +[[package]] +name = "lockdrop-vault-for-cl-pools" +version = "0.1.0" +dependencies = [ + "anyhow", + "astroport 2.0.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?rev=e306308dd23d567399c15d899f295a910ede945b)", + "astroport-periphery 1.1.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?rev=e306308dd23d567399c15d899f295a910ede945b)", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", + "cw20 1.1.2", + "cwd-interface", + "cwd-macros", + "cwd-voting", + "neutron-lockdrop-vault-for-cl-pools", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "neutron-dao-pre-propose-overrule" version = "0.1.0" @@ -1710,7 +1845,7 @@ dependencies = [ name = "neutron-lockdrop-vault" version = "0.1.0" dependencies = [ - "astroport-periphery", + "astroport-periphery 1.1.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main)", "cosmwasm-schema", "cosmwasm-std", "cwd-interface", @@ -1721,6 +1856,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "neutron-lockdrop-vault-for-cl-pools" +version = "0.1.0" +dependencies = [ + "astroport 2.0.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?rev=e306308dd23d567399c15d899f295a910ede945b)", + "astroport-periphery 1.1.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?rev=e306308dd23d567399c15d899f295a910ede945b)", + "cosmwasm-schema", + "cosmwasm-std", + "cwd-interface", + "cwd-macros", + "neutron-voting-power", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "neutron-oracle" version = "0.1.0" @@ -1731,12 +1882,14 @@ dependencies = [ [[package]] name = "neutron-reserve" -version = "0.1.2" +version = "0.2.0" dependencies = [ + "astroport 2.5.0", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", "cw2 1.1.2", + "cw20 0.13.4", "cwd-macros", "exec-control", "neutron-sdk", @@ -1757,7 +1910,7 @@ dependencies = [ "cosmwasm-std", "prost 0.12.3", "prost-types", - "protobuf", + "protobuf 3.4.0", "schemars", "serde", "serde-json-wasm 1.0.1", @@ -1873,6 +2026,27 @@ dependencies = [ "thiserror", ] +[[package]] +name = "neutron-vesting-lp-vault-for-cl-pools" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cwd-interface", + "cwd-macros", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "neutron-voting-power" +version = "0.1.0" +dependencies = [ + "astroport 2.8.0", + "cosmwasm-std", +] + [[package]] name = "neutron-voting-registry" version = "0.3.1" @@ -1895,6 +2069,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-derive" version = "0.3.3" @@ -1908,9 +2088,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -1923,9 +2103,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "paste" @@ -1967,9 +2147,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -2028,7 +2208,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.55", ] [[package]] @@ -2042,9 +2222,18 @@ dependencies = [ [[package]] name = "protobuf" -version = "3.3.0" +version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65f4a8ec18723a734e5dc09c173e0abf9690432da5340285d536edcb4dac190" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" +dependencies = [ + "bytes", +] + +[[package]] +name = "protobuf" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58678a64de2fced2bdec6bca052a6716a0efe692d6e3f53d1bda6a1def64cfc0" dependencies = [ "once_cell", "protobuf-support", @@ -2053,18 +2242,18 @@ dependencies = [ [[package]] name = "protobuf-support" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6872f4d4f4b98303239a2b5838f5bbbb77b01ffc892d627957f37a22d7cfe69c" +checksum = "e1ed294a835b0f30810e13616b1cd34943c6d1e84a8f3b0dcfe466d256c3e7e7" dependencies = [ "thiserror", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -2134,9 +2323,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "schemars" @@ -2192,15 +2381,15 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] @@ -2225,22 +2414,22 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.12" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.55", ] [[package]] @@ -2256,9 +2445,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -2309,6 +2498,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 = "speedate" version = "0.13.0" @@ -2364,7 +2574,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.41", + "syn 2.0.55", ] [[package]] @@ -2395,9 +2605,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.41" +version = "2.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" dependencies = [ "proc-macro2", "quote", @@ -2406,9 +2616,9 @@ dependencies = [ [[package]] name = "tendermint-proto" -version = "0.34.0" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc728a4f9e891d71adf66af6ecaece146f9c7a11312288a3107b3e1d6979aaf" +checksum = "b797dd3d2beaaee91d2f065e7bdf239dc8d80bba4a183a288bc1279dd5a69a1e" dependencies = [ "bytes", "flex-error", @@ -2422,33 +2632,49 @@ dependencies = [ "time", ] +[[package]] +name = "terraswap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73bbfb46c586aeaaa91da9532bff8b12fd909dcc80ff2817d58b87eab2f3a2b5" +dependencies = [ + "cosmwasm-std", + "cosmwasm-storage", + "cw2 0.13.4", + "cw20 0.13.4", + "protobuf 2.28.0", + "schemars", + "serde", +] + [[package]] name = "thiserror" -version = "1.0.51" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.51" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.55", ] [[package]] name = "time" -version = "0.3.30" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", + "num-conv", "powerfmt", "time-core", "time-macros", @@ -2462,10 +2688,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" dependencies = [ + "num-conv", "time-core", ] @@ -2502,7 +2729,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vesting-base" version = "1.1.0" -source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main#c73552b5ed09c2a74e5854cb1d5c3c4c9fe599af" +source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main#2dc53b4f011bd8c0de838f9e76345a6766620464" dependencies = [ "astroport 2.0.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main)", "cosmwasm-schema", @@ -2516,7 +2743,23 @@ dependencies = [ [[package]] name = "vesting-base" version = "1.1.0" -source = "git+https://github.com/neutron-org/neutron-tge-contracts.git#c73552b5ed09c2a74e5854cb1d5c3c4c9fe599af" +source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?rev=e306308dd23d567399c15d899f295a910ede945b#e306308dd23d567399c15d899f295a910ede945b" +dependencies = [ + "astroport 2.0.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?rev=e306308dd23d567399c15d899f295a910ede945b)", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "cw2 0.15.1", + "cw20 0.15.1", + "serde", + "thiserror", +] + +[[package]] +name = "vesting-base" +version = "1.1.0" +source = "git+https://github.com/neutron-org/neutron-tge-contracts.git#2dc53b4f011bd8c0de838f9e76345a6766620464" dependencies = [ "astroport 2.0.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git)", "cosmwasm-schema", @@ -2530,7 +2773,7 @@ dependencies = [ [[package]] name = "vesting-lp" version = "1.1.0" -source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main#c73552b5ed09c2a74e5854cb1d5c3c4c9fe599af" +source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main#2dc53b4f011bd8c0de838f9e76345a6766620464" dependencies = [ "astroport 2.0.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main)", "cosmwasm-schema", @@ -2540,6 +2783,19 @@ dependencies = [ "vesting-base 1.1.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main)", ] +[[package]] +name = "vesting-lp" +version = "1.1.0" +source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?rev=e306308dd23d567399c15d899f295a910ede945b#e306308dd23d567399c15d899f295a910ede945b" +dependencies = [ + "astroport 2.0.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?rev=e306308dd23d567399c15d899f295a910ede945b)", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw2 0.15.1", + "vesting-base 1.1.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?rev=e306308dd23d567399c15d899f295a910ede945b)", +] + [[package]] name = "vesting-lp-vault" version = "0.1.1" @@ -2558,7 +2814,32 @@ dependencies = [ "serde", "thiserror", "vesting-base 1.1.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main)", - "vesting-lp", + "vesting-lp 1.1.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main)", +] + +[[package]] +name = "vesting-lp-vault-for-cl-pools" +version = "0.1.0" +dependencies = [ + "anyhow", + "astroport 2.0.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?rev=e306308dd23d567399c15d899f295a910ede945b)", + "astroport 2.8.0", + "astroport-pair-concentrated", + "astroport-xastro-token", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", + "cwd-interface", + "cwd-macros", + "neutron-vesting-lp-vault-for-cl-pools", + "neutron-voting-power", + "schemars", + "serde", + "thiserror", + "vesting-base 1.1.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?rev=e306308dd23d567399c15d899f295a910ede945b)", + "vesting-lp 1.1.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?rev=e306308dd23d567399c15d899f295a910ede945b)", ] [[package]] diff --git a/contracts/dao/voting/lockdrop-vault-for-cl-pools/.cargo/config b/contracts/dao/voting/lockdrop-vault-for-cl-pools/.cargo/config new file mode 100644 index 00000000..336b618a --- /dev/null +++ b/contracts/dao/voting/lockdrop-vault-for-cl-pools/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" diff --git a/contracts/dao/voting/lockdrop-vault-for-cl-pools/Cargo.toml b/contracts/dao/voting/lockdrop-vault-for-cl-pools/Cargo.toml new file mode 100644 index 00000000..579a5177 --- /dev/null +++ b/contracts/dao/voting/lockdrop-vault-for-cl-pools/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "lockdrop-vault-for-cl-pools" +version = "0.1.0" +authors = ["Sergei Sotnikov "] +edition = "2021" +license = "Apache-2.0" +repository = "https://github.com/neutron/neutron-dao" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-std = { version = "1.3.0" } +cw-storage-plus = "1.1.0" +cw2 = "1.1.0" +cw20 = "1.1.0" +schemars = "0.8.8" +serde = { version = "1.0.175", default-features = false, features = ["derive"] } +thiserror = { version = "1.0" } +cwd-macros = { path = "../../../../packages/cwd-macros" } +cwd-interface = { path = "../../../../packages/cwd-interface" } +cwd-voting = { path = "../../../../packages/cwd-voting" } +neutron-lockdrop-vault-for-cl-pools = { path = "../../../../packages/neutron-lockdrop-vault-for-cl-pools" } +astroport-periphery = { package="astroport-periphery", git = "https://github.com/neutron-org/neutron-tge-contracts.git", rev = "e306308dd23d567399c15d899f295a910ede945b" } +astroport = { package="astroport", git = "https://github.com/neutron-org/neutron-tge-contracts.git", rev = "e306308dd23d567399c15d899f295a910ede945b" } + +[dev-dependencies] +cosmwasm-schema = { version = "^1.2.1" } +cw-multi-test = "0.16.5" +anyhow = "1.0.57" diff --git a/contracts/dao/voting/lockdrop-vault-for-cl-pools/README.md b/contracts/dao/voting/lockdrop-vault-for-cl-pools/README.md new file mode 100644 index 00000000..d9277633 --- /dev/null +++ b/contracts/dao/voting/lockdrop-vault-for-cl-pools/README.md @@ -0,0 +1,3 @@ +### Neutron Lockdrop Voting Vault + +This contract is not really a voting vault. It's rather an interface to get voting power from a Lockdrop contract. It's not possible to Bond or Unbond funds to this vault cause these ExecuteMsg handlers are introduced just to make the contract comply with the voting vault interface. diff --git a/contracts/dao/voting/lockdrop-vault-for-cl-pools/examples/schema.rs b/contracts/dao/voting/lockdrop-vault-for-cl-pools/examples/schema.rs new file mode 100644 index 00000000..9293d06d --- /dev/null +++ b/contracts/dao/voting/lockdrop-vault-for-cl-pools/examples/schema.rs @@ -0,0 +1,31 @@ +// Copyright 2022 Neutron +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; + +use neutron_lockdrop_vault_for_cl_pools::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(InstantiateMsg), &out_dir); + export_schema(&schema_for!(ExecuteMsg), &out_dir); + export_schema(&schema_for!(QueryMsg), &out_dir); +} diff --git a/contracts/dao/voting/lockdrop-vault-for-cl-pools/schema/execute_msg.json b/contracts/dao/voting/lockdrop-vault-for-cl-pools/schema/execute_msg.json new file mode 100644 index 00000000..f9553568 --- /dev/null +++ b/contracts/dao/voting/lockdrop-vault-for-cl-pools/schema/execute_msg.json @@ -0,0 +1,94 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "atom_cl_pool_contract": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "lockdrop_contract": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "owner": { + "type": [ + "string", + "null" + ] + }, + "usdc_cl_pool_contract": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "bond" + ], + "properties": { + "bond": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unbond" + ], + "properties": { + "unbond": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/dao/voting/lockdrop-vault-for-cl-pools/schema/instantiate_msg.json b/contracts/dao/voting/lockdrop-vault-for-cl-pools/schema/instantiate_msg.json new file mode 100644 index 00000000..b8baf0bb --- /dev/null +++ b/contracts/dao/voting/lockdrop-vault-for-cl-pools/schema/instantiate_msg.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "atom_cl_pool_contract", + "description", + "lockdrop_contract", + "name", + "owner", + "usdc_cl_pool_contract" + ], + "properties": { + "atom_cl_pool_contract": { + "description": "The ATOM/NTRN CL pool oracle contract.", + "type": "string" + }, + "description": { + "description": "Description contains information that characterizes the vault.", + "type": "string" + }, + "lockdrop_contract": { + "description": "The lockdrop contract behind the vault.", + "type": "string" + }, + "name": { + "description": "Name contains the vault name which is used to ease the vault's recognition.", + "type": "string" + }, + "owner": { + "description": "Owner can update all configs including changing the owner. This will generally be a DAO.", + "type": "string" + }, + "usdc_cl_pool_contract": { + "description": "The USDC/NTRN CL pool contract.", + "type": "string" + } + } +} diff --git a/contracts/dao/voting/lockdrop-vault-for-cl-pools/schema/query_msg.json b/contracts/dao/voting/lockdrop-vault-for-cl-pools/schema/query_msg.json new file mode 100644 index 00000000..da19bedd --- /dev/null +++ b/contracts/dao/voting/lockdrop-vault-for-cl-pools/schema/query_msg.json @@ -0,0 +1,181 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "voting_power_at_height" + ], + "properties": { + "voting_power_at_height": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "total_power_at_height" + ], + "properties": { + "total_power_at_height": { + "type": "object", + "properties": { + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "bonding_status" + ], + "properties": { + "bonding_status": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "dao" + ], + "properties": { + "dao": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "description" + ], + "properties": { + "description": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "list_bonders" + ], + "properties": { + "list_bonders": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/dao/voting/lockdrop-vault-for-cl-pools/src/contract.rs b/contracts/dao/voting/lockdrop-vault-for-cl-pools/src/contract.rs new file mode 100644 index 00000000..a82c64b0 --- /dev/null +++ b/contracts/dao/voting/lockdrop-vault-for-cl-pools/src/contract.rs @@ -0,0 +1,295 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, Uint128}; +use cw2::set_contract_version; +use cwd_interface::voting::{ + BondingStatusResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, +}; +use neutron_lockdrop_vault_for_cl_pools::voting_power::{ + get_voting_power_for_address, get_voting_power_total, +}; + +use crate::state::{CONFIG, DAO}; + +use astroport_periphery::lockdrop::PoolType; +use neutron_lockdrop_vault_for_cl_pools::error::{ContractError, ContractResult}; +use neutron_lockdrop_vault_for_cl_pools::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use neutron_lockdrop_vault_for_cl_pools::types::Config; + +pub(crate) const CONTRACT_NAME: &str = "crates.io:neutron-lockdrop-vault-for-cl-pools"; +pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> ContractResult { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let owner = deps.api.addr_validate(&msg.owner)?; + + let config = Config { + name: msg.name, + description: msg.description, + lockdrop_contract: deps.api.addr_validate(&msg.lockdrop_contract)?, + usdc_cl_pool_contract: deps.api.addr_validate(&msg.usdc_cl_pool_contract)?, + atom_cl_pool_contract: deps.api.addr_validate(&msg.atom_cl_pool_contract)?, + owner, + }; + config.validate()?; + CONFIG.save(deps.storage, &config)?; + DAO.save(deps.storage, &info.sender)?; + + Ok(Response::new() + .add_attribute("action", "instantiate") + .add_attribute("name", config.name) + .add_attribute("description", config.description) + .add_attribute("owner", config.owner) + .add_attribute("lockdrop_contract", config.lockdrop_contract) + .add_attribute("oracle_usdc_contract", config.usdc_cl_pool_contract) + .add_attribute("oracle_atom_contract", config.atom_cl_pool_contract)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> ContractResult { + match msg { + ExecuteMsg::Bond {} => execute_bond(deps, env, info), + ExecuteMsg::Unbond { amount } => execute_unbond(deps, env, info, amount), + ExecuteMsg::UpdateConfig { + owner, + lockdrop_contract, + usdc_cl_pool_contract, + atom_cl_pool_contract, + name, + description, + } => execute_update_config( + deps, + info, + owner, + lockdrop_contract, + usdc_cl_pool_contract, + atom_cl_pool_contract, + name, + description, + ), + } +} + +pub fn execute_bond(_deps: DepsMut, _env: Env, _info: MessageInfo) -> ContractResult { + Err(ContractError::BondingDisabled {}) +} + +pub fn execute_unbond( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _amount: Uint128, +) -> ContractResult { + Err(ContractError::DirectUnbondingDisabled {}) +} + +#[allow(clippy::too_many_arguments)] +pub fn execute_update_config( + deps: DepsMut, + info: MessageInfo, + new_owner: Option, + new_lockdrop_contract: Option, + new_usdc_cl_pool_contract: Option, + new_atom_cl_pool_contract: Option, + new_name: Option, + new_description: Option, +) -> ContractResult { + let mut config: Config = CONFIG.load(deps.storage)?; + if info.sender != config.owner { + return Err(ContractError::Unauthorized {}); + } + + let new_owner = new_owner + .map(|new_owner| deps.api.addr_validate(&new_owner)) + .transpose()?; + + let new_lockdrop_contract = new_lockdrop_contract + .map(|new_lockdrop_contract| deps.api.addr_validate(&new_lockdrop_contract)) + .transpose()?; + + let new_usdc_pool_contract = new_usdc_cl_pool_contract + .map(|new_usdc_pool_contract| deps.api.addr_validate(&new_usdc_pool_contract)) + .transpose()?; + + let new_atom_pool_contract = new_atom_cl_pool_contract + .map(|new_atom_pool_contract| deps.api.addr_validate(&new_atom_pool_contract)) + .transpose()?; + + if let Some(owner) = new_owner { + config.owner = owner; + } + + if let Some(lockdrop_contract) = new_lockdrop_contract { + config.lockdrop_contract = lockdrop_contract; + } + if let Some(oracle_contract) = new_usdc_pool_contract { + config.usdc_cl_pool_contract = oracle_contract; + } + if let Some(oracle_contract) = new_atom_pool_contract { + config.atom_cl_pool_contract = oracle_contract; + } + if let Some(name) = new_name { + config.name = name; + } + if let Some(description) = new_description { + config.description = description; + } + + config.validate()?; + CONFIG.save(deps.storage, &config)?; + + Ok(Response::new() + .add_attribute("action", "update_config") + .add_attribute("description", config.description) + .add_attribute("owner", config.owner) + .add_attribute("lockdrop_contract", config.lockdrop_contract) + .add_attribute("oracle_usdc_contract", config.usdc_cl_pool_contract) + .add_attribute("oracle_atom_contract", config.atom_cl_pool_contract)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { + match msg { + QueryMsg::VotingPowerAtHeight { address, height } => Ok(to_json_binary( + &query_voting_power_at_height(deps, env, address, height)?, + )?), + QueryMsg::TotalPowerAtHeight { height } => Ok(to_json_binary( + &query_total_power_at_height(deps, env, height)?, + )?), + QueryMsg::Info {} => query_info(deps), + QueryMsg::Dao {} => query_dao(deps), + QueryMsg::Name {} => query_name(deps), + QueryMsg::Description {} => query_description(deps), + QueryMsg::Config {} => query_config(deps), + QueryMsg::ListBonders { start_after, limit } => { + query_list_bonders(deps, start_after, limit) + } + QueryMsg::BondingStatus { height, address } => Ok(to_json_binary(&query_bonding_status( + deps, env, height, address, + )?)?), + } +} + +pub fn query_voting_power_at_height( + deps: Deps, + env: Env, + address: String, + height: Option, +) -> ContractResult { + let config = CONFIG.load(deps.storage)?; + + let height = height.unwrap_or(env.block.height); + + let atom_power = get_voting_power_for_address( + deps, + &config.lockdrop_contract, + &config.atom_cl_pool_contract, + PoolType::ATOM, + address.clone(), + height, + )?; + let usdc_power = get_voting_power_for_address( + deps, + &config.lockdrop_contract, + &config.usdc_cl_pool_contract, + PoolType::USDC, + address, + height, + )?; + + let power = atom_power + usdc_power; + + Ok(VotingPowerAtHeightResponse { power, height }) +} + +pub fn query_total_power_at_height( + deps: Deps, + env: Env, + height: Option, +) -> ContractResult { + let config = CONFIG.load(deps.storage)?; + + let height = height.unwrap_or(env.block.height); + + let atom_power = get_voting_power_total( + deps, + &config.lockdrop_contract, + &config.atom_cl_pool_contract, + PoolType::ATOM, + height, + )?; + let usdc_power = get_voting_power_total( + deps, + &config.lockdrop_contract, + &config.usdc_cl_pool_contract, + PoolType::USDC, + height, + )?; + + let power = atom_power.checked_add(usdc_power)?; + + Ok(TotalPowerAtHeightResponse { power, height }) +} + +pub fn query_info(deps: Deps) -> ContractResult { + let info = cw2::get_contract_version(deps.storage)?; + Ok(to_json_binary(&cwd_interface::voting::InfoResponse { + info, + })?) +} + +pub fn query_dao(deps: Deps) -> ContractResult { + let dao = DAO.load(deps.storage)?; + Ok(to_json_binary(&dao)?) +} + +pub fn query_name(deps: Deps) -> ContractResult { + let config = CONFIG.load(deps.storage)?; + Ok(to_json_binary(&config.name)?) +} + +pub fn query_description(deps: Deps) -> ContractResult { + let config = CONFIG.load(deps.storage)?; + Ok(to_json_binary(&config.description)?) +} + +pub fn query_config(deps: Deps) -> ContractResult { + let config = CONFIG.load(deps.storage)?; + Ok(to_json_binary(&config)?) +} + +pub fn query_list_bonders( + _deps: Deps, + _start_after: Option, + _limit: Option, +) -> ContractResult { + Err(ContractError::BondingDisabled {}) +} + +pub fn query_bonding_status( + _deps: Deps, + _env: Env, + _height: Option, + _address: String, +) -> ContractResult { + Err(ContractError::BondingDisabled {}) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, _: MigrateMsg) -> ContractResult { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + Ok(Response::default()) +} diff --git a/contracts/dao/voting/lockdrop-vault-for-cl-pools/src/lib.rs b/contracts/dao/voting/lockdrop-vault-for-cl-pools/src/lib.rs new file mode 100644 index 00000000..da3bbc25 --- /dev/null +++ b/contracts/dao/voting/lockdrop-vault-for-cl-pools/src/lib.rs @@ -0,0 +1,5 @@ +pub mod contract; +pub mod state; + +#[cfg(test)] +mod tests; diff --git a/contracts/dao/voting/lockdrop-vault-for-cl-pools/src/state.rs b/contracts/dao/voting/lockdrop-vault-for-cl-pools/src/state.rs new file mode 100644 index 00000000..fd89a2d1 --- /dev/null +++ b/contracts/dao/voting/lockdrop-vault-for-cl-pools/src/state.rs @@ -0,0 +1,6 @@ +use cosmwasm_std::Addr; +use cw_storage_plus::Item; +use neutron_lockdrop_vault_for_cl_pools::types::Config; + +pub const CONFIG: Item = Item::new("config"); +pub const DAO: Item = Item::new("dao"); diff --git a/contracts/dao/voting/lockdrop-vault-for-cl-pools/src/tests.rs b/contracts/dao/voting/lockdrop-vault-for-cl-pools/src/tests.rs new file mode 100644 index 00000000..d97a7e64 --- /dev/null +++ b/contracts/dao/voting/lockdrop-vault-for-cl-pools/src/tests.rs @@ -0,0 +1,444 @@ +use crate::contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}; +use cosmwasm_std::testing::{mock_dependencies, mock_env}; +use cosmwasm_std::{coins, Addr, Coin, Empty, Uint128}; +use cw_multi_test::{custom_app, App, AppResponse, Contract, ContractWrapper, Executor}; +use cwd_interface::voting::InfoResponse; +use neutron_lockdrop_vault_for_cl_pools::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use neutron_lockdrop_vault_for_cl_pools::types::Config; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +const DAO_ADDR: &str = "dao"; +const NAME: &str = "name"; +const NEW_NAME: &str = "new_name"; +const DESCRIPTION: &str = "description"; +const NEW_DESCRIPTION: &str = "new description"; +const LOCKDROP_ADDR: &str = "lockdrop"; +const USDC_CL_POOL_ADDR: &str = "usdc_cl_pool"; +const ATOM_CL_POOL_ADDR: &str = "atom_cl_pool"; +const NEW_LOCKDROP_ADDR: &str = "new_lockdrop"; +const NEW_USDC_CL_POOL_ADDR: &str = "new_usdc_cl_pool"; +const NEW_ATOM_CL_POOL_ADDR: &str = "new_atom_cl_pool"; +const ADDR1: &str = "addr1"; +const ADDR2: &str = "addr2"; +const DENOM: &str = "ujuno"; +const INIT_BALANCE: Uint128 = Uint128::new(10000); + +fn vault_contract() -> Box> { + let contract = ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ); + Box::new(contract) +} + +fn mock_app() -> App { + custom_app(|r, _a, s| { + r.bank + .init_balance( + s, + &Addr::unchecked(DAO_ADDR), + vec![Coin { + denom: DENOM.to_string(), + amount: INIT_BALANCE, + }], + ) + .unwrap(); + r.bank + .init_balance( + s, + &Addr::unchecked(ADDR1), + vec![Coin { + denom: DENOM.to_string(), + amount: INIT_BALANCE, + }], + ) + .unwrap(); + r.bank + .init_balance( + s, + &Addr::unchecked(ADDR2), + vec![Coin { + denom: DENOM.to_string(), + amount: INIT_BALANCE, + }], + ) + .unwrap(); + }) +} + +#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub struct EmptyMsg {} + +fn instantiate_vault(app: &mut App, id: u64, msg: InstantiateMsg) -> Addr { + app.instantiate_contract(id, Addr::unchecked(DAO_ADDR), &msg, &[], "vault", None) + .unwrap() +} + +fn bond_tokens( + app: &mut App, + contract_addr: Addr, + sender: &str, + amount: u128, + denom: &str, +) -> anyhow::Result { + app.execute_contract( + Addr::unchecked(sender), + contract_addr, + &ExecuteMsg::Bond {}, + &coins(amount, denom), + ) +} + +fn unbond_tokens( + app: &mut App, + contract_addr: Addr, + sender: &str, + amount: u128, +) -> anyhow::Result { + app.execute_contract( + Addr::unchecked(sender), + contract_addr, + &ExecuteMsg::Unbond { + amount: Uint128::new(amount), + }, + &[], + ) +} + +#[allow(clippy::too_many_arguments)] +fn update_config( + app: &mut App, + contract_addr: Addr, + sender: &str, + owner: Option, + lockdrop_contract: Option, + usdc_cl_pool_contract: Option, + atom_cl_pool_contract: Option, + name: Option, + description: Option, +) -> anyhow::Result { + app.execute_contract( + Addr::unchecked(sender), + contract_addr, + &ExecuteMsg::UpdateConfig { + owner, + lockdrop_contract, + usdc_cl_pool_contract, + atom_cl_pool_contract, + name, + description, + }, + &[], + ) +} + +fn get_config(app: &mut App, contract_addr: Addr) -> Config { + app.wrap() + .query_wasm_smart(contract_addr, &QueryMsg::Config {}) + .unwrap() +} + +fn get_dao(app: &App, contract_addr: &Addr) -> String { + app.wrap() + .query_wasm_smart(contract_addr, &QueryMsg::Dao {}) + .unwrap() +} + +#[test] +fn test_instantiate() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + lockdrop_contract: LOCKDROP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + }, + ); + assert_eq!(get_dao(&app, &addr), String::from(DAO_ADDR)); +} + +#[test] +#[should_panic(expected = "Bonding is not available for this contract")] +fn test_bond() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + lockdrop_contract: LOCKDROP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + }, + ); + + // Try and bond an invalid denom + bond_tokens(&mut app, addr, ADDR1, 100, DENOM).unwrap(); +} + +#[test] +#[should_panic(expected = "Direct unbonding is not available for this contract")] +fn test_unbond() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + lockdrop_contract: LOCKDROP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + }, + ); + + unbond_tokens(&mut app, addr, ADDR1, 100).unwrap(); +} + +#[test] +#[should_panic(expected = "Unauthorized")] +fn test_update_config_unauthorized() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + lockdrop_contract: LOCKDROP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + }, + ); + + // From ADDR2, so not owner + update_config( + &mut app, + addr, + ADDR2, + Some(ADDR1.to_string()), + Some(NEW_LOCKDROP_ADDR.to_string()), + Some(NEW_USDC_CL_POOL_ADDR.to_string()), + Some(NEW_ATOM_CL_POOL_ADDR.to_string()), + Some(NEW_NAME.to_string()), + Some(NEW_DESCRIPTION.to_string()), + ) + .unwrap(); +} + +#[test] +fn test_update_config_as_owner() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + lockdrop_contract: LOCKDROP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + }, + ); + + // Change owner, description, name and lockdrop contract + update_config( + &mut app, + addr.clone(), + DAO_ADDR, + Some(ADDR1.to_string()), + Some(NEW_LOCKDROP_ADDR.to_string()), + Some(NEW_USDC_CL_POOL_ADDR.to_string()), + Some(NEW_ATOM_CL_POOL_ADDR.to_string()), + Some(NEW_NAME.to_string()), + Some(NEW_DESCRIPTION.to_string()), + ) + .unwrap(); + + let config = get_config(&mut app, addr); + assert_eq!( + Config { + name: NEW_NAME.to_string(), + description: NEW_DESCRIPTION.to_string(), + owner: Addr::unchecked(ADDR1), + lockdrop_contract: Addr::unchecked(NEW_LOCKDROP_ADDR), + usdc_cl_pool_contract: Addr::unchecked(NEW_USDC_CL_POOL_ADDR), + atom_cl_pool_contract: Addr::unchecked(NEW_ATOM_CL_POOL_ADDR), + }, + config + ); +} + +#[test] +#[should_panic(expected = "config description cannot be empty.")] +fn test_update_config_invalid_description() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + lockdrop_contract: LOCKDROP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + }, + ); + + // Change name + update_config( + &mut app, + addr, + DAO_ADDR, + Some(DAO_ADDR.to_string()), + Some(LOCKDROP_ADDR.to_string()), + Some(USDC_CL_POOL_ADDR.to_string()), + Some(ATOM_CL_POOL_ADDR.to_string()), + Some(NEW_NAME.to_string()), + Some(String::from("")), + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "config name cannot be empty.")] +fn test_update_config_invalid_name() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + lockdrop_contract: LOCKDROP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + }, + ); + + // Change description + update_config( + &mut app, + addr, + DAO_ADDR, + Some(DAO_ADDR.to_string()), + Some(LOCKDROP_ADDR.to_string()), + Some(USDC_CL_POOL_ADDR.to_string()), + Some(ATOM_CL_POOL_ADDR.to_string()), + Some(String::from("")), + Some(NEW_DESCRIPTION.to_string()), + ) + .unwrap(); +} + +#[test] +fn test_query_dao() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + lockdrop_contract: LOCKDROP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + }, + ); + + let msg = QueryMsg::Dao {}; + let dao: Addr = app.wrap().query_wasm_smart(addr, &msg).unwrap(); + assert_eq!(dao, Addr::unchecked(DAO_ADDR)); +} + +#[test] +fn test_query_info() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + lockdrop_contract: LOCKDROP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + }, + ); + + let msg = QueryMsg::Info {}; + let resp: InfoResponse = app.wrap().query_wasm_smart(addr, &msg).unwrap(); + assert_eq!( + resp.info.contract, + "crates.io:neutron-lockdrop-vault-for-cl-pools" + ); +} + +#[test] +fn test_query_get_config() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + lockdrop_contract: LOCKDROP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + }, + ); + + let config = get_config(&mut app, addr); + assert_eq!( + config, + Config { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: Addr::unchecked(DAO_ADDR), + lockdrop_contract: Addr::unchecked(LOCKDROP_ADDR), + usdc_cl_pool_contract: Addr::unchecked(USDC_CL_POOL_ADDR), + atom_cl_pool_contract: Addr::unchecked(ATOM_CL_POOL_ADDR), + } + ) +} + +#[test] +pub fn test_migrate_update_version() { + let mut deps = mock_dependencies(); + cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); + + migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); + let version = cw2::get_contract_version(&deps.storage).unwrap(); + + assert_eq!(version.version, CONTRACT_VERSION); + assert_eq!(version.contract, CONTRACT_NAME); +} diff --git a/contracts/dao/voting/lockdrop-vault/src/contract.rs b/contracts/dao/voting/lockdrop-vault/src/contract.rs index 8fd7cf9f..7abca329 100644 --- a/contracts/dao/voting/lockdrop-vault/src/contract.rs +++ b/contracts/dao/voting/lockdrop-vault/src/contract.rs @@ -211,7 +211,7 @@ pub fn query_voting_power_at_height( height, )?; - let power = atom_power + usdc_power; + let power = atom_power.checked_add(usdc_power)?; Ok(VotingPowerAtHeightResponse { power: power.numerator().try_into().map_err(StdError::from)?, diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/.cargo/config b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/.cargo/config new file mode 100644 index 00000000..336b618a --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/Cargo.toml b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/Cargo.toml new file mode 100644 index 00000000..b950ea48 --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "vesting-lp-vault-for-cl-pools" +version = "0.1.0" +authors = ["Sergei Sotnikov ", "Murad Karammaev "] +edition = "2021" +license = "Apache-2.0" +repository = "https://github.com/neutron/neutron-dao" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-std = { version = "1.3.0" } +cw-storage-plus = "1.1.0" +cw2 = "1.1.0" +schemars = "0.8.8" +serde = { version = "1.0.175", default-features = false, features = ["derive"] } +thiserror = { version = "1.0" } +cwd-macros = { path = "../../../../packages/cwd-macros" } +cwd-interface = { path = "../../../../packages/cwd-interface" } +neutron-vesting-lp-vault-for-cl-pools = { path = "../../../../packages/neutron-vesting-lp-vault-for-cl-pools" } +neutron-voting-power = { path = "../../../../packages/neutron-voting-power" } +vesting-base = { git = "https://github.com/neutron-org/neutron-tge-contracts", rev = "e306308dd23d567399c15d899f295a910ede945b" } +vesting-lp = { git = "https://github.com/neutron-org/neutron-tge-contracts", rev = "e306308dd23d567399c15d899f295a910ede945b" } +astroport = { package="astroport", git = "https://github.com/neutron-org/neutron-tge-contracts.git", rev = "e306308dd23d567399c15d899f295a910ede945b" } + +[dev-dependencies] +cosmwasm-schema = { version = "1.3.0" } +cw-multi-test = "0.16.5" +anyhow = "1.0.57" +astroport-xastro-token = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.8.0" } +astroport-pair-concentrated = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.8.0" } +astroport-original = { package = "astroport", git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.8.0" } \ No newline at end of file diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/README.md b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/README.md new file mode 100644 index 00000000..b7c0e790 --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/README.md @@ -0,0 +1,3 @@ +### Neutron Vesting LP Voting Vault + +This contract is not really a voting vault. It's rather an interface to get voting power from a Vesting LP contract. It's not possible to Bond or Unbond funds to this vault cause these ExecuteMsg handlers are introduced just to make the contract comply with the voting vault interface. diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/examples/schema.rs b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/examples/schema.rs new file mode 100644 index 00000000..2d4c537b --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/examples/schema.rs @@ -0,0 +1,34 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; +use cosmwasm_std::Addr; +use cwd_interface::voting::{ + BondingStatusResponse, InfoResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, +}; +use neutron_vesting_lp_vault_for_cl_pools::{ + msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, + types::Config, +}; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(InstantiateMsg), &out_dir); + export_schema(&schema_for!(ExecuteMsg), &out_dir); + export_schema(&schema_for!(QueryMsg), &out_dir); + export_schema(&schema_for!(MigrateMsg), &out_dir); + + export_schema(&schema_for!(InfoResponse), &out_dir); + export_schema(&schema_for!(TotalPowerAtHeightResponse), &out_dir); + export_schema(&schema_for!(VotingPowerAtHeightResponse), &out_dir); + export_schema(&schema_for!(BondingStatusResponse), &out_dir); + + // Auto TS code generation expects the query return type as QueryNameResponse + // Here we map query resonses to the correct name + export_schema_with_title(&schema_for!(Addr), &out_dir, "DaoResponse"); + export_schema_with_title(&schema_for!(Config), &out_dir, "GetConfigResponse"); +} diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/bonding_status_response.json b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/bonding_status_response.json new file mode 100644 index 00000000..8fcbb2be --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/bonding_status_response.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BondingStatusResponse", + "type": "object", + "required": [ + "bonding_enabled", + "height", + "unbondable_abount" + ], + "properties": { + "bonding_enabled": { + "type": "boolean" + }, + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "unbondable_abount": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/dao_response.json b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/dao_response.json new file mode 100644 index 00000000..9518ba3b --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/dao_response.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DaoResponse", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" +} diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/execute_msg.json b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/execute_msg.json new file mode 100644 index 00000000..ad012325 --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/execute_msg.json @@ -0,0 +1,88 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "atom_cl_pool_contract", + "atom_vesting_lp_contract", + "description", + "name", + "owner", + "usdc_cl_pool_contract", + "usdc_vesting_lp_contract" + ], + "properties": { + "atom_cl_pool_contract": { + "type": "string" + }, + "atom_vesting_lp_contract": { + "type": "string" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "usdc_cl_pool_contract": { + "type": "string" + }, + "usdc_vesting_lp_contract": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "bond" + ], + "properties": { + "bond": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unbond" + ], + "properties": { + "unbond": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/get_config_response.json b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/get_config_response.json new file mode 100644 index 00000000..9d7c2f88 --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/get_config_response.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GetConfigResponse", + "type": "object", + "required": [ + "atom_cl_pool_contract", + "atom_vesting_lp_contract", + "description", + "name", + "owner", + "usdc_cl_pool_contract", + "usdc_vesting_lp_contract" + ], + "properties": { + "atom_cl_pool_contract": { + "$ref": "#/definitions/Addr" + }, + "atom_vesting_lp_contract": { + "$ref": "#/definitions/Addr" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/Addr" + }, + "usdc_cl_pool_contract": { + "$ref": "#/definitions/Addr" + }, + "usdc_vesting_lp_contract": { + "$ref": "#/definitions/Addr" + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/info_response.json b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/info_response.json new file mode 100644 index 00000000..1419f2c2 --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/info_response.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InfoResponse", + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "$ref": "#/definitions/ContractVersion" + } + }, + "definitions": { + "ContractVersion": { + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/instantiate_msg.json b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/instantiate_msg.json new file mode 100644 index 00000000..ea001c18 --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/instantiate_msg.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "atom_cl_pool_contract", + "atom_vesting_lp_contract", + "description", + "name", + "owner", + "usdc_cl_pool_contract", + "usdc_vesting_lp_contract" + ], + "properties": { + "atom_cl_pool_contract": { + "description": "The ATOM/NTRN CL pool contract.", + "type": "string" + }, + "atom_vesting_lp_contract": { + "description": "The ATOM Vesting LP contract behind the vault.", + "type": "string" + }, + "description": { + "description": "Description contains information that characterizes the vault.", + "type": "string" + }, + "name": { + "description": "Name contains the vault name which is used to ease the vault's recognition.", + "type": "string" + }, + "owner": { + "description": "Owner can update all configs including changing the owner. This will generally be a DAO.", + "type": "string" + }, + "usdc_cl_pool_contract": { + "description": "The USDC/NTRN CL pool oracle contract.", + "type": "string" + }, + "usdc_vesting_lp_contract": { + "description": "The USDC Vesting LP contract behind the vault.", + "type": "string" + } + } +} diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/migrate_msg.json b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/migrate_msg.json new file mode 100644 index 00000000..87b18ea7 --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/migrate_msg.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object" +} diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/query_msg.json b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/query_msg.json new file mode 100644 index 00000000..da19bedd --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/query_msg.json @@ -0,0 +1,181 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "voting_power_at_height" + ], + "properties": { + "voting_power_at_height": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "total_power_at_height" + ], + "properties": { + "total_power_at_height": { + "type": "object", + "properties": { + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "bonding_status" + ], + "properties": { + "bonding_status": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "dao" + ], + "properties": { + "dao": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "description" + ], + "properties": { + "description": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "list_bonders" + ], + "properties": { + "list_bonders": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/total_power_at_height_response.json b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/total_power_at_height_response.json new file mode 100644 index 00000000..8018462b --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/total_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TotalPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/voting_power_at_height_response.json b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/voting_power_at_height_response.json new file mode 100644 index 00000000..15e986bf --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/voting_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VotingPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/src/contract.rs b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/src/contract.rs new file mode 100644 index 00000000..390cee85 --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/src/contract.rs @@ -0,0 +1,303 @@ +use astroport::asset::AssetInfo; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, Uint128, +}; +use cw2::set_contract_version; +use cwd_interface::voting::{ + BondingStatusResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, +}; +use serde::Serialize; + +use crate::state::{CONFIG, DAO}; +use neutron_vesting_lp_vault_for_cl_pools::{ + error::{ContractError, ContractResult}, + msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, + types::Config, +}; +use neutron_voting_power::voting_power::voting_power_from_lp_tokens; +use vesting_base::msg::{QueryMsg as VestingLpQueryMsg, QueryMsgHistorical}; +use vesting_base::types::Config as VestingBaseConfig; + +pub(crate) const CONTRACT_NAME: &str = "crates.io:neutron-vesting-lp-vault-for-cl-pools"; +pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> ContractResult { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let owner = deps.api.addr_validate(&msg.owner)?; + + let config = Config { + name: msg.name, + description: msg.description, + atom_vesting_lp_contract: deps.api.addr_validate(&msg.atom_vesting_lp_contract)?, + atom_cl_pool_contract: deps.api.addr_validate(&msg.atom_cl_pool_contract)?, + usdc_vesting_lp_contract: deps.api.addr_validate(&msg.usdc_vesting_lp_contract)?, + usdc_cl_pool_contract: deps.api.addr_validate(&msg.usdc_cl_pool_contract)?, + owner, + }; + config.validate()?; + CONFIG.save(deps.storage, &config)?; + DAO.save(deps.storage, &info.sender)?; + + Ok(Response::new() + .add_attribute("action", "instantiate") + .add_attribute("name", config.name) + .add_attribute("description", config.description) + .add_attribute("owner", config.owner) + .add_attribute("atom_vesting_lp_contract", config.atom_vesting_lp_contract) + .add_attribute("atom_cl_pool_contract", config.atom_cl_pool_contract) + .add_attribute("usdc_vesting_lp_contract", config.usdc_vesting_lp_contract) + .add_attribute("usdc_cl_pool_contract", config.usdc_cl_pool_contract)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> ContractResult { + match msg { + ExecuteMsg::Bond {} => execute_bond(deps, env, info), + ExecuteMsg::Unbond { amount } => execute_unbond(deps, env, info, amount), + ExecuteMsg::UpdateConfig { + owner, + atom_vesting_lp_contract, + atom_cl_pool_contract: atom_oracle_contract, + usdc_vesting_lp_contract, + usdc_cl_pool_contract: usdc_oracle_contract, + name, + description, + } => execute_update_config( + deps, + info, + owner, + atom_vesting_lp_contract, + atom_oracle_contract, + usdc_vesting_lp_contract, + usdc_oracle_contract, + name, + description, + ), + } +} + +pub fn execute_bond(_deps: DepsMut, _env: Env, _info: MessageInfo) -> ContractResult { + Err(ContractError::BondingDisabled {}) +} + +pub fn execute_unbond( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _amount: Uint128, +) -> ContractResult { + Err(ContractError::DirectUnbondingDisabled {}) +} + +#[allow(clippy::too_many_arguments)] +pub fn execute_update_config( + deps: DepsMut, + info: MessageInfo, + new_owner: String, + new_atom_vesting_lp_contract: String, + new_atom_oracle_contract: String, + new_usdc_vesting_lp_contract: String, + new_usdc_oracle_contract: String, + new_name: String, + new_description: String, +) -> ContractResult { + let mut config: Config = CONFIG.load(deps.storage)?; + if info.sender != config.owner { + return Err(ContractError::Unauthorized {}); + } + + let new_owner = deps.api.addr_validate(&new_owner)?; + let new_atom_vesting_lp_contract = deps.api.addr_validate(&new_atom_vesting_lp_contract)?; + let new_atom_oracle_contract = deps.api.addr_validate(&new_atom_oracle_contract)?; + let new_usdc_vesting_lp_contract = deps.api.addr_validate(&new_usdc_vesting_lp_contract)?; + let new_usdc_oracle_contract = deps.api.addr_validate(&new_usdc_oracle_contract)?; + + config.owner = new_owner; + config.atom_vesting_lp_contract = new_atom_vesting_lp_contract; + config.atom_cl_pool_contract = new_atom_oracle_contract; + config.usdc_vesting_lp_contract = new_usdc_vesting_lp_contract; + config.usdc_cl_pool_contract = new_usdc_oracle_contract; + config.name = new_name; + config.description = new_description; + config.validate()?; + CONFIG.save(deps.storage, &config)?; + + Ok(Response::new() + .add_attribute("action", "update_config") + .add_attribute("description", config.description) + .add_attribute("owner", config.owner) + .add_attribute("atom_vesting_lp_contract", config.atom_vesting_lp_contract) + .add_attribute("atom_oracle_contract", config.atom_cl_pool_contract) + .add_attribute("usdc_vesting_lp_contract", config.usdc_vesting_lp_contract) + .add_attribute("usdc_oracle_contract", config.usdc_cl_pool_contract)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { + match msg { + QueryMsg::VotingPowerAtHeight { address, height } => Ok(to_json_binary( + &query_voting_power_at_height(deps, env, address, height)?, + )?), + QueryMsg::TotalPowerAtHeight { height } => Ok(to_json_binary( + &query_total_power_at_height(deps, env, height)?, + )?), + QueryMsg::Info {} => query_info(deps), + QueryMsg::Dao {} => query_dao(deps), + QueryMsg::Name {} => query_name(deps), + QueryMsg::Description {} => query_description(deps), + QueryMsg::Config {} => query_config(deps), + QueryMsg::ListBonders { start_after, limit } => { + query_list_bonders(deps, start_after, limit) + } + QueryMsg::BondingStatus { height, address } => Ok(to_json_binary(&query_bonding_status( + deps, env, height, address, + )?)?), + } +} + +fn get_voting_power( + deps: Deps, + config: &Config, + height: u64, + query_msg: &impl Serialize, +) -> ContractResult { + let mut voting_power = Uint128::zero(); + for (vesting_lp, cl_pool) in [ + ( + &config.atom_vesting_lp_contract, + &config.atom_cl_pool_contract, + ), + ( + &config.usdc_vesting_lp_contract, + &config.usdc_cl_pool_contract, + ), + ] { + let vesting_base_config: VestingBaseConfig = deps + .querier + .query_wasm_smart(vesting_lp, &VestingLpQueryMsg::Config {})?; + let lp_token_address: AssetInfo = if vesting_base_config.vesting_token.is_some() { + vesting_base_config.vesting_token.unwrap() + } else { + return Err(ContractError::Std(StdError::generic_err(format!( + "vesting token is not set in {:?} contract", + vesting_lp + )))); + }; + + let lp_total_supply: Uint128 = deps.querier.query_wasm_smart( + lp_token_address.to_string(), + &astroport::xastro_token::QueryMsg::TotalSupplyAt { block: height }, + )?; + + voting_power = voting_power.checked_add(voting_power_from_lp_tokens( + deps, + deps.querier + .query_wasm_smart::>(vesting_lp, &query_msg)? + .unwrap_or_default(), + lp_total_supply, + cl_pool, + height, + )?)?; + } + Ok(voting_power) +} + +pub fn query_voting_power_at_height( + deps: Deps, + env: Env, + address: String, + height: Option, +) -> ContractResult { + let config = CONFIG.load(deps.storage)?; + let height = height.unwrap_or(env.block.height); + let query_msg = VestingLpQueryMsg::HistoricalExtension { + msg: QueryMsgHistorical::UnclaimedAmountAtHeight { address, height }, + }; + + Ok(VotingPowerAtHeightResponse { + power: get_voting_power(deps, &config, height, &query_msg)?, + height, + }) +} + +pub fn query_total_power_at_height( + deps: Deps, + env: Env, + height: Option, +) -> ContractResult { + let config = CONFIG.load(deps.storage)?; + let height = height.unwrap_or(env.block.height); + let query_msg = VestingLpQueryMsg::HistoricalExtension { + msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height }, + }; + + Ok(TotalPowerAtHeightResponse { + power: get_voting_power(deps, &config, height, &query_msg)?, + height, + }) +} + +pub fn query_info(deps: Deps) -> ContractResult { + let info = cw2::get_contract_version(deps.storage)?; + Ok(to_json_binary(&cwd_interface::voting::InfoResponse { + info, + })?) +} + +pub fn query_dao(deps: Deps) -> ContractResult { + let dao = DAO.load(deps.storage)?; + Ok(to_json_binary(&dao)?) +} + +pub fn query_name(deps: Deps) -> ContractResult { + let config = CONFIG.load(deps.storage)?; + Ok(to_json_binary(&config.name)?) +} + +pub fn query_description(deps: Deps) -> ContractResult { + let config = CONFIG.load(deps.storage)?; + Ok(to_json_binary(&config.description)?) +} + +pub fn query_config(deps: Deps) -> ContractResult { + let config = CONFIG.load(deps.storage)?; + Ok(to_json_binary(&config)?) +} + +pub fn query_list_bonders( + _deps: Deps, + _start_after: Option, + _limit: Option, +) -> ContractResult { + Err(ContractError::BondingDisabled {}) +} + +pub fn query_bonding_status( + _deps: Deps, + _env: Env, + _height: Option, + _address: String, +) -> ContractResult { + Err(ContractError::BondingDisabled {}) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, _: MigrateMsg) -> ContractResult { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + Ok(Response::default()) +} diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/src/lib.rs b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/src/lib.rs new file mode 100644 index 00000000..da3bbc25 --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/src/lib.rs @@ -0,0 +1,5 @@ +pub mod contract; +pub mod state; + +#[cfg(test)] +mod tests; diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/src/state.rs b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/src/state.rs new file mode 100644 index 00000000..bfdb04be --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/src/state.rs @@ -0,0 +1,6 @@ +use cosmwasm_std::Addr; +use cw_storage_plus::Item; +use neutron_vesting_lp_vault_for_cl_pools::types::Config; + +pub const CONFIG: Item = Item::new("config"); +pub const DAO: Item = Item::new("dao"); diff --git a/contracts/dao/voting/vesting-lp-vault-for-cl-pools/src/tests.rs b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/src/tests.rs new file mode 100644 index 00000000..e519061a --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/src/tests.rs @@ -0,0 +1,727 @@ +use crate::contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}; +use astroport::asset::AssetInfo; +use cosmwasm_std::testing::{mock_dependencies, mock_env}; +use cosmwasm_std::{coins, Addr, Coin, Empty, Uint128}; +use cw_multi_test::{custom_app, App, AppResponse, Contract, ContractWrapper, Executor}; +use cwd_interface::voting::{ + InfoResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, +}; +use neutron_vesting_lp_vault_for_cl_pools::{ + msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, + types::Config, +}; + +const DAO_ADDR: &str = "dao"; +const NAME: &str = "name"; +const NEW_NAME: &str = "new_name"; +const DESCRIPTION: &str = "description"; +const NEW_DESCRIPTION: &str = "new description"; +const ATOM_VESTING_LP_ADDR: &str = "atom_vesting_lp"; +const USDC_VESTING_LP_ADDR: &str = "usdc_vesting_lp"; +const ATOM_CL_POOL_ADDR: &str = "atom_cl_pool"; +const USDC_CL_POOL_ADDR: &str = "usdc_cl_pool"; +const NEW_ATOM_VESTING_LP_ADDR: &str = "new_atom_vesting_lp"; +const NEW_USDC_VESTING_LP_ADDR: &str = "new_usdc_vesting_lp"; +const NEW_ATOM_CL_POOL_ADDR: &str = "new_atom_cl_pool"; +const NEW_USDC_CL_POOL_ADDR: &str = "new_usdc_cl_pool"; +const ADDR1: &str = "addr1"; +const ADDR2: &str = "addr2"; +const DENOM: &str = "ujuno"; +const INIT_BALANCE: Uint128 = Uint128::new(10000); + +fn vault_contract() -> Box> { + let contract = ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ); + Box::new(contract) +} + +fn lp_token_contract() -> Box> { + let contract = ContractWrapper::new( + astroport_xastro_token::contract::execute, + astroport_xastro_token::contract::instantiate, + astroport_xastro_token::contract::query, + ); + Box::new(contract) +} + +fn vesting_lp_contract() -> Box> { + let contract = ContractWrapper::new( + vesting_lp::contract::execute, + vesting_lp::contract::instantiate, + vesting_lp::contract::query, + ); + Box::new(contract) +} + +fn mock_app() -> App { + custom_app(|r, _a, s| { + r.bank + .init_balance( + s, + &Addr::unchecked(DAO_ADDR), + vec![Coin { + denom: DENOM.to_string(), + amount: INIT_BALANCE, + }], + ) + .unwrap(); + r.bank + .init_balance( + s, + &Addr::unchecked(ADDR1), + vec![Coin { + denom: DENOM.to_string(), + amount: INIT_BALANCE, + }], + ) + .unwrap(); + r.bank + .init_balance( + s, + &Addr::unchecked(ADDR2), + vec![Coin { + denom: DENOM.to_string(), + amount: INIT_BALANCE, + }], + ) + .unwrap(); + }) +} + +fn instantiate_vault(app: &mut App, id: u64, msg: InstantiateMsg) -> Addr { + app.instantiate_contract(id, Addr::unchecked(DAO_ADDR), &msg, &[], "vault", None) + .unwrap() +} + +fn instantiate_lp_token( + app: &mut App, + id: u64, + msg: astroport_original::xastro_token::InstantiateMsg, +) -> Addr { + app.instantiate_contract(id, Addr::unchecked(DAO_ADDR), &msg, &[], "lp-token", None) + .unwrap() +} + +fn instantiate_vesting_lp(app: &mut App, id: u64, msg: vesting_lp::msg::InstantiateMsg) -> Addr { + app.instantiate_contract(id, Addr::unchecked(DAO_ADDR), &msg, &[], "vesting_lp", None) + .unwrap() +} + +fn set_vesting_token( + app: &mut App, + sender: Addr, + vesting_contract: Addr, + vesting_token: AssetInfo, +) { + app.execute_contract( + sender, + vesting_contract, + &vesting_base::msg::ExecuteMsg::SetVestingToken { vesting_token }, + &[], + ) + .unwrap(); +} + +fn bond_tokens( + app: &mut App, + contract_addr: Addr, + sender: &str, + amount: u128, + denom: &str, +) -> anyhow::Result { + app.execute_contract( + Addr::unchecked(sender), + contract_addr, + &ExecuteMsg::Bond {}, + &coins(amount, denom), + ) +} + +fn unbond_tokens( + app: &mut App, + contract_addr: Addr, + sender: &str, + amount: u128, +) -> anyhow::Result { + app.execute_contract( + Addr::unchecked(sender), + contract_addr, + &ExecuteMsg::Unbond { + amount: Uint128::new(amount), + }, + &[], + ) +} + +#[allow(clippy::too_many_arguments)] +fn update_config( + app: &mut App, + contract_addr: Addr, + sender: &str, + owner: String, + atom_vesting_lp_contract: String, + atom_cl_pool_contract: String, + usdc_vesting_lp_contract: String, + usdc_cl_pool_contract: String, + name: String, + description: String, +) -> anyhow::Result { + app.execute_contract( + Addr::unchecked(sender), + contract_addr, + &ExecuteMsg::UpdateConfig { + owner, + atom_vesting_lp_contract, + atom_cl_pool_contract, + usdc_vesting_lp_contract, + usdc_cl_pool_contract, + name, + description, + }, + &[], + ) +} + +fn get_voting_power_at_height( + app: &mut App, + contract_addr: Addr, + address: String, + height: Option, +) -> VotingPowerAtHeightResponse { + app.wrap() + .query_wasm_smart( + contract_addr, + &QueryMsg::VotingPowerAtHeight { address, height }, + ) + .unwrap() +} + +fn get_total_power_at_height( + app: &mut App, + contract_addr: Addr, + height: Option, +) -> TotalPowerAtHeightResponse { + app.wrap() + .query_wasm_smart(contract_addr, &QueryMsg::TotalPowerAtHeight { height }) + .unwrap() +} + +fn get_config(app: &mut App, contract_addr: Addr) -> Config { + app.wrap() + .query_wasm_smart(contract_addr, &QueryMsg::Config {}) + .unwrap() +} + +fn get_dao(app: &App, contract_addr: &Addr) -> String { + app.wrap() + .query_wasm_smart(contract_addr, &QueryMsg::Dao {}) + .unwrap() +} + +#[test] +fn test_instantiate() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + // Populated fields + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + atom_vesting_lp_contract: ATOM_VESTING_LP_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + usdc_vesting_lp_contract: USDC_VESTING_LP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + }, + ); + assert_eq!(get_dao(&app, &addr), String::from(DAO_ADDR)); + + // Non populated fields + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + atom_vesting_lp_contract: ATOM_VESTING_LP_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + usdc_vesting_lp_contract: USDC_VESTING_LP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + }, + ); + assert_eq!(get_dao(&app, &addr), String::from(DAO_ADDR)); +} + +#[test] +#[should_panic(expected = "Bonding is not available for this contract")] +fn test_bond() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + atom_vesting_lp_contract: ATOM_VESTING_LP_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + usdc_vesting_lp_contract: USDC_VESTING_LP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + }, + ); + + // Try and bond an invalid denom + bond_tokens(&mut app, addr, ADDR1, 100, DENOM).unwrap(); +} + +#[test] +#[should_panic(expected = "Direct unbonding is not available for this contract")] +fn test_unbond() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + atom_vesting_lp_contract: ATOM_VESTING_LP_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + usdc_vesting_lp_contract: USDC_VESTING_LP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + }, + ); + + unbond_tokens(&mut app, addr, ADDR1, 100).unwrap(); +} + +#[test] +#[should_panic(expected = "Unauthorized")] +fn test_update_config_unauthorized() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + atom_vesting_lp_contract: ATOM_VESTING_LP_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + usdc_vesting_lp_contract: USDC_VESTING_LP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + }, + ); + + // From ADDR2, so not owner + update_config( + &mut app, + addr, + ADDR2, + ADDR1.to_string(), + NEW_ATOM_VESTING_LP_ADDR.to_string(), + NEW_ATOM_CL_POOL_ADDR.to_string(), + NEW_USDC_VESTING_LP_ADDR.to_string(), + NEW_USDC_CL_POOL_ADDR.to_string(), + NEW_NAME.to_string(), + NEW_DESCRIPTION.to_string(), + ) + .unwrap(); +} + +#[test] +fn test_update_config() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + atom_vesting_lp_contract: ATOM_VESTING_LP_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + usdc_vesting_lp_contract: USDC_VESTING_LP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + }, + ); + + // Change owner, description, name, lp-vesting and cl pool contracts + update_config( + &mut app, + addr.clone(), + DAO_ADDR, + ADDR1.to_string(), + NEW_ATOM_VESTING_LP_ADDR.to_string(), + NEW_ATOM_CL_POOL_ADDR.to_string(), + NEW_USDC_VESTING_LP_ADDR.to_string(), + NEW_USDC_CL_POOL_ADDR.to_string(), + NEW_NAME.to_string(), + NEW_DESCRIPTION.to_string(), + ) + .unwrap(); + + let config = get_config(&mut app, addr); + assert_eq!( + Config { + name: NEW_NAME.to_string(), + description: NEW_DESCRIPTION.to_string(), + owner: Addr::unchecked(ADDR1), + atom_vesting_lp_contract: Addr::unchecked(NEW_ATOM_VESTING_LP_ADDR), + atom_cl_pool_contract: Addr::unchecked(NEW_ATOM_CL_POOL_ADDR), + usdc_vesting_lp_contract: Addr::unchecked(NEW_USDC_VESTING_LP_ADDR), + usdc_cl_pool_contract: Addr::unchecked(NEW_USDC_CL_POOL_ADDR), + }, + config + ); +} + +#[test] +#[should_panic(expected = "config description cannot be empty.")] +fn test_update_config_invalid_description() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + atom_vesting_lp_contract: ATOM_VESTING_LP_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + usdc_vesting_lp_contract: USDC_VESTING_LP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + }, + ); + + // Change name + update_config( + &mut app, + addr, + DAO_ADDR, + DAO_ADDR.to_string(), + ATOM_VESTING_LP_ADDR.to_string(), + ATOM_CL_POOL_ADDR.to_string(), + USDC_VESTING_LP_ADDR.to_string(), + USDC_CL_POOL_ADDR.to_string(), + NEW_NAME.to_string(), + String::from(""), + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "config name cannot be empty.")] +fn test_update_config_invalid_name() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + atom_vesting_lp_contract: ATOM_VESTING_LP_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + usdc_vesting_lp_contract: USDC_VESTING_LP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + }, + ); + + // Change description + update_config( + &mut app, + addr, + DAO_ADDR, + DAO_ADDR.to_string(), + ATOM_VESTING_LP_ADDR.to_string(), + ATOM_CL_POOL_ADDR.to_string(), + USDC_VESTING_LP_ADDR.to_string(), + USDC_CL_POOL_ADDR.to_string(), + String::from(""), + NEW_DESCRIPTION.to_string(), + ) + .unwrap(); +} + +#[test] +fn test_query_dao() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + atom_vesting_lp_contract: ATOM_VESTING_LP_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + usdc_vesting_lp_contract: USDC_VESTING_LP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + }, + ); + + let msg = QueryMsg::Dao {}; + let dao: Addr = app.wrap().query_wasm_smart(addr, &msg).unwrap(); + assert_eq!(dao, Addr::unchecked(DAO_ADDR)); +} + +#[test] +fn test_query_info() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + atom_vesting_lp_contract: ATOM_VESTING_LP_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + usdc_vesting_lp_contract: USDC_VESTING_LP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + }, + ); + + let msg = QueryMsg::Info {}; + let resp: InfoResponse = app.wrap().query_wasm_smart(addr, &msg).unwrap(); + assert_eq!( + resp.info.contract, + "crates.io:neutron-vesting-lp-vault-for-cl-pools" + ); +} + +#[test] +fn test_query_get_config() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + atom_vesting_lp_contract: ATOM_VESTING_LP_ADDR.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + usdc_vesting_lp_contract: USDC_VESTING_LP_ADDR.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + }, + ); + + let config = get_config(&mut app, addr); + assert_eq!( + config, + Config { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: Addr::unchecked(DAO_ADDR), + atom_vesting_lp_contract: Addr::unchecked(ATOM_VESTING_LP_ADDR), + atom_cl_pool_contract: Addr::unchecked(ATOM_CL_POOL_ADDR), + usdc_vesting_lp_contract: Addr::unchecked(USDC_VESTING_LP_ADDR), + usdc_cl_pool_contract: Addr::unchecked(USDC_CL_POOL_ADDR), + } + ) +} + +#[test] +fn test_voting_power_at_height() { + let mut app = mock_app(); + + let vesting_lp_id = app.store_code(vesting_lp_contract()); + let lp_token_id = app.store_code(lp_token_contract()); + + let atom_lp_token_address = instantiate_lp_token( + &mut app, + lp_token_id, + astroport_original::xastro_token::InstantiateMsg { + name: "atom".to_string(), + symbol: "atom-lp".to_string(), + decimals: 6, + initial_balances: vec![], + mint: None, + marketing: None, + }, + ); + let usdc_lp_token_address = instantiate_lp_token( + &mut app, + lp_token_id, + astroport_original::xastro_token::InstantiateMsg { + name: "usdc".to_string(), + symbol: "usdc-lp".to_string(), + decimals: 6, + initial_balances: vec![], + mint: None, + marketing: None, + }, + ); + + let atom_vesting_lp_addr = instantiate_vesting_lp( + &mut app, + vesting_lp_id, + vesting_lp::msg::InstantiateMsg { + owner: DAO_ADDR.to_string(), + vesting_managers: vec![], + token_info_manager: "manager".to_string(), + }, + ); + let usdc_vesting_lp_addr = instantiate_vesting_lp( + &mut app, + vesting_lp_id, + vesting_lp::msg::InstantiateMsg { + owner: DAO_ADDR.to_string(), + vesting_managers: vec![], + token_info_manager: "manager".to_string(), + }, + ); + + set_vesting_token( + &mut app, + Addr::unchecked("manager".to_string()), + usdc_vesting_lp_addr.clone(), + AssetInfo::Token { + contract_addr: usdc_lp_token_address, + }, + ); + set_vesting_token( + &mut app, + Addr::unchecked("manager".to_string()), + atom_vesting_lp_addr.clone(), + AssetInfo::Token { + contract_addr: atom_lp_token_address, + }, + ); + + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + atom_vesting_lp_contract: atom_vesting_lp_addr.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + usdc_vesting_lp_contract: usdc_vesting_lp_addr.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + }, + ); + + // describe test when lockdrop contract is implemented. use neutron vault tests as template. + let resp = get_voting_power_at_height(&mut app, addr, ADDR1.to_string(), None); + assert!(resp.power.is_zero()); +} + +#[test] +fn test_total_power_at_height() { + let mut app = mock_app(); + + let vesting_lp_id = app.store_code(vesting_lp_contract()); + let lp_token_id = app.store_code(lp_token_contract()); + + let atom_lp_token_address = instantiate_lp_token( + &mut app, + lp_token_id, + astroport_original::xastro_token::InstantiateMsg { + name: "atom".to_string(), + symbol: "atom-lp".to_string(), + decimals: 6, + initial_balances: vec![], + mint: None, + marketing: None, + }, + ); + let usdc_lp_token_address = instantiate_lp_token( + &mut app, + lp_token_id, + astroport_original::xastro_token::InstantiateMsg { + name: "usdc".to_string(), + symbol: "usdc-lp".to_string(), + decimals: 6, + initial_balances: vec![], + mint: None, + marketing: None, + }, + ); + + let atom_vesting_lp_addr = instantiate_vesting_lp( + &mut app, + vesting_lp_id, + vesting_lp::msg::InstantiateMsg { + owner: DAO_ADDR.to_string(), + vesting_managers: vec![], + token_info_manager: "manager".to_string(), + }, + ); + let usdc_vesting_lp_addr = instantiate_vesting_lp( + &mut app, + vesting_lp_id, + vesting_lp::msg::InstantiateMsg { + owner: DAO_ADDR.to_string(), + vesting_managers: vec![], + token_info_manager: "manager".to_string(), + }, + ); + + set_vesting_token( + &mut app, + Addr::unchecked("manager".to_string()), + usdc_vesting_lp_addr.clone(), + AssetInfo::Token { + contract_addr: usdc_lp_token_address, + }, + ); + set_vesting_token( + &mut app, + Addr::unchecked("manager".to_string()), + atom_vesting_lp_addr.clone(), + AssetInfo::Token { + contract_addr: atom_lp_token_address, + }, + ); + + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + name: NAME.to_string(), + description: DESCRIPTION.to_string(), + owner: DAO_ADDR.to_string(), + atom_vesting_lp_contract: atom_vesting_lp_addr.to_string(), + atom_cl_pool_contract: ATOM_CL_POOL_ADDR.to_string(), + usdc_vesting_lp_contract: usdc_vesting_lp_addr.to_string(), + usdc_cl_pool_contract: USDC_CL_POOL_ADDR.to_string(), + }, + ); + + // describe test when lockdrop contract is implemented. use neutron vault tests as template. + let resp = get_total_power_at_height(&mut app, addr, None); + assert!(resp.power.is_zero()); +} + +#[test] +pub fn test_migrate_update_version() { + let mut deps = mock_dependencies(); + cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); + + migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); + let version = cw2::get_contract_version(&deps.storage).unwrap(); + + assert_eq!(version.version, CONTRACT_VERSION); + assert_eq!(version.contract, CONTRACT_NAME); +} diff --git a/contracts/subdaos/cwd-subdao-timelock-single/src/testing/tests.rs b/contracts/subdaos/cwd-subdao-timelock-single/src/testing/tests.rs index 831e1c95..1076025f 100644 --- a/contracts/subdaos/cwd-subdao-timelock-single/src/testing/tests.rs +++ b/contracts/subdaos/cwd-subdao-timelock-single/src/testing/tests.rs @@ -565,7 +565,7 @@ fn test_query_proposals() { for i in 1..=100 { let query_msg = QueryMsg::Proposal { proposal_id: i }; let res = query(deps.as_ref(), mock_env(), query_msg).unwrap(); - let queried_prop: SingleChoiceProposal = from_json(res).unwrap(); + let queried_prop: SingleChoiceProposal = from_json(&res).unwrap(); let expected_prop = SingleChoiceProposal { id: i, msgs: vec![correct_proposal_msg()], @@ -579,7 +579,7 @@ fn test_query_proposals() { limit: None, }; let res = query(deps.as_ref(), mock_env(), query_msg).unwrap(); - let queried_props: ProposalListResponse = from_json(res).unwrap(); + let queried_props: ProposalListResponse = from_json(&res).unwrap(); for (p, i) in queried_props.proposals.iter().zip(1..) { let expected_prop = SingleChoiceProposal { id: i, @@ -595,7 +595,7 @@ fn test_query_proposals() { limit: Some(100), }; let res = query(deps.as_ref(), mock_env(), query_msg).unwrap(); - let queried_props: ProposalListResponse = from_json(res).unwrap(); + let queried_props: ProposalListResponse = from_json(&res).unwrap(); for (p, i) in queried_props.proposals.iter().zip(1..) { let expected_prop = SingleChoiceProposal { id: i, @@ -611,7 +611,7 @@ fn test_query_proposals() { limit: Some(10), }; let res = query(deps.as_ref(), mock_env(), query_msg).unwrap(); - let queried_props: ProposalListResponse = from_json(res).unwrap(); + let queried_props: ProposalListResponse = from_json(&res).unwrap(); for (p, i) in queried_props.proposals.iter().zip(1..) { let expected_prop = SingleChoiceProposal { id: i, @@ -627,7 +627,7 @@ fn test_query_proposals() { limit: None, }; let res = query(deps.as_ref(), mock_env(), query_msg).unwrap(); - let queried_props: ProposalListResponse = from_json(res).unwrap(); + let queried_props: ProposalListResponse = from_json(&res).unwrap(); for (p, i) in queried_props.proposals.iter().zip(51..) { let expected_prop = SingleChoiceProposal { id: i, @@ -643,7 +643,7 @@ fn test_query_proposals() { limit: None, }; let res = query(deps.as_ref(), mock_env(), query_msg).unwrap(); - let queried_props: ProposalListResponse = from_json(res).unwrap(); + let queried_props: ProposalListResponse = from_json(&res).unwrap(); for (p, i) in queried_props.proposals.iter().zip(91..) { let expected_prop = SingleChoiceProposal { id: i, diff --git a/contracts/tokenomics/distribution/src/contract.rs b/contracts/tokenomics/distribution/src/contract.rs index 233e548d..1a6cec4f 100644 --- a/contracts/tokenomics/distribution/src/contract.rs +++ b/contracts/tokenomics/distribution/src/contract.rs @@ -26,6 +26,8 @@ pub fn instantiate( _info: MessageInfo, msg: InstantiateMsg, ) -> StdResult { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + let config = Config { denom: msg.denom, main_dao_address: deps.api.addr_validate(&msg.main_dao_address)?, @@ -78,12 +80,6 @@ pub fn execute( } } -#[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)?; - Ok(Response::default()) -} - pub fn execute_pause( deps: DepsMut, env: Env, @@ -316,3 +312,9 @@ fn get_pause_info(deps: Deps, env: &Env) -> StdResult { None => PauseInfoResponse::Unpaused {}, }) } + +#[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)?; + Ok(Response::default()) +} diff --git a/contracts/tokenomics/reserve/Cargo.toml b/contracts/tokenomics/reserve/Cargo.toml index 8e0ea288..1b49a2eb 100644 --- a/contracts/tokenomics/reserve/Cargo.toml +++ b/contracts/tokenomics/reserve/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "Apache-2.0" name = "neutron-reserve" repository = "https://github.com/neutron/neutron-dao" -version = "0.1.2" +version = "0.2.0" [lib] crate-type = ["cdylib", "rlib"] @@ -16,10 +16,12 @@ backtraces = ["cosmwasm-std/backtraces"] cosmwasm-schema = { version = "1.3.0", default-features = false } cosmwasm-std = { version = "1.3.0" } cw-storage-plus = "1.1.0" -cwd-macros = { path = "../../../packages/cwd-macros" } -exec-control = { path = "../../../packages/exec-control" } neutron-sdk = "0.10.0" schemars = "0.8.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0" } cw2 = "1.1.0" +cwd-macros = { path = "../../../packages/cwd-macros" } +exec-control = { path = "../../../packages/exec-control" } +astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.5.0" } +cw20 = "0.13" diff --git a/contracts/tokenomics/reserve/schema/neutron-reserve.json b/contracts/tokenomics/reserve/schema/neutron-reserve.json index 374bce0a..1b1917e3 100644 --- a/contracts/tokenomics/reserve/schema/neutron-reserve.json +++ b/contracts/tokenomics/reserve/schema/neutron-reserve.json @@ -154,6 +154,64 @@ }, "additionalProperties": false }, + { + "description": "Processes either partial or full xyk->CL migration of contract's liquidity.", + "type": "object", + "required": [ + "migrate_from_xyk_to_cl" + ], + "properties": { + "migrate_from_xyk_to_cl": { + "type": "object", + "properties": { + "ntrn_atom_amount": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "ntrn_usdc_amount": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Callbacks; only callable by the contract itself.", + "type": "object", + "required": [ + "callback" + ], + "properties": { + "callback": { + "$ref": "#/definitions/CallbackMsg" + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -190,9 +248,137 @@ } ], "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "CallbackMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "migrate_liquidity_to_cl_pair" + ], + "properties": { + "migrate_liquidity_to_cl_pair": { + "type": "object", + "required": [ + "amount", + "cl_pair", + "ntrn_denom", + "paired_asset_denom", + "slippage_tolerance", + "xyk_lp_token", + "xyk_pair" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "cl_pair": { + "$ref": "#/definitions/Addr" + }, + "ntrn_denom": { + "type": "string" + }, + "paired_asset_denom": { + "type": "string" + }, + "slippage_tolerance": { + "$ref": "#/definitions/Decimal" + }, + "xyk_lp_token": { + "$ref": "#/definitions/Addr" + }, + "xyk_pair": { + "$ref": "#/definitions/Addr" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "provide_liquidity_to_cl_pair_after_withdrawal" + ], + "properties": { + "provide_liquidity_to_cl_pair_after_withdrawal": { + "type": "object", + "required": [ + "cl_pair", + "ntrn_denom", + "ntrn_init_balance", + "paired_asset_denom", + "paired_asset_init_balance", + "slippage_tolerance" + ], + "properties": { + "cl_pair": { + "$ref": "#/definitions/Addr" + }, + "ntrn_denom": { + "type": "string" + }, + "ntrn_init_balance": { + "$ref": "#/definitions/Uint128" + }, + "paired_asset_denom": { + "type": "string" + }, + "paired_asset_init_balance": { + "$ref": "#/definitions/Uint128" + }, + "slippage_tolerance": { + "$ref": "#/definitions/Decimal" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "post_migration_balances_check" + ], + "properties": { + "post_migration_balances_check": { + "type": "object", + "required": [ + "ntrn_denom", + "ntrn_init_balance", + "paired_asset_denom", + "paired_asset_init_balance" + ], + "properties": { + "ntrn_denom": { + "type": "string" + }, + "ntrn_init_balance": { + "$ref": "#/definitions/Uint128" + }, + "paired_asset_denom": { + "type": "string" + }, + "paired_asset_init_balance": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + } + ] + }, "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" + }, + "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" } } }, @@ -246,7 +432,49 @@ "migrate": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MigrateMsg", - "type": "object" + "type": "object", + "required": [ + "atom_denom", + "max_slippage", + "ntrn_atom_cl_pair", + "ntrn_atom_xyk_pair", + "ntrn_denom", + "ntrn_usdc_cl_pair", + "ntrn_usdc_xyk_pair", + "usdc_denom" + ], + "properties": { + "atom_denom": { + "type": "string" + }, + "max_slippage": { + "$ref": "#/definitions/Decimal" + }, + "ntrn_atom_cl_pair": { + "type": "string" + }, + "ntrn_atom_xyk_pair": { + "type": "string" + }, + "ntrn_denom": { + "type": "string" + }, + "ntrn_usdc_cl_pair": { + "type": "string" + }, + "ntrn_usdc_xyk_pair": { + "type": "string" + }, + "usdc_denom": { + "type": "string" + } + }, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } }, "sudo": null, "responses": { diff --git a/contracts/tokenomics/reserve/schema/raw/execute.json b/contracts/tokenomics/reserve/schema/raw/execute.json index 2d179055..abc72a7b 100644 --- a/contracts/tokenomics/reserve/schema/raw/execute.json +++ b/contracts/tokenomics/reserve/schema/raw/execute.json @@ -87,6 +87,64 @@ }, "additionalProperties": false }, + { + "description": "Processes either partial or full xyk->CL migration of contract's liquidity.", + "type": "object", + "required": [ + "migrate_from_xyk_to_cl" + ], + "properties": { + "migrate_from_xyk_to_cl": { + "type": "object", + "properties": { + "ntrn_atom_amount": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "ntrn_usdc_amount": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Callbacks; only callable by the contract itself.", + "type": "object", + "required": [ + "callback" + ], + "properties": { + "callback": { + "$ref": "#/definitions/CallbackMsg" + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -123,9 +181,137 @@ } ], "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "CallbackMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "migrate_liquidity_to_cl_pair" + ], + "properties": { + "migrate_liquidity_to_cl_pair": { + "type": "object", + "required": [ + "amount", + "cl_pair", + "ntrn_denom", + "paired_asset_denom", + "slippage_tolerance", + "xyk_lp_token", + "xyk_pair" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "cl_pair": { + "$ref": "#/definitions/Addr" + }, + "ntrn_denom": { + "type": "string" + }, + "paired_asset_denom": { + "type": "string" + }, + "slippage_tolerance": { + "$ref": "#/definitions/Decimal" + }, + "xyk_lp_token": { + "$ref": "#/definitions/Addr" + }, + "xyk_pair": { + "$ref": "#/definitions/Addr" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "provide_liquidity_to_cl_pair_after_withdrawal" + ], + "properties": { + "provide_liquidity_to_cl_pair_after_withdrawal": { + "type": "object", + "required": [ + "cl_pair", + "ntrn_denom", + "ntrn_init_balance", + "paired_asset_denom", + "paired_asset_init_balance", + "slippage_tolerance" + ], + "properties": { + "cl_pair": { + "$ref": "#/definitions/Addr" + }, + "ntrn_denom": { + "type": "string" + }, + "ntrn_init_balance": { + "$ref": "#/definitions/Uint128" + }, + "paired_asset_denom": { + "type": "string" + }, + "paired_asset_init_balance": { + "$ref": "#/definitions/Uint128" + }, + "slippage_tolerance": { + "$ref": "#/definitions/Decimal" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "post_migration_balances_check" + ], + "properties": { + "post_migration_balances_check": { + "type": "object", + "required": [ + "ntrn_denom", + "ntrn_init_balance", + "paired_asset_denom", + "paired_asset_init_balance" + ], + "properties": { + "ntrn_denom": { + "type": "string" + }, + "ntrn_init_balance": { + "$ref": "#/definitions/Uint128" + }, + "paired_asset_denom": { + "type": "string" + }, + "paired_asset_init_balance": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + } + ] + }, "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" } } } diff --git a/contracts/tokenomics/reserve/schema/raw/migrate.json b/contracts/tokenomics/reserve/schema/raw/migrate.json index 87b18ea7..ee8739f5 100644 --- a/contracts/tokenomics/reserve/schema/raw/migrate.json +++ b/contracts/tokenomics/reserve/schema/raw/migrate.json @@ -1,5 +1,47 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MigrateMsg", - "type": "object" + "type": "object", + "required": [ + "atom_denom", + "max_slippage", + "ntrn_atom_cl_pair", + "ntrn_atom_xyk_pair", + "ntrn_denom", + "ntrn_usdc_cl_pair", + "ntrn_usdc_xyk_pair", + "usdc_denom" + ], + "properties": { + "atom_denom": { + "type": "string" + }, + "max_slippage": { + "$ref": "#/definitions/Decimal" + }, + "ntrn_atom_cl_pair": { + "type": "string" + }, + "ntrn_atom_xyk_pair": { + "type": "string" + }, + "ntrn_denom": { + "type": "string" + }, + "ntrn_usdc_cl_pair": { + "type": "string" + }, + "ntrn_usdc_xyk_pair": { + "type": "string" + }, + "usdc_denom": { + "type": "string" + } + }, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } } diff --git a/contracts/tokenomics/reserve/src/contract.rs b/contracts/tokenomics/reserve/src/contract.rs index 1db74a0a..56a4a34b 100644 --- a/contracts/tokenomics/reserve/src/contract.rs +++ b/contracts/tokenomics/reserve/src/contract.rs @@ -1,26 +1,32 @@ use crate::distribution_params::DistributionParams; use crate::error::ContractError; +use crate::msg::{ + CallbackMsg, DistributeMsg, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StatsResponse, +}; +use crate::state::{ + Config, XykToClMigrationConfig, CONFIG, LAST_BURNED_COINS_AMOUNT, LAST_DISTRIBUTION_TIME, + PAUSED_UNTIL, TOTAL_DISTRIBUTED, TOTAL_RESERVED, XYK_TO_CL_MIGRATION_CONFIG, +}; +use crate::vesting::{ + get_burned_coins, safe_burned_coins_for_period, update_distribution_stats, vesting_function, +}; +use astroport::asset::{native_asset, PairInfo}; +use astroport::pair::{ + Cw20HookMsg as PairCw20HookMsg, ExecuteMsg as PairExecuteMsg, QueryMsg as PairQueryMsg, +}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - coins, to_json_binary, Addr, BankMsg, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, - Response, StdResult, Uint128, WasmMsg, + coins, to_json_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, + MessageInfo, Response, StdResult, Uint128, WasmMsg, }; use cw2::set_contract_version; +use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg}; use exec_control::pause::{ can_pause, can_unpause, validate_duration, PauseError, PauseInfoResponse, }; use neutron_sdk::bindings::query::NeutronQuery; -use crate::msg::{DistributeMsg, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StatsResponse}; -use crate::state::{ - Config, CONFIG, LAST_BURNED_COINS_AMOUNT, LAST_DISTRIBUTION_TIME, PAUSED_UNTIL, - TOTAL_DISTRIBUTED, TOTAL_RESERVED, -}; -use crate::vesting::{ - get_burned_coins, safe_burned_coins_for_period, update_distribution_stats, vesting_function, -}; - pub(crate) const CONTRACT_NAME: &str = "crates.io:neutron-reserve"; pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -35,6 +41,8 @@ pub fn instantiate( _info: MessageInfo, msg: InstantiateMsg, ) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + let config = Config { denom: msg.denom, min_period: msg.min_period, @@ -172,6 +180,18 @@ pub fn execute( ), ExecuteMsg::Pause { duration } => execute_pause(deps, env, info.sender, duration), ExecuteMsg::Unpause {} => execute_unpause(deps, info.sender), + ExecuteMsg::MigrateFromXykToCl { + slippage_tolerance, + ntrn_atom_amount, + ntrn_usdc_amount, + } => execute_migrate_from_xyk_to_cl( + deps, + env, + slippage_tolerance, + ntrn_atom_amount, + ntrn_usdc_amount, + ), + ExecuteMsg::Callback(msg) => _handle_callback(deps, env, info, msg), } } pub fn execute_transfer_ownership( @@ -298,6 +318,311 @@ pub fn execute_distribute( .add_attribute("distributed", to_distribute)) } +fn execute_migrate_from_xyk_to_cl( + deps: DepsMut, + env: Env, + slippage_tolerance: Option, + ntrn_atom_amount: Option, + ntrn_usdc_amount: Option, +) -> Result { + let migration_config: XykToClMigrationConfig = XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; + + // get pairs LP token addresses + let ntrn_atom_pair_info: PairInfo = deps.querier.query_wasm_smart( + migration_config.ntrn_atom_xyk_pair.clone(), + &PairQueryMsg::Pair {}, + )?; + let ntrn_usdc_pair_info: PairInfo = deps.querier.query_wasm_smart( + migration_config.ntrn_usdc_xyk_pair.clone(), + &PairQueryMsg::Pair {}, + )?; + + // query max available amounts to be withdrawn from both pairs + let max_available_ntrn_atom_amount = { + let resp: BalanceResponse = deps.querier.query_wasm_smart( + ntrn_atom_pair_info.liquidity_token.clone(), + &Cw20QueryMsg::Balance { + address: env.contract.address.to_string(), + }, + )?; + resp.balance + }; + let max_available_ntrn_usdc_amount = { + let resp: BalanceResponse = deps.querier.query_wasm_smart( + ntrn_usdc_pair_info.liquidity_token.clone(), + &Cw20QueryMsg::Balance { + address: env.contract.address.to_string(), + }, + )?; + resp.balance + }; + if max_available_ntrn_atom_amount.is_zero() && max_available_ntrn_usdc_amount.is_zero() { + return Err(ContractError::MigrationComplete {}); + } + + let ntrn_atom_amount = ntrn_atom_amount.unwrap_or(max_available_ntrn_atom_amount); + let ntrn_usdc_amount = ntrn_usdc_amount.unwrap_or(max_available_ntrn_usdc_amount); + let slippage_tolerance = slippage_tolerance.unwrap_or(migration_config.max_slippage); + + // validate parameters to the max available values + if ntrn_atom_amount.gt(&max_available_ntrn_atom_amount) { + return Err(ContractError::MigrationAmountUnavailable { + amount: ntrn_atom_amount, + max_amount: max_available_ntrn_atom_amount, + }); + } + if ntrn_usdc_amount.gt(&max_available_ntrn_usdc_amount) { + return Err(ContractError::MigrationAmountUnavailable { + amount: ntrn_usdc_amount, + max_amount: max_available_ntrn_usdc_amount, + }); + } + if slippage_tolerance.gt(&migration_config.max_slippage) { + return Err(ContractError::MigrationSlippageToBig { + slippage_tolerance, + max_slippage_tolerance: migration_config.max_slippage, + }); + } + + let mut resp = Response::default(); + if !ntrn_atom_amount.is_zero() { + resp = resp.add_message( + CallbackMsg::MigrateLiquidityToClPair { + ntrn_denom: migration_config.ntrn_denom.clone(), + amount: ntrn_atom_amount, + slippage_tolerance, + xyk_pair: migration_config.ntrn_atom_xyk_pair, + xyk_lp_token: ntrn_atom_pair_info.liquidity_token, + cl_pair: migration_config.ntrn_atom_cl_pair, + paired_asset_denom: migration_config.atom_denom, + } + .to_cosmos_msg(&env)?, + ); + } + if !ntrn_usdc_amount.is_zero() { + resp = resp.add_message( + CallbackMsg::MigrateLiquidityToClPair { + ntrn_denom: migration_config.ntrn_denom, + amount: ntrn_usdc_amount, + slippage_tolerance, + xyk_pair: migration_config.ntrn_usdc_xyk_pair, + xyk_lp_token: ntrn_usdc_pair_info.liquidity_token, + cl_pair: migration_config.ntrn_usdc_cl_pair, + paired_asset_denom: migration_config.usdc_denom, + } + .to_cosmos_msg(&env)?, + ); + } + + Ok(resp) +} + +fn _handle_callback( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: CallbackMsg, +) -> Result { + // Only the contract itself can call callbacks + if info.sender != env.contract.address { + return Err(ContractError::Unauthorized {}); + } + match msg { + CallbackMsg::MigrateLiquidityToClPair { + xyk_pair, + xyk_lp_token, + amount, + slippage_tolerance, + cl_pair, + ntrn_denom, + paired_asset_denom, + } => migrate_liquidity_to_cl_pair_callback( + deps, + env, + xyk_pair, + xyk_lp_token, + amount, + slippage_tolerance, + cl_pair, + ntrn_denom, + paired_asset_denom, + ), + CallbackMsg::ProvideLiquidityToClPairAfterWithdrawal { + ntrn_denom, + ntrn_init_balance, + paired_asset_denom, + paired_asset_init_balance, + cl_pair: cl_pair_address, + slippage_tolerance, + } => provide_liquidity_to_cl_pair_after_withdrawal_callback( + deps, + env, + ntrn_denom, + ntrn_init_balance, + paired_asset_denom, + paired_asset_init_balance, + cl_pair_address, + slippage_tolerance, + ), + CallbackMsg::PostMigrationBalancesCheck { + ntrn_denom, + ntrn_init_balance, + paired_asset_denom, + paired_asset_init_balance, + } => post_migration_balances_check_callback( + deps, + env, + ntrn_denom, + ntrn_init_balance, + paired_asset_denom, + paired_asset_init_balance, + ), + } +} + +#[allow(clippy::too_many_arguments)] +fn migrate_liquidity_to_cl_pair_callback( + deps: DepsMut, + env: Env, + xyk_pair: Addr, + xyk_lp_token: Addr, + amount: Uint128, + slippage_tolerance: Decimal, + cl_pair: Addr, + ntrn_denom: String, + paired_asset_denom: String, +) -> Result { + let ntrn_init_balance = deps + .querier + .query_balance(env.contract.address.to_string(), ntrn_denom.clone())? + .amount; + let paired_asset_init_balance = deps + .querier + .query_balance(env.contract.address.to_string(), paired_asset_denom.clone())? + .amount; + + let msgs: Vec = vec![ + // push message to withdraw liquidity from the xyk pair + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: xyk_lp_token.to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::Send { + contract: xyk_pair.to_string(), + amount, + msg: to_json_binary(&PairCw20HookMsg::WithdrawLiquidity { assets: vec![] })?, + })?, + funds: vec![], + }), + // push the next migration step as a callback message + CallbackMsg::ProvideLiquidityToClPairAfterWithdrawal { + ntrn_denom, + ntrn_init_balance, + paired_asset_denom, + paired_asset_init_balance, + cl_pair, + slippage_tolerance, + } + .to_cosmos_msg(&env)?, + ]; + + Ok(Response::default().add_messages(msgs)) +} + +#[allow(clippy::too_many_arguments)] +fn provide_liquidity_to_cl_pair_after_withdrawal_callback( + deps: DepsMut, + env: Env, + ntrn_denom: String, + ntrn_init_balance: Uint128, + paired_asset_denom: String, + paired_asset_init_balance: Uint128, + cl_pair_address: Addr, + slippage_tolerance: Decimal, +) -> Result { + let ntrn_balance_after_withdrawal = deps + .querier + .query_balance(env.contract.address.to_string(), ntrn_denom.clone())? + .amount; + let paired_asset_balance_after_withdrawal = deps + .querier + .query_balance(env.contract.address.to_string(), paired_asset_denom.clone())? + .amount; + + // calc amount of assets that's been withdrawn + let withdrawn_ntrn_amount = ntrn_balance_after_withdrawal.checked_sub(ntrn_init_balance)?; + let withdrawn_paired_asset_amount = + paired_asset_balance_after_withdrawal.checked_sub(paired_asset_init_balance)?; + + let config = CONFIG.load(deps.storage)?; + + let msgs: Vec = vec![ + // push message to provide liquidity to the CL pair + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: cl_pair_address.to_string(), + msg: to_json_binary(&PairExecuteMsg::ProvideLiquidity { + assets: vec![ + native_asset(ntrn_denom.clone(), withdrawn_ntrn_amount), + native_asset(paired_asset_denom.clone(), withdrawn_paired_asset_amount), + ], + slippage_tolerance: Some(slippage_tolerance), + auto_stake: None, + receiver: Option::from(config.main_dao_address.to_string()), + })?, + funds: vec![ + Coin::new(withdrawn_ntrn_amount.into(), ntrn_denom.clone()), + Coin::new( + withdrawn_paired_asset_amount.into(), + paired_asset_denom.clone(), + ), + ], + }), + // push the next migration step as a callback message + CallbackMsg::PostMigrationBalancesCheck { + ntrn_denom, + ntrn_init_balance, + paired_asset_denom, + paired_asset_init_balance, + } + .to_cosmos_msg(&env)?, + ]; + + Ok(Response::default().add_messages(msgs)) +} + +fn post_migration_balances_check_callback( + deps: DepsMut, + env: Env, + ntrn_denom: String, + ntrn_init_balance: Uint128, + paired_asset_denom: String, + paired_asset_init_balance: Uint128, +) -> Result { + let ntrn_balance = deps + .querier + .query_balance(env.contract.address.to_string(), ntrn_denom.clone())? + .amount; + let paired_asset_balance = deps + .querier + .query_balance(env.contract.address.to_string(), paired_asset_denom.clone())? + .amount; + + if !ntrn_balance.eq(&ntrn_init_balance) { + return Err(ContractError::MigrationBalancesMismatch { + denom: ntrn_denom, + initial_balance: ntrn_init_balance, + final_balance: ntrn_balance, + }); + } + if !paired_asset_balance.eq(&paired_asset_init_balance) { + return Err(ContractError::MigrationBalancesMismatch { + denom: paired_asset_denom, + initial_balance: paired_asset_init_balance, + final_balance: paired_asset_balance, + }); + } + + Ok(Response::default()) +} + //-------------------------------------------------------------------------------------------------- // Queries //-------------------------------------------------------------------------------------------------- @@ -363,9 +688,27 @@ pub fn create_distribution_response( Ok(resp) } +//-------------------------------------------------------------------------------------------------- +// Migration +//-------------------------------------------------------------------------------------------------- + +/// Withdraws liquidity from Astroport xyk pairs and provides it to the concentrated liquidity ones. #[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - // Set contract to version to latest +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { + XYK_TO_CL_MIGRATION_CONFIG.save( + deps.storage, + &XykToClMigrationConfig { + max_slippage: msg.max_slippage, + ntrn_denom: msg.ntrn_denom, + atom_denom: msg.atom_denom, + ntrn_atom_xyk_pair: deps.api.addr_validate(msg.ntrn_atom_xyk_pair.as_str())?, + ntrn_atom_cl_pair: deps.api.addr_validate(msg.ntrn_atom_cl_pair.as_str())?, + usdc_denom: msg.usdc_denom, + ntrn_usdc_xyk_pair: deps.api.addr_validate(msg.ntrn_usdc_xyk_pair.as_str())?, + ntrn_usdc_cl_pair: deps.api.addr_validate(msg.ntrn_usdc_cl_pair.as_str())?, + }, + )?; + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; Ok(Response::default()) } diff --git a/contracts/tokenomics/reserve/src/error.rs b/contracts/tokenomics/reserve/src/error.rs index 2bb2f131..366fedad 100644 --- a/contracts/tokenomics/reserve/src/error.rs +++ b/contracts/tokenomics/reserve/src/error.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{OverflowError, StdError}; +use cosmwasm_std::{Decimal, OverflowError, StdError, Uint128}; use exec_control::pause::PauseError; use thiserror::Error; @@ -31,6 +31,35 @@ pub enum ContractError { #[error("no coins were burned, nothing to distribute")] NoBurnedCoins {}, + #[error("Unknown reply ID {reply_id}")] + UnkownReplyID { reply_id: u64 }, + + #[error("{denom} balance {final_balance} after liquidity withdrawal and providing doesn't match the initial one {initial_balance}")] + MigrationBalancesMismatch { + denom: String, + initial_balance: Uint128, + final_balance: Uint128, + }, + + #[error( + "Amount to be migrated is greater that the max available amount: {amount} > {max_amount}" + )] + MigrationAmountUnavailable { + amount: Uint128, + max_amount: Uint128, + }, + + #[error( + "Provided slippage tolerance {slippage_tolerance} is more than the max allowed {max_slippage_tolerance}" + )] + MigrationSlippageToBig { + slippage_tolerance: Decimal, + max_slippage_tolerance: Decimal, + }, + + #[error("Migration from xyk pairs to CL ones is complete: nothing to migrate")] + MigrationComplete {}, + #[error("Overflow")] OverflowError(#[from] OverflowError), } diff --git a/contracts/tokenomics/reserve/src/msg.rs b/contracts/tokenomics/reserve/src/msg.rs index e9c54b33..ba729c7d 100644 --- a/contracts/tokenomics/reserve/src/msg.rs +++ b/contracts/tokenomics/reserve/src/msg.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Decimal, Uint128}; +use cosmwasm_std::{to_json_binary, Addr, CosmosMsg, Decimal, Env, StdResult, Uint128, WasmMsg}; use cwd_macros::{pausable, pausable_query}; use exec_control::pause::PauseInfoResponse; use schemars::JsonSchema; @@ -44,6 +44,56 @@ pub enum ExecuteMsg { security_dao_address: Option, vesting_denominator: Option, }, + + /// Processes either partial or full xyk->CL migration of contract's liquidity. + MigrateFromXykToCl { + slippage_tolerance: Option, + ntrn_atom_amount: Option, + ntrn_usdc_amount: Option, + }, + + /// Callbacks; only callable by the contract itself. + Callback(CallbackMsg), +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CallbackMsg { + MigrateLiquidityToClPair { + xyk_pair: Addr, + xyk_lp_token: Addr, + amount: Uint128, + slippage_tolerance: Decimal, + cl_pair: Addr, + ntrn_denom: String, + paired_asset_denom: String, + }, + ProvideLiquidityToClPairAfterWithdrawal { + ntrn_denom: String, + ntrn_init_balance: Uint128, + paired_asset_denom: String, + paired_asset_init_balance: Uint128, + cl_pair: Addr, + slippage_tolerance: Decimal, + }, + PostMigrationBalancesCheck { + ntrn_denom: String, + ntrn_init_balance: Uint128, + paired_asset_denom: String, + paired_asset_init_balance: Uint128, + }, +} + +// Modified from +// https://github.com/CosmWasm/cosmwasm-plus/blob/v0.2.3/packages/cw20/src/receiver.rs#L15 +impl CallbackMsg { + pub fn to_cosmos_msg(self, env: &Env) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + msg: to_json_binary(&ExecuteMsg::Callback(self))?, + funds: vec![], + })) + } } #[pausable_query] @@ -77,4 +127,13 @@ pub enum DistributeMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub struct MigrateMsg {} +pub struct MigrateMsg { + pub max_slippage: Decimal, + pub ntrn_denom: String, + pub atom_denom: String, + pub ntrn_atom_xyk_pair: String, + pub ntrn_atom_cl_pair: String, + pub usdc_denom: String, + pub ntrn_usdc_xyk_pair: String, + pub ntrn_usdc_cl_pair: String, +} diff --git a/contracts/tokenomics/reserve/src/state.rs b/contracts/tokenomics/reserve/src/state.rs index 689ff150..b1394e15 100644 --- a/contracts/tokenomics/reserve/src/state.rs +++ b/contracts/tokenomics/reserve/src/state.rs @@ -44,6 +44,20 @@ impl Config { } } +/// Config for xyk->CL liquidity migration. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct XykToClMigrationConfig { + /// The maximum allowed slippage tolerance for xyk to CL liquidity migration calls. + pub max_slippage: Decimal, + pub ntrn_denom: String, + pub atom_denom: String, + pub ntrn_atom_xyk_pair: Addr, + pub ntrn_atom_cl_pair: Addr, + pub usdc_denom: String, + pub ntrn_usdc_xyk_pair: Addr, + pub ntrn_usdc_cl_pair: Addr, +} + pub const TOTAL_DISTRIBUTED: Item = Item::new("total_distributed"); pub const TOTAL_RESERVED: Item = Item::new("total_reserved"); @@ -55,6 +69,9 @@ pub const CONFIG: Item = Item::new("config"); /// The height the contract is paused until. If it's None, the contract is not paused. pub const PAUSED_UNTIL: Item> = Item::new("paused_until"); +pub const XYK_TO_CL_MIGRATION_CONFIG: Item = + Item::new("xyk_to_cl_migration_config"); + #[cfg(test)] mod tests { use super::Config; diff --git a/packages/cw-denom/src/lib.rs b/packages/cw-denom/src/lib.rs index ad11e0c0..97c16cec 100644 --- a/packages/cw-denom/src/lib.rs +++ b/packages/cw-denom/src/lib.rs @@ -244,7 +244,7 @@ mod tests { err, DenomError::InvalidCw20 { err: StdError::GenericErr { - msg: format!("Querier system error: No such contract: {}", CW20_ADDR) + msg: format!("Querier system error: No such contract: {}", CW20_ADDR), } } ) diff --git a/packages/neutron-lockdrop-vault-for-cl-pools/Cargo.toml b/packages/neutron-lockdrop-vault-for-cl-pools/Cargo.toml new file mode 100644 index 00000000..4a386738 --- /dev/null +++ b/packages/neutron-lockdrop-vault-for-cl-pools/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "neutron-lockdrop-vault-for-cl-pools" +version = "0.1.0" +authors = ["Sergei Sotnikov "] +edition = "2021" +license = "Apache-2.0" +repository = "https://github.com/neutron/neutron-dao" + +[dependencies] +cosmwasm-schema = {version = "1.3.0"} +cwd-interface = {path = "../cwd-interface"} +cosmwasm-std = { version = "1.3.0" } +cwd-macros = { path = "../cwd-macros" } +schemars = "0.8.8" +serde = { version = "1.0.175", default-features = false, features = ["derive"] } +thiserror = { version = "1.0" } +astroport-periphery = { package="astroport-periphery", git = "https://github.com/neutron-org/neutron-tge-contracts.git", rev = "e306308dd23d567399c15d899f295a910ede945b" } +neutron-voting-power = { path = "../neutron-voting-power" } +astroport = { package="astroport", git = "https://github.com/neutron-org/neutron-tge-contracts.git", rev = "e306308dd23d567399c15d899f295a910ede945b" } diff --git a/packages/neutron-lockdrop-vault-for-cl-pools/src/error.rs b/packages/neutron-lockdrop-vault-for-cl-pools/src/error.rs new file mode 100644 index 00000000..bfd14f61 --- /dev/null +++ b/packages/neutron-lockdrop-vault-for-cl-pools/src/error.rs @@ -0,0 +1,28 @@ +use cosmwasm_std::{OverflowError, StdError}; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Overflow {0}")] + Overflow(#[from] OverflowError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Bonding is not available for this contract")] + BondingDisabled {}, + + #[error("Direct unbonding is not available for this contract")] + DirectUnbondingDisabled {}, + + #[error("config name cannot be empty.")] + NameIsEmpty {}, + + #[error("config description cannot be empty.")] + DescriptionIsEmpty {}, +} + +pub type ContractResult = Result; diff --git a/packages/neutron-lockdrop-vault-for-cl-pools/src/lib.rs b/packages/neutron-lockdrop-vault-for-cl-pools/src/lib.rs new file mode 100644 index 00000000..428eca92 --- /dev/null +++ b/packages/neutron-lockdrop-vault-for-cl-pools/src/lib.rs @@ -0,0 +1,4 @@ +pub mod error; +pub mod msg; +pub mod types; +pub mod voting_power; diff --git a/packages/neutron-lockdrop-vault-for-cl-pools/src/msg.rs b/packages/neutron-lockdrop-vault-for-cl-pools/src/msg.rs new file mode 100644 index 00000000..4c4b787b --- /dev/null +++ b/packages/neutron-lockdrop-vault-for-cl-pools/src/msg.rs @@ -0,0 +1,51 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Uint128}; +use cwd_interface::voting::{ + BondingStatusResponse, InfoResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, +}; +use cwd_macros::{info_query, voting_query, voting_vault, voting_vault_query}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] +pub struct InstantiateMsg { + /// Name contains the vault name which is used to ease the vault's recognition. + pub name: String, + /// Description contains information that characterizes the vault. + pub description: String, + /// The lockdrop contract behind the vault. + pub lockdrop_contract: String, + /// The USDC/NTRN CL pool contract. + pub usdc_cl_pool_contract: String, + /// The ATOM/NTRN CL pool oracle contract. + pub atom_cl_pool_contract: String, + /// Owner can update all configs including changing the owner. This will generally be a DAO. + pub owner: String, +} + +#[voting_vault] +#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + UpdateConfig { + owner: Option, + lockdrop_contract: Option, + usdc_cl_pool_contract: Option, + atom_cl_pool_contract: Option, + name: Option, + description: Option, + }, +} + +#[voting_query] +#[voting_vault_query] +#[info_query] +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(crate::types::Config)] + Config {}, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MigrateMsg {} diff --git a/packages/neutron-lockdrop-vault-for-cl-pools/src/types.rs b/packages/neutron-lockdrop-vault-for-cl-pools/src/types.rs new file mode 100644 index 00000000..e6c48440 --- /dev/null +++ b/packages/neutron-lockdrop-vault-for-cl-pools/src/types.rs @@ -0,0 +1,73 @@ +use crate::error::ContractError; +use cosmwasm_std::Addr; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +pub struct Config { + pub name: String, + pub description: String, + pub lockdrop_contract: Addr, + pub usdc_cl_pool_contract: Addr, + pub atom_cl_pool_contract: Addr, + pub owner: Addr, +} + +impl Config { + /// checks whether the config fields are valid. + pub fn validate(&self) -> Result<(), ContractError> { + if self.name.is_empty() { + return Err(ContractError::NameIsEmpty {}); + }; + if self.description.is_empty() { + return Err(ContractError::DescriptionIsEmpty {}); + }; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::Config; + use crate::error::ContractError; + use cosmwasm_std::Addr; + + #[test] + fn test_config_validate() { + let cfg_ok = Config { + name: String::from("name"), + description: String::from("description"), + lockdrop_contract: Addr::unchecked("lockdrop_contract"), + usdc_cl_pool_contract: Addr::unchecked("cl_pool_usdc_contract"), + atom_cl_pool_contract: Addr::unchecked("cl_pool_atom_contract"), + owner: Addr::unchecked("owner"), + }; + assert_eq!(cfg_ok.validate(), Ok(())); + + let cfg_empty_name = Config { + name: String::from(""), + description: String::from("description"), + lockdrop_contract: Addr::unchecked("lockdrop_contract"), + usdc_cl_pool_contract: Addr::unchecked("cl_pool_usdc_contract"), + atom_cl_pool_contract: Addr::unchecked("cl_pool_atom_contract"), + owner: Addr::unchecked("owner"), + }; + assert_eq!( + cfg_empty_name.validate(), + Err(ContractError::NameIsEmpty {}) + ); + + let cfg_empty_description = Config { + name: String::from("name"), + description: String::from(""), + lockdrop_contract: Addr::unchecked("lockdrop_contract"), + usdc_cl_pool_contract: Addr::unchecked("cl_pool_usdc_contract"), + atom_cl_pool_contract: Addr::unchecked("cl_pool_atom_contract"), + owner: Addr::unchecked("owner"), + }; + assert_eq!( + cfg_empty_description.validate(), + Err(ContractError::DescriptionIsEmpty {}) + ); + } +} diff --git a/packages/neutron-lockdrop-vault-for-cl-pools/src/voting_power.rs b/packages/neutron-lockdrop-vault-for-cl-pools/src/voting_power.rs new file mode 100644 index 00000000..7ddcb919 --- /dev/null +++ b/packages/neutron-lockdrop-vault-for-cl-pools/src/voting_power.rs @@ -0,0 +1,69 @@ +use astroport_periphery::lockdrop::{PoolType, QueryMsg as LockdropQueryMsg}; +use cosmwasm_std::{Addr, Deps, StdResult, Uint128}; +use neutron_voting_power::voting_power::voting_power_from_lp_tokens; +use serde::Serialize; + +pub fn get_voting_power_for_address( + deps: Deps, + lockdrop_contract: &Addr, + pool_contract: &Addr, + pool_type: PoolType, + address: String, + height: u64, +) -> StdResult { + get_voting_power( + deps, + lockdrop_contract, + pool_contract, + &LockdropQueryMsg::QueryUserLockupTotalAtHeight { + pool_type, + user_address: address, + height, + }, + height, + ) +} + +pub fn get_voting_power_total( + deps: Deps, + lp_contract: &Addr, + pool_contract: &Addr, + pool_type: PoolType, + height: u64, +) -> StdResult { + get_voting_power( + deps, + lp_contract, + pool_contract, + &LockdropQueryMsg::QueryLockupTotalAtHeight { pool_type, height }, + height, + ) +} + +pub fn get_voting_power( + deps: Deps, + lockdrop_contract: &Addr, + pool_contract: &Addr, + msg: &impl Serialize, + height: u64, +) -> StdResult { + let lp_tokens: Option = deps.querier.query_wasm_smart(lockdrop_contract, msg)?; + + let pair_info: astroport::asset::PairInfo = deps.querier.query_wasm_smart( + pool_contract, + &astroport::pair_concentrated::QueryMsg::Pair {}, + )?; + + let lp_total_supply: Uint128 = deps.querier.query_wasm_smart( + pair_info.liquidity_token, + &astroport::xastro_token::QueryMsg::TotalSupplyAt { block: height }, + )?; + + voting_power_from_lp_tokens( + deps, + lp_tokens.unwrap_or_default(), + lp_total_supply, + pool_contract, + height, + ) +} diff --git a/packages/neutron-lockdrop-vault/src/error.rs b/packages/neutron-lockdrop-vault/src/error.rs index 2f42b205..bfd14f61 100644 --- a/packages/neutron-lockdrop-vault/src/error.rs +++ b/packages/neutron-lockdrop-vault/src/error.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::StdError; +use cosmwasm_std::{OverflowError, StdError}; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -6,6 +6,9 @@ pub enum ContractError { #[error("{0}")] Std(#[from] StdError), + #[error("Overflow {0}")] + Overflow(#[from] OverflowError), + #[error("Unauthorized")] Unauthorized {}, diff --git a/packages/neutron-vesting-lp-vault-for-cl-pools/Cargo.toml b/packages/neutron-vesting-lp-vault-for-cl-pools/Cargo.toml new file mode 100644 index 00000000..705e8812 --- /dev/null +++ b/packages/neutron-vesting-lp-vault-for-cl-pools/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "neutron-vesting-lp-vault-for-cl-pools" +version = "0.1.0" +authors = ["Sergei Sotnikov ", "Murad Karammaev "] +edition = "2021" +license = "Apache-2.0" +repository = "https://github.com/neutron/neutron-dao" + +[dependencies] +cosmwasm-schema = {version = "1.3.0"} +cwd-interface = {path = "../cwd-interface"} +serde = { version = "1.0.175", default-features = false, features = ["derive"] } +schemars = "0.8.8" +cwd-macros = {path = "../cwd-macros"} +cosmwasm-std = { version = "1.3.0" } +thiserror = { version = "1.0" } diff --git a/packages/neutron-vesting-lp-vault-for-cl-pools/src/error.rs b/packages/neutron-vesting-lp-vault-for-cl-pools/src/error.rs new file mode 100644 index 00000000..3d0bd284 --- /dev/null +++ b/packages/neutron-vesting-lp-vault-for-cl-pools/src/error.rs @@ -0,0 +1,28 @@ +use cosmwasm_std::{OverflowError, StdError}; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Bonding is not available for this contract")] + BondingDisabled {}, + + #[error("Direct unbonding is not available for this contract")] + DirectUnbondingDisabled {}, + + #[error("config name cannot be empty.")] + NameIsEmpty {}, + + #[error("config description cannot be empty.")] + DescriptionIsEmpty {}, + + #[error("{0}")] + OverflowError(#[from] OverflowError), +} + +pub type ContractResult = Result; diff --git a/packages/neutron-vesting-lp-vault-for-cl-pools/src/lib.rs b/packages/neutron-vesting-lp-vault-for-cl-pools/src/lib.rs new file mode 100644 index 00000000..691626ce --- /dev/null +++ b/packages/neutron-vesting-lp-vault-for-cl-pools/src/lib.rs @@ -0,0 +1,3 @@ +pub mod error; +pub mod msg; +pub mod types; diff --git a/packages/neutron-vesting-lp-vault-for-cl-pools/src/msg.rs b/packages/neutron-vesting-lp-vault-for-cl-pools/src/msg.rs new file mode 100644 index 00000000..b9a79c4e --- /dev/null +++ b/packages/neutron-vesting-lp-vault-for-cl-pools/src/msg.rs @@ -0,0 +1,54 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Uint128}; +use cwd_interface::voting::{ + BondingStatusResponse, InfoResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, +}; +use cwd_macros::{info_query, voting_query, voting_vault, voting_vault_query}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] +pub struct InstantiateMsg { + /// Name contains the vault name which is used to ease the vault's recognition. + pub name: String, + /// Description contains information that characterizes the vault. + pub description: String, + /// The ATOM Vesting LP contract behind the vault. + pub atom_vesting_lp_contract: String, + /// The ATOM/NTRN CL pool contract. + pub atom_cl_pool_contract: String, + /// The USDC Vesting LP contract behind the vault. + pub usdc_vesting_lp_contract: String, + /// The USDC/NTRN CL pool oracle contract. + pub usdc_cl_pool_contract: String, + /// Owner can update all configs including changing the owner. This will generally be a DAO. + pub owner: String, +} + +#[voting_vault] +#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + UpdateConfig { + owner: String, + atom_vesting_lp_contract: String, + atom_cl_pool_contract: String, + usdc_vesting_lp_contract: String, + usdc_cl_pool_contract: String, + name: String, + description: String, + }, +} + +#[voting_query] +#[voting_vault_query] +#[info_query] +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(crate::types::Config)] + Config {}, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MigrateMsg {} diff --git a/packages/neutron-vesting-lp-vault-for-cl-pools/src/types.rs b/packages/neutron-vesting-lp-vault-for-cl-pools/src/types.rs new file mode 100644 index 00000000..46fe9972 --- /dev/null +++ b/packages/neutron-vesting-lp-vault-for-cl-pools/src/types.rs @@ -0,0 +1,76 @@ +use crate::error::{ContractError, ContractResult}; +use cosmwasm_std::Addr; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +pub struct Config { + pub name: String, + pub description: String, + pub atom_vesting_lp_contract: Addr, + pub atom_cl_pool_contract: Addr, + pub usdc_vesting_lp_contract: Addr, + pub usdc_cl_pool_contract: Addr, + pub owner: Addr, +} + +impl Config { + /// checks whether the config fields are valid. + pub fn validate(&self) -> ContractResult<()> { + if self.name.is_empty() { + return Err(ContractError::NameIsEmpty {}); + } + if self.description.is_empty() { + return Err(ContractError::DescriptionIsEmpty {}); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::{error::ContractError, types::Config}; + use cosmwasm_std::Addr; + + #[test] + fn valid_config() { + let cfg = Config { + name: String::from("name"), + description: String::from("description"), + atom_vesting_lp_contract: Addr::unchecked("atom_vesting_lp_contract"), + atom_cl_pool_contract: Addr::unchecked("atom_cl_pool_contract"), + usdc_vesting_lp_contract: Addr::unchecked("usdc_vesting_lp_contract"), + usdc_cl_pool_contract: Addr::unchecked("usdc_cl_pool_contract"), + owner: Addr::unchecked("owner"), + }; + assert!(cfg.validate().is_ok()); + } + + #[test] + fn empty_name() { + let cfg = Config { + name: String::from(""), + description: String::from("description"), + atom_vesting_lp_contract: Addr::unchecked("atom_vesting_lp_contract"), + atom_cl_pool_contract: Addr::unchecked("atom_cl_pool_contract"), + usdc_vesting_lp_contract: Addr::unchecked("usdc_vesting_lp_contract"), + usdc_cl_pool_contract: Addr::unchecked("usdc_cl_pool_contract"), + owner: Addr::unchecked("owner"), + }; + assert_eq!(cfg.validate(), Err(ContractError::NameIsEmpty {})); + } + + #[test] + fn empty_description() { + let cfg = Config { + name: String::from("name"), + description: String::from(""), + atom_vesting_lp_contract: Addr::unchecked("atom_vesting_lp_contract"), + atom_cl_pool_contract: Addr::unchecked("atom_cl_pool_contract"), + usdc_vesting_lp_contract: Addr::unchecked("usdc_vesting_lp_contract"), + usdc_cl_pool_contract: Addr::unchecked("usdc_cl_pool_contract"), + owner: Addr::unchecked("owner"), + }; + assert_eq!(cfg.validate(), Err(ContractError::DescriptionIsEmpty {})); + } +} diff --git a/packages/neutron-voting-power/Cargo.toml b/packages/neutron-voting-power/Cargo.toml new file mode 100644 index 00000000..de4f5172 --- /dev/null +++ b/packages/neutron-voting-power/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "neutron-voting-power" +version = "0.1.0" +authors = ["Murad Karammaev "] +edition = "2021" +license = "Apache-2.0" +repository = "https://github.com/neutron/neutron-dao" + +[dependencies] +astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.8.0" } +cosmwasm-std = { version = "1.3.0" } \ No newline at end of file diff --git a/packages/neutron-voting-power/src/lib.rs b/packages/neutron-voting-power/src/lib.rs new file mode 100644 index 00000000..2febfaf6 --- /dev/null +++ b/packages/neutron-voting-power/src/lib.rs @@ -0,0 +1 @@ +pub mod voting_power; diff --git a/packages/neutron-voting-power/src/voting_power.rs b/packages/neutron-voting-power/src/voting_power.rs new file mode 100644 index 00000000..6fb019c3 --- /dev/null +++ b/packages/neutron-voting-power/src/voting_power.rs @@ -0,0 +1,34 @@ +use cosmwasm_std::{Addr, Deps, StdResult, Uint128, Uint64}; + +pub fn voting_power_from_lp_tokens( + deps: Deps, + lp_tokens: Uint128, + total_lp_tokens: Uint128, + cl_pool: &Addr, + height: u64, +) -> StdResult { + if lp_tokens.is_zero() { + Ok(Uint128::zero()) + } else { + let balance_resp: Option = deps.querier.query_wasm_smart( + cl_pool, + &astroport::pair_concentrated::QueryMsg::AssetBalanceAt { + asset_info: astroport::asset::AssetInfo::NativeToken { + denom: "untrn".to_string(), + }, + block_height: Uint64::from(height), + }, + )?; + let ntrn_balance_in_pool = if let Some(ntrn_balance) = balance_resp { + ntrn_balance + } else { + return Ok(Uint128::zero()); + }; + + if ntrn_balance_in_pool.is_zero() { + return Ok(Uint128::zero()); + } + + Ok(lp_tokens.multiply_ratio(ntrn_balance_in_pool, total_lp_tokens)) + } +}