From 4a3d380394bb019b79a97503b59c06c46929d83a Mon Sep 17 00:00:00 2001 From: pr0n00gler Date: Wed, 18 Oct 2023 23:41:06 +0300 Subject: [PATCH 1/9] PCL migration --- Cargo.lock | 282 ++++++- .../lockdrop-vault-for-cl-pools/.cargo/config | 4 + .../lockdrop-vault-for-cl-pools/Cargo.toml | 36 + .../lockdrop-vault-for-cl-pools/README.md | 3 + .../examples/schema.rs | 31 + .../schema/execute_msg.json | 94 +++ .../schema/instantiate_msg.json | 39 + .../schema/query_msg.json | 181 +++++ .../src/contract.rs | 293 +++++++ .../lockdrop-vault-for-cl-pools/src/lib.rs | 5 + .../lockdrop-vault-for-cl-pools/src/state.rs | 6 + .../lockdrop-vault-for-cl-pools/src/tests.rs | 444 +++++++++++ .../.cargo/config | 4 + .../vesting-lp-vault-for-cl-pools/Cargo.toml | 39 + .../vesting-lp-vault-for-cl-pools/README.md | 3 + .../examples/schema.rs | 34 + .../schema/bonding_status_response.json | 29 + .../schema/dao_response.json | 6 + .../schema/execute_msg.json | 88 +++ .../schema/get_config_response.json | 43 ++ .../schema/info_response.json | 33 + .../schema/instantiate_msg.json | 44 ++ .../schema/migrate_msg.json | 5 + .../schema/query_msg.json | 181 +++++ .../total_power_at_height_response.json | 25 + .../voting_power_at_height_response.json | 25 + .../src/contract.rs | 301 ++++++++ .../vesting-lp-vault-for-cl-pools/src/lib.rs | 5 + .../src/state.rs | 6 + .../src/tests.rs | 727 ++++++++++++++++++ contracts/tokenomics/reserve/Cargo.toml | 17 +- .../reserve/schema/neutron-reserve.json | 186 +++++ .../reserve/schema/raw/execute.json | 186 +++++ contracts/tokenomics/reserve/src/contract.rs | 375 ++++++++- contracts/tokenomics/reserve/src/error.rs | 31 +- contracts/tokenomics/reserve/src/msg.rs | 63 +- contracts/tokenomics/reserve/src/state.rs | 17 + .../Cargo.toml | 19 + .../src/error.rs | 25 + .../src/lib.rs | 4 + .../src/msg.rs | 51 ++ .../src/types.rs | 73 ++ .../src/voting_power.rs | 69 ++ .../Cargo.toml | 16 + .../src/error.rs | 28 + .../src/lib.rs | 3 + .../src/msg.rs | 54 ++ .../src/types.rs | 76 ++ packages/neutron-voting-power/Cargo.toml | 11 + packages/neutron-voting-power/src/lib.rs | 1 + .../neutron-voting-power/src/voting_power.rs | 34 + 51 files changed, 4309 insertions(+), 46 deletions(-) create mode 100644 contracts/dao/voting/lockdrop-vault-for-cl-pools/.cargo/config create mode 100644 contracts/dao/voting/lockdrop-vault-for-cl-pools/Cargo.toml create mode 100644 contracts/dao/voting/lockdrop-vault-for-cl-pools/README.md create mode 100644 contracts/dao/voting/lockdrop-vault-for-cl-pools/examples/schema.rs create mode 100644 contracts/dao/voting/lockdrop-vault-for-cl-pools/schema/execute_msg.json create mode 100644 contracts/dao/voting/lockdrop-vault-for-cl-pools/schema/instantiate_msg.json create mode 100644 contracts/dao/voting/lockdrop-vault-for-cl-pools/schema/query_msg.json create mode 100644 contracts/dao/voting/lockdrop-vault-for-cl-pools/src/contract.rs create mode 100644 contracts/dao/voting/lockdrop-vault-for-cl-pools/src/lib.rs create mode 100644 contracts/dao/voting/lockdrop-vault-for-cl-pools/src/state.rs create mode 100644 contracts/dao/voting/lockdrop-vault-for-cl-pools/src/tests.rs create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/.cargo/config create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/Cargo.toml create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/README.md create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/examples/schema.rs create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/bonding_status_response.json create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/dao_response.json create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/execute_msg.json create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/get_config_response.json create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/info_response.json create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/instantiate_msg.json create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/migrate_msg.json create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/query_msg.json create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/total_power_at_height_response.json create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/schema/voting_power_at_height_response.json create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/src/contract.rs create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/src/lib.rs create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/src/state.rs create mode 100644 contracts/dao/voting/vesting-lp-vault-for-cl-pools/src/tests.rs create mode 100644 packages/neutron-lockdrop-vault-for-cl-pools/Cargo.toml create mode 100644 packages/neutron-lockdrop-vault-for-cl-pools/src/error.rs create mode 100644 packages/neutron-lockdrop-vault-for-cl-pools/src/lib.rs create mode 100644 packages/neutron-lockdrop-vault-for-cl-pools/src/msg.rs create mode 100644 packages/neutron-lockdrop-vault-for-cl-pools/src/types.rs create mode 100644 packages/neutron-lockdrop-vault-for-cl-pools/src/voting_power.rs create mode 100644 packages/neutron-vesting-lp-vault-for-cl-pools/Cargo.toml create mode 100644 packages/neutron-vesting-lp-vault-for-cl-pools/src/error.rs create mode 100644 packages/neutron-vesting-lp-vault-for-cl-pools/src/lib.rs create mode 100644 packages/neutron-vesting-lp-vault-for-cl-pools/src/msg.rs create mode 100644 packages/neutron-vesting-lp-vault-for-cl-pools/src/types.rs create mode 100644 packages/neutron-voting-power/Cargo.toml create mode 100644 packages/neutron-voting-power/src/lib.rs create mode 100644 packages/neutron-voting-power/src/voting_power.rs diff --git a/Cargo.lock b/Cargo.lock index 8a0c1f08..05e3ff54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,7 +22,7 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "astroport" version = "2.0.0" -source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main#54592b0aa59f16150015d06c5a06aaf333046e92" +source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main#8afcc769f1e577e54b4969d7936c47d08f9dfbad" 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#54592b0aa59f16150015d06c5a06aaf333046e92" +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#8afcc769f1e577e54b4969d7936c47d08f9dfbad" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -59,10 +72,71 @@ 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#8afcc769f1e577e54b4969d7936c47d08f9dfbad" +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-periphery" version = "1.1.0" -source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main#54592b0aa59f16150015d06c5a06aaf333046e92" +source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?rev=e306308dd23d567399c15d899f295a910ede945b#e306308dd23d567399c15d899f295a910ede945b" dependencies = [ "astroport 2.5.0", "cosmwasm-schema", @@ -74,6 +148,21 @@ dependencies = [ "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.1.0" @@ -406,7 +495,7 @@ dependencies = [ "cosmwasm-std", "cw-multi-test", "cw20 1.1.1", - "cw20-base", + "cw20-base 1.1.1", "schemars", "serde", "thiserror", @@ -694,6 +783,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.1" @@ -945,7 +1052,7 @@ dependencies = [ "cw-utils 1.0.2", "cw2 1.1.1", "cw20 1.1.1", - "cw20-base", + "cw20-base 1.1.1", "cw4-group", "cwd-core", "cwd-interface", @@ -994,7 +1101,7 @@ dependencies = [ "cw-utils 1.0.2", "cw2 1.1.1", "cw20 1.1.1", - "cw20-base", + "cw20-base 1.1.1", "cw4-group", "cwd-core", "cwd-interface", @@ -1031,7 +1138,7 @@ dependencies = [ "cw-utils 1.0.2", "cw2 1.1.1", "cw20 1.1.1", - "cw20-base", + "cw20-base 1.1.1", "cw3 1.1.1", "cw4", "cw4-group", @@ -1071,7 +1178,7 @@ dependencies = [ "cw-utils 1.0.2", "cw2 1.1.1", "cw20 1.1.1", - "cw20-base", + "cw20-base 1.1.1", "cw3 1.1.1", "cw4", "cw4-group", @@ -1182,7 +1289,7 @@ dependencies = [ "cw-utils 1.0.2", "cw2 1.1.1", "cw20 1.1.1", - "cw20-base", + "cw20-base 1.1.1", "cw3 1.1.1", "cw4", "cw4-group", @@ -1245,7 +1352,7 @@ dependencies = [ "cw-utils 1.0.2", "cw2 1.1.1", "cw20 1.1.1", - "cw20-base", + "cw20-base 1.1.1", "cw4", "cw4-group", "cw721-base 0.18.0", @@ -1352,6 +1459,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "dyn-clone" version = "1.0.14" @@ -1655,7 +1768,7 @@ version = "0.1.0" 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", @@ -1671,6 +1784,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.1.0", + "cw2 1.1.1", + "cw20 1.1.1", + "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" @@ -1698,7 +1833,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", @@ -1709,6 +1844,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" @@ -1721,10 +1872,11 @@ dependencies = [ name = "neutron-reserve" version = "0.1.1" dependencies = [ + "astroport 2.5.0", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", - "cw2 1.1.1", + "cw20 0.13.4", "cwd-macros", "exec-control", "neutron-sdk", @@ -1857,6 +2009,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.0" @@ -2296,6 +2469,27 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "snafu" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "spki" version = "0.6.0" @@ -2472,7 +2666,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vesting-base" version = "1.1.0" -source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main#54592b0aa59f16150015d06c5a06aaf333046e92" +source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main#8afcc769f1e577e54b4969d7936c47d08f9dfbad" dependencies = [ "astroport 2.0.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main)", "cosmwasm-schema", @@ -2488,7 +2682,23 @@ dependencies = [ [[package]] name = "vesting-base" version = "1.1.0" -source = "git+https://github.com/neutron-org/neutron-tge-contracts.git#54592b0aa59f16150015d06c5a06aaf333046e92" +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#8afcc769f1e577e54b4969d7936c47d08f9dfbad" dependencies = [ "astroport 2.0.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git)", "cosmwasm-schema", @@ -2504,7 +2714,7 @@ dependencies = [ [[package]] name = "vesting-lp" version = "1.1.0" -source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main#54592b0aa59f16150015d06c5a06aaf333046e92" +source = "git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main#8afcc769f1e577e54b4969d7936c47d08f9dfbad" dependencies = [ "astroport 2.0.0 (git+https://github.com/neutron-org/neutron-tge-contracts.git?branch=main)", "cosmwasm-schema", @@ -2514,6 +2724,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.0" @@ -2532,7 +2755,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.1.0", + "cw2 1.1.1", + "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..fccd8a11 --- /dev/null +++ b/contracts/dao/voting/lockdrop-vault-for-cl-pools/src/contract.rs @@ -0,0 +1,293 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{to_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_binary( + &query_voting_power_at_height(deps, env, address, height)?, + )?), + QueryMsg::TotalPowerAtHeight { height } => { + Ok(to_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_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 + usdc_power; + + Ok(TotalPowerAtHeightResponse { power, height }) +} + +pub fn query_info(deps: Deps) -> ContractResult { + let info = cw2::get_contract_version(deps.storage)?; + Ok(to_binary(&cwd_interface::voting::InfoResponse { info })?) +} + +pub fn query_dao(deps: Deps) -> ContractResult { + let dao = DAO.load(deps.storage)?; + Ok(to_binary(&dao)?) +} + +pub fn query_name(deps: Deps) -> ContractResult { + let config = CONFIG.load(deps.storage)?; + Ok(to_binary(&config.name)?) +} + +pub fn query_description(deps: Deps) -> ContractResult { + let config = CONFIG.load(deps.storage)?; + Ok(to_binary(&config.description)?) +} + +pub fn query_config(deps: Deps) -> ContractResult { + let config = CONFIG.load(deps.storage)?; + Ok(to_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/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..1f21cdb5 --- /dev/null +++ b/contracts/dao/voting/vesting-lp-vault-for-cl-pools/src/contract.rs @@ -0,0 +1,301 @@ +use astroport::asset::AssetInfo; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_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_binary( + &query_voting_power_at_height(deps, env, address, height)?, + )?), + QueryMsg::TotalPowerAtHeight { height } => { + Ok(to_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_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_binary(&cwd_interface::voting::InfoResponse { info })?) +} + +pub fn query_dao(deps: Deps) -> ContractResult { + let dao = DAO.load(deps.storage)?; + Ok(to_binary(&dao)?) +} + +pub fn query_name(deps: Deps) -> ContractResult { + let config = CONFIG.load(deps.storage)?; + Ok(to_binary(&config.name)?) +} + +pub fn query_description(deps: Deps) -> ContractResult { + let config = CONFIG.load(deps.storage)?; + Ok(to_binary(&config.description)?) +} + +pub fn query_config(deps: Deps) -> ContractResult { + let config = CONFIG.load(deps.storage)?; + Ok(to_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/tokenomics/reserve/Cargo.toml b/contracts/tokenomics/reserve/Cargo.toml index c6d32f5c..ae3db207 100644 --- a/contracts/tokenomics/reserve/Cargo.toml +++ b/contracts/tokenomics/reserve/Cargo.toml @@ -13,15 +13,16 @@ crate-type = ["cdylib", "rlib"] backtraces = ["cosmwasm-std/backtraces"] [dependencies] -cosmwasm-schema = {version = "1.3.0", default-features = false} -cosmwasm-std = {version = "1.3.0"} +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 = {package = "neutron-sdk", version = "0.7.0" } +cwd-macros = { path = "../../../packages/cwd-macros" } +exec-control = { path = "../../../packages/exec-control" } +neutron-sdk = { package = "neutron-sdk", version = "0.7.0" } schemars = "0.8.8" -serde = {version = "1.0.103", default-features = false, features = ["derive"]} -thiserror = {version = "1.0"} -cw2 = "1.1.0" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } +thiserror = { version = "1.0" } +astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.5.0" } +cw20 = "0.13" [dev-dependencies] diff --git a/contracts/tokenomics/reserve/schema/neutron-reserve.json b/contracts/tokenomics/reserve/schema/neutron-reserve.json index 1f617234..1bd1df40 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" } } }, 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/src/contract.rs b/contracts/tokenomics/reserve/src/contract.rs index 75199b85..7cb89fbe 100644 --- a/contracts/tokenomics/reserve/src/contract.rs +++ b/contracts/tokenomics/reserve/src/contract.rs @@ -1,29 +1,31 @@ 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_binary, Addr, BankMsg, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, - StdResult, Uint128, WasmMsg, + coins, to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, + MessageInfo, Response, StdResult, Uint128, WasmMsg, }; +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, -}; -use cw2::set_contract_version; - -pub(crate) const CONTRACT_NAME: &str = "crates.io:neutron-reserve"; -pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - //-------------------------------------------------------------------------------------------------- // Instantiation //-------------------------------------------------------------------------------------------------- @@ -172,6 +174,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), } } @@ -299,6 +313,315 @@ 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 {}); + } + + // validate parameters to the max available values + if let Some(ntrn_atom_amount) = ntrn_atom_amount { + 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 let Some(ntrn_usdc_amount) = ntrn_usdc_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 let Some(slippage_tolerance) = slippage_tolerance { + if slippage_tolerance.gt(&migration_config.max_slippage) { + return Err(ContractError::MigrationSlippageToBig { + slippage_tolerance, + max_slippage_tolerance: migration_config.max_slippage, + }); + } + } + + 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); + + 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_binary(&Cw20ExecuteMsg::Send { + contract: xyk_pair.to_string(), + amount, + msg: to_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 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_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: None, + })?, + 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 //-------------------------------------------------------------------------------------------------- @@ -364,9 +687,25 @@ 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 - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; +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())?, + }, + )?; 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 b0e8d173..10d24b8c 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_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_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 enum 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/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..2f42b205 --- /dev/null +++ b/packages/neutron-lockdrop-vault-for-cl-pools/src/error.rs @@ -0,0 +1,25 @@ +use cosmwasm_std::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 {}, +} + +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-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)) + } +} From 2a1fa72ecaf4da06e8119b3cbd635bf87b287930 Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Fri, 20 Oct 2023 15:55:02 +0300 Subject: [PATCH 2/9] add missing set_contract_version in reserve's migrate --- Cargo.lock | 1 + contracts/tokenomics/reserve/Cargo.toml | 1 + contracts/tokenomics/reserve/src/contract.rs | 8 ++++++++ 3 files changed, 10 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 05e3ff54..2b278326 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1876,6 +1876,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", + "cw2 1.1.1", "cw20 0.13.4", "cwd-macros", "exec-control", diff --git a/contracts/tokenomics/reserve/Cargo.toml b/contracts/tokenomics/reserve/Cargo.toml index ae3db207..549b8b61 100644 --- a/contracts/tokenomics/reserve/Cargo.toml +++ b/contracts/tokenomics/reserve/Cargo.toml @@ -16,6 +16,7 @@ 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" +cw2 = "1.1.0" cwd-macros = { path = "../../../packages/cwd-macros" } exec-control = { path = "../../../packages/exec-control" } neutron-sdk = { package = "neutron-sdk", version = "0.7.0" } diff --git a/contracts/tokenomics/reserve/src/contract.rs b/contracts/tokenomics/reserve/src/contract.rs index 7cb89fbe..d9184919 100644 --- a/contracts/tokenomics/reserve/src/contract.rs +++ b/contracts/tokenomics/reserve/src/contract.rs @@ -20,12 +20,16 @@ use cosmwasm_std::{ coins, to_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; +pub(crate) const CONTRACT_NAME: &str = "crates.io:reserve"; +pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + //-------------------------------------------------------------------------------------------------- // Instantiation //-------------------------------------------------------------------------------------------------- @@ -37,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, @@ -707,5 +713,7 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result Date: Fri, 20 Oct 2023 17:06:14 +0300 Subject: [PATCH 3/9] improve reserve migration params validation --- contracts/tokenomics/reserve/src/contract.rs | 44 +++++++++----------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/contracts/tokenomics/reserve/src/contract.rs b/contracts/tokenomics/reserve/src/contract.rs index d9184919..c3ed355c 100644 --- a/contracts/tokenomics/reserve/src/contract.rs +++ b/contracts/tokenomics/reserve/src/contract.rs @@ -361,36 +361,30 @@ fn execute_migrate_from_xyk_to_cl( 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 let Some(ntrn_atom_amount) = ntrn_atom_amount { - 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_atom_amount.gt(&max_available_ntrn_atom_amount) { + return Err(ContractError::MigrationAmountUnavailable { + amount: ntrn_atom_amount, + max_amount: max_available_ntrn_atom_amount, + }); } - if let Some(ntrn_usdc_amount) = ntrn_usdc_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 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 let Some(slippage_tolerance) = slippage_tolerance { - if slippage_tolerance.gt(&migration_config.max_slippage) { - return Err(ContractError::MigrationSlippageToBig { - slippage_tolerance, - max_slippage_tolerance: migration_config.max_slippage, - }); - } + if slippage_tolerance.gt(&migration_config.max_slippage) { + return Err(ContractError::MigrationSlippageToBig { + slippage_tolerance, + max_slippage_tolerance: migration_config.max_slippage, + }); } - 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); - let mut resp = Response::default(); if !ntrn_atom_amount.is_zero() { resp = resp.add_message( From b3403aaae222af4abd18e9ca779bcf4baf30291e Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Wed, 25 Oct 2023 11:07:20 +0300 Subject: [PATCH 4/9] rename reserve contract name to neutron-reserve --- contracts/tokenomics/reserve/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/tokenomics/reserve/src/contract.rs b/contracts/tokenomics/reserve/src/contract.rs index c3ed355c..ce1e0417 100644 --- a/contracts/tokenomics/reserve/src/contract.rs +++ b/contracts/tokenomics/reserve/src/contract.rs @@ -27,7 +27,7 @@ use exec_control::pause::{ }; use neutron_sdk::bindings::query::NeutronQuery; -pub(crate) const CONTRACT_NAME: &str = "crates.io:reserve"; +pub(crate) const CONTRACT_NAME: &str = "neutron-reserve"; pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); //-------------------------------------------------------------------------------------------------- From 6fee749bb83f523b3ac03483ec1ae09a4a729d5f Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Wed, 25 Oct 2023 11:07:34 +0300 Subject: [PATCH 5/9] add set_contract_version for distribution contract --- Cargo.lock | 1 + contracts/tokenomics/distribution/Cargo.toml | 13 +++++++------ contracts/tokenomics/distribution/src/contract.rs | 6 ++++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b278326..62c3ac40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1822,6 +1822,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", + "cw2 1.1.1", "cwd-macros", "exec-control", "schemars", diff --git a/contracts/tokenomics/distribution/Cargo.toml b/contracts/tokenomics/distribution/Cargo.toml index 57db3887..7eb4f298 100644 --- a/contracts/tokenomics/distribution/Cargo.toml +++ b/contracts/tokenomics/distribution/Cargo.toml @@ -13,13 +13,14 @@ crate-type = ["cdylib", "rlib"] backtraces = ["cosmwasm-std/backtraces"] [dependencies] -cosmwasm-schema = {version = "1.3.0", default-features = false} -cosmwasm-std = {version = "1.3.0"} +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"} +cwd-macros = { path = "../../../packages/cwd-macros" } +exec-control = { path = "../../../packages/exec-control" } schemars = "0.8.8" -serde = {version = "1.0.175", default-features = false, features = ["derive"]} -thiserror = {version = "1.0"} +serde = { version = "1.0.175", default-features = false, features = ["derive"] } +thiserror = { version = "1.0" } +cw2 = "1.1.0" [dev-dependencies] diff --git a/contracts/tokenomics/distribution/src/contract.rs b/contracts/tokenomics/distribution/src/contract.rs index 33113827..5c4d5f93 100644 --- a/contracts/tokenomics/distribution/src/contract.rs +++ b/contracts/tokenomics/distribution/src/contract.rs @@ -7,10 +7,14 @@ use cosmwasm_std::{ to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, Storage, Uint128, }; +use cw2::set_contract_version; use exec_control::pause::{ can_pause, can_unpause, validate_duration, PauseError, PauseInfoResponse, }; +pub(crate) const CONTRACT_NAME: &str = "neutron-distribution"; +pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + //-------------------------------------------------------------------------------------------------- // Instantiation //-------------------------------------------------------------------------------------------------- @@ -22,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)?, From 47dc5fa8ca1ad9d56e83ea950eed7a10e0e1b1fb Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Wed, 25 Oct 2023 13:59:28 +0300 Subject: [PATCH 6/9] add migrate fn for distribution contract --- contracts/tokenomics/distribution/src/contract.rs | 8 +++++++- contracts/tokenomics/distribution/src/msg.rs | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/contracts/tokenomics/distribution/src/contract.rs b/contracts/tokenomics/distribution/src/contract.rs index 5c4d5f93..ad2a6da8 100644 --- a/contracts/tokenomics/distribution/src/contract.rs +++ b/contracts/tokenomics/distribution/src/contract.rs @@ -1,5 +1,5 @@ use crate::error::ContractError; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; use crate::state::{Config, CONFIG, FUND_COUNTER, PAUSED_UNTIL, PENDING_DISTRIBUTION, SHARES}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; @@ -312,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/distribution/src/msg.rs b/contracts/tokenomics/distribution/src/msg.rs index 6cf2f4f6..1b063b81 100644 --- a/contracts/tokenomics/distribution/src/msg.rs +++ b/contracts/tokenomics/distribution/src/msg.rs @@ -47,3 +47,7 @@ pub enum QueryMsg { #[returns(Vec<(Addr, Uint128)>)] Shares {}, } + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct MigrateMsg {} From 865d5a7345f08b87a780e05fc7a593ca3ae9c6b4 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Fri, 27 Oct 2023 10:58:38 +0400 Subject: [PATCH 7/9] send lps to dao --- contracts/tokenomics/reserve/src/contract.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/tokenomics/reserve/src/contract.rs b/contracts/tokenomics/reserve/src/contract.rs index 7cb89fbe..538245ad 100644 --- a/contracts/tokenomics/reserve/src/contract.rs +++ b/contracts/tokenomics/reserve/src/contract.rs @@ -553,6 +553,8 @@ fn provide_liquidity_to_cl_pair_after_withdrawal_callback( 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 { @@ -564,7 +566,7 @@ fn provide_liquidity_to_cl_pair_after_withdrawal_callback( ], slippage_tolerance: Some(slippage_tolerance), auto_stake: None, - receiver: None, + receiver: Option::from(config.main_dao_address.to_string()), })?, funds: vec![ Coin::new(withdrawn_ntrn_amount.into(), ntrn_denom.clone()), From cc3da98d926e39675580fd48312191b2b05b3577 Mon Sep 17 00:00:00 2001 From: pr0n00gler Date: Tue, 26 Mar 2024 13:45:12 +0200 Subject: [PATCH 8/9] checked_add --- .../dao/voting/lockdrop-vault-for-cl-pools/src/contract.rs | 2 +- contracts/dao/voting/lockdrop-vault/src/contract.rs | 2 +- packages/neutron-lockdrop-vault-for-cl-pools/src/error.rs | 5 ++++- packages/neutron-lockdrop-vault/src/error.rs | 5 ++++- 4 files changed, 10 insertions(+), 4 deletions(-) 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 index d60218e7..a82c64b0 100644 --- a/contracts/dao/voting/lockdrop-vault-for-cl-pools/src/contract.rs +++ b/contracts/dao/voting/lockdrop-vault-for-cl-pools/src/contract.rs @@ -238,7 +238,7 @@ pub fn query_total_power_at_height( height, )?; - let power = atom_power + usdc_power; + let power = atom_power.checked_add(usdc_power)?; Ok(TotalPowerAtHeightResponse { power, height }) } 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/packages/neutron-lockdrop-vault-for-cl-pools/src/error.rs b/packages/neutron-lockdrop-vault-for-cl-pools/src/error.rs index 2f42b205..bfd14f61 100644 --- a/packages/neutron-lockdrop-vault-for-cl-pools/src/error.rs +++ b/packages/neutron-lockdrop-vault-for-cl-pools/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-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 {}, From 361d8eac6ca7444b5d15ce0db424b2a21cf33215 Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Sun, 28 Apr 2024 17:42:17 +0300 Subject: [PATCH 9/9] bump reserve contract's minor version --- Cargo.lock | 2 +- contracts/tokenomics/reserve/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6231bd1e..053c799b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1882,7 +1882,7 @@ dependencies = [ [[package]] name = "neutron-reserve" -version = "0.1.2" +version = "0.2.0" dependencies = [ "astroport 2.5.0", "cosmwasm-schema", diff --git a/contracts/tokenomics/reserve/Cargo.toml b/contracts/tokenomics/reserve/Cargo.toml index c9b5f780..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"]