diff --git a/Cargo.lock b/Cargo.lock index 137e897..622bf6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,60 +4,167 @@ version = 3 [[package]] name = "ahash" -version = "0.7.8" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ - "getrandom", + "cfg-if", "once_cell", "version_check", + "zerocopy", ] [[package]] -name = "astroport" -version = "2.0.0" +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "ark-bls12-381" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.2.0", - "cw20 1.1.2", + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", "itertools", - "uint", + "num-traits", + "rayon", + "zeroize", ] [[package]] -name = "base16ct" -version = "0.2.0" +name = "ark-ff" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rayon", + "rustc_version", + "zeroize", +] [[package]] -name = "base64" -version = "0.21.7" +name = "ark-ff-asm" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] [[package]] -name = "base64ct" -version = "1.8.0" +name = "ark-ff-macros" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] -name = "bech32" -version = "0.9.1" +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest", + "num-bigint", +] [[package]] -name = "block-buffer" -version = "0.9.0" +name = "ark-serialize-derive" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" dependencies = [ - "generic-array", + "proc-macro2", + "quote", + "syn 1.0.109", ] +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", + "rayon", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + [[package]] name = "block-buffer" version = "0.10.4" @@ -69,9 +176,9 @@ dependencies = [ [[package]] name = "bnum" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" +checksum = "3e31ea183f6ee62ac8b8a8cf7feddd766317adfb13ff469de57ce033efd6a790" [[package]] name = "byteorder" @@ -91,33 +198,52 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "cosmwasm-core" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b6dc17e7fd89d0a0a58f12ef33f0bbdf09a6a14c3dfb383eae665e5889250e" + [[package]] name = "cosmwasm-crypto" -version = "1.5.11" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c82e56962f0f18c9a292aa59940e03a82ce15ef79b93679d5838bb8143f0df" +checksum = "aa2f53285517db3e33d825b3e46301efe845135778527e1295154413b2f0469e" dependencies = [ - "digest 0.10.7", + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-serialize", + "cosmwasm-core", + "curve25519-dalek", + "digest", + "ecdsa", "ed25519-zebra", "k256", - "rand_core 0.6.4", + "num-traits", + "p256", + "rand_core", + "rayon", + "sha2", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "1.5.11" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b804ff15a0e059c88f85ae0e868cf8c7aba9d61221e46f1ad7250f270628c7" +checksum = "a782b93fae93e57ca8ad3e9e994e784583f5933aeaaa5c80a545c4b437be2047" dependencies = [ - "syn 1.0.109", + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] name = "cosmwasm-schema" -version = "1.5.11" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5526ea839acb47bbf8fff031ed9aad86e74d43f77b089255417328c3664367d5" +checksum = "6984ab21b47a096e17ae4c73cea2123a704d4b6686c39421247ad67020d76f95" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -128,33 +254,35 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.5.11" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f41b99f41f840765d02ae858956bb52af910755976312082e90493c67db512" +checksum = "e01c9214319017f6ebd8e299036e1f717fa9bb6724e758f7d6fb2477599d1a29" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.104", ] [[package]] name = "cosmwasm-std" -version = "1.5.11" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763340055b84e5482ed90fec8194ff7d59112267a09bbf5819c9e3edca8c052e" +checksum = "bf82335c14bd94eeb4d3c461b7aa419ecd7ea13c2efe24b97cd972bdb8044e7d" dependencies = [ "base64", "bech32", "bnum", + "cosmwasm-core", "cosmwasm-crypto", "cosmwasm-derive", - "derivative", - "forward_ref", + "derive_more", "hex", + "rand_core", + "rmp-serde", "schemars", "serde", "serde-json-wasm", - "sha2 0.10.9", + "sha2", "static_assertions", "thiserror", ] @@ -169,10 +297,29 @@ dependencies = [ ] [[package]] -name = "crunchy" -version = "0.2.4" +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-bigint" @@ -181,7 +328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core 0.6.4", + "rand_core", "subtle", "zeroize", ] @@ -198,119 +345,64 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.2.0" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", "subtle", "zeroize", ] [[package]] -name = "cw-storage-plus" -version = "0.15.1" +name = "curve25519-dalek-derive" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6cf70ef7686e2da9ad7b067c5942cd3e88dd9453f7af42f54557f8af300fb0" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ - "cosmwasm-std", - "schemars", - "serde", + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] name = "cw-storage-plus" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5ff29294ee99373e2cd5fd21786a3c0ced99a52fec2ca347d565489c61b723c" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - -[[package]] -name = "cw-utils" -version = "0.15.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae0b69fa7679de78825b4edeeec045066aa2b2c4b6e063d80042e565bb4da5c" +checksum = "f13360e9007f51998d42b1bc6b7fa0141f74feae61ed5fd1e5b0a89eec7b5de1" dependencies = [ - "cosmwasm-schema", "cosmwasm-std", - "cw2 0.15.1", "schemars", - "semver", "serde", - "thiserror", ] [[package]] name = "cw-utils" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c4a657e5caacc3a0d00ee96ca8618745d050b8f757c709babafb81208d4239c" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw2 1.1.2", - "schemars", - "semver", - "serde", - "thiserror", -] - -[[package]] -name = "cw2" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5abb8ecea72e09afff830252963cb60faf945ce6cef2c20a43814516082653da" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.15.1", - "schemars", - "serde", -] - -[[package]] -name = "cw2" -version = "1.1.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" +checksum = "07dfee7f12f802431a856984a32bce1cb7da1e6c006b5409e3981035ce562dec" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.2.0", "schemars", - "semver", "serde", "thiserror", ] [[package]] name = "cw20" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6025276fb6e603e974c21f3e4606982cdc646080e8fba3198816605505e1d9a" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-utils 0.15.1", - "schemars", - "serde", -] - -[[package]] -name = "cw20" -version = "1.1.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "526e39bb20534e25a1cd0386727f0038f4da294e5e535729ba3ef54055246abd" +checksum = "a42212b6bf29bbdda693743697c621894723f35d3db0d5df930be22903d0e27c" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-utils 1.0.3", + "cw-utils", "schemars", "serde", ] @@ -337,12 +429,24 @@ dependencies = [ ] [[package]] -name = "digest" -version = "0.9.0" +name = "derive_more" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "generic-array", + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "unicode-xid", ] [[package]] @@ -351,7 +455,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", + "block-buffer", "const-oid", "crypto-common", "subtle", @@ -370,25 +474,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest 0.10.7", + "digest", "elliptic-curve", "rfc6979", "signature", - "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "signature", ] [[package]] name = "ed25519-zebra" -version = "3.1.0" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" dependencies = [ "curve25519-dalek", - "hashbrown", + "ed25519", + "hashbrown 0.14.5", "hex", - "rand_core 0.6.4", - "serde", - "sha2 0.9.9", + "rand_core", + "sha2", "zeroize", ] @@ -406,12 +518,11 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest 0.10.7", + "digest", "ff", "generic-array", "group", - "pkcs8", - "rand_core 0.6.4", + "rand_core", "sec1", "subtle", "zeroize", @@ -423,15 +534,15 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "rand_core 0.6.4", + "rand_core", "subtle", ] [[package]] -name = "forward_ref" -version = "1.0.0" +name = "fiat-crypto" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "generic-array" @@ -462,17 +573,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core 0.6.4", + "rand_core", "subtle", ] [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", + "allocator-api2", ] [[package]] @@ -487,14 +608,14 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.7", + "digest", ] [[package]] name = "itertools" -version = "0.11.0" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] @@ -514,9 +635,7 @@ dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "once_cell", - "sha2 0.10.9", - "signature", + "sha2", ] [[package]] @@ -531,6 +650,34 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -538,19 +685,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] -name = "opaque-debug" -version = "0.3.1" +name = "p256" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] [[package]] -name = "pkcs8" -version = "0.10.2" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "der", - "spki", + "zerocopy", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", ] [[package]] @@ -572,10 +739,24 @@ dependencies = [ ] [[package]] -name = "rand_core" -version = "0.5.1" +name = "rand" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] [[package]] name = "rand_core" @@ -586,6 +767,26 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -596,6 +797,37 @@ dependencies = [ "subtle", ] +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.20" @@ -635,7 +867,6 @@ dependencies = [ "base16ct", "der", "generic-array", - "pkcs8", "subtle", "zeroize", ] @@ -657,9 +888,9 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.5.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e9213a07d53faa0b8dd81e767a54a8188a242fdb9be99ab75ec576a774bfdd7" +checksum = "f05da0d153dd4595bdffd5099dc0e9ce425b205ee648eb93437ff7302af8c9a5" dependencies = [ "serde", ] @@ -698,19 +929,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha2" version = "0.10.9" @@ -719,7 +937,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.7", + "digest", ] [[package]] @@ -728,18 +946,8 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", + "digest", + "rand_core", ] [[package]] @@ -802,24 +1010,18 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "version_check" version = "0.9.5" @@ -830,12 +1032,11 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" name = "vesting-base" version = "1.1.0" dependencies = [ - "astroport", "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.2.0", - "cw-utils 0.15.1", - "cw20 0.15.1", + "cw-storage-plus", + "cw-utils", + "cw20", "thiserror", ] @@ -845,8 +1046,42 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] diff --git a/Cargo.toml b/Cargo.toml index aa9abbf..0ab76dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ - "packages/*" + "packages/vesting-base", ] [profile.release] @@ -15,28 +15,9 @@ incremental = false overflow-checks = true [workspace.dependencies] -cosmwasm-std = { version = "1.4.1", default-features = false } -cw-storage-plus = "1.1.0" +cosmwasm-schema = { version = "2.2.2", default-features = false } +cosmwasm-std = { version = "2.2.2", default-features = false, features = ["std", "iterator"] } +cw-storage-plus = { version = "2.0.0", default-features = false, features = ["iterator"] } +cw-utils = { version = "2.0.0", default-features = false } +cw20 = { version = "2.0.0", default-features = false } thiserror = "1.0.49" -anyhow = "1.0.75" -cw2 = "1.1.1" -cw20 = "1.1.1" -cosmwasm-schema = { version = "1.4.1", default-features = false } -astroport = { path = "packages/astroport", default-features = false } -astroport-periphery = { path = "packages/astroport_periphery" } -vesting-base = { path = "packages/vesting-base" } -vesting-base-pcl = { path = "packages/vesting-base-pcl" } -# setting cw-multi-test to 0.17.0 enables cosmwasm_1_1, we don't want that -cw-multi-test = "0.16.5" -itertools = "0.11.0" -schemars = "0.8.15" -serde = { version = "1.0.189", default-features = false } -sha2 = { version = "0.10.8", default-features = false } -hex = "0.4.3" -bech32 = "0.9.1" -ripemd = "0.1.3" -cw20-base = { version = "1.1.1", features = ["library"] } -semver = "1.0.20" -obi = "0.0.2" -cw-band = "0.1.1" -uint = "0.9.5" diff --git a/packages/astroport/.cargo/config b/packages/astroport/.cargo/config deleted file mode 100644 index 71deaf2..0000000 --- a/packages/astroport/.cargo/config +++ /dev/null @@ -1,4 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib" diff --git a/packages/astroport/Cargo.toml b/packages/astroport/Cargo.toml deleted file mode 100644 index b212d16..0000000 --- a/packages/astroport/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "astroport" -version = "2.0.0" -authors = ["Astroport"] -edition = "2021" -description = "Common Astroport types, queriers and other utils" -license = "Apache-2.0" -repository = "https://github.com/astroport-fi/astroport" -homepage = "https://astroport.fi" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[features] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -cw20 = { workspace = true } -cosmwasm-std = { workspace = true } -uint = { workspace = true } -cw-storage-plus = { workspace = true } -itertools = { workspace = true } -cosmwasm-schema = { workspace = true } diff --git a/packages/astroport/README.md b/packages/astroport/README.md deleted file mode 100644 index 461bc7a..0000000 --- a/packages/astroport/README.md +++ /dev/null @@ -1,115 +0,0 @@ -# Astroport: Common Types - -This is a collection of common types and queriers which are commonly used in Astroport contracts. - -## Data Types - -### AssetInfo - -AssetInfo is a convenience wrapper to represent whether a token is the native one (from a specific chain, like LUNA for Terra) or not. It also returns the contract address of that token. - -```rust -pub enum AssetInfo { - Token { contract_addr: Addr }, - NativeToken { denom: String }, -} -``` - -### Asset - -It contains asset info and a token amount. - -```rust -pub struct Asset { - pub info: AssetInfo, - pub amount: Uint128, -} -``` - -### PairInfo - -It is used to represent response data coming from a [Pair-Info-Querier](#Pair-Info-Querier). - -```rust -pub struct PairInfo { - pub asset_infos: [AssetInfo; 2], - pub contract_addr: Addr, - pub liquidity_token: Addr, - pub pair_type: PairType, -} -``` - -## Queriers - -### Native Token Balance Querier - -It uses the CosmWasm standard interface to query an account's balance. - -```rust -pub fn query_balance( - querier: &QuerierWrapper, - account_addr: impl Into, - denom: impl Into, -) -> StdResult -``` - -### Token Balance Querier - -It provides a similar query interface to [Native-Token-Balance-Querier](Native-Token-Balance-Querier) for fetching CW20 token balances. - -```rust -pub fn query_token_balance( - querier: &QuerierWrapper, - contract_addr: impl Into, - account_addr: impl Into, -) -> StdResult -``` - -### Token Supply Querier - -It fetches a CW20 token's total supply. - -```rust -pub fn query_supply( - querier: &QuerierWrapper, - contract_addr: impl Into, -) -> StdResult -``` - -### Pair Info Querier - -Accepts two tokens as input and returns a pair's information. - -```rust -pub fn query_pair_info( - querier: &QuerierWrapper, - factory_contract: impl Into, - asset_infos: &[AssetInfo; 2], -) -> StdResult -``` - -## Swap Pairs Simulating - -### Simulate - -Simulates a swap and returns the output amount, the spread and commission amounts. - -```rust -pub fn simulate( - querier: &QuerierWrapper, - pair_contract: impl Into, - offer_asset: &Asset, -) -> StdResult -``` - -### Reverse Simulate - -Simulates a reverse swap and returns an input amount, the spread and commission amounts. - -```rust -pub fn reverse_simulate( - querier: &QuerierWrapper, - pair_contract: impl Into, - offer_asset: &Asset, -) -> StdResult -``` diff --git a/packages/astroport/src/cosmwasm_ext.rs b/packages/astroport/src/cosmwasm_ext.rs deleted file mode 100644 index 3413235..0000000 --- a/packages/astroport/src/cosmwasm_ext.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::ops; - -use crate::asset::Decimal256Ext; -use cosmwasm_std::{ - ConversionOverflowError, Decimal, Decimal256, Fraction, StdResult, Uint128, Uint256, Uint64, -}; - -pub trait AbsDiff -where - Self: Copy + PartialOrd + ops::Sub, -{ - fn diff(self, rhs: Self) -> Self { - if self > rhs { - self - rhs - } else { - rhs - self - } - } -} - -impl AbsDiff for Uint256 {} -impl AbsDiff for Uint128 {} -impl AbsDiff for Uint64 {} -impl AbsDiff for Decimal {} -impl AbsDiff for Decimal256 {} - -pub trait IntegerToDecimal -where - Self: Copy + Into + Into, -{ - fn to_decimal(self) -> Decimal { - Decimal::from_ratio(self, 1u8) - } - - fn to_decimal256(self, precision: impl Into) -> StdResult { - Decimal256::with_precision(self, precision) - } -} - -impl IntegerToDecimal for u64 {} -impl IntegerToDecimal for Uint128 {} - -pub trait DecimalToInteger { - fn to_uint(self, precision: impl Into) -> Result; -} - -impl DecimalToInteger for Decimal256 { - fn to_uint(self, precision: impl Into) -> Result { - let multiplier = Uint256::from(10u8).pow(precision.into()); - (multiplier * self.numerator() / self.denominator()).try_into() - } -} diff --git a/packages/astroport/src/factory.rs b/packages/astroport/src/factory.rs deleted file mode 100644 index de113d1..0000000 --- a/packages/astroport/src/factory.rs +++ /dev/null @@ -1,232 +0,0 @@ -use crate::asset::{AssetInfo, PairInfo}; - -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Binary}; -use cw_storage_plus::Map; -use std::fmt::{Display, Formatter, Result}; - -const MAX_TOTAL_FEE_BPS: u16 = 10_000; -const MAX_MAKER_FEE_BPS: u16 = 10_000; - -/// This enum describes available pair types. -/// ## Available pool types -/// ``` -/// # use astroport::factory::PairType::{Custom, Stable, Xyk}; -/// Xyk {}; -/// Stable {}; -/// Custom(String::from("Custom")); -/// ``` -#[cw_serde] -pub enum PairType { - /// XYK pair type - Xyk {}, - /// Stable pair type - Stable {}, - /// Concentrated liquidity pair type - Concentrated {}, - /// Custom pair type - Custom(String), -} - -/// Returns a raw encoded string representing the name of each pool type -impl Display for PairType { - fn fmt(&self, fmt: &mut Formatter) -> Result { - match self { - PairType::Xyk {} => fmt.write_str("xyk"), - PairType::Stable {} => fmt.write_str("stable"), - PairType::Concentrated {} => fmt.write_str("concentrated"), - PairType::Custom(pair_type) => fmt.write_str(format!("custom-{}", pair_type).as_str()), - } - } -} - -/// This structure stores a pair type's configuration. -#[cw_serde] -pub struct PairConfig { - /// ID of contract which is allowed to create pairs of this type - pub code_id: u64, - /// The pair type (provided in a [`PairType`]) - pub pair_type: PairType, - /// The total fees (in bps) charged by a pair of this type - pub total_fee_bps: u16, - /// The amount of fees (in bps) collected by the Maker contract from this pair type - pub maker_fee_bps: u16, - /// Whether a pair type is disabled or not. If it is disabled, new pairs cannot be - /// created, but existing ones can still read the pair configuration - pub is_disabled: bool, - /// Setting this to true means that pairs of this type will not be able - /// to get an ASTRO generator - pub is_generator_disabled: bool, -} - -impl PairConfig { - /// This method is used to check fee bps. - pub fn valid_fee_bps(&self) -> bool { - self.total_fee_bps <= MAX_TOTAL_FEE_BPS && self.maker_fee_bps <= MAX_MAKER_FEE_BPS - } -} - -/// This structure stores the basic settings for creating a new factory contract. -#[cw_serde] -pub struct InstantiateMsg { - /// IDs of contracts that are allowed to instantiate pairs - pub pair_configs: Vec, - /// CW20 token contract code identifier - pub token_code_id: u64, - /// Contract address to send governance fees to (the Maker) - pub fee_address: Option, - /// Address of contract that is used to auto_stake LP tokens once someone provides liquidity in a pool - pub generator_address: Option, - /// Address of owner that is allowed to change factory contract parameters - pub owner: String, - /// CW1 whitelist contract code id used to store 3rd party rewards for staking Astroport LP tokens - pub whitelist_code_id: u64, - pub coin_registry_address: String, -} - -/// This structure describes the execute messages of the contract. -#[cw_serde] -pub enum ExecuteMsg { - /// UpdateConfig updates relevant code IDs - UpdateConfig { - /// CW20 token contract code identifier - token_code_id: Option, - /// Contract address to send governance fees to (the Maker) - fee_address: Option, - /// Contract address where Lp tokens can be auto_staked after someone provides liquidity in an incentivized Astroport pool - generator_address: Option, - /// CW1 whitelist contract code id used to store 3rd party rewards for staking Astroport LP tokens - whitelist_code_id: Option, - }, - /// UpdatePairConfig updates the config for a pair type. - UpdatePairConfig { - /// New [`PairConfig`] settings for a pair type - config: PairConfig, - }, - /// CreatePair instantiates a new pair contract. - CreatePair { - /// The pair type (exposed in [`PairType`]) - pair_type: PairType, - /// The assets to create the pool for - asset_infos: Vec, - /// Optional binary serialised parameters for custom pool types - init_params: Option, - }, - /// Deregister removes a previously created pair. - Deregister { - /// The assets for which we deregister a pool - asset_infos: Vec, - }, - /// ProposeNewOwner creates a proposal to change contract ownership. - /// The validity period for the proposal is set in the `expires_in` variable. - ProposeNewOwner { - /// Newly proposed contract owner - owner: String, - /// The date after which this proposal expires - expires_in: u64, - }, - /// DropOwnershipProposal removes the existing offer to change contract ownership. - DropOwnershipProposal {}, - /// Used to claim contract ownership. - ClaimOwnership {}, - /// MarkAsMigrated marks pairs as migrated - MarkAsMigrated { pairs: Vec }, -} - -/// This structure describes the available query messages for the factory contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Config returns contract settings specified in the custom [`ConfigResponse`] structure. - #[returns(ConfigResponse)] - Config {}, - /// Pair returns information about a specific pair according to the specified assets. - #[returns(PairInfo)] - Pair { - /// The assets for which we return a pair - asset_infos: Vec, - }, - /// Pairs returns an array of pairs and their information according to the specified parameters in `start_after` and `limit` variables. - #[returns(PairsResponse)] - Pairs { - /// The pair item to start reading from. It is an [`Option`] type that accepts [`AssetInfo`] elements. - start_after: Option>, - /// The number of pairs to read and return. It is an [`Option`] type. - limit: Option, - }, - /// FeeInfo returns fee parameters for a specific pair. The response is returned using a [`FeeInfoResponse`] structure - #[returns(FeeInfoResponse)] - FeeInfo { - /// The pair type for which we return fee information. Pair type is a [`PairType`] struct - pair_type: PairType, - }, - /// Returns a vector that contains blacklisted pair types - #[returns(Vec)] - BlacklistedPairTypes {}, - /// Returns a vector that contains pair addresses that are not migrated - #[returns(Vec)] - PairsToMigrate {}, -} - -/// A custom struct for each query response that returns general contract settings/configs. -#[cw_serde] -pub struct ConfigResponse { - /// Address of owner that is allowed to change contract parameters - pub owner: Addr, - /// IDs of contracts which are allowed to create pairs - pub pair_configs: Vec, - /// CW20 token contract code identifier - pub token_code_id: u64, - /// Address of contract to send governance fees to (the Maker) - pub fee_address: Option, - /// Address of contract used to auto_stake LP tokens for Astroport pairs that are incentivized - pub generator_address: Option, - /// CW1 whitelist contract code id used to store 3rd party rewards for staking Astroport LP tokens - pub whitelist_code_id: u64, - /// The address of the contract that contains the coins with their precision - pub coin_registry_address: Addr, -} - -/// This structure stores the parameters used in a migration message. -#[cw_serde] -pub struct MigrateMsg { - pub params: Binary, -} - -/// A custom struct for each query response that returns an array of objects of type [`PairInfo`]. -#[cw_serde] -pub struct PairsResponse { - /// Arrays of structs containing information about multiple pairs - pub pairs: Vec, -} - -/// A custom struct for each query response that returns an object of type [`FeeInfoResponse`]. -#[cw_serde] -pub struct FeeInfoResponse { - /// Contract address to send governance fees to - pub fee_address: Option, - /// Total amount of fees (in bps) charged on a swap - pub total_fee_bps: u16, - /// Amount of fees (in bps) sent to the Maker contract - pub maker_fee_bps: u16, -} - -/// This is an enum used for setting and removing a contract address. -#[cw_serde] -pub enum UpdateAddr { - /// Sets a new contract address. - Set(String), - /// Removes a contract address. - Remove {}, -} - -/// Map which contains a list of all pairs which are able to convert X <> Y assets. -/// Example: given 3 pools (X, Y), (X,Y,Z) and (X,Y,Z,W), the map will contain the following entries -/// (pair addresses): -/// `ROUTE[X][Y] = [(X,Y), (X,Y,Z), (X,Y,Z,W)]` -/// `ROUTE[X][Z] = [(X,Y,Z), (X,Y,Z,W)]` -/// `ROUTE[X][W] = [(X,Y,Z,W)]` -/// ... -/// -/// Notice that `ROUTE[X][Y] = ROUTE[Y][X]` -pub const ROUTE: Map<(String, String), Vec> = Map::new("routes"); diff --git a/packages/astroport/src/generator.rs b/packages/astroport/src/generator.rs deleted file mode 100644 index bf52fc9..0000000 --- a/packages/astroport/src/generator.rs +++ /dev/null @@ -1,434 +0,0 @@ -use crate::asset::{Asset, AssetInfo}; -use crate::factory::PairType; -use crate::restricted_vector::RestrictedVector; -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{ - to_json_binary, Addr, Decimal, Env, StdResult, SubMsg, Uint128, Uint64, WasmMsg, -}; -use cw20::Cw20ReceiveMsg; - -/// This structure describes the parameters used for creating a contract. -#[cw_serde] -pub struct InstantiateMsg { - /// Address that can change contract settings - pub owner: String, - /// Address of factory contract - pub factory: String, - /// Address that can set active generators and their alloc points - pub generator_controller: Option, - /// The voting escrow delegation contract address - pub voting_escrow_delegation: Option, - /// The voting escrow contract address - pub voting_escrow: Option, - /// Address of guardian - pub guardian: Option, - /// ASTRO token contract address - pub astro_token: String, - /// Amount of ASTRO distributed per block among all pairs - pub tokens_per_block: Uint128, - /// Start block for distributing ASTRO - pub start_block: Uint64, - /// The ASTRO vesting contract that drips ASTRO rewards - pub vesting_contract: String, - /// Whitelist code id - pub whitelist_code_id: u64, -} - -#[cw_serde] -pub enum ExecuteMsg { - /// Update the address of the ASTRO vesting contract - /// ## Executor - /// Only the owner can execute it. - UpdateConfig { - /// The new vesting contract address - vesting_contract: Option, - /// The new generator controller contract address - generator_controller: Option, - /// The new generator guardian - guardian: Option, - /// The new voting escrow delegation contract address - voting_escrow_delegation: Option, - /// The new voting escrow contract address - voting_escrow: Option, - /// The amount of generators - checkpoint_generator_limit: Option, - }, - /// Setup generators with their respective allocation points. - /// ## Executor - /// Only the owner or generator controller can execute this. - SetupPools { - /// The list of pools with allocation point. - pools: Vec<(String, Uint128)>, - }, - /// Update the given pool's ASTRO allocation slice - /// ## Executor - /// Only the owner or generator controller can execute this. - UpdatePool { - /// The address of the LP token contract address whose allocation we change - lp_token: String, - /// This flag determines whether the pool gets 3rd party token rewards - has_asset_rewards: bool, - }, - /// Update rewards and return it to user. - ClaimRewards { - /// the LP token contract address - lp_tokens: Vec, - }, - /// Withdraw LP tokens from the Generator - Withdraw { - /// The address of the LP token to withdraw - lp_token: String, - /// The amount to withdraw - amount: Uint128, - }, - /// Withdraw LP tokens from the Generator without withdrawing outstanding rewards - EmergencyWithdraw { - /// The address of the LP token to withdraw - lp_token: String, - }, - /// Sends orphan proxy rewards (which were left behind after emergency withdrawals) to another address - SendOrphanProxyReward { - /// The transfer recipient - recipient: String, - /// The address of the LP token contract for which we send orphaned rewards - lp_token: String, - }, - /// Receives a message of type [`Cw20ReceiveMsg`] - Receive(Cw20ReceiveMsg), - /// Set a new amount of ASTRO to distribute per block - /// ## Executor - /// Only the owner can execute this. - SetTokensPerBlock { - /// The new amount of ASTRO to distro per block - amount: Uint128, - }, - /// Creates a request to change contract ownership - /// ## Executor - /// Only the current owner can execute this. - ProposeNewOwner { - /// The newly proposed owner - owner: String, - /// The validity period of the proposal to change the contract owner - expires_in: u64, - }, - /// Removes a request to change contract ownership - /// ## Executor - /// Only the current owner can execute this - DropOwnershipProposal {}, - /// Claims contract ownership - /// ## Executor - /// Only the newly proposed owner can execute this - ClaimOwnership {}, - /// Sets a new proxy contract for a specific generator - /// Sets a proxy for the pool - /// ## Executor - /// Only the current owner or generator controller can execute this - MoveToProxy { - lp_token: String, - proxy: String, - }, - MigrateProxy { - lp_token: String, - new_proxy: String, - }, - /// Add or remove token to the block list - UpdateBlockedTokenslist { - /// Tokens to add - add: Option>, - /// Tokens to remove - remove: Option>, - }, - /// Sets the allocation point to zero for the specified pool - DeactivatePool { - lp_token: String, - }, - /// Sets the allocation point to zero for each pool by the pair type - DeactivatePools { - pair_types: Vec, - }, - /// Updates the boost emissions for specified user and generators - CheckpointUserBoost { - generators: Vec, - user: Option, - }, - /// Process action after the callback - Callback { - action: ExecuteOnReply, - }, -} - -#[cw_serde] -pub enum ExecuteOnReply { - /// Updates reward and returns it to user. - ClaimRewards { - /// The list of LP tokens contract - lp_tokens: Vec, - /// The rewards recipient - account: Addr, - }, - /// Stake LP tokens in the Generator to receive token emissions - Deposit { - /// The LP token to stake - lp_token: Addr, - /// The account that receives ownership of the staked tokens - account: Addr, - /// The amount of tokens to deposit - amount: Uint128, - }, - /// Withdraw LP tokens from the Generator - Withdraw { - /// The LP tokens to withdraw - lp_token: Addr, - /// The account that receives the withdrawn LP tokens - account: Addr, - /// The amount of tokens to withdraw - amount: Uint128, - }, - /// Sets a new amount of ASTRO to distribute per block between all active generators - SetTokensPerBlock { - /// The new amount of ASTRO to distribute per block - amount: Uint128, - }, - /// Migrate LP tokens and collected rewards to new proxy - MigrateProxy { lp_addr: Addr, new_proxy_addr: Addr }, - /// Stake LP tokens into new reward proxy - MigrateProxyDepositLP { - lp_addr: Addr, - prev_proxy_addr: Addr, - amount: Uint128, - }, -} - -impl ExecuteOnReply { - pub fn into_submsg(self, env: &Env) -> StdResult { - let msg = SubMsg::new(WasmMsg::Execute { - contract_addr: env.contract.address.to_string(), - msg: to_json_binary(&ExecuteMsg::Callback { action: self })?, - funds: vec![], - }); - - Ok(msg) - } -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Returns the length of the array that contains all the active pool generators - #[returns(usize)] - ActivePoolLength {}, - /// PoolLength returns the length of the array that contains all the instantiated pool generators - #[returns(usize)] - PoolLength {}, - /// Deposit returns the LP token amount deposited in a specific generator - #[returns(Uint128)] - Deposit { lp_token: String, user: String }, - /// Returns the current virtual amount in a specific generator - #[returns(Uint128)] - UserVirtualAmount { lp_token: String, user: String }, - /// Returns the total virtual supply of generator - #[returns(Uint128)] - TotalVirtualSupply { generator: String }, - /// PendingToken returns the amount of rewards that can be claimed by an account that deposited a specific LP token in a generator - #[returns(PendingTokenResponse)] - PendingToken { lp_token: String, user: String }, - /// Config returns the main contract parameters - #[returns(Config)] - Config {}, - /// RewardInfo returns reward information for a specified LP token - #[returns(RewardInfoResponse)] - RewardInfo { lp_token: String }, - /// OrphanProxyRewards returns orphaned reward information for the specified LP token - #[returns(Vec<(AssetInfo, Uint128)>)] - OrphanProxyRewards { lp_token: String }, - /// PoolInfo returns information about a pool associated with the specified LP token alongside - /// the total pending amount of ASTRO and proxy rewards claimable by generator stakers (for that LP token) - #[returns(PoolInfoResponse)] - PoolInfo { lp_token: String }, - /// SimulateFutureReward returns the amount of ASTRO that will be distributed until a future block and for a specific generator - #[returns(Uint128)] - SimulateFutureReward { lp_token: String, future_block: u64 }, - /// Returns a list of stakers for a specific generator - #[returns(Vec)] - PoolStakers { - lp_token: String, - start_after: Option, - limit: Option, - }, - /// Returns the blocked list of tokens - #[returns(Vec)] - BlockedTokensList {}, - /// Returns a list of reward proxy contracts which have been ever used - #[returns(Vec)] - RewardProxiesList {}, -} - -/// This structure holds the response returned when querying the amount of pending rewards that can be withdrawn from a 3rd party -/// rewards contract -#[cw_serde] -pub struct PendingTokenResponse { - /// The amount of pending ASTRO - pub pending: Uint128, - /// The amount of pending 3rd party reward tokens - pub pending_on_proxy: Option>, -} - -/// This structure describes the main information of pool -#[cw_serde] -pub struct PoolInfo { - /// Accumulated amount of reward per share unit. Used for reward calculations - pub last_reward_block: Uint64, - pub reward_global_index: Decimal, - /// the reward proxy contract - pub reward_proxy: Option, - /// Accumulated reward indexes per reward proxy. Vector of pairs (reward_proxy, index). - pub accumulated_proxy_rewards_per_share: RestrictedVector, - /// for calculation of new proxy rewards - pub proxy_reward_balance_before_update: Uint128, - /// the orphan proxy rewards which are left by emergency withdrawals. Vector of pairs (reward_proxy, index). - pub orphan_proxy_rewards: RestrictedVector, - /// The pool has assets giving additional rewards - pub has_asset_rewards: bool, - /// Total virtual amount - pub total_virtual_supply: Uint128, -} - -/// This structure stores the outstanding amount of token rewards that a user accrued. -/// Currently the contract works with UserInfoV2 structure, but this structure is kept for -/// compatibility with the old version. -#[cw_serde] -#[derive(Default)] -pub struct UserInfo { - /// The amount of LP tokens staked - pub amount: Uint128, - /// The amount of ASTRO rewards a user already received or is not eligible for; used for proper reward calculation - pub reward_debt: Uint128, - /// Proxy reward amount a user already received or is not eligible for; used for proper reward calculation - pub reward_debt_proxy: Uint128, -} - -/// This structure stores the outstanding amount of token rewards that a user accrued. -#[cw_serde] -#[derive(Default)] -pub struct UserInfoV2 { - /// The amount of LP tokens staked - pub amount: Uint128, - /// The amount of ASTRO rewards a user already received or is not eligible for; used for proper reward calculation - pub reward_user_index: Decimal, - /// Proxy reward amount a user already received per reward proxy; used for proper reward calculation - /// Vector of pairs (reward_proxy, reward debited). - pub reward_debt_proxy: RestrictedVector, - /// The amount of user boosted emissions - pub virtual_amount: Uint128, -} - -/// This structure holds the response returned when querying for the token addresses used to reward a specific generator -#[cw_serde] -pub struct RewardInfoResponse { - /// The address of the base reward token - pub base_reward_token: Addr, - /// The address of the 3rd party reward token - pub proxy_reward_token: Option, -} - -/// This structure holds the response returned when querying for a pool's information -#[cw_serde] -pub struct PoolInfoResponse { - /// The slice of ASTRO that this pool's generator gets per block - pub alloc_point: Uint128, - /// Amount of ASTRO tokens being distributed per block to this LP pool - pub astro_tokens_per_block: Uint128, - /// The last block when token emissions were snapshotted (distributed) - pub last_reward_block: u64, - /// Current block number. Useful for computing APRs off-chain - pub current_block: u64, - /// Total amount of ASTRO rewards already accumulated per LP token staked - pub global_reward_index: Decimal, - /// Pending amount of total ASTRO rewards which are claimable by stakers right now - pub pending_astro_rewards: Uint128, - /// The address of the 3rd party reward proxy contract - pub reward_proxy: Option, - /// Pending amount of total proxy rewards which are claimable by stakers right now - pub pending_proxy_rewards: Option, - /// Total amount of 3rd party token rewards already accumulated per LP token staked per proxy - pub accumulated_proxy_rewards_per_share: Vec<(Addr, Decimal)>, - /// Reward balance for the dual rewards proxy before updating accrued rewards - pub proxy_reward_balance_before_update: Uint128, - /// The amount of orphan proxy rewards which are left behind by emergency withdrawals and not yet transferred out - pub orphan_proxy_rewards: Vec<(Addr, Uint128)>, - /// Total amount of lp tokens staked in the pool's generator - pub lp_supply: Uint128, -} - -/// This structure stores the core parameters for the Generator contract. -#[cw_serde] -pub struct Config { - /// Address allowed to change contract parameters - pub owner: Addr, - /// The Factory address - pub factory: Addr, - /// Contract address which can only set active generators and their alloc points - pub generator_controller: Option, - /// The voting escrow contract address - pub voting_escrow: Option, - /// The voting escrow delegation contract address - pub voting_escrow_delegation: Option, - /// The ASTRO token address - pub astro_token: Addr, - /// Total amount of ASTRO rewards per block - pub tokens_per_block: Uint128, - /// Total allocation points. Must be the sum of all allocation points in all active generators - pub total_alloc_point: Uint128, - /// The block number when the ASTRO distribution starts - pub start_block: Uint64, - /// The vesting contract from which rewards are distributed - pub vesting_contract: Addr, - /// The list of active pools with allocation points - pub active_pools: Vec<(Addr, Uint128)>, - /// The list of blocked tokens - pub blocked_tokens_list: Vec, - /// The guardian address which can add or remove tokens from blacklist - pub guardian: Option, - /// The amount of generators - pub checkpoint_generator_limit: Option, -} - -/// This structure describes a migration message. -#[cw_serde] -pub struct MigrateMsg { - /// The Factory address - pub factory: Option, - /// Contract address which can only set active generators and their alloc points - pub generator_controller: Option, - /// The blocked list of tokens - pub blocked_list_tokens: Option>, - /// The guardian address - pub guardian: Option, - /// Whitelist code id - pub whitelist_code_id: Option, - /// The voting escrow contract - pub voting_escrow: Option, - /// The voting escrow delegation contract - pub voting_escrow_delegation: Option, - /// The limit of generators - pub generator_limit: Option, -} - -/// This structure describes custom hooks for the CW20. -#[cw_serde] -pub enum Cw20HookMsg { - /// Deposit performs a token deposit on behalf of the message sender. - Deposit {}, - /// DepositFor performs a token deposit on behalf of another address that's not the message sender. - DepositFor(Addr), -} - -/// This structure holds the parameters used to return information about a staked in -/// a specific generator. -#[cw_serde] -pub struct StakerResponse { - // The staker's address - pub account: String, - // The amount that the staker currently has in the generator - pub amount: Uint128, -} diff --git a/packages/astroport/src/generator_proxy.rs b/packages/astroport/src/generator_proxy.rs deleted file mode 100644 index 9e2cd7c..0000000 --- a/packages/astroport/src/generator_proxy.rs +++ /dev/null @@ -1,90 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Uint128}; -use cw20::Cw20ReceiveMsg; - -/// This structure describes the basic parameters for creating a contract. -#[cw_serde] -pub struct InstantiateMsg { - /// The generator contract address - pub generator_contract_addr: String, - /// The pair contract address used in this generator proxy - pub pair_addr: String, - /// The LP contract address which can be staked in the reward_contract - pub lp_token_addr: String, - /// The 3rd party reward contract address - pub reward_contract_addr: String, - /// The 3rd party reward token contract address - pub reward_token_addr: String, -} - -#[cw_serde] -pub enum Cw20HookMsg { - Deposit {}, -} - -/// This structure describes the execute messages available in the contract. -#[cw_serde] -pub enum ExecuteMsg { - /// Receives a message of type [`Cw20ReceiveMsg`] - Receive(Cw20ReceiveMsg), - /// Withdraw pending token rewards from the 3rd party staking contract - UpdateRewards {}, - /// Sends rewards to a recipient - SendRewards { account: String, amount: Uint128 }, - /// Withdraw LP tokens and outstanding token rewards - Withdraw { - /// The address that will receive the withdrawn tokens and rewards - account: String, - /// The amount of LP tokens to withdraw - amount: Uint128, - }, - /// Withdraw LP tokens without claiming rewards - EmergencyWithdraw { - /// The address that will receive the withdrawn tokens - account: String, - /// The amount of LP tokens to withdraw - amount: Uint128, - }, - /// Callback of type [`CallbackMsg`] - Callback(CallbackMsg), -} - -/// This structure describes the callback messages available in the contract. -#[cw_serde] -pub enum CallbackMsg { - TransferLpTokensAfterWithdraw { - /// The LP token recipient - account: Addr, - /// The previous LP balance for the contract. This is used to calculate - /// the amount of received LP tokens after withdrawing from a third party contract - prev_lp_balance: Uint128, - }, -} - -/// This structure describes query messages available in the contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Returns the contract's core configuration - #[returns(ConfigResponse)] - Config {}, - /// Returns the amount of deposited LP tokens - #[returns(Uint128)] - Deposit {}, - /// Returns the amount of rewards to be distributed - #[returns(Uint128)] - Reward {}, - /// Returns the amount of pending rewards which can be claimed right now - #[returns(Uint128)] - PendingToken {}, - /// Returns the 3rd party reward token contract address - #[returns(Addr)] - RewardInfo {}, -} - -pub type ConfigResponse = InstantiateMsg; - -/// This structure describes a migration message. -/// We currently take no arguments for migrations -#[cw_serde] -pub struct MigrateMsg {} diff --git a/packages/astroport/src/lib.rs b/packages/astroport/src/lib.rs deleted file mode 100644 index 06abd7f..0000000 --- a/packages/astroport/src/lib.rs +++ /dev/null @@ -1,90 +0,0 @@ -pub mod asset; -pub mod common; -pub mod cosmwasm_ext; -pub mod factory; -pub mod generator; -pub mod generator_proxy; -pub mod maker; -pub mod native_coin_registry; -pub mod oracle; -pub mod pair; -pub mod pair_bonded; -pub mod pair_concentrated; -pub mod pair_stable_bluna; -pub mod querier; -pub mod restricted_vector; -pub mod router; -pub mod staking; -pub mod token; -pub mod xastro_token; - -#[cfg(test)] -mod mock_querier; - -#[cfg(test)] -mod testing; - -#[allow(clippy::all)] -mod uints { - use uint::construct_uint; - construct_uint! { - pub struct U256(4); - } -} - -mod decimal_checked_ops { - use cosmwasm_std::{Decimal, Fraction, OverflowError, Uint128, Uint256}; - use std::convert::TryInto; - pub trait DecimalCheckedOps { - fn checked_add(self, other: Decimal) -> Result; - fn checked_mul_uint128(self, other: Uint128) -> Result; - } - - impl DecimalCheckedOps for Decimal { - fn checked_add(self, other: Decimal) -> Result { - self.numerator() - .checked_add(other.numerator()) - .map(|_| self + other) - } - fn checked_mul_uint128(self, other: Uint128) -> Result { - if self.is_zero() || other.is_zero() { - return Ok(Uint128::zero()); - } - let multiply_ratio = - other.full_mul(self.numerator()) / Uint256::from(self.denominator()); - if multiply_ratio > Uint256::from(Uint128::MAX) { - Err(OverflowError::new( - cosmwasm_std::OverflowOperation::Mul, - self, - other, - )) - } else { - Ok(multiply_ratio.try_into().unwrap()) - } - } - } -} - -use cosmwasm_std::{Decimal, Decimal256, StdError, StdResult, Uint128}; - -/// Converts [`Decimal`] to [`Decimal256`]. -/// TODO: can be safely removed as there is Decimal256::from(v: Decimal) -pub fn decimal2decimal256(dec_value: Decimal) -> StdResult { - Decimal256::from_atomics(dec_value.atomics(), dec_value.decimal_places()).map_err(|_| { - StdError::generic_err(format!( - "Failed to convert Decimal {} to Decimal256", - dec_value - )) - }) -} - -/// Converts [`Decimal256`] to [`Decimal`]. -pub fn to_decimal(value: Decimal256) -> StdResult { - let atomics = Uint128::try_from(value.atomics())?; - Decimal::from_atomics(atomics, value.decimal_places()).map_err(|_| { - StdError::generic_err(format!("Failed to convert Decimal256 {} to Decimal", value)) - }) -} - -pub use decimal_checked_ops::DecimalCheckedOps; -pub use uints::U256; diff --git a/packages/astroport/src/maker.rs b/packages/astroport/src/maker.rs deleted file mode 100644 index f8b1775..0000000 --- a/packages/astroport/src/maker.rs +++ /dev/null @@ -1,125 +0,0 @@ -use crate::asset::{Asset, AssetInfo}; -use crate::factory::UpdateAddr; -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Decimal, Uint128, Uint64}; - -/// This structure stores general parameters for the contract. -#[cw_serde] -pub struct InstantiateMsg { - /// Address that's allowed to change contract parameters - pub owner: String, - /// The ASTRO token contract address - pub astro_token_contract: String, - /// The factory contract address - pub factory_contract: String, - /// The xASTRO staking contract address - pub staking_contract: String, - /// The governance contract address (fee distributor for vxASTRO) - pub governance_contract: Option, - /// The percentage of fees that go to governance_contract - pub governance_percent: Option, - /// The maximum spread used when swapping fee tokens to ASTRO - pub max_spread: Option, -} - -/// This structure describes the functions that can be executed in this contract. -#[cw_serde] -pub enum ExecuteMsg { - /// Collects and swaps fee tokens to ASTRO - Collect { - /// The assets to swap to ASTRO - assets: Vec, - }, - /// Updates general settings - UpdateConfig { - /// The factory contract address - factory_contract: Option, - /// The xASTRO staking contract address - staking_contract: Option, - /// The governance contract address (fee distributor for vxASTRO) - governance_contract: Option, - /// The percentage of fees that go to governance_contract - governance_percent: Option, - /// The maximum spread used when swapping fee tokens to ASTRO - max_spread: Option, - }, - /// Add bridge tokens used to swap specific fee tokens to ASTRO (effectively declaring a swap route) - UpdateBridges { - add: Option>, - remove: Option>, - }, - /// Swap fee tokens via bridge assets - SwapBridgeAssets { assets: Vec, depth: u64 }, - /// Distribute ASTRO to stakers and to governance - DistributeAstro {}, - /// Creates a request to change the contract's ownership - ProposeNewOwner { - /// The newly proposed owner - owner: String, - /// The validity period of the proposal to change the owner - expires_in: u64, - }, - /// Removes a request to change contract ownership - DropOwnershipProposal {}, - /// Claims contract ownership - ClaimOwnership {}, - /// Enables the distribution of current fees accrued in the contract over "blocks" number of blocks - EnableRewards { blocks: u64 }, -} - -/// This structure describes the query functions available in the contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Returns information about the maker configs that contains in the [`ConfigResponse`] - #[returns(ConfigResponse)] - Config {}, - /// Returns the balance for each asset in the specified input parameters - #[returns(BalancesResponse)] - Balances { assets: Vec }, - #[returns(Vec<(String, String)>)] - Bridges {}, -} - -/// A custom struct that holds contract parameters and is used to retrieve them. -#[cw_serde] -pub struct ConfigResponse { - /// Address that is allowed to update contract parameters - pub owner: Addr, - /// The ASTRO token contract address - pub astro_token_contract: Addr, - /// The factory contract address - pub factory_contract: Addr, - /// The xASTRO staking contract address - pub staking_contract: Addr, - /// The governance contract address (fee distributor for vxASTRO stakers) - pub governance_contract: Option, - /// The percentage of fees that go to governance_contract - pub governance_percent: Uint64, - /// The maximum spread used when swapping fee tokens to ASTRO - pub max_spread: Decimal, - /// The remainder ASTRO tokens (accrued before the Maker is upgraded) to be distributed to xASTRO stakers - pub remainder_reward: Uint128, - /// The amount of ASTRO tokens accrued before upgrading the Maker implementation and enabling reward distribution - pub pre_upgrade_astro_amount: Uint128, -} - -/// A custom struct used to return multiple asset balances. -#[cw_serde] -pub struct BalancesResponse { - pub balances: Vec, -} - -/// This structure describes a migration message. -/// We currently take no arguments for migrations. -#[cw_serde] -pub struct MigrateMsg {} - -/// This struct holds parameters to help with swapping a specific amount of a fee token to ASTRO. -#[cw_serde] -pub struct AssetWithLimit { - /// Information about the fee token to swap - pub info: AssetInfo, - /// The amount of tokens to swap - pub limit: Option, -} diff --git a/packages/astroport/src/mock_querier.rs b/packages/astroport/src/mock_querier.rs deleted file mode 100644 index d8ad034..0000000 --- a/packages/astroport/src/mock_querier.rs +++ /dev/null @@ -1,236 +0,0 @@ -use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::{ - from_json, to_json_binary, Coin, Empty, OwnedDeps, Querier, QuerierResult, QueryRequest, - SystemError, SystemResult, Uint128, WasmQuery, -}; - -use std::collections::HashMap; - -use crate::asset::PairInfo; -use crate::factory::QueryMsg as FactoryQueryMsg; -use cw20::{BalanceResponse, Cw20QueryMsg, TokenInfoResponse}; - -/// mock_dependencies is a drop-in replacement for cosmwasm_std::tests::mock_dependencies -/// This uses the Astroport CustomQuerier. -pub fn mock_dependencies( - contract_balance: &[Coin], -) -> OwnedDeps { - let custom_querier: WasmMockQuerier = - WasmMockQuerier::new(MockQuerier::new(&[(MOCK_CONTRACT_ADDR, contract_balance)])); - - OwnedDeps { - storage: MockStorage::default(), - api: MockApi::default(), - querier: custom_querier, - custom_query_type: Default::default(), - } -} -enum QueryHandler { - Default, - Cw20, -} - -pub struct WasmMockQuerier { - query_handler: DefaultQueryHandler, - cw20_query_handler: CW20QueryHandler, - handler: QueryHandler, -} - -#[derive(Clone, Default)] -pub struct TokenQuerier { - /// This lets us iterate over all pairs that match the first string - balances: HashMap>, -} - -impl TokenQuerier { - pub fn new(balances: &[(&String, &[(&String, &Uint128)])]) -> Self { - TokenQuerier { - balances: balances_to_map(balances), - } - } -} - -pub(crate) fn balances_to_map( - balances: &[(&String, &[(&String, &Uint128)])], -) -> HashMap> { - let mut balances_map: HashMap> = HashMap::new(); - for (contract_addr, balances) in balances.iter() { - let mut contract_balances_map: HashMap = HashMap::new(); - for (addr, balance) in balances.iter() { - contract_balances_map.insert(addr.to_string(), **balance); - } - - balances_map.insert(String::from(*contract_addr), contract_balances_map); - } - balances_map -} - -#[derive(Clone, Default)] -pub struct AstroportFactoryQuerier { - pairs: HashMap, -} - -impl AstroportFactoryQuerier { - pub fn new(pairs: &[(&String, &PairInfo)]) -> Self { - AstroportFactoryQuerier { - pairs: pairs_to_map(pairs), - } - } -} - -pub(crate) fn pairs_to_map(pairs: &[(&String, &PairInfo)]) -> HashMap { - let mut pairs_map: HashMap = HashMap::new(); - for (key, pair) in pairs.iter() { - pairs_map.insert(key.to_string(), (*pair).clone()); - } - pairs_map -} - -impl Querier for WasmMockQuerier { - fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - // MockQuerier doesn't support Custom, so we ignore it completely here - let request: QueryRequest = match from_json(bin_request) { - Ok(v) => v, - Err(e) => { - return SystemResult::Err(SystemError::InvalidRequest { - error: format!("Parsing query request: {}", e), - request: bin_request.into(), - }); - } - }; - self.handle_query(&request) - } -} - -impl WasmMockQuerier { - pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { - match self.handler { - QueryHandler::Default => self.query_handler.execute(request), - QueryHandler::Cw20 => self.cw20_query_handler.execute(request), - } - } -} - -struct CW20QueryHandler { - token_querier: TokenQuerier, -} - -impl CW20QueryHandler { - pub fn execute(&self, request: &QueryRequest) -> QuerierResult { - match &request { - QueryRequest::Wasm(WasmQuery::Smart { contract_addr, msg }) => { - match from_json(msg).unwrap() { - Cw20QueryMsg::TokenInfo {} => { - let balances: &HashMap = - match self.token_querier.balances.get(contract_addr) { - Some(balances) => balances, - None => { - return SystemResult::Err(SystemError::Unknown {}); - } - }; - - let mut total_supply = Uint128::zero(); - - for balance in balances { - total_supply += *balance.1; - } - - SystemResult::Ok( - to_json_binary(&TokenInfoResponse { - name: "mAPPL".to_string(), - symbol: "mAPPL".to_string(), - decimals: 6, - total_supply, - }) - .into(), - ) - } - Cw20QueryMsg::Balance { address } => { - let balances: &HashMap = - match self.token_querier.balances.get(contract_addr) { - Some(balances) => balances, - None => { - return SystemResult::Err(SystemError::Unknown {}); - } - }; - - let balance = match balances.get(&address) { - Some(v) => v, - None => { - return SystemResult::Err(SystemError::Unknown {}); - } - }; - - SystemResult::Ok( - to_json_binary(&BalanceResponse { balance: *balance }).into(), - ) - } - _ => panic!("DO NOT ENTER HERE"), - } - } - _ => panic!("DO NOT ENTER HERE"), - } - } -} - -struct DefaultQueryHandler { - base: MockQuerier, - astroport_factory_querier: AstroportFactoryQuerier, -} - -impl DefaultQueryHandler { - pub fn execute(&self, request: &QueryRequest) -> QuerierResult { - match &request { - QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: _, - msg, - }) => match from_json(msg).unwrap() { - FactoryQueryMsg::Pair { asset_infos } => { - let key = asset_infos[0].to_string() + asset_infos[1].to_string().as_str(); - match self.astroport_factory_querier.pairs.get(&key) { - Some(v) => SystemResult::Ok(to_json_binary(&v).into()), - None => SystemResult::Err(SystemError::InvalidRequest { - error: "No pair info exists".to_string(), - request: msg.as_slice().into(), - }), - } - } - _ => panic!("DO NOT ENTER HERE"), - }, - _ => self.base.handle_query(request), - } - } -} - -impl WasmMockQuerier { - pub fn new(base: MockQuerier) -> Self { - WasmMockQuerier { - query_handler: DefaultQueryHandler { - base, - astroport_factory_querier: AstroportFactoryQuerier::default(), - }, - cw20_query_handler: CW20QueryHandler { - token_querier: TokenQuerier::default(), - }, - handler: QueryHandler::Default, - } - } - - // Configure the mint whitelist mock querier - pub fn with_token_balances(&mut self, balances: &[(&String, &[(&String, &Uint128)])]) { - self.cw20_query_handler.token_querier = TokenQuerier::new(balances); - } - - // Configure the Astroport pair - pub fn with_astroport_pairs(&mut self, pairs: &[(&String, &PairInfo)]) { - self.query_handler.astroport_factory_querier = AstroportFactoryQuerier::new(pairs); - } - - pub fn with_default_query_handler(&mut self) { - self.handler = QueryHandler::Default; - } - - pub fn with_cw20_query_handler(&mut self) { - self.handler = QueryHandler::Cw20; - } -} diff --git a/packages/astroport/src/native_coin_registry.rs b/packages/astroport/src/native_coin_registry.rs deleted file mode 100644 index 38b697f..0000000 --- a/packages/astroport/src/native_coin_registry.rs +++ /dev/null @@ -1,81 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Addr; -use cw_storage_plus::Map; - -/// This structure stores the main parameters for the native coin registry contract. -#[cw_serde] -pub struct Config { - /// Address that's allowed to change contract parameters - pub owner: Addr, -} - -/// This structure describes the parameters used for creating a contract. -#[cw_serde] -pub struct InstantiateMsg { - /// Address allowed to change contract parameters - pub owner: String, -} - -/// This structure describes the execute messages available in the contract. -#[cw_serde] -pub enum ExecuteMsg { - /// Adds or updates native assets with specified precisions - /// ## Executor - /// Only the current owner can execute this - Add { native_coins: Vec<(String, u8)> }, - /// Removes the native assets by specified parameters - /// ## Executor - /// Only the current owner can execute this - Remove { native_coins: Vec }, - /// Creates a request to change contract ownership - /// ## Executor - /// Only the current owner can execute this - ProposeNewOwner { - /// The newly proposed owner - owner: String, - /// The validity period of the offer to change the owner - expires_in: u64, - }, - /// Removes a request to change contract ownership - /// ## Executor - /// Only the current owner can execute this - DropOwnershipProposal {}, - /// Claims contract ownership - /// ## Executor - /// Only the newly proposed owner can execute this - ClaimOwnership {}, -} - -/// This structure describes the query messages available in the contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Returns the configuration for the contract. - #[returns(Config)] - Config {}, - /// Returns the information about Asset by specified denominator. - #[returns(CoinResponse)] - NativeToken { denom: String }, - /// Returns a vector which contains the native assets. - #[returns(Vec)] - NativeTokens { - start_after: Option, - limit: Option, - }, -} - -#[cw_serde] -pub struct CoinResponse { - /// The asset name - pub denom: String, - /// The asset precision - pub decimals: u8, -} - -/// This structure describes a migration message. -/// We currently take no arguments for migrations. -#[cw_serde] -pub struct MigrateMsg {} - -/// The first key is denom, the second key is a precision. -pub const COINS_INFO: Map = Map::new("coins_info"); diff --git a/packages/astroport/src/oracle.rs b/packages/astroport/src/oracle.rs deleted file mode 100644 index 64e8a44..0000000 --- a/packages/astroport/src/oracle.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::asset::{AssetInfo, PairInfo}; -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Addr; -use cosmwasm_std::{Decimal256, Uint128, Uint256, Uint64}; - -/// This structure stores general parameters for the contract. -/// Modified by us -#[cw_serde] -pub struct InstantiateMsg { - /// The factory contract address - pub factory_contract: String, - /// Minimal interval between Update{}'s - pub period: u64, - /// Manager is the only one who can set pair info, if not set already - pub manager: String, -} - -/// This structure describes the execute functions available in the contract. -#[cw_serde] -pub enum ExecuteMsg { - /// Update/accumulate prices - Update {}, - /// Update period - UpdatePeriod { new_period: u64 }, - /// Set a new manager, only owner can use this message - UpdateManager { new_manager: String }, - /// Set asset infos that have a pool for which this contract provides price feeds. - /// Only manager can use this message - SetAssetInfos(Vec), -} - -/// This structure describes the query messages available in the contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Calculates a new TWAP with updated precision - #[returns(Vec<(AssetInfo, Uint256)>)] - Consult { - /// The asset for which to compute a new TWAP value - token: AssetInfo, - /// The amount of tokens for which to compute the token price - amount: Uint128, - }, - #[returns(Vec<(AssetInfo, Decimal256)>)] - TWAPAtHeight { - /// The asset for which to compute a new TWAP value - token: AssetInfo, - /// The amount of tokens for which to compute the token price - height: Uint64, - }, - /// Returns the contract's conriguration structure - #[returns(Config)] - Config {}, - /// Returns the timestamp of the block when the previous update happened - #[returns(u64)] - LastUpdateTimestamp {}, -} - -/// This structure describes a migration message. -/// We currently take no arguments for migrations. -#[cw_serde] -pub struct MigrateMsg {} - -/// Global configuration for the contract -#[cw_serde] -pub struct Config { - /// The address that's allowed to change contract parameters - pub owner: Addr, - /// The factory contract address - pub factory: Addr, - /// The assets in the pool. Each asset is described using a [`AssetInfo`] - pub asset_infos: Option>, - /// Information about the pair (LP token address, pair type etc) - pub pair: Option, - /// Time between two consecutive TWAP updates. - pub period: u64, - /// Manager is the only one who can set pair info, if not set already - pub manager: Addr, -} diff --git a/packages/astroport/src/pair.rs b/packages/astroport/src/pair.rs deleted file mode 100644 index cfcbc70..0000000 --- a/packages/astroport/src/pair.rs +++ /dev/null @@ -1,276 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; - -use crate::asset::{Asset, AssetInfo, PairInfo}; - -use cosmwasm_std::{from_json, Addr, Binary, Decimal, QuerierWrapper, StdResult, Uint128}; -use cw20::Cw20ReceiveMsg; - -/// The default swap slippage -pub const DEFAULT_SLIPPAGE: &str = "0.005"; -/// The maximum allowed swap slippage -pub const MAX_ALLOWED_SLIPPAGE: &str = "0.5"; - -/// Decimal precision for TWAP results -pub const TWAP_PRECISION: u8 = 6; - -/// This structure describes the parameters used for creating a contract. -#[cw_serde] -pub struct InstantiateMsg { - /// Information about assets in the pool - pub asset_infos: Vec, - /// The token contract code ID used for the tokens in the pool - pub token_code_id: u64, - /// The factory contract address - pub factory_addr: String, - /// Optional binary serialised parameters for custom pool types - pub init_params: Option, -} - -/// This structure describes the execute messages available in the contract. -#[cw_serde] -pub enum ExecuteMsg { - /// Receives a message of type [`Cw20ReceiveMsg`] - Receive(Cw20ReceiveMsg), - /// ProvideLiquidity allows someone to provide liquidity in the pool - ProvideLiquidity { - /// The assets available in the pool - assets: Vec, - /// The slippage tolerance that allows liquidity provision only if the price in the pool doesn't move too much - slippage_tolerance: Option, - /// Determines whether the LP tokens minted for the user is auto_staked in the Generator contract - auto_stake: Option, - /// The receiver of LP tokens - receiver: Option, - }, - /// Swap performs a swap in the pool - Swap { - offer_asset: Asset, - ask_asset_info: Option, - belief_price: Option, - max_spread: Option, - to: Option, - }, - /// Update the pair configuration - UpdateConfig { params: Binary }, - /// ProposeNewOwner creates a proposal to change contract ownership. - /// The validity period for the proposal is set in the `expires_in` variable. - ProposeNewOwner { - /// Newly proposed contract owner - owner: String, - /// The date after which this proposal expires - expires_in: u64, - }, - /// DropOwnershipProposal removes the existing offer to change contract ownership. - DropOwnershipProposal {}, - /// Used to claim contract ownership. - ClaimOwnership {}, -} - -/// This structure describes a CW20 hook message. -#[cw_serde] -pub enum Cw20HookMsg { - /// Swap a given amount of asset - Swap { - ask_asset_info: Option, - belief_price: Option, - max_spread: Option, - to: Option, - }, - /// Withdraw liquidity from the pool - WithdrawLiquidity { - #[serde(default)] - assets: Vec, - }, -} - -/// This structure describes the query messages available in the contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Returns information about a pair in an object of type [`super::asset::PairInfo`]. - #[returns(PairInfo)] - Pair {}, - /// Returns information about a pool in an object of type [`PoolResponse`]. - #[returns(PoolResponse)] - Pool {}, - /// Returns contract configuration settings in a custom [`ConfigResponse`] structure. - #[returns(ConfigResponse)] - Config {}, - /// Returns information about the share of the pool in a vector that contains objects of type [`Asset`]. - #[returns(Vec)] - Share { amount: Uint128 }, - /// Returns information about a swap simulation in a [`SimulationResponse`] object. - #[returns(SimulationResponse)] - Simulation { - offer_asset: Asset, - ask_asset_info: Option, - }, - /// Returns information about cumulative prices in a [`ReverseSimulationResponse`] object. - #[returns(ReverseSimulationResponse)] - ReverseSimulation { - offer_asset_info: Option, - ask_asset: Asset, - }, - /// Returns information about the cumulative prices in a [`CumulativePricesResponse`] object - #[returns(CumulativePricesResponse)] - CumulativePrices {}, - /// Returns current D invariant in as a [`u128`] value - #[returns(Uint128)] - QueryComputeD {}, -} - -/// This struct is used to return a query result with the total amount of LP tokens and assets in a specific pool. -#[cw_serde] -pub struct PoolResponse { - /// The assets in the pool together with asset amounts - pub assets: Vec, - /// The total amount of LP tokens currently issued - pub total_share: Uint128, -} - -/// This struct is used to return a query result with the general contract configuration. -#[cw_serde] -pub struct ConfigResponse { - /// Last timestamp when the cumulative prices in the pool were updated - pub block_time_last: u64, - /// The pool's parameters - pub params: Option, - /// The contract owner - pub owner: Option, -} - -/// This structure holds the parameters that are returned from a swap simulation response -#[cw_serde] -pub struct SimulationResponse { - /// The amount of ask assets returned by the swap - pub return_amount: Uint128, - /// The spread used in the swap operation - pub spread_amount: Uint128, - /// The amount of fees charged by the transaction - pub commission_amount: Uint128, -} - -/// This structure holds the parameters that are returned from a reverse swap simulation response. -#[cw_serde] -pub struct ReverseSimulationResponse { - /// The amount of offer assets returned by the reverse swap - pub offer_amount: Uint128, - /// The spread used in the swap operation - pub spread_amount: Uint128, - /// The amount of fees charged by the transaction - pub commission_amount: Uint128, -} - -/// This structure is used to return a cumulative prices query response. -#[cw_serde] -pub struct CumulativePricesResponse { - /// The assets in the pool to query - pub assets: Vec, - /// The total amount of LP tokens currently issued - pub total_share: Uint128, - /// The vector contains cumulative prices for each pair of assets in the pool - pub cumulative_prices: Vec<(AssetInfo, AssetInfo, Uint128)>, -} - -/// This structure describes a migration message. -/// We currently take no arguments for migrations. -#[cw_serde] -pub struct MigrateMsg {} - -/// This structure holds stableswap pool parameters. -#[cw_serde] -pub struct StablePoolParams { - /// The current stableswap pool amplification - pub amp: u64, - /// The contract owner - pub owner: Option, -} - -/// This structure stores a stableswap pool's configuration. -#[cw_serde] -pub struct StablePoolConfig { - /// The stableswap pool amplification - pub amp: Decimal, -} - -/// This enum stores the options available to start and stop changing a stableswap pool's amplification. -#[cw_serde] -pub enum StablePoolUpdateParams { - StartChangingAmp { next_amp: u64, next_amp_time: u64 }, - StopChangingAmp {}, -} - -/// This function makes raw query to the factory contract and -/// checks whether the pair needs to update an owner or not. -pub fn migration_check( - querier: QuerierWrapper, - factory: &Addr, - pair_addr: &Addr, -) -> StdResult { - if let Some(res) = querier.query_wasm_raw(factory, b"pairs_to_migrate".as_slice())? { - let res: Vec = from_json(res)?; - Ok(res.contains(pair_addr)) - } else { - Ok(false) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::asset::native_asset_info; - use cosmwasm_std::{from_json, to_json_binary}; - - #[cw_serde] - pub struct LegacyInstantiateMsg { - pub asset_infos: [AssetInfo; 2], - pub token_code_id: u64, - pub factory_addr: String, - pub init_params: Option, - } - - #[cw_serde] - pub struct LegacyConfigResponse { - pub block_time_last: u64, - pub params: Option, - } - - #[test] - fn test_init_msg_compatability() { - let inst_msg = LegacyInstantiateMsg { - asset_infos: [ - native_asset_info("uusd".to_string()), - native_asset_info("uluna".to_string()), - ], - token_code_id: 0, - factory_addr: "factory".to_string(), - init_params: None, - }; - - let ser_msg = to_json_binary(&inst_msg).unwrap(); - // This .unwrap() is enough to make sure that [AssetInfo; 2] and Vec are compatible. - let _: InstantiateMsg = from_json(ser_msg).unwrap(); - } - - #[test] - fn test_config_response_compatability() { - let ser_msg = to_json_binary(&LegacyConfigResponse { - block_time_last: 12, - params: Some( - to_json_binary(&StablePoolConfig { - amp: Decimal::one(), - }) - .unwrap(), - ), - }) - .unwrap(); - - let _: ConfigResponse = from_json(ser_msg).unwrap(); - } - - #[test] - fn check_empty_vec_deserialization() { - let variant: Cw20HookMsg = from_json(br#"{"withdraw_liquidity": {} }"#).unwrap(); - assert_eq!(variant, Cw20HookMsg::WithdrawLiquidity { assets: vec![] }); - } -} diff --git a/packages/astroport/src/pair_bonded.rs b/packages/astroport/src/pair_bonded.rs deleted file mode 100644 index c2fc7c0..0000000 --- a/packages/astroport/src/pair_bonded.rs +++ /dev/null @@ -1,88 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; - -use crate::asset::{Asset, AssetInfo, PairInfo}; -use crate::pair::{ - ConfigResponse, CumulativePricesResponse, PoolResponse, ReverseSimulationResponse, - SimulationResponse, -}; - -use cosmwasm_std::{Addr, Binary, Decimal, Uint128}; -use cw20::Cw20ReceiveMsg; - -/// The default swap slippage -pub const DEFAULT_SLIPPAGE: &str = "0.005"; -/// The maximum allowed swap slippage -pub const MAX_ALLOWED_SLIPPAGE: &str = "0.5"; - -/// This structure stores the main config parameters for a constant product pair contract. -#[cw_serde] -pub struct Config { - /// General pair information (e.g pair type) - pub pair_info: PairInfo, - /// The factory contract address - pub factory_addr: Addr, -} - -/// This structure describes the execute messages available in the contract. -#[cw_serde] -pub enum ExecuteMsg { - /// Receives a message of type [`Cw20ReceiveMsg`] - Receive(Cw20ReceiveMsg), - /// ProvideLiquidity allows someone to provide liquidity in the pool - ProvideLiquidity { - /// The assets available in the pool - assets: [Asset; 2], - /// The slippage tolerance that allows liquidity provision only if the price in the pool doesn't move too much - slippage_tolerance: Option, - /// Determines whether the LP tokens minted for the user is auto_staked in the Generator contract - auto_stake: Option, - /// The receiver of LP tokens - receiver: Option, - }, - /// Swap performs a swap in the pool - Swap { - offer_asset: Asset, - belief_price: Option, - max_spread: Option, - to: Option, - }, - /// Update the pair configuration - UpdateConfig { params: Binary }, - /// Callback to process post-swap operation - AssertAndSend { - offer_asset: Asset, - /// Information about an asset stored in a [`AssetInfo`] struct - ask_asset_info: AssetInfo, - /// Receiver who should receive the funds - receiver: Addr, - /// Sender who initiated the transaction - sender: Addr, - }, -} - -/// This structure describes the query messages available in the contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Returns information about a pair in an object of type [`super::asset::PairInfo`]. - #[returns(PairInfo)] - Pair {}, - /// Returns information about a pool in an object of type [`PoolResponse`]. - #[returns(PoolResponse)] - Pool {}, - /// Returns contract configuration settings in a custom [`ConfigResponse`] structure. - #[returns(ConfigResponse)] - Config {}, - /// Returns information about the share of the pool in a vector that contains objects of type [`Asset`]. - #[returns(Vec)] - Share { amount: Uint128 }, - /// Returns information about a swap simulation in a [`SimulationResponse`] object. - #[returns(SimulationResponse)] - Simulation { offer_asset: Asset }, - /// Returns information about cumulative prices in a [`ReverseSimulationResponse`] object. - #[returns(ReverseSimulationResponse)] - ReverseSimulation { ask_asset: Asset }, - /// Returns information about the cumulative prices in a [`CumulativePricesResponse`] object - #[returns(CumulativePricesResponse)] - CumulativePrices {}, -} diff --git a/packages/astroport/src/pair_concentrated.rs b/packages/astroport/src/pair_concentrated.rs deleted file mode 100644 index 702f0fe..0000000 --- a/packages/astroport/src/pair_concentrated.rs +++ /dev/null @@ -1,103 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Decimal, Decimal256, Uint128}; - -use crate::asset::PairInfo; -use crate::asset::{Asset, AssetInfo}; -use crate::pair::{ - ConfigResponse, CumulativePricesResponse, PoolResponse, ReverseSimulationResponse, - SimulationResponse, -}; - -/// This structure holds concentrated pool parameters. -#[cw_serde] -pub struct ConcentratedPoolParams { - /// Amplification coefficient affects trades close to price_scale - pub amp: Decimal, - /// Affects how gradual the curve changes from constant sum to constant product - /// as price moves away from price scale. Low values mean more gradual. - pub gamma: Decimal, - /// The minimum fee, charged when pool is fully balanced - pub mid_fee: Decimal, - /// The maximum fee, charged when pool is imbalanced - pub out_fee: Decimal, - /// Parameter that defines how gradual the fee changes from fee_mid to fee_out - /// based on distance from price_scale. - pub fee_gamma: Decimal, - /// Minimum profit before initiating a new repeg - pub repeg_profit_threshold: Decimal, - /// Minimum amount to change price_scale when repegging. - pub min_price_scale_delta: Decimal, - /// 1 x\[0] = price_scale * x\[1]. - pub price_scale: Decimal, - /// Half-time used for calculating the price oracle. - pub ma_half_time: u64, -} - -/// This structure holds concentrated pool parameters which can be changed immediately. -#[cw_serde] -pub struct UpdatePoolParams { - pub mid_fee: Option, - pub out_fee: Option, - pub fee_gamma: Option, - pub repeg_profit_threshold: Option, - pub min_price_scale_delta: Option, - pub ma_half_time: Option, -} - -/// Amp and gamma should be changed gradually. This structure holds all necessary parameters. -#[cw_serde] -pub struct PromoteParams { - pub next_amp: Decimal, - pub next_gamma: Decimal, - pub future_time: u64, -} - -/// This enum intended for parameters update. -#[cw_serde] -pub enum ConcentratedPoolUpdateParams { - /// Allows to update fee parameters as well as repeg_profit_threshold, min_price_scale_delta and EMA interval. - Update(UpdatePoolParams), - /// Starts gradual (de/in)crease of Amp or Gamma parameters. Can handle an update of both of them. - Promote(PromoteParams), - /// Stops Amp and Gamma update and stores current values. - StopChangingAmpGamma {}, -} - -/// This structure describes the query messages available in the contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Returns information about a pair - #[returns(PairInfo)] - Pair {}, - /// Returns information about a pool - #[returns(PoolResponse)] - Pool {}, - /// Returns contract configuration - #[returns(ConfigResponse)] - Config {}, - /// Returns information about the share of the pool in a vector that contains objects of type [`Asset`]. - #[returns(Vec)] - Share { amount: Uint128 }, - /// Returns information about a swap simulation - #[returns(SimulationResponse)] - Simulation { - offer_asset: Asset, - ask_asset_info: Option, - }, - /// Returns information about a reverse swap simulation - #[returns(ReverseSimulationResponse)] - ReverseSimulation { - offer_asset_info: Option, - ask_asset: Asset, - }, - /// Returns information about the cumulative prices - #[returns(CumulativePricesResponse)] - CumulativePrices {}, - /// Returns current D invariant - #[returns(Decimal256)] - ComputeD {}, - /// Query LP token virtual price - #[returns(Decimal256)] - LpPrice {}, -} diff --git a/packages/astroport/src/pair_stable_bluna.rs b/packages/astroport/src/pair_stable_bluna.rs deleted file mode 100644 index f954338..0000000 --- a/packages/astroport/src/pair_stable_bluna.rs +++ /dev/null @@ -1,127 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; - -use crate::asset::{Asset, PairInfo}; -use crate::pair::{ - ConfigResponse, CumulativePricesResponse, PoolResponse, ReverseSimulationResponse, - SimulationResponse, -}; -use cosmwasm_std::{Addr, Binary, Decimal, Uint128}; -use cw20::Cw20ReceiveMsg; - -/// This structure describes the execute messages available in the contract. -#[cw_serde] -pub enum ExecuteMsg { - /// Receives a message of type [`Cw20ReceiveMsg`] - Receive(Cw20ReceiveMsg), - /// ProvideLiquidity allows an account to provide liquidity in a pool with bLUNA - ProvideLiquidity { - /// The two assets available in the pool - assets: [Asset; 2], - /// The slippage tolerance that allows liquidity provision only if the price in the pool doesn't move too much - slippage_tolerance: Option, - /// Determines whether the LP tokens minted for the user is auto_staked in the Generator contract - auto_stake: Option, - /// The receiver of LP tokens - receiver: Option, - }, - /// Swap performs a swap in the pool - Swap { - offer_asset: Asset, - belief_price: Option, - max_spread: Option, - to: Option, - }, - /// Update the pair configuration - UpdateConfig { params: Binary }, - /// Claims bLUNA rewards and sends them to the specified receiver - ClaimReward { - /// An address which will receive the bLUNA reward - receiver: Option, - }, - /// Claims the bLUNA reward for a user that deposited their LP tokens in the Generator contract - ClaimRewardByGenerator { - /// The user whose LP tokens are/were staked in the Generator - user: String, - /// The user's LP token amount before the LP token transfer between their wallet and the Generator - user_share: Uint128, - /// The total LP token amount already deposited by all users in the Generator - total_share: Uint128, - }, - /// Callback for distributing bLUNA rewards - HandleReward { - previous_reward_balance: Uint128, - user: Addr, - user_share: Uint128, - total_share: Uint128, - receiver: Option, - }, -} - -/// This structure describes the query messages available in the contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Returns information about a pair in an object of type [`super::asset::PairInfo`]. - #[returns(PairInfo)] - Pair {}, - /// Returns information about a pool in an object of type [`super::pair::PoolResponse`]. - #[returns(PoolResponse)] - Pool {}, - /// Returns contract configuration settings in a custom [`super::pair::ConfigResponse`] structure. - #[returns(ConfigResponse)] - Config {}, - /// Returns information about the share of the pool in a vector that contains objects of type [`Asset`]. - #[returns(Vec)] - Share { amount: Uint128 }, - /// Returns information about a swap simulation in a [`super::pair::SimulationResponse`] object. - #[returns(SimulationResponse)] - Simulation { offer_asset: Asset }, - /// Returns information about a reverse simulation in a [`super::pair::ReverseSimulationResponse`] object. - #[returns(ReverseSimulationResponse)] - ReverseSimulation { ask_asset: Asset }, - /// Returns information about cumulative prices (used for TWAPs) in a [`super::pair::CumulativePricesResponse`] object. - #[returns(CumulativePricesResponse)] - CumulativePrices {}, - /// Returns pending token rewards that can be claimed by a specific user using a [`Asset`] object. - #[returns(Asset)] - PendingReward { user: String }, -} - -/// This struct is used to store bLUNA stableswap specific parameters. -#[cw_serde] -pub struct StablePoolParams { - /// The current pool amplification - pub amp: u64, - /// The bLUNA rewarder contract - pub bluna_rewarder: String, - /// The Astroport Generator contract - pub generator: String, -} - -/// This struct is used to store the stableswap pool configuration. -#[cw_serde] -pub struct StablePoolConfig { - /// The current pool amplification - pub amp: Decimal, - /// The bLUNA rewarder contract - pub bluna_rewarder: Addr, - /// The Astroport Generator contract - pub generator: Addr, -} - -/// This enum stores the options available to update bLUNA stableswap pool parameters. -#[cw_serde] -pub enum StablePoolUpdateParams { - StartChangingAmp { next_amp: u64, next_amp_time: u64 }, - StopChangingAmp {}, - BlunaRewarder { address: String }, -} - -/// This struct contains the parameters used to migrate the bLUNA-LUNA stableswap pool implementation. -#[cw_serde] -pub struct MigrateMsg { - /// The bLUNA rewarder contract - pub bluna_rewarder: Option, - /// The Astroport Generator contract - pub generator: Option, -} diff --git a/packages/astroport/src/querier.rs b/packages/astroport/src/querier.rs deleted file mode 100644 index 061c203..0000000 --- a/packages/astroport/src/querier.rs +++ /dev/null @@ -1,224 +0,0 @@ -use crate::asset::{Asset, AssetInfo, PairInfo}; -use crate::factory::{ - ConfigResponse as FactoryConfigResponse, FeeInfoResponse, PairType, PairsResponse, - QueryMsg as FactoryQueryMsg, -}; -use crate::pair::{QueryMsg as PairQueryMsg, ReverseSimulationResponse, SimulationResponse}; - -use cosmwasm_std::{ - Addr, AllBalanceResponse, BankQuery, Coin, Decimal, QuerierWrapper, QueryRequest, StdError, - StdResult, Uint128, -}; - -use cw20::{BalanceResponse as Cw20BalanceResponse, Cw20QueryMsg, TokenInfoResponse}; - -/// Returns a native token's balance for a specific account. -/// -/// * **denom** specifies the denomination used to return the balance (e.g uluna). -pub fn query_balance( - querier: &QuerierWrapper, - account_addr: impl Into, - denom: impl Into, -) -> StdResult { - querier - .query_balance(account_addr, denom) - .map(|coin| coin.amount) -} - -/// Returns the total balances for all coins at a specified account address. -/// -/// * **account_addr** address for which we query balances. -pub fn query_all_balances(querier: &QuerierWrapper, account_addr: Addr) -> StdResult> { - let all_balances: AllBalanceResponse = - querier.query(&QueryRequest::Bank(BankQuery::AllBalances { - address: String::from(account_addr), - }))?; - Ok(all_balances.amount) -} - -/// Returns a token balance for an account. -/// -/// * **contract_addr** token contract for which we return a balance. -/// -/// * **account_addr** account address for which we return a balance. -pub fn query_token_balance( - querier: &QuerierWrapper, - contract_addr: impl Into, - account_addr: impl Into, -) -> StdResult { - // load balance from the token contract - let resp: Cw20BalanceResponse = querier - .query_wasm_smart( - contract_addr, - &Cw20QueryMsg::Balance { - address: account_addr.into(), - }, - ) - .unwrap_or_else(|_| Cw20BalanceResponse { - balance: Uint128::zero(), - }); - - Ok(resp.balance) -} - -/// Returns the number of decimals that a token has. -/// -/// * **asset_info** is an object of type [`AssetInfo`] and contains the asset details for a specific token. -pub fn query_token_precision( - querier: &QuerierWrapper, - asset_info: &AssetInfo, - factory_addr: &Addr, -) -> StdResult { - Ok(match asset_info { - AssetInfo::NativeToken { denom } => { - let config = query_factory_config(querier, factory_addr)?; - let result = crate::native_coin_registry::COINS_INFO.query( - querier, - config.coin_registry_address, - denom.to_string(), - )?; - - if let Some(decimals) = result { - decimals - } else { - return Err(StdError::generic_err(format!( - "The {denom} precision was not found" - ))); - } - } - AssetInfo::Token { contract_addr } => { - let res: TokenInfoResponse = - querier.query_wasm_smart(contract_addr, &Cw20QueryMsg::TokenInfo {})?; - - res.decimals - } - }) -} - -/// Returns a token's symbol. -/// -/// * **contract_addr** token contract address. -pub fn query_token_symbol( - querier: &QuerierWrapper, - contract_addr: impl Into, -) -> StdResult { - let res: TokenInfoResponse = - querier.query_wasm_smart(contract_addr, &Cw20QueryMsg::TokenInfo {})?; - - Ok(res.symbol) -} - -/// Returns the total supply of a specific token. -/// -/// * **contract_addr** token contract address. -pub fn query_supply( - querier: &QuerierWrapper, - contract_addr: impl Into, -) -> StdResult { - let res: TokenInfoResponse = - querier.query_wasm_smart(contract_addr, &Cw20QueryMsg::TokenInfo {})?; - - Ok(res.total_supply) -} - -/// Returns the configuration for the factory contract. -pub fn query_factory_config( - querier: &QuerierWrapper, - factory_contract: impl Into, -) -> StdResult { - querier.query_wasm_smart(factory_contract, &FactoryQueryMsg::Config {}) -} - -/// This structure holds parameters that describe the fee structure for a pool. -pub struct FeeInfo { - /// The fee address - pub fee_address: Option, - /// The total amount of fees charged per swap - pub total_fee_rate: Decimal, - /// The amount of fees sent to the Maker contract - pub maker_fee_rate: Decimal, -} - -/// Returns the fee information for a specific pair type. -/// -/// * **pair_type** pair type we query information for. -pub fn query_fee_info( - querier: &QuerierWrapper, - factory_contract: impl Into, - pair_type: PairType, -) -> StdResult { - let res: FeeInfoResponse = - querier.query_wasm_smart(factory_contract, &FactoryQueryMsg::FeeInfo { pair_type })?; - - Ok(FeeInfo { - fee_address: res.fee_address, - total_fee_rate: Decimal::from_ratio(res.total_fee_bps, 10000u16), - maker_fee_rate: Decimal::from_ratio(res.maker_fee_bps, 10000u16), - }) -} - -/// Accepts two tokens as input and returns a pair's information. -pub fn query_pair_info( - querier: &QuerierWrapper, - factory_contract: impl Into, - asset_infos: &[AssetInfo], -) -> StdResult { - querier.query_wasm_smart( - factory_contract, - &FactoryQueryMsg::Pair { - asset_infos: asset_infos.to_vec(), - }, - ) -} - -/// Returns a vector that contains items of type [`PairInfo`] which -/// symbolize pairs instantiated in the Astroport factory -pub fn query_pairs_info( - querier: &QuerierWrapper, - factory_contract: impl Into, - start_after: Option>, - limit: Option, -) -> StdResult { - querier.query_wasm_smart( - factory_contract, - &FactoryQueryMsg::Pairs { start_after, limit }, - ) -} - -/// Returns information about a swap simulation using a [`SimulationResponse`] object. -/// -/// * **pair_contract** address of the pair for which we return swap simulation info. -/// -/// * **offer_asset** asset that is being swapped. -pub fn simulate( - querier: &QuerierWrapper, - pair_contract: impl Into, - offer_asset: &Asset, -) -> StdResult { - querier.query_wasm_smart( - pair_contract, - &PairQueryMsg::Simulation { - offer_asset: offer_asset.clone(), - ask_asset_info: None, - }, - ) -} - -/// Returns information about a reverse swap simulation using a [`ReverseSimulationResponse`] object. -/// -/// * **pair_contract** address of the pair for which we return swap simulation info. -/// -/// * **ask_asset** represents the asset that we swap to. -pub fn reverse_simulate( - querier: &QuerierWrapper, - pair_contract: impl Into, - ask_asset: &Asset, -) -> StdResult { - querier.query_wasm_smart( - pair_contract, - &PairQueryMsg::ReverseSimulation { - offer_asset_info: None, - ask_asset: ask_asset.clone(), - }, - ) -} diff --git a/packages/astroport/src/restricted_vector.rs b/packages/astroport/src/restricted_vector.rs deleted file mode 100644 index 3bbf277..0000000 --- a/packages/astroport/src/restricted_vector.rs +++ /dev/null @@ -1,90 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Decimal, StdError, StdResult, Uint128}; -use std::fmt::Display; - -/// Vec wrapper for internal use. -/// Some business logic relies on an order of this vector, thus it is forbidden to sort it -/// or remove elements. New values can be added using .update() ONLY. -#[cw_serde] -pub struct RestrictedVector(Vec<(K, V)>); - -pub trait Increaseable -where - Self: Sized, -{ - fn increase(self, new: Self) -> StdResult; -} - -impl RestrictedVector -where - K: Clone + PartialEq + Display, - V: Copy + Increaseable, -{ - pub fn new(key: K, value: V) -> Self { - Self(vec![(key, value)]) - } - - pub fn get_last(&self, key: &K) -> StdResult { - self.0 - .last() - .filter(|(k, _)| k == key) - .map(|(_, v)| v) - .cloned() - .ok_or_else(|| StdError::generic_err(format!("Key {} not found", key))) - } - - pub fn update(&mut self, key: &K, value: V) -> StdResult { - let found = self.0.iter_mut().find(|(k, _)| k == key); - let r = match found { - Some((_, v)) => { - *v = v.increase(value)?; - *v - } - None => { - self.0.push((key.clone(), value)); - value - } - }; - - Ok(r) - } - - pub fn load(&self, key: &K) -> Option { - self.0 - .iter() - .find(|(k, _)| k == key) - .map(|(_, value)| *value) - } - - pub fn inner_ref(&self) -> &Vec<(K, V)> { - &self.0 - } - - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -impl Increaseable for Decimal { - fn increase(self, new: Decimal) -> StdResult { - self.checked_add(new).map_err(Into::into) - } -} - -impl Increaseable for Uint128 { - fn increase(self, new: Uint128) -> StdResult { - self.checked_add(new).map_err(Into::into) - } -} - -impl Default for RestrictedVector { - fn default() -> Self { - Self(vec![]) - } -} - -impl From> for RestrictedVector { - fn from(v: Vec<(K, V)>) -> Self { - Self(v) - } -} diff --git a/packages/astroport/src/router.rs b/packages/astroport/src/router.rs deleted file mode 100644 index b927fd1..0000000 --- a/packages/astroport/src/router.rs +++ /dev/null @@ -1,126 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; - -use cosmwasm_std::{Decimal, Uint128}; -use cw20::Cw20ReceiveMsg; - -use crate::asset::AssetInfo; - -pub const MAX_SWAP_OPERATIONS: usize = 50; - -/// This structure holds the parameters used for creating a contract. -#[cw_serde] -pub struct InstantiateMsg { - /// The astroport factory contract address - pub astroport_factory: String, -} - -/// This enum describes a swap operation. -#[cw_serde] -pub enum SwapOperation { - /// Native swap - NativeSwap { - /// The name (denomination) of the native asset to swap from - offer_denom: String, - /// The name (denomination) of the native asset to swap to - ask_denom: String, - }, - /// ASTRO swap - AstroSwap { - /// Information about the asset being swapped - offer_asset_info: AssetInfo, - /// Information about the asset we swap to - ask_asset_info: AssetInfo, - }, -} - -impl SwapOperation { - pub fn get_target_asset_info(&self) -> AssetInfo { - match self { - SwapOperation::NativeSwap { ask_denom, .. } => AssetInfo::NativeToken { - denom: ask_denom.clone(), - }, - SwapOperation::AstroSwap { ask_asset_info, .. } => ask_asset_info.clone(), - } - } -} - -/// This structure describes the execute messages available in the contract. -#[cw_serde] -pub enum ExecuteMsg { - /// Receive receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template - Receive(Cw20ReceiveMsg), - /// ExecuteSwapOperations processes multiple swaps while mentioning the minimum amount of tokens to receive for the last swap operation - ExecuteSwapOperations { - operations: Vec, - minimum_receive: Option, - to: Option, - max_spread: Option, - }, - - /// Internal use - /// ExecuteSwapOperation executes a single swap operation - ExecuteSwapOperation { - operation: SwapOperation, - to: Option, - max_spread: Option, - single: bool, - }, - /// Internal use - /// AssertMinimumReceive checks that a receiver will get a minimum amount of tokens from a swap - AssertMinimumReceive { - asset_info: AssetInfo, - prev_balance: Uint128, - minimum_receive: Uint128, - receiver: String, - }, -} - -#[cw_serde] -pub enum Cw20HookMsg { - ExecuteSwapOperations { - /// A vector of swap operations - operations: Vec, - /// The minimum amount of tokens to get from a swap - minimum_receive: Option, - /// - to: Option, - /// Max spread - max_spread: Option, - }, -} - -/// This structure describes the query messages available in the contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Config returns configuration parameters for the contract using a custom [`ConfigResponse`] structure - #[returns(ConfigResponse)] - Config {}, - /// SimulateSwapOperations simulates multi-hop swap operations - #[returns(SimulateSwapOperationsResponse)] - SimulateSwapOperations { - /// The amount of tokens to swap - offer_amount: Uint128, - /// The swap operations to perform, each swap involving a specific pool - operations: Vec, - }, -} - -/// This structure describes a custom struct to return a query response containing the base contract configuration. -#[cw_serde] -pub struct ConfigResponse { - /// The Astroport factory contract address - pub astroport_factory: String, -} - -/// This structure describes a custom struct to return a query response containing the end amount of a swap simulation -#[cw_serde] -pub struct SimulateSwapOperationsResponse { - /// The amount of tokens received in a swap simulation - pub amount: Uint128, -} - -/// This structure describes a migration message. -/// We currently take no arguments for migrations. -#[cw_serde] -pub struct MigrateMsg {} diff --git a/packages/astroport/src/staking.rs b/packages/astroport/src/staking.rs deleted file mode 100644 index 7cc9b9a..0000000 --- a/packages/astroport/src/staking.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::xastro_token::InstantiateMarketingInfo; -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Uint128}; -use cw20::Cw20ReceiveMsg; - -/// This structure describes the parameters used for creating a contract. -#[cw_serde] -pub struct InstantiateMsg { - /// The contract owner address - pub owner: String, - /// CW20 token code identifier - pub token_code_id: u64, - /// The ASTRO token contract address - pub deposit_token_addr: String, - /// the marketing info of type [`InstantiateMarketingInfo`] - pub marketing: Option, -} - -/// This structure describes the execute messages available in the contract. -#[cw_serde] -pub enum ExecuteMsg { - /// Receive receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template. - Receive(Cw20ReceiveMsg), -} - -/// This structure describes the query messages available in the contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Config returns the contract configuration specified in a custom [`ConfigResponse`] structure - #[returns(ConfigResponse)] - Config {}, - #[returns(Uint128)] - TotalShares {}, - #[returns(Uint128)] - TotalDeposit {}, -} - -#[cw_serde] -pub struct ConfigResponse { - /// The ASTRO token address - pub deposit_token_addr: Addr, - /// The xASTRO token address - pub share_token_addr: Addr, -} - -/// This structure describes a migration message. -#[cw_serde] -pub struct MigrateMsg {} - -/// This structure describes a CW20 hook message. -#[cw_serde] -pub enum Cw20HookMsg { - /// Deposits ASTRO in exchange for xASTRO - Enter {}, - /// Burns xASTRO in exchange for ASTRO - Leave {}, -} diff --git a/packages/astroport/src/testing.rs b/packages/astroport/src/testing.rs deleted file mode 100644 index 2b4c695..0000000 --- a/packages/astroport/src/testing.rs +++ /dev/null @@ -1,343 +0,0 @@ -use crate::asset::{format_lp_token_name, Asset, AssetInfo, PairInfo}; -use crate::mock_querier::mock_dependencies; -use crate::querier::{ - query_all_balances, query_balance, query_pair_info, query_supply, query_token_balance, -}; - -use crate::factory::PairType; -use crate::DecimalCheckedOps; -use cosmwasm_std::testing::MOCK_CONTRACT_ADDR; -use cosmwasm_std::{to_json_binary, Addr, BankMsg, Coin, CosmosMsg, Decimal, Uint128, WasmMsg}; -use cw20::Cw20ExecuteMsg; - -#[test] -fn token_balance_querier() { - let mut deps = mock_dependencies(&[]); - - deps.querier.with_token_balances(&[( - &String::from("liquidity0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(123u128))], - )]); - - deps.querier.with_cw20_query_handler(); - assert_eq!( - Uint128::new(123u128), - query_token_balance( - &deps.as_ref().querier, - Addr::unchecked("liquidity0000"), - Addr::unchecked(MOCK_CONTRACT_ADDR), - ) - .unwrap() - ); - deps.querier.with_default_query_handler() -} - -#[test] -fn balance_querier() { - let deps = mock_dependencies(&[Coin { - denom: "uusd".to_string(), - amount: Uint128::new(200u128), - }]); - - assert_eq!( - query_balance( - &deps.as_ref().querier, - Addr::unchecked(MOCK_CONTRACT_ADDR), - "uusd".to_string() - ) - .unwrap(), - Uint128::new(200u128) - ); -} - -#[test] -fn all_balances_querier() { - let deps = mock_dependencies(&[ - Coin { - denom: "uusd".to_string(), - amount: Uint128::new(200u128), - }, - Coin { - denom: "ukrw".to_string(), - amount: Uint128::new(300u128), - }, - ]); - - assert_eq!( - query_all_balances(&deps.as_ref().querier, Addr::unchecked(MOCK_CONTRACT_ADDR),).unwrap(), - vec![ - Coin { - denom: "uusd".to_string(), - amount: Uint128::new(200u128), - }, - Coin { - denom: "ukrw".to_string(), - amount: Uint128::new(300u128), - } - ] - ); -} - -#[test] -fn supply_querier() { - let mut deps = mock_dependencies(&[]); - - deps.querier.with_token_balances(&[( - &String::from("liquidity0000"), - &[ - (&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(123u128)), - (&String::from("addr00000"), &Uint128::new(123u128)), - (&String::from("addr00001"), &Uint128::new(123u128)), - (&String::from("addr00002"), &Uint128::new(123u128)), - ], - )]); - - deps.querier.with_cw20_query_handler(); - - assert_eq!( - query_supply(&deps.as_ref().querier, Addr::unchecked("liquidity0000")).unwrap(), - Uint128::new(492u128) - ) -} - -#[test] -fn test_asset_info() { - let token_info: AssetInfo = AssetInfo::Token { - contract_addr: Addr::unchecked("asset0000"), - }; - let native_token_info: AssetInfo = AssetInfo::NativeToken { - denom: "uusd".to_string(), - }; - - assert!(!token_info.equal(&native_token_info)); - - assert!(!token_info.equal(&AssetInfo::Token { - contract_addr: Addr::unchecked("asset0001"), - })); - - assert!(token_info.equal(&AssetInfo::Token { - contract_addr: Addr::unchecked("asset0000"), - })); - - assert!(native_token_info.is_native_token()); - assert!(!token_info.is_native_token()); - - let mut deps = mock_dependencies(&[Coin { - denom: "uusd".to_string(), - amount: Uint128::new(123), - }]); - deps.querier.with_token_balances(&[( - &String::from("asset0000"), - &[ - (&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(123u128)), - (&String::from("addr00000"), &Uint128::new(123u128)), - (&String::from("addr00001"), &Uint128::new(123u128)), - (&String::from("addr00002"), &Uint128::new(123u128)), - ], - )]); - - assert_eq!( - native_token_info - .query_pool(&deps.as_ref().querier, Addr::unchecked(MOCK_CONTRACT_ADDR)) - .unwrap(), - Uint128::new(123u128) - ); - deps.querier.with_cw20_query_handler(); - assert_eq!( - token_info - .query_pool(&deps.as_ref().querier, Addr::unchecked(MOCK_CONTRACT_ADDR)) - .unwrap(), - Uint128::new(123u128) - ); -} - -#[test] -fn test_asset() { - let mut deps = mock_dependencies(&[Coin { - denom: "uusd".to_string(), - amount: Uint128::new(123), - }]); - - deps.querier.with_token_balances(&[( - &String::from("asset0000"), - &[ - (&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(123u128)), - (&String::from("addr00000"), &Uint128::new(123u128)), - (&String::from("addr00001"), &Uint128::new(123u128)), - (&String::from("addr00002"), &Uint128::new(123u128)), - ], - )]); - - let token_asset = Asset { - amount: Uint128::new(123123u128), - info: AssetInfo::Token { - contract_addr: Addr::unchecked("asset0000"), - }, - }; - - let native_token_asset = Asset { - amount: Uint128::new(123123u128), - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - }; - - assert_eq!( - token_asset.compute_tax(&deps.as_ref().querier).unwrap(), - Uint128::zero() - ); - assert_eq!( - native_token_asset - .compute_tax(&deps.as_ref().querier) - .unwrap(), - Uint128::zero() - ); - - assert_eq!( - native_token_asset - .deduct_tax(&deps.as_ref().querier) - .unwrap(), - Coin { - denom: "uusd".to_string(), - amount: Uint128::new(123123u128), - } - ); - - assert_eq!( - token_asset - .into_msg(&deps.as_ref().querier, Addr::unchecked("addr0000")) - .unwrap(), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::from("asset0000"), - msg: to_json_binary(&Cw20ExecuteMsg::Transfer { - recipient: String::from("addr0000"), - amount: Uint128::new(123123u128), - }) - .unwrap(), - funds: vec![], - }) - ); - - assert_eq!( - native_token_asset - .into_msg(&deps.as_ref().querier, Addr::unchecked("addr0000")) - .unwrap(), - CosmosMsg::Bank(BankMsg::Send { - to_address: String::from("addr0000"), - amount: vec![Coin { - denom: "uusd".to_string(), - amount: Uint128::new(123123u128), - }] - }) - ); -} - -#[test] -fn query_astroport_pair_contract() { - let mut deps = mock_dependencies(&[]); - - deps.querier.with_astroport_pairs(&[( - &"asset0000uusd".to_string(), - &PairInfo { - asset_infos: vec![ - AssetInfo::Token { - contract_addr: Addr::unchecked("asset0000"), - }, - AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - ], - contract_addr: Addr::unchecked("pair0000"), - liquidity_token: Addr::unchecked("liquidity0000"), - pair_type: PairType::Xyk {}, - }, - )]); - - let pair_info: PairInfo = query_pair_info( - &deps.as_ref().querier, - Addr::unchecked(MOCK_CONTRACT_ADDR), - &[ - AssetInfo::Token { - contract_addr: Addr::unchecked("asset0000"), - }, - AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - ], - ) - .unwrap(); - - assert_eq!(pair_info.contract_addr, String::from("pair0000"),); - assert_eq!(pair_info.liquidity_token, String::from("liquidity0000"),); -} - -#[test] -fn test_format_lp_token_name() { - let mut deps = mock_dependencies(&[]); - deps.querier.with_astroport_pairs(&[( - &"asset0000uusd".to_string(), - &PairInfo { - asset_infos: vec![ - AssetInfo::Token { - contract_addr: Addr::unchecked("asset0000"), - }, - AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - ], - contract_addr: Addr::unchecked("pair0000"), - liquidity_token: Addr::unchecked("liquidity0000"), - pair_type: PairType::Xyk {}, - }, - )]); - - let pair_info: PairInfo = query_pair_info( - &deps.as_ref().querier, - Addr::unchecked(MOCK_CONTRACT_ADDR), - &[ - AssetInfo::Token { - contract_addr: Addr::unchecked("asset0000"), - }, - AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - ], - ) - .unwrap(); - - deps.querier.with_token_balances(&[( - &String::from("asset0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(123u128))], - )]); - - deps.querier.with_cw20_query_handler(); - - let lp_name = format_lp_token_name(&pair_info.asset_infos, &deps.as_ref().querier).unwrap(); - assert_eq!(lp_name, "MAPP-UUSD-LP") -} - -#[test] -fn test_decimal_checked_ops() { - for i in 0u32..100u32 { - let dec = Decimal::from_ratio(i, 1u32); - assert_eq!(dec + dec, dec.checked_add(dec).unwrap()); - } - assert!( - Decimal::from_ratio(Uint128::MAX, Uint128::from(10u128.pow(18u32))) - .checked_add(Decimal::one()) - .is_err() - ); - - for i in 0u128..100u128 { - let dec = Decimal::from_ratio(i, 1u128); - assert_eq!( - dec * Uint128::new(i), - dec.checked_mul_uint128(Uint128::from(i)).unwrap() - ); - } - assert!( - Decimal::from_ratio(Uint128::MAX, Uint128::from(10u128.pow(18u32))) - .checked_mul(Decimal::new(Uint128::from(10u128.pow(18u32) + 1u128))) - .is_err() - ); -} diff --git a/packages/astroport/src/token.rs b/packages/astroport/src/token.rs deleted file mode 100644 index 9ad56e2..0000000 --- a/packages/astroport/src/token.rs +++ /dev/null @@ -1,85 +0,0 @@ -use cosmwasm_schema::cw_serde; - -use cosmwasm_std::{StdError, StdResult, Uint128}; -use cw20::{Cw20Coin, Logo, MinterResponse}; - -/// This structure describes the marketing info settings such as project, description, and token logo. -#[cw_serde] -pub struct InstantiateMarketingInfo { - /// The project name - pub project: Option, - /// The project description - pub description: Option, - /// The address of an admin who is able to update marketing info - pub marketing: Option, - /// The token logo - pub logo: Option, -} - -/// This structure describes the parameters used for creating a token contract. -#[cw_serde] -pub struct InstantiateMsg { - /// Token name - pub name: String, - /// Token symbol - pub symbol: String, - /// The amount of decimals the token has - pub decimals: u8, - /// Initial token balances - pub initial_balances: Vec, - /// Minting controls specified in a [`MinterResponse`] structure - pub mint: Option, - /// the marketing info of type [`InstantiateMarketingInfo`] - pub marketing: Option, -} - -/// This structure describes a migration message. -#[cw_serde] -pub struct MigrateMsg {} - -impl InstantiateMsg { - pub fn get_cap(&self) -> Option { - self.mint.as_ref().and_then(|v| v.cap) - } - - pub fn validate(&self) -> StdResult<()> { - // Check name, symbol, decimals - if !is_valid_name(&self.name) { - return Err(StdError::generic_err( - "Name is not in the expected format (3-50 UTF-8 bytes)", - )); - } - if !is_valid_symbol(&self.symbol) { - return Err(StdError::generic_err( - "Ticker symbol is not in expected format [a-zA-Z\\-]{3,12}", - )); - } - if self.decimals > 18 { - return Err(StdError::generic_err("Decimals must not exceed 18")); - } - Ok(()) - } -} - -/// Checks the validity of the token name -fn is_valid_name(name: &str) -> bool { - let bytes = name.as_bytes(); - if bytes.len() < 3 || bytes.len() > 50 { - return false; - } - true -} - -/// Checks the validity of the token symbol -fn is_valid_symbol(symbol: &str) -> bool { - let bytes = symbol.as_bytes(); - if bytes.len() < 3 || bytes.len() > 12 { - return false; - } - for byte in bytes.iter() { - if (*byte != 45) && (*byte < 65 || *byte > 90) && (*byte < 97 || *byte > 122) { - return false; - } - } - true -} diff --git a/packages/astroport/src/xastro_token.rs b/packages/astroport/src/xastro_token.rs deleted file mode 100644 index b725155..0000000 --- a/packages/astroport/src/xastro_token.rs +++ /dev/null @@ -1,132 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; - -use cosmwasm_std::{StdError, StdResult, Uint128}; -use cw20::{ - AllAccountsResponse, AllAllowancesResponse, AllowanceResponse, BalanceResponse, Cw20Coin, - DownloadLogoResponse, Logo, MarketingInfoResponse, MinterResponse, TokenInfoResponse, -}; - -/// This structure describes the marketing info settings such as project, description, and token logo. -#[cw_serde] -pub struct InstantiateMarketingInfo { - /// The project name - pub project: Option, - /// The project description - pub description: Option, - /// The address of an admin who is able to update marketing info - pub marketing: Option, - /// The token logo - pub logo: Option, -} - -/// This structure describes the parameters used for creating a xASTRO token contract. -#[cw_serde] -pub struct InstantiateMsg { - /// Token name - pub name: String, - /// Token symbol - pub symbol: String, - /// The number of decimals the token has - pub decimals: u8, - /// Initial token balances - pub initial_balances: Vec, - /// Token minting permissions - pub mint: Option, - /// the marketing info of type [`InstantiateMarketingInfo`] - pub marketing: Option, -} - -/// This enum describes the query messages available in the contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Balance returns the current balance of a given address, 0 if unset. - #[returns(BalanceResponse)] - Balance { address: String }, - /// BalanceAt returns balance of the given address at the given block, 0 if unset. - #[returns(BalanceResponse)] - BalanceAt { address: String, block: u64 }, - /// TotalSupplyAt returns the total token supply at the given block. - #[returns(Uint128)] - TotalSupplyAt { block: u64 }, - /// TokenInfo returns the contract's metadata - name, decimals, supply, etc. - #[returns(TokenInfoResponse)] - TokenInfo {}, - /// Returns who can mint xASTRO and the hard cap on maximum tokens after minting. - #[returns(Option)] - Minter {}, - /// Allowance returns an amount of tokens the spender can spend from the owner account, 0 if unset. - #[returns(AllowanceResponse)] - Allowance { owner: String, spender: String }, - /// AllAllowances returns all the allowances this token holder has approved. Supports pagination. - #[returns(AllAllowancesResponse)] - AllAllowances { - owner: String, - start_after: Option, - limit: Option, - }, - /// AllAccounts returns all the accounts that have xASTRO balances. Supports pagination. - #[returns(AllAccountsResponse)] - AllAccounts { - start_after: Option, - limit: Option, - }, - /// Returns marketing related contract metadata: - /// - description, logo, project url, etc. - #[returns(MarketingInfoResponse)] - MarketingInfo {}, - /// Downloads embeded logo data (if stored on chain). Errors if no logo data was stored for this contract. - #[returns(DownloadLogoResponse)] - DownloadLogo {}, -} - -/// This structure describes a migration message. -#[cw_serde] -pub struct MigrateMsg {} - -impl InstantiateMsg { - pub fn get_cap(&self) -> Option { - self.mint.as_ref().and_then(|v| v.cap) - } - - pub fn validate(&self) -> StdResult<()> { - // Check name, symbol, decimals - if !is_valid_name(&self.name) { - return Err(StdError::generic_err( - "Name is not in the expected format (3-50 UTF-8 bytes)", - )); - } - if !is_valid_symbol(&self.symbol) { - return Err(StdError::generic_err( - "Ticker symbol is not in expected format [a-zA-Z\\-]{3,12}", - )); - } - if self.decimals > 18 { - return Err(StdError::generic_err("Decimals must not exceed 18")); - } - Ok(()) - } -} - -/// Checks the validity of a token's name. -fn is_valid_name(name: &str) -> bool { - let bytes = name.as_bytes(); - if bytes.len() < 3 || bytes.len() > 50 { - return false; - } - true -} - -/// Checks the validity of a token's symbol. -fn is_valid_symbol(symbol: &str) -> bool { - let bytes = symbol.as_bytes(); - if bytes.len() < 3 || bytes.len() > 12 { - return false; - } - for byte in bytes.iter() { - if (*byte != 45) && (*byte < 65 || *byte > 90) && (*byte < 97 || *byte > 122) { - return false; - } - } - true -} diff --git a/packages/vesting-base/Cargo.toml b/packages/vesting-base/Cargo.toml index f533e36..b27b6e6 100644 --- a/packages/vesting-base/Cargo.toml +++ b/packages/vesting-base/Cargo.toml @@ -8,16 +8,15 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [features] -backtraces = ["cosmwasm-std/backtraces"] +# for more explicit tests, cargo test --features=backtraces +#backtraces = ["cosmwasm-std/backtraces"] # use library feature to disable all init/handle/query exports library = [] [dependencies] -cw20 = { version = "0.15" } +cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } -astroport = { path = "../../packages/astroport" } +cw-utils = { workspace = true } +cw20 = { workspace = true } thiserror = { workspace = true } -# we keep it at 0.15 instead of latest version just for vesting investors contract -cw-utils = "0.15" -cosmwasm-schema = { workspace = true } diff --git a/packages/vesting-base/README.md b/packages/vesting-base/README.md index 57accc6..74f0567 100644 --- a/packages/vesting-base/README.md +++ b/packages/vesting-base/README.md @@ -5,6 +5,7 @@ This library contains basis for configuration and initialisation of vesting cont ## Usage 1. To use the library for initialisation of a simple vesting contract just build a default vesting base in its instantiate message: + ```rust use vesting_base::builder::VestingBaseBuilder; @@ -22,6 +23,7 @@ pub fn instantiate( Read about more advanced building in the [Extensions](#extensions) section. 2. Simply pass the execute and query requests to the vesting base's execute and query handlers: + ```rust use vesting_base::handlers::{execute as base_execute, query as base_query}; @@ -219,9 +221,10 @@ pub enum QueryMsgHistorical { ### Extensions usage The following example adds all three extensions to the contract, but it's allowed to combine them in any way. + ```rust +use vesting_base::asset::AssetInfo; use vesting_base::builder::VestingBaseBuilder; -use astroport::asset::AssetInfo; use cosmwasm_schema::cw_serde; /// This structure describes the parameters used for creating a contract. diff --git a/packages/astroport/src/asset.rs b/packages/vesting-base/src/asset.rs similarity index 69% rename from packages/astroport/src/asset.rs rename to packages/vesting-base/src/asset.rs index cff5ca2..d0e3caf 100644 --- a/packages/astroport/src/asset.rs +++ b/packages/vesting-base/src/asset.rs @@ -1,24 +1,10 @@ use cosmwasm_schema::cw_serde; -use std::fmt; - -use crate::factory::PairType; -use crate::pair::QueryMsg as PairQueryMsg; -use crate::querier::{ - query_balance, query_token_balance, query_token_precision, query_token_symbol, -}; use cosmwasm_std::{ - to_json_binary, Addr, Api, BankMsg, Coin, ConversionOverflowError, CosmosMsg, Decimal256, - Fraction, MessageInfo, QuerierWrapper, StdError, StdResult, Uint128, Uint256, WasmMsg, + to_json_binary, Addr, Api, BankMsg, Coin, CosmosMsg, Decimal256, Fraction, MessageInfo, + QuerierWrapper, StdError, StdResult, Uint128, Uint256, WasmMsg, }; -use cw20::{Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse}; -use itertools::Itertools; - -/// UST token denomination -pub const UUSD_DENOM: &str = "uusd"; -/// LUNA token denomination -pub const ULUNA_DENOM: &str = "uluna"; -/// Minimum initial LP share -pub const MINIMUM_LIQUIDITY_AMOUNT: Uint128 = Uint128::new(1_000); +use cw20::Cw20ExecuteMsg; +use std::fmt; /// This enum describes a Terra asset (native or CW20). #[cw_serde] @@ -128,7 +114,7 @@ impl Asset { /// ## Examples /// ``` /// # use cosmwasm_std::Addr; -/// # use astroport::asset::AssetInfo::{NativeToken, Token}; +/// # use vesting_base::asset::AssetInfo::{NativeToken, Token}; /// Token { contract_addr: Addr::unchecked("stake...") }; /// NativeToken { denom: String::from("uluna") }; /// ``` @@ -159,27 +145,6 @@ impl AssetInfo { } } - /// Returns the balance of token in a pool. - /// - /// * **pool_addr** is the address of the contract whose token balance we check. - pub fn query_pool( - &self, - querier: &QuerierWrapper, - pool_addr: impl Into, - ) -> StdResult { - match self { - AssetInfo::Token { contract_addr, .. } => { - query_token_balance(querier, contract_addr, pool_addr) - } - AssetInfo::NativeToken { denom } => query_balance(querier, pool_addr, denom), - } - } - - /// Returns the number of decimals that a token has. - pub fn decimals(&self, querier: &QuerierWrapper, factory_address: &Addr) -> StdResult { - query_token_precision(querier, self, factory_address) - } - /// Returns **true** if the calling token is the same as the token specified in the input parameters. /// Otherwise returns **false**. pub fn equal(&self, asset: &AssetInfo) -> bool { @@ -226,66 +191,6 @@ impl AssetInfo { } } -/// This structure stores the main parameters for an Astroport pair -#[cw_serde] -pub struct PairInfo { - /// Asset information for the assets in the pool - pub asset_infos: Vec, - /// Pair contract address - pub contract_addr: Addr, - /// Pair LP token address - pub liquidity_token: Addr, - /// The pool type (xyk, stableswap etc) available in [`PairType`] - pub pair_type: PairType, -} - -impl PairInfo { - /// Returns the balance for each asset in the pool. - /// - /// * **contract_addr** is pair's pool address. - pub fn query_pools( - &self, - querier: &QuerierWrapper, - contract_addr: impl Into, - ) -> StdResult> { - let contract_addr = contract_addr.into(); - self.asset_infos - .iter() - .map(|asset_info| { - Ok(Asset { - info: asset_info.clone(), - amount: asset_info.query_pool(querier, &contract_addr)?, - }) - }) - .collect() - } - - /// Returns the balance for each asset in the pool in decimal. - /// - /// * **contract_addr** is pair's pool address. - pub fn query_pools_decimal( - &self, - querier: &QuerierWrapper, - contract_addr: impl Into, - factory_address: &Addr, - ) -> StdResult> { - let contract_addr = contract_addr.into(); - self.asset_infos - .iter() - .map(|asset_info| { - Ok(DecimalAsset { - info: asset_info.clone(), - amount: Decimal256::from_atomics( - asset_info.query_pool(querier, &contract_addr)?, - asset_info.decimals(querier, factory_address)?.into(), - ) - .map_err(|_| StdError::generic_err("Decimal256RangeExceeded"))?, - }) - }) - .collect() - } -} - /// Returns a lowercased, validated address upon success. pub fn addr_validate_to_lower(api: &dyn Api, addr: impl Into) -> StdResult { let addr = addr.into(); @@ -305,29 +210,6 @@ pub fn addr_opt_validate(api: &dyn Api, addr: &Option) -> StdResult StdResult { - let mut short_symbols: Vec = vec![]; - for asset_info in asset_infos { - let short_symbol = match &asset_info { - AssetInfo::NativeToken { denom } => { - denom.chars().take(TOKEN_SYMBOL_MAX_LENGTH).collect() - } - AssetInfo::Token { contract_addr } => { - let token_symbol = query_token_symbol(querier, contract_addr)?; - token_symbol.chars().take(TOKEN_SYMBOL_MAX_LENGTH).collect() - } - }; - short_symbols.push(short_symbol); - } - Ok(format!("{}-LP", short_symbols.iter().join("-")).to_uppercase()) -} - /// Returns an [`Asset`] object representing a native token and an amount of tokens. /// /// * **denom** native asset denomination. @@ -362,18 +244,6 @@ pub fn token_asset_info(contract_addr: Addr) -> AssetInfo { AssetInfo::Token { contract_addr } } -/// Returns [`PairInfo`] by specified pool address. -/// -/// * **pool_addr** address of the pool. -pub fn pair_info_by_pool(querier: &QuerierWrapper, pool: impl Into) -> StdResult { - let minter_info: MinterResponse = querier.query_wasm_smart(pool, &Cw20QueryMsg::Minter {})?; - - let pair_info: PairInfo = - querier.query_wasm_smart(minter_info.minter, &PairQueryMsg::Pair {})?; - - Ok(pair_info) -} - /// Checks swap parameters. /// /// * **pools** amount of tokens in pools. @@ -435,13 +305,11 @@ impl Decimal256Ext for Decimal256 { fn to_uint128_with_precision(&self, precision: impl Into) -> StdResult { let value = self.atomics(); let precision = precision.into(); + let converted = value.checked_div(10u128.pow(self.decimal_places() - precision).into())?; - value - .checked_div(10u128.pow(self.decimal_places() - precision).into())? + converted .try_into() - .map_err(|o: ConversionOverflowError| { - StdError::generic_err(format!("Error converting {}", o.value)) - }) + .map_err(|_| StdError::generic_err(format!("Error converting {}", converted))) } fn to_uint256_with_precision(&self, precision: impl Into) -> StdResult { diff --git a/packages/vesting-base/src/builder.rs b/packages/vesting-base/src/builder.rs index 8595724..aa8146d 100644 --- a/packages/vesting-base/src/builder.rs +++ b/packages/vesting-base/src/builder.rs @@ -31,7 +31,7 @@ impl VestingBaseBuilder { self } - /// Validates the inputs and initialises the created contract state. + /// Validates the inputs and initializes the created contract state. pub fn build(&self, deps: DepsMut, owner: String, token_info_manager: String) -> StdResult<()> { let owner = deps.api.addr_validate(&owner)?; CONFIG.save( diff --git a/packages/astroport/src/common.rs b/packages/vesting-base/src/common.rs similarity index 100% rename from packages/astroport/src/common.rs rename to packages/vesting-base/src/common.rs diff --git a/packages/vesting-base/src/ext_managed.rs b/packages/vesting-base/src/ext_managed.rs index 94b68d2..a5f8522 100644 --- a/packages/vesting-base/src/ext_managed.rs +++ b/packages/vesting-base/src/ext_managed.rs @@ -1,8 +1,8 @@ +use crate::asset::AssetInfoExt; use crate::error::{ext_unsupported_err, ContractError}; use crate::handlers::get_vesting_token; use crate::msg::{ExecuteMsgManaged, QueryMsgManaged}; use crate::state::{vesting_info, vesting_state, CONFIG}; -use astroport::asset::AssetInfoExt; use cosmwasm_std::{ attr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, SubMsg, Uint128, }; diff --git a/packages/vesting-base/src/handlers.rs b/packages/vesting-base/src/handlers.rs index b05062e..7452cbb 100644 --- a/packages/vesting-base/src/handlers.rs +++ b/packages/vesting-base/src/handlers.rs @@ -1,3 +1,5 @@ +use crate::asset::{addr_opt_validate, token_asset_info, AssetInfo, AssetInfoExt}; +use crate::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; use crate::error::ContractError; use crate::ext_historical::{handle_execute_historical_msg, handle_query_historical_msg}; use crate::ext_managed::{handle_execute_managed_msg, handle_query_managed_msg}; @@ -9,8 +11,6 @@ use crate::types::{ Config, OrderBy, VestingAccount, VestingAccountResponse, VestingAccountsResponse, VestingInfo, VestingSchedule, VestingState, }; -use astroport::asset::{addr_opt_validate, token_asset_info, AssetInfo, AssetInfoExt}; -use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; use cosmwasm_std::{ attr, from_json, to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, SubMsg, Uint128, @@ -27,6 +27,7 @@ pub fn execute( ) -> Result { match msg { ExecuteMsg::Claim { recipient, amount } => claim(deps, env, info, recipient, amount), + ExecuteMsg::ForceClaim { recipient } => force_claim(deps, env, info, recipient), ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), ExecuteMsg::RegisterVestingAccounts { vesting_accounts } => { let config = CONFIG.load(deps.storage)?; @@ -34,11 +35,11 @@ pub fn execute( match &vesting_token { AssetInfo::NativeToken { denom } - if is_sender_whitelisted(deps.storage, &config, &info.sender) => - { - let amount = must_pay(&info, denom)?; - register_vesting_accounts(deps, vesting_accounts, amount, env.block.height) - } + if is_sender_whitelisted(deps.storage, &config, &info.sender) => + { + let amount = must_pay(&info, denom)?; + register_vesting_accounts(deps, vesting_accounts, amount, env.block.height) + } _ => Err(ContractError::Unauthorized {}), } } @@ -54,7 +55,7 @@ pub fn execute( config.owner, &OWNERSHIP_PROPOSAL, ) - .map_err(Into::into) + .map_err(Into::into) } ExecuteMsg::DropOwnershipProposal {} => { let config: Config = CONFIG.load(deps.storage)?; @@ -71,7 +72,7 @@ pub fn execute( Ok(()) }) - .map_err(Into::into) + .map_err(Into::into) } ExecuteMsg::SetVestingToken { vesting_token } => { set_vesting_token(deps, env, info, vesting_token) @@ -196,58 +197,36 @@ fn claim( recipient: Option, amount: Option, ) -> Result { - let config = CONFIG.load(deps.storage)?; - let vesting_token = get_vesting_token(&config)?; - let vesting_info = vesting_info(config.extensions.historical); - let mut sender_vesting_info = vesting_info.load(deps.storage, info.sender.clone())?; - - let available_amount = - compute_available_amount(env.block.time.seconds(), &sender_vesting_info)?; - - let claim_amount = if let Some(a) = amount { - if a > available_amount { - return Err(ContractError::AmountIsNotAvailable {}); - }; - a - } else { - available_amount - }; - - let mut response = Response::new(); - - if !claim_amount.is_zero() { - let transfer_msg = vesting_token.with_balance(claim_amount).into_msg( - &deps.querier, - recipient.unwrap_or_else(|| info.sender.to_string()), - )?; - response = response.add_submessage(SubMsg::new(transfer_msg)); - - sender_vesting_info.released_amount = sender_vesting_info - .released_amount - .checked_add(claim_amount)?; - vesting_info.save( - deps.storage, - info.sender.clone(), - &sender_vesting_info, - env.block.height, - )?; - vesting_state(config.extensions.historical).update::<_, ContractError>( - deps.storage, - env.block.height, - |s| { - let mut state = s.ok_or(ContractError::AmountIsNotAvailable {})?; - state.total_released = state.total_released.checked_add(claim_amount)?; - Ok(state) - }, - )?; - }; + claim_tokens( + deps, + env, + info, + recipient, + amount, + compute_available_amount, + "claim", + ) +} - Ok(response.add_attributes(vec![ - attr("action", "claim"), - attr("address", &info.sender), - attr("available_amount", available_amount), - attr("claimed_amount", claim_amount), - ])) +/// Stops the vesting, claims the vested tokens plus 50% of the unvested ones +/// and transfers them to a recipient. +/// +/// * **recipient** vesting recipient for which to claim tokens. +fn force_claim( + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: Option, +) -> Result { + claim_tokens( + deps, + env, + info, + recipient, + None, + compute_available_amount_to_force_claim, + "force_claim", + ) } pub(crate) fn set_vesting_token( @@ -373,8 +352,8 @@ fn query_vesting_available_amount(deps: Deps, env: Env, address: String) -> StdR let address = deps.api.addr_validate(&address)?; let config = CONFIG.load(deps.storage)?; - let info = vesting_info(config.extensions.historical).load(deps.storage, address)?; - let available_amount = compute_available_amount(env.block.time.seconds(), &info)?; + let mut info = vesting_info(config.extensions.historical).load(deps.storage, address)?; + let available_amount = compute_available_amount(env.block.time.seconds(), &mut info)?; Ok(available_amount) } @@ -383,7 +362,7 @@ pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result bool { +fn is_sender_whitelisted(store: &dyn Storage, config: &Config, sender: &Addr) -> bool { if *sender == config.owner { return true; } @@ -422,10 +401,15 @@ pub fn assert_vesting_schedules( /// /// * **vesting_info** vesting schedules for which to compute the amount of tokens /// that are vested and can be claimed by the recipient. -fn compute_available_amount(current_time: u64, vesting_info: &VestingInfo) -> StdResult { +fn compute_available_amount( + current_time: u64, + vesting_info: &mut VestingInfo, +) -> StdResult { let mut available_amount: Uint128 = Uint128::zero(); for sch in &vesting_info.schedules { - if sch.start_point.time > current_time { + if sch.start_point.time > current_time || sch.disabled { + // for accounting purposes, add the amount that was forced claimed (released), to make math correct + available_amount = available_amount.checked_add(sch.force_claimed)?; continue; } @@ -448,3 +432,148 @@ fn compute_available_amount(current_time: u64, vesting_info: &VestingInfo) -> St .checked_sub(vesting_info.released_amount) .map_err(StdError::from) } + +/// Computes the amount of the vested and yet unclaimed tokens plus 50% of the unvested ones +/// for a specific vesting recipient and stops the vesting. +/// Returns the computed amount if the operation is successful. +/// +/// * **current_time** timestamp from which to start querying for vesting schedules. +/// +/// * **vesting_info** vesting schedules for which to compute the amount of tokens +/// that will be claimed by the recipient. +fn compute_available_amount_to_force_claim( + current_time: u64, + vesting_info: &mut VestingInfo, +) -> StdResult { + let mut available_amount = Uint128::zero(); + let half = Uint128::new(2); + + for sch in &mut vesting_info.schedules { + if sch.disabled { + // for accounting purposes, add the amount that was forced claimed (released), to make math correct + available_amount = available_amount.checked_add(sch.force_claimed)?; + continue; + } + + // Start with the initial amount at the start_point + let mut release_amount = sch.start_point.amount; + + if let Some(end_point) = &sch.end_point { + // If vesting hasn't started yet, force unlock 50% of the end_point amount + if current_time < sch.start_point.time { + release_amount = end_point.amount.checked_div(half)?; + + // Disable the end_point — the schedule becomes one-time unlock + sch.disabled = true; + sch.force_claimed = release_amount; + + // Add to the total claimable amount + available_amount = available_amount.checked_add(release_amount)?; + + continue; + } + + // Calculate how much time has passed since start + let passed_time = current_time.min(end_point.time) - sch.start_point.time; + let time_period = end_point.time - sch.start_point.time; + + // Сalculate how many tokens have been unlocked over time + if passed_time != 0 && time_period != 0 { + let additional_amount = end_point + .amount + .checked_sub(sch.start_point.amount)? + .multiply_ratio(passed_time, time_period); + + release_amount = release_amount.checked_add(additional_amount)?; + } + + // If vesting is still ongoing — apply forced unlock (50% of remaining) + if current_time < end_point.time { + let remain_amount = end_point + .amount + .checked_sub(release_amount)? + .checked_div(half)?; + + release_amount = release_amount.checked_add(remain_amount)?; + + sch.disabled = true; + } + } else if current_time < sch.start_point.time { + // If vesting hasn’t started yet, force unlock 50% of the start_point amount + release_amount = release_amount.checked_div(half)?; + + sch.disabled = true; + } + + // Add to the total claimable amount + available_amount = available_amount.checked_add(release_amount)?; + sch.force_claimed = release_amount; + } + + // Subtract already claimed (released) tokens from the total available + available_amount + .checked_sub(vesting_info.released_amount) + .map_err(StdError::from) +} + +fn claim_tokens( + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: Option, + amount: Option, + func: fn(u64, &mut VestingInfo) -> StdResult, + action: &str, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let vesting_token = get_vesting_token(&config)?; + let vesting_info = vesting_info(config.extensions.historical); + let mut sender_vesting_info = vesting_info.load(deps.storage, info.sender.clone())?; + + let available_amount = func(env.block.time.seconds(), &mut sender_vesting_info)?; + + let claim_amount = if let Some(a) = amount { + if a > available_amount { + return Err(ContractError::AmountIsNotAvailable {}); + }; + a + } else { + available_amount + }; + + let mut response = Response::new(); + + if !claim_amount.is_zero() { + let transfer_msg = vesting_token.with_balance(claim_amount).into_msg( + &deps.querier, + recipient.unwrap_or_else(|| info.sender.to_string()), + )?; + response = response.add_submessage(SubMsg::new(transfer_msg)); + + sender_vesting_info.released_amount = sender_vesting_info + .released_amount + .checked_add(claim_amount)?; + vesting_info.save( + deps.storage, + info.sender.clone(), + &sender_vesting_info, + env.block.height, + )?; + vesting_state(config.extensions.historical).update::<_, ContractError>( + deps.storage, + env.block.height, + |s| { + let mut state = s.ok_or(ContractError::AmountIsNotAvailable {})?; + state.total_released = state.total_released.checked_add(claim_amount)?; + Ok(state) + }, + )?; + }; + + Ok(response.add_attributes(vec![ + attr("action", action), + attr("address", &info.sender), + attr("available_amount", available_amount), + attr("claimed_amount", claim_amount), + ])) +} diff --git a/packages/vesting-base/src/lib.rs b/packages/vesting-base/src/lib.rs index 2de7b37..f8e49fb 100644 --- a/packages/vesting-base/src/lib.rs +++ b/packages/vesting-base/src/lib.rs @@ -1,4 +1,6 @@ +pub mod asset; pub mod builder; +pub mod common; pub mod error; pub mod handlers; pub mod msg; diff --git a/packages/vesting-base/src/msg.rs b/packages/vesting-base/src/msg.rs index f3c1660..71b04bc 100644 --- a/packages/vesting-base/src/msg.rs +++ b/packages/vesting-base/src/msg.rs @@ -1,7 +1,7 @@ +use crate::asset::AssetInfo; use crate::types::{ Config, OrderBy, VestingAccount, VestingAccountResponse, VestingAccountsResponse, VestingState, }; -use astroport::asset::AssetInfo; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Binary, Uint128}; use cw20::Cw20ReceiveMsg; @@ -16,6 +16,11 @@ pub enum ExecuteMsg { /// The amount of tokens to claim amount: Option, }, + /// Stops the vesting, claims the vested tokens plus 50% of the unvested ones and transfers them to a recipient + ForceClaim { + /// The address that receives the vested tokens + recipient: Option, + }, /// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template Receive(Cw20ReceiveMsg), /// RegisterVestingAccounts registers vesting targets/accounts diff --git a/packages/vesting-base/src/state.rs b/packages/vesting-base/src/state.rs index 9178f7c..6a1cecf 100644 --- a/packages/vesting-base/src/state.rs +++ b/packages/vesting-base/src/state.rs @@ -1,5 +1,5 @@ +use crate::common::OwnershipProposal; use crate::types::{Config, OrderBy, VestingInfo, VestingState}; -use astroport::common::OwnershipProposal; use cosmwasm_std::{Addr, Deps, StdResult}; use cw_storage_plus::{Bound, Item, Map, SnapshotItem, SnapshotMap, Strategy}; @@ -31,14 +31,14 @@ pub(crate) const VESTING_INFO_HISTORICAL: SnapshotMap = Snaps Strategy::EveryBlock, ); -pub fn vesting_state(historical: bool) -> SnapshotItem<'static, VestingState> { +pub fn vesting_state(historical: bool) -> SnapshotItem { if historical { return VESTING_STATE_HISTORICAL; } VESTING_STATE } -pub fn vesting_info(historical: bool) -> SnapshotMap<'static, Addr, VestingInfo> { +pub fn vesting_info(historical: bool) -> SnapshotMap { if historical { return VESTING_INFO_HISTORICAL; } @@ -114,7 +114,7 @@ mod testing { None, Some(OrderBy::Asc), ) - .unwrap(); + .unwrap(); assert_eq!( res, vec![ @@ -129,7 +129,7 @@ mod testing { Some(1), Some(OrderBy::Asc), ) - .unwrap(); + .unwrap(); assert_eq!(res, vec![(Addr::unchecked("address3"), vi_mock.clone())]); let res = read_vesting_infos( @@ -138,7 +138,7 @@ mod testing { None, Some(OrderBy::Desc), ) - .unwrap(); + .unwrap(); assert_eq!( res, vec![ @@ -153,7 +153,7 @@ mod testing { Some(1), Some(OrderBy::Desc), ) - .unwrap(); + .unwrap(); assert_eq!(res, vec![(Addr::unchecked("address2"), vi_mock.clone())]); } } diff --git a/packages/vesting-base/src/testing.rs b/packages/vesting-base/src/testing.rs index 094ddeb..8abac37 100644 --- a/packages/vesting-base/src/testing.rs +++ b/packages/vesting-base/src/testing.rs @@ -1,129 +1,142 @@ +use crate::asset::{native_asset_info, token_asset_info}; use crate::builder::VestingBaseBuilder; +use crate::error::ContractError::AmountIsNotAvailable; use crate::error::{ext_unsupported_err, ContractError}; use crate::handlers::{execute, query}; use crate::msg::{ - ExecuteMsg, ExecuteMsgManaged, QueryMsg, QueryMsgHistorical, QueryMsgWithManagers, + Cw20HookMsg, ExecuteMsg, ExecuteMsgManaged, ExecuteMsgWithManagers, QueryMsg, + QueryMsgHistorical, QueryMsgWithManagers, }; -use crate::types::{Config, Extensions}; -use astroport::asset::token_asset_info; -use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; -use cosmwasm_std::{from_json, Addr}; +use crate::state::vesting_info; +use crate::types::{ + Config, Extensions, OrderBy, VestingAccount, VestingAccountResponse, VestingAccountsResponse, + VestingSchedule, VestingSchedulePoint, VestingState, +}; +use cosmwasm_std::testing::{ + message_info, mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage, +}; +use cosmwasm_std::{ + from_json, to_json_binary, Addr, BankMsg, Coin, CosmosMsg, Env, OwnedDeps, StdError, Uint128, + WasmMsg, +}; +use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; -#[test] -fn set_vesting_token() { +type MockDeps = OwnedDeps; + +fn setup_contract() -> (MockDeps, Env, Addr, Addr) { let mut deps = mock_dependencies(); - let owner = String::from("owner"); - let token_info_manager = "token_info_manager"; let env = mock_env(); + let owner = deps.api.addr_make("owner"); + let token_info_manager = deps.api.addr_make("token_info_manager"); + VestingBaseBuilder::default() - .build(deps.as_mut(), owner, String::from(token_info_manager)) + .build( + deps.as_mut(), + owner.to_string(), + token_info_manager.to_string(), + ) .unwrap(); - // check initialisation - assert_eq!( - from_json::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) - .unwrap(), - Config { - owner: Addr::unchecked("owner"), - token_info_manager: Addr::unchecked(token_info_manager), - vesting_token: None, - extensions: Extensions { - historical: false, - managed: false, - with_managers: false - } - } - ); + (deps, env, owner, token_info_manager) +} - let info = mock_info("stranger", &[]); - // set vesting token by a stranger -> Unauthorized - assert_eq!( - execute( +fn setup_contract_with_token() -> (MockDeps, Env, Addr, Addr, Addr) { + let (mut deps, env, owner, token_info_manager) = setup_contract(); + let vesting_token = deps.api.addr_make("vesting_token"); + + VestingBaseBuilder::default() + .historical() + .managed() + .build( deps.as_mut(), - env.clone(), - info, - ExecuteMsg::SetVestingToken { - vesting_token: token_asset_info(Addr::unchecked("ntrn_token")), - }, + owner.to_string(), + token_info_manager.to_string(), ) - .unwrap_err(), - ContractError::Unauthorized {}, - ); + .unwrap(); - // set vesting token by the manager -> Success - let info = mock_info("token_info_manager", &[]); + let info = message_info(&token_info_manager, &[]); execute( deps.as_mut(), env.clone(), - info.clone(), + info, ExecuteMsg::SetVestingToken { - vesting_token: token_asset_info(Addr::unchecked("ntrn_token")), + vesting_token: token_asset_info(vesting_token.clone()), }, ) - .unwrap(); + .unwrap(); - assert_eq!( - from_json::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) - .unwrap(), - Config { - owner: Addr::unchecked("owner"), - token_info_manager: Addr::unchecked(token_info_manager), - vesting_token: Some(token_asset_info(Addr::unchecked("ntrn_token"))), - extensions: Extensions { - historical: false, - managed: false, - with_managers: false - } - } - ); + (deps, env, owner, token_info_manager, vesting_token) +} - // set vesting token second time by the owner -> VestingTokenAlreadySet - assert_eq!( - execute( +fn setup_contract_with_extensions() -> (MockDeps, Env, Addr, Addr, Vec) { + let mut deps = mock_dependencies(); + let env = mock_env(); + let owner = deps.api.addr_make("owner"); + let token_info_manager = deps.api.addr_make("token_info_manager"); + let vesting_managers = vec![ + deps.api.addr_make("manager1").into_string(), + deps.api.addr_make("manager2").into_string(), + ]; + + VestingBaseBuilder::default() + .historical() + .managed() + .with_managers(vesting_managers.clone()) + .build( deps.as_mut(), - env.clone(), - info, - ExecuteMsg::SetVestingToken { - vesting_token: token_asset_info(Addr::unchecked("not_a_ntrn_token")), - }, + owner.to_string(), + token_info_manager.to_string(), ) - .unwrap_err(), - ContractError::VestingTokenAlreadySet {}, - ); + .unwrap(); - assert_eq!( - from_json::(&query(deps.as_ref(), env, QueryMsg::Config {}).unwrap()).unwrap(), - Config { - owner: Addr::unchecked("owner"), - token_info_manager: Addr::unchecked(token_info_manager), - vesting_token: Some(token_asset_info(Addr::unchecked("ntrn_token"))), - extensions: Extensions { - historical: false, - managed: false, - with_managers: false - } - } - ); + (deps, env, owner, token_info_manager, vesting_managers) } +fn create_vesting_schedule( + start_time: u64, + start_amount: Uint128, + end_time: Option, + end_amount: Option, +) -> VestingSchedule { + VestingSchedule { + start_point: VestingSchedulePoint { + time: start_time, + amount: start_amount, + }, + end_point: if let (Some(time), Some(amount)) = (end_time, end_amount) { + Some(VestingSchedulePoint { time, amount }) + } else { + None + }, + disabled: false, + force_claimed: Uint128::zero(), + } +} + +// ========== BUILDER TESTS ========== + #[test] -fn proper_building_standard() { +fn test_proper_building_standard() { let mut deps = mock_dependencies(); - let owner = String::from("owner"); - let token_info_manager = "token_info_manager"; + let owner = deps.api.addr_make("owner"); + let token_info_manager = deps.api.addr_make("token_info_manager"); let env = mock_env(); - let info = mock_info("owner", &[]); + let info = message_info(&owner, &[]); VestingBaseBuilder::default() - .build(deps.as_mut(), owner, String::from(token_info_manager)) + .build( + deps.as_mut(), + owner.to_string(), + token_info_manager.to_string(), + ) .unwrap(); - // check initialisation + // check initialization assert_eq!( from_json::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) .unwrap(), Config { - owner: Addr::unchecked("owner"), - token_info_manager: Addr::unchecked(token_info_manager), + owner: owner.clone(), + token_info_manager: token_info_manager.clone(), vesting_token: None, extensions: Extensions { historical: false, @@ -142,7 +155,7 @@ fn proper_building_standard() { msg: QueryMsgWithManagers::VestingManagers {} } ) - .unwrap_err(), + .unwrap_err(), ext_unsupported_err("with_managers") ); @@ -155,7 +168,7 @@ fn proper_building_standard() { msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: 1000u64 } } ) - .unwrap_err(), + .unwrap_err(), ext_unsupported_err("historical") ); @@ -172,31 +185,38 @@ fn proper_building_standard() { } }, ) - .unwrap_err(), + .unwrap_err(), ext_unsupported_err("managed").into() ); } #[test] -fn proper_building_managers() { +fn test_proper_building_managers() { let mut deps = mock_dependencies(); - let owner = String::from("owner"); - let token_info_manager = "token_info_manager"; + let owner = deps.api.addr_make("owner"); + let token_info_manager = deps.api.addr_make("token_info_manager"); let env = mock_env(); - let info = mock_info("owner", &[]); - let vesting_managers = vec!["manager1".to_string(), "manager2".to_string()]; + let info = message_info(&owner, &[]); + let vesting_managers = vec![ + deps.api.addr_make("manager1").into_string(), + deps.api.addr_make("manager2").into_string(), + ]; VestingBaseBuilder::default() .with_managers(vesting_managers.clone()) - .build(deps.as_mut(), owner, String::from(token_info_manager)) + .build( + deps.as_mut(), + owner.to_string(), + token_info_manager.to_string(), + ) .unwrap(); - // check initialisation + // check initialization assert_eq!( from_json::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) .unwrap(), Config { - owner: Addr::unchecked("owner"), - token_info_manager: Addr::unchecked(token_info_manager), + owner: owner.clone(), + token_info_manager: token_info_manager.clone(), vesting_token: None, extensions: Extensions { historical: false, @@ -207,20 +227,19 @@ fn proper_building_managers() { ); // make sure with_managers extension is enabled - assert_eq!( - from_json::>( - &query( - deps.as_ref(), - env.clone(), - QueryMsg::WithManagersExtension { - msg: QueryMsgWithManagers::VestingManagers {}, - }, - ) - .unwrap() + let managers_addrs = from_json::>( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::WithManagersExtension { + msg: QueryMsgWithManagers::VestingManagers {}, + }, ) - .unwrap(), - vesting_managers - ); + .unwrap(), + ) + .unwrap(); + let managers_strings: Vec = managers_addrs.iter().map(|a| a.to_string()).collect(); + assert_eq!(managers_strings, vesting_managers); // make sure historical extension is not enabled assert_eq!( @@ -231,7 +250,7 @@ fn proper_building_managers() { msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: 1000u64 } } ) - .unwrap_err(), + .unwrap_err(), ext_unsupported_err("historical") ); @@ -248,30 +267,34 @@ fn proper_building_managers() { }, }, ) - .unwrap_err(), + .unwrap_err(), ext_unsupported_err("managed").into() ); } #[test] -fn proper_building_historical() { +fn test_proper_building_historical() { let mut deps = mock_dependencies(); - let owner = String::from("owner"); - let token_info_manager = "token_info_manager"; + let owner = deps.api.addr_make("owner"); + let token_info_manager = deps.api.addr_make("token_info_manager"); let env = mock_env(); - let info = mock_info("owner", &[]); + let info = message_info(&owner, &[]); VestingBaseBuilder::default() .historical() - .build(deps.as_mut(), owner, String::from(token_info_manager)) + .build( + deps.as_mut(), + owner.to_string(), + token_info_manager.to_string(), + ) .unwrap(); - // check initialisation + // check initialization assert_eq!( from_json::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) .unwrap(), Config { - owner: Addr::unchecked("owner"), - token_info_manager: Addr::unchecked(token_info_manager), + owner: owner.clone(), + token_info_manager: token_info_manager.clone(), vesting_token: None, extensions: Extensions { historical: true, @@ -290,7 +313,7 @@ fn proper_building_historical() { msg: QueryMsgWithManagers::VestingManagers {} } ) - .unwrap_err(), + .unwrap_err(), ext_unsupported_err("with_managers") ); @@ -302,7 +325,7 @@ fn proper_building_historical() { msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: 1000u64 }, }, ) - .unwrap(); + .unwrap(); // make sure managed extension is not enabled assert_eq!( @@ -317,29 +340,33 @@ fn proper_building_historical() { } }, ) - .unwrap_err(), + .unwrap_err(), ext_unsupported_err("managed").into() ); } #[test] -fn proper_building_managed() { +fn test_proper_building_managed() { let mut deps = mock_dependencies(); - let owner = String::from("owner"); - let token_info_manager = "token_info_manager"; + let owner = deps.api.addr_make("owner"); + let token_info_manager = deps.api.addr_make("token_info_manager"); let env = mock_env(); VestingBaseBuilder::default() .managed() - .build(deps.as_mut(), owner, String::from(token_info_manager)) + .build( + deps.as_mut(), + owner.to_string(), + token_info_manager.to_string(), + ) .unwrap(); - // check initialisation and set vesting token + // check initialization and set vesting token assert_eq!( from_json::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) .unwrap(), Config { - owner: Addr::unchecked("owner"), - token_info_manager: Addr::unchecked(token_info_manager), + owner: owner.clone(), + token_info_manager: token_info_manager.clone(), vesting_token: None, extensions: Extensions { historical: false, @@ -348,16 +375,18 @@ fn proper_building_managed() { } } ); - let info = mock_info("token_info_manager", &[]); + + let info = message_info(&token_info_manager, &[]); + let ntrn_token = deps.api.addr_make("ntrn_token"); execute( deps.as_mut(), env.clone(), info, ExecuteMsg::SetVestingToken { - vesting_token: token_asset_info(Addr::unchecked("ntrn_token")), + vesting_token: token_asset_info(ntrn_token), }, ) - .unwrap(); + .unwrap(); // make sure with_managers extension is not enabled assert_eq!( @@ -368,7 +397,7 @@ fn proper_building_managed() { msg: QueryMsgWithManagers::VestingManagers {} } ) - .unwrap_err(), + .unwrap_err(), ext_unsupported_err("with_managers") ); @@ -381,12 +410,13 @@ fn proper_building_managed() { msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: 1000u64 } } ) - .unwrap_err(), + .unwrap_err(), ext_unsupported_err("historical") ); + let info = message_info(&owner, &[]); + let clawback = deps.api.addr_make("ntrn_token").into_string(); // make sure managed extension is enabled - let info = mock_info("owner", &[]); execute( deps.as_mut(), env, @@ -394,34 +424,41 @@ fn proper_building_managed() { ExecuteMsg::ManagedExtension { msg: ExecuteMsgManaged::RemoveVestingAccounts { vesting_accounts: vec![], - clawback_account: String::from("clawback"), + clawback_account: clawback, }, }, ) - .unwrap(); + .unwrap(); } #[test] -fn proper_building_all_extensions() { +fn test_proper_building_all_extensions() { let mut deps = mock_dependencies(); - let owner = String::from("owner"); - let token_info_manager = "token_info_manager"; + let owner = deps.api.addr_make("owner"); + let token_info_manager = deps.api.addr_make("token_info_manager"); let env = mock_env(); - let vesting_managers = vec!["manager1".to_string(), "manager2".to_string()]; + let vesting_managers = vec![ + deps.api.addr_make("manager1").into_string(), + deps.api.addr_make("manager2").into_string(), + ]; VestingBaseBuilder::default() .historical() .managed() .with_managers(vesting_managers.clone()) - .build(deps.as_mut(), owner, String::from(token_info_manager)) + .build( + deps.as_mut(), + owner.to_string(), + token_info_manager.to_string(), + ) .unwrap(); - // check initialisation and set vesting token + // check initialization and set vesting token assert_eq!( from_json::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) .unwrap(), Config { - owner: Addr::unchecked("owner"), - token_info_manager: Addr::unchecked(token_info_manager), + owner: owner.clone(), + token_info_manager: token_info_manager.clone(), vesting_token: None, extensions: Extensions { historical: true, @@ -430,32 +467,33 @@ fn proper_building_all_extensions() { } } ); - let info = mock_info("token_info_manager", &[]); + + let info = message_info(&token_info_manager, &[]); + let ntrn_token = deps.api.addr_make("ntrn_token"); execute( deps.as_mut(), env.clone(), info, ExecuteMsg::SetVestingToken { - vesting_token: token_asset_info(Addr::unchecked("ntrn_token")), + vesting_token: token_asset_info(ntrn_token), }, ) - .unwrap(); + .unwrap(); // make sure with_managers extension is enabled - assert_eq!( - from_json::>( - &query( - deps.as_ref(), - env.clone(), - QueryMsg::WithManagersExtension { - msg: QueryMsgWithManagers::VestingManagers {}, - }, - ) - .unwrap() + let managers_addrs = from_json::>( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::WithManagersExtension { + msg: QueryMsgWithManagers::VestingManagers {}, + }, ) - .unwrap(), - vesting_managers - ); + .unwrap(), + ) + .unwrap(); + let managers_strings: Vec = managers_addrs.iter().map(|a| a.to_string()).collect(); + assert_eq!(managers_strings, vesting_managers); // make sure historical extension is enabled query( @@ -465,10 +503,11 @@ fn proper_building_all_extensions() { msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: 1000u64 }, }, ) - .unwrap(); + .unwrap(); // make sure managed extension is enabled - let info = mock_info("owner", &[]); + let info = message_info(&owner, &[]); + let clawback = deps.api.addr_make("ntrn_token").into_string(); execute( deps.as_mut(), env, @@ -476,9 +515,2469 @@ fn proper_building_all_extensions() { ExecuteMsg::ManagedExtension { msg: ExecuteMsgManaged::RemoveVestingAccounts { vesting_accounts: vec![], - clawback_account: String::from("clawback"), + clawback_account: clawback, + }, + }, + ) + .unwrap(); +} + +// ========== EXECUTE MESSAGE TESTS ========== + +#[test] +fn test_set_vesting_token() { + let mut deps = mock_dependencies(); + let owner = deps.api.addr_make("owner"); + let token_info_manager = deps.api.addr_make("token_info_manager"); + let env = mock_env(); + VestingBaseBuilder::default() + .build( + deps.as_mut(), + owner.to_string(), + token_info_manager.to_string(), + ) + .unwrap(); + + // check initialization + assert_eq!( + from_json::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) + .unwrap(), + Config { + owner: owner.clone(), + token_info_manager: token_info_manager.clone(), + vesting_token: None, + extensions: Extensions { + historical: false, + managed: false, + with_managers: false + } + } + ); + + let info = message_info(&deps.api.addr_make("stranger"), &[]); + let ntrn_token = deps.api.addr_make("ntrn_token"); + // set vesting token by a stranger -> Unauthorized + assert_eq!( + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::SetVestingToken { + vesting_token: token_asset_info(ntrn_token.clone()), + }, + ) + .unwrap_err(), + ContractError::Unauthorized {}, + ); + + // set vesting token by the manager -> Success + let info = message_info(&token_info_manager, &[]); + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::SetVestingToken { + vesting_token: token_asset_info(ntrn_token.clone()), + }, + ) + .unwrap(); + + assert_eq!( + from_json::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) + .unwrap(), + Config { + owner: owner.clone(), + token_info_manager: token_info_manager.clone(), + vesting_token: Some(token_asset_info(ntrn_token.clone())), + extensions: Extensions { + historical: false, + managed: false, + with_managers: false + } + } + ); + + let info = message_info(&owner, &[]); + let not_ntrn_token = deps.api.addr_make("not_ntrn_token"); + // set vesting token second time by the owner -> VestingTokenAlreadySet + assert_eq!( + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::SetVestingToken { + vesting_token: token_asset_info(not_ntrn_token), }, + ) + .unwrap_err(), + ContractError::VestingTokenAlreadySet {}, + ); + + assert_eq!( + from_json::(&query(deps.as_ref(), env, QueryMsg::Config {}).unwrap()).unwrap(), + Config { + owner, + token_info_manager, + vesting_token: Some(token_asset_info(ntrn_token)), + extensions: Extensions { + historical: false, + managed: false, + with_managers: false + } + } + ); +} + +#[test] +fn test_set_vesting_token_by_owner() { + let (mut deps, env, owner, _) = setup_contract(); + let vesting_token = deps.api.addr_make("vesting_token"); + + // Owner can set vesting token + let info = message_info(&owner, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::SetVestingToken { + vesting_token: token_asset_info(vesting_token.clone()), + }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "set_vesting_token"); + assert_eq!(res.attributes[1].key, "vesting_token"); + assert_eq!(res.attributes[1].value, vesting_token.to_string()); + + // Verify token is set + let config = + from_json::(&query(deps.as_ref(), env, QueryMsg::Config {}).unwrap()).unwrap(); + assert_eq!(config.vesting_token, Some(token_asset_info(vesting_token))); +} + +#[test] +fn test_set_vesting_token_native() { + let (mut deps, env, owner, _) = setup_contract(); + let native_denom = "uatom"; + + // Set native token as vesting token + let info = message_info(&owner, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::SetVestingToken { + vesting_token: native_asset_info(native_denom.to_string()), }, ) - .unwrap(); + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "set_vesting_token"); + assert_eq!(res.attributes[1].key, "vesting_token"); + assert_eq!(res.attributes[1].value, native_denom); + + // Verify token is set + let config = + from_json::(&query(deps.as_ref(), env, QueryMsg::Config {}).unwrap()).unwrap(); + assert_eq!( + config.vesting_token, + Some(native_asset_info(native_denom.to_string())) + ); } + +#[test] +fn test_register_vesting_accounts_cw20() { + let (mut deps, env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + let user2 = deps.api.addr_make("user2"); + let amount = Uint128::new(1000); + + let vesting_accounts = vec![ + VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + Uint128::new(500), + None, + None, + )], + }, + VestingAccount { + address: user2.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + Uint128::new(500), + None, + None, + )], + }, + ]; + + // Register vesting accounts via CW20 receive + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "register_vesting_accounts"); + assert_eq!(res.attributes[1].key, "deposited"); + assert_eq!(res.attributes[1].value, amount.to_string()); + + // Verify vesting accounts are registered + let user1_account = from_json::( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::VestingAccount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(user1_account.address, user1); + assert_eq!(user1_account.info.schedules.len(), 1); + + let user2_account = from_json::( + &query( + deps.as_ref(), + env, + QueryMsg::VestingAccount { + address: user2.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(user2_account.address, user2); + assert_eq!(user2_account.info.schedules.len(), 1); +} + +#[test] +fn test_register_vesting_accounts_native() { + let (mut deps, env, owner, _) = setup_contract(); + let native_denom = "uatom"; + + // Set native token as vesting token + let info = message_info(&owner, &[]); + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::SetVestingToken { + vesting_token: native_asset_info(native_denom.to_string()), + }, + ) + .unwrap(); + + let user1 = deps.api.addr_make("user1"); + let amount = Uint128::new(1000); + + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + amount, + None, + None, + )], + }]; + + // Register vesting accounts with native token payment + let info = message_info( + &owner, + &[Coin { + denom: native_denom.to_string(), + amount, + }], + ); + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::RegisterVestingAccounts { vesting_accounts }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "register_vesting_accounts"); + assert_eq!(res.attributes[1].key, "deposited"); + assert_eq!(res.attributes[1].value, amount.to_string()); + + // Verify vesting account is registered + let user1_account = from_json::( + &query( + deps.as_ref(), + env, + QueryMsg::VestingAccount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(user1_account.address, user1); + assert_eq!(user1_account.info.schedules.len(), 1); +} + +#[test] +fn test_register_vesting_accounts_unauthorized() { + let (mut deps, env, _, _, vesting_token) = setup_contract_with_token(); + + let stranger = deps.api.addr_make("stranger"); + let user1 = deps.api.addr_make("user1"); + let amount = Uint128::new(1000); + + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + amount, + None, + None, + )], + }]; + + // Try to register from unauthorized sender + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: stranger.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + let err = execute(deps.as_mut(), env, info, ExecuteMsg::Receive(cw20_msg)).unwrap_err(); + + assert_eq!(err, ContractError::Unauthorized {}); +} + +#[test] +fn test_register_vesting_accounts_amount_mismatch() { + let (mut deps, env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + let sent_amount = Uint128::new(1000); + let schedule_amount = Uint128::new(500); // Different from sent amount + + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + schedule_amount, + None, + None, + )], + }]; + + // Register with mismatched amounts + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount: sent_amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + let err = execute(deps.as_mut(), env, info, ExecuteMsg::Receive(cw20_msg)).unwrap_err(); + + assert_eq!(err, ContractError::VestingScheduleAmountError {}); +} + +#[test] +fn test_register_vesting_accounts_invalid_schedule() { + let (mut deps, env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + let amount = Uint128::new(1000); + + // Invalid schedule: start_time >= end_time + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds() + 100, + Uint128::new(500), + Some(env.block.time.seconds()), + Some(amount), + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + let err = execute(deps.as_mut(), env, info, ExecuteMsg::Receive(cw20_msg)).unwrap_err(); + + assert_eq!(err, ContractError::VestingScheduleError(user1.to_string())); +} + +#[test] +fn test_claim_tokens_cw20() { + let (mut deps, mut env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + let amount = Uint128::new(1000); + + // Register vesting account + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + Uint128::new(500), + Some(env.block.time.seconds() + 100), + Some(amount), + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Move time forward to make some tokens available + env.block.time = env.block.time.plus_seconds(50); + + // Claim tokens + let info = message_info(&user1, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Claim { + recipient: None, + amount: None, + }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "claim"); + assert_eq!(res.attributes[1].key, "address"); + assert_eq!(res.attributes[1].value, user1.to_string()); + + // Check that transfer message is created + assert_eq!(res.messages.len(), 1); + match &res.messages[0].msg { + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr, msg, .. + }) => { + assert_eq!(contract_addr, &vesting_token.to_string()); + let transfer_msg: Cw20ExecuteMsg = from_json(msg).unwrap(); + match transfer_msg { + Cw20ExecuteMsg::Transfer { recipient, amount } => { + assert_eq!(recipient, user1.to_string()); + assert!(amount > Uint128::zero()); + } + _ => panic!("Expected Transfer message"), + } + } + _ => panic!("Expected Wasm message"), + } +} + +#[test] +fn test_claim_tokens_native() { + let (mut deps, env, owner, _) = setup_contract(); + let native_denom = "uatom"; + + // Set native token as vesting token + let info = message_info(&owner, &[]); + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::SetVestingToken { + vesting_token: native_asset_info(native_denom.to_string()), + }, + ) + .unwrap(); + + let user1 = deps.api.addr_make("user1"); + let amount = Uint128::new(1000); + + // Register vesting account + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + amount, + None, + None, + )], + }]; + + let info = message_info( + &owner, + &[Coin { + denom: native_denom.to_string(), + amount, + }], + ); + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::RegisterVestingAccounts { vesting_accounts }, + ) + .unwrap(); + + // Claim tokens + let info = message_info(&user1, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Claim { + recipient: None, + amount: None, + }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "claim"); + assert_eq!(res.attributes[1].key, "address"); + assert_eq!(res.attributes[1].value, user1.to_string()); + + // Check that bank send message is created + assert_eq!(res.messages.len(), 1); + match &res.messages[0].msg { + CosmosMsg::Bank(BankMsg::Send { + to_address, + amount: coins, + }) => { + assert_eq!(to_address, &user1.to_string()); + assert_eq!(coins.len(), 1); + assert_eq!(coins[0].denom, native_denom); + assert_eq!(coins[0].amount, amount); + } + _ => panic!("Expected Bank message"), + } +} + +#[test] +fn test_claim_tokens_with_recipient() { + let (mut deps, env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + let recipient = deps.api.addr_make("recipient"); + let amount = Uint128::new(1000); + + // Register vesting account + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + amount, + None, + None, + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Claim tokens with specific recipient + let info = message_info(&user1, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Claim { + recipient: Some(recipient.to_string()), + amount: None, + }, + ) + .unwrap(); + + // Check that transfer message goes to recipient + assert_eq!(res.messages.len(), 1); + match &res.messages[0].msg { + CosmosMsg::Wasm(WasmMsg::Execute { msg, .. }) => { + let transfer_msg: Cw20ExecuteMsg = from_json(msg).unwrap(); + match transfer_msg { + Cw20ExecuteMsg::Transfer { + recipient: msg_recipient, + .. + } => { + assert_eq!(msg_recipient, recipient.to_string()); + } + _ => panic!("Expected Transfer message"), + } + } + _ => panic!("Expected Wasm message"), + } +} + +#[test] +fn test_claim_tokens_partial_amount() { + let (mut deps, env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + let total_amount = Uint128::new(1000); + let claim_amount = Uint128::new(500); + + // Register vesting account + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + total_amount, + None, + None, + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount: total_amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Claim partial amount + let info = message_info(&user1, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Claim { + recipient: None, + amount: Some(claim_amount), + }, + ) + .unwrap(); + + // Check that transfer message has correct amount + assert_eq!(res.messages.len(), 1); + match &res.messages[0].msg { + CosmosMsg::Wasm(WasmMsg::Execute { msg, .. }) => { + let transfer_msg: Cw20ExecuteMsg = from_json(msg).unwrap(); + match transfer_msg { + Cw20ExecuteMsg::Transfer { amount, .. } => { + assert_eq!(amount, claim_amount); + } + _ => panic!("Expected Transfer message"), + } + } + _ => panic!("Expected Wasm message"), + } +} + +#[test] +fn test_claim_tokens_insufficient_amount() { + let (mut deps, env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + let total_amount = Uint128::new(1000); + let claim_amount = Uint128::new(1500); // More than available + + // Register vesting account + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + total_amount, + None, + None, + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount: total_amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Try to claim more than available + let info = message_info(&user1, &[]); + let err = execute( + deps.as_mut(), + env, + info, + ExecuteMsg::Claim { + recipient: None, + amount: Some(claim_amount), + }, + ) + .unwrap_err(); + + assert_eq!(err, ContractError::AmountIsNotAvailable {}); +} + +#[test] +fn test_claim_tokens_no_vesting_account() { + let (mut deps, env, _, _, _) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + + // Try to claim without having a vesting account + let info = message_info(&user1, &[]); + let err = execute( + deps.as_mut(), + env, + info, + ExecuteMsg::Claim { + recipient: None, + amount: None, + }, + ) + .unwrap_err(); + + // Should get a storage error since the account doesn't exist + assert!(matches!(err, ContractError::Std(_))); +} + +#[test] +fn test_force_claim_tokens() { + let (mut deps, mut env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + let amount = Uint128::new(1000); + + // Register vesting account with future vesting + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + Uint128::new(0), + Some(env.block.time.seconds() + 1000), + Some(amount), + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Move time forward a bit but not to full vesting + env.block.time = env.block.time.plus_seconds(200); + + // Force claim tokens + let info = message_info(&user1, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info.clone(), + ExecuteMsg::ForceClaim { recipient: None }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "force_claim"); + assert_eq!(res.attributes[1].key, "address"); + assert_eq!(res.attributes[1].value, user1.to_string()); + + // Check that some amount was claimed (should be more than normal vesting due to force claim) + assert_eq!(res.messages.len(), 1); + match &res.messages[0].msg { + CosmosMsg::Wasm(WasmMsg::Execute { msg, .. }) => { + let transfer_msg: Cw20ExecuteMsg = from_json(msg).unwrap(); + match transfer_msg { + Cw20ExecuteMsg::Transfer { amount, .. } => { + assert_eq!(amount, Uint128::new(600)); // 200 + 800 / 2 = 600 (available to claim + 50% of the remaining) + } + _ => panic!("Expected Transfer message"), + } + } + _ => panic!("Expected Wasm message"), + } + + // User hasn't anything to claim anymore + let available = from_json::( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::AvailableAmount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(available, Uint128::zero()); + + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Claim { + recipient: None, + amount: None, + }, + ) + .unwrap(); + assert_eq!(res.messages.len(), 0); + assert_eq!(res.attributes[2].value, "0"); + assert_eq!(res.attributes[3].value, "0"); + + // Owner can get remove vesting accounts and get unclaimed amount + let clawback_account = deps.api.addr_make("clawback"); + let info = message_info(&owner, &[]); + let res = execute( + deps.as_mut(), + env, + info, + ExecuteMsg::ManagedExtension { + msg: ExecuteMsgManaged::RemoveVestingAccounts { + vesting_accounts: vec![user1.to_string()], + clawback_account: clawback_account.to_string(), + }, + }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "remove_vesting_accounts"); + assert_eq!(res.messages.len(), 1); + match &res.messages[0].msg { + CosmosMsg::Wasm(WasmMsg::Execute { msg, .. }) => { + let transfer_msg: Cw20ExecuteMsg = from_json(msg).unwrap(); + match transfer_msg { + Cw20ExecuteMsg::Transfer { amount, recipient } => { + assert_eq!(amount, Uint128::new(400)); // 400 - the remaining of the users vesting + assert_eq!(recipient, clawback_account.to_string()); + } + _ => panic!("Expected Transfer message"), + } + } + _ => panic!("Expected Wasm message"), + } +} + +#[test] +fn test_ownership_proposal() { + let (mut deps, env, owner, _) = setup_contract(); + let new_owner = deps.api.addr_make("new_owner"); + let expires_in = 86400u64; // 1 day + + // Propose new owner + let info = message_info(&owner, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::ProposeNewOwner { + owner: new_owner.to_string(), + expires_in, + }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "propose_new_owner"); + assert_eq!(res.attributes[1].key, "new_owner"); + assert_eq!(res.attributes[1].value, new_owner.to_string()); + + // Claim ownership + let info = message_info(&new_owner, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::ClaimOwnership {}, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "claim_ownership"); + assert_eq!(res.attributes[1].key, "new_owner"); + assert_eq!(res.attributes[1].value, new_owner.to_string()); + + // Verify ownership changed + let config = + from_json::(&query(deps.as_ref(), env, QueryMsg::Config {}).unwrap()).unwrap(); + assert_eq!(config.owner, new_owner); +} + +#[test] +fn test_ownership_proposal_unauthorized() { + let (mut deps, env, _, _) = setup_contract(); + let stranger = deps.api.addr_make("stranger"); + let new_owner = deps.api.addr_make("new_owner"); + + // Try to propose new owner as stranger + let info = message_info(&stranger, &[]); + let err = execute( + deps.as_mut(), + env, + info, + ExecuteMsg::ProposeNewOwner { + owner: new_owner.to_string(), + expires_in: 86400u64, + }, + ) + .unwrap_err(); + + assert!(matches!(err, ContractError::Std(_))); +} + +#[test] +fn test_ownership_proposal_expired() { + let (mut deps, mut env, owner, _) = setup_contract(); + let new_owner = deps.api.addr_make("new_owner"); + let expires_in = 100u64; + + // Propose new owner + let info = message_info(&owner, &[]); + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::ProposeNewOwner { + owner: new_owner.to_string(), + expires_in, + }, + ) + .unwrap(); + + // Move time past expiration + env.block.time = env.block.time.plus_seconds(expires_in + 1); + + // Try to claim expired ownership + let info = message_info(&new_owner, &[]); + let err = execute(deps.as_mut(), env, info, ExecuteMsg::ClaimOwnership {}).unwrap_err(); + + assert!(matches!(err, ContractError::Std(_))); +} + +#[test] +fn test_drop_ownership_proposal() { + let (mut deps, env, owner, _) = setup_contract(); + let new_owner = deps.api.addr_make("new_owner"); + + // Propose new owner + let info = message_info(&owner, &[]); + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::ProposeNewOwner { + owner: new_owner.to_string(), + expires_in: 86400u64, + }, + ) + .unwrap(); + + // Drop ownership proposal + let info = message_info(&owner, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::DropOwnershipProposal {}, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "drop_ownership_proposal"); + + // Try to claim ownership after dropping proposal + let info = message_info(&new_owner, &[]); + let err = execute(deps.as_mut(), env, info, ExecuteMsg::ClaimOwnership {}).unwrap_err(); + + assert!(matches!(err, ContractError::Std(_))); +} + +// ========== QUERY MESSAGE TESTS ========== + +#[test] +fn test_query_config() { + let (deps, env, owner, token_info_manager) = setup_contract(); + + let config = + from_json::(&query(deps.as_ref(), env, QueryMsg::Config {}).unwrap()).unwrap(); + + assert_eq!(config.owner, owner); + assert_eq!(config.token_info_manager, token_info_manager); + assert_eq!(config.vesting_token, None); + assert!(!config.extensions.historical); + assert!(!config.extensions.managed); + assert!(!config.extensions.with_managers); +} + +#[test] +fn test_query_vesting_account() { + let (mut deps, env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + let amount = Uint128::new(1000); + + // Register vesting account + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + amount, + None, + None, + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Query vesting account + let account = from_json::( + &query( + deps.as_ref(), + env, + QueryMsg::VestingAccount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + + assert_eq!(account.address, user1); + assert_eq!(account.info.schedules.len(), 1); + assert_eq!(account.info.released_amount, Uint128::zero()); + assert_eq!(account.info.schedules[0].start_point.amount, amount); +} + +#[test] +fn test_query_vesting_account_not_found() { + let (deps, env, _, _, _) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + + // Query non-existent vesting account + let err = query( + deps.as_ref(), + env, + QueryMsg::VestingAccount { + address: user1.to_string(), + }, + ) + .unwrap_err(); + + assert!(matches!(err, StdError::NotFound { .. })); +} + +#[test] +fn test_query_vesting_accounts() { + let (mut deps, env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + let user2 = deps.api.addr_make("user2"); + let amount = Uint128::new(1000); + + // Register multiple vesting accounts + let vesting_accounts = vec![ + VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + Uint128::new(500), + None, + None, + )], + }, + VestingAccount { + address: user2.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + Uint128::new(500), + None, + None, + )], + }, + ]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Query all vesting accounts + let accounts = from_json::( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::VestingAccounts { + start_after: None, + limit: None, + order_by: None, + }, + ) + .unwrap(), + ) + .unwrap(); + + assert_eq!(accounts.vesting_accounts.len(), 2); + + // Query with limit + let accounts = from_json::( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::VestingAccounts { + start_after: None, + limit: Some(1), + order_by: None, + }, + ) + .unwrap(), + ) + .unwrap(); + + assert_eq!(accounts.vesting_accounts.len(), 1); + + // Query with start_after + let accounts = from_json::( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::VestingAccounts { + start_after: Some(user1.to_string()), + limit: None, + order_by: Some(OrderBy::Asc), + }, + ) + .unwrap(), + ) + .unwrap(); + + assert_eq!(accounts.vesting_accounts.len(), 1); + assert_eq!(accounts.vesting_accounts[0].address, user2); +} + +#[test] +fn test_query_available_amount() { + let (mut deps, mut env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + let amount = Uint128::new(1000); + + // Register vesting account with linear vesting + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + Uint128::new(500), + Some(env.block.time.seconds() + 100), + Some(amount), + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Query available amount at start + let available = from_json::( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::AvailableAmount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(available, Uint128::new(500)); // Start amount + + // Move time forward halfway + env.block.time = env.block.time.plus_seconds(50); + + let available = from_json::( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::AvailableAmount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(available, Uint128::new(750)); // Start + half of linear vesting + + // Move time to end + env.block.time = env.block.time.plus_seconds(50); + + let available = from_json::( + &query( + deps.as_ref(), + env, + QueryMsg::AvailableAmount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(available, amount); // Full amount +} + +#[test] +fn test_query_vesting_state() { + let (mut deps, env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + let amount = Uint128::new(1000); + + // Register vesting account + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + amount, + None, + None, + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Query vesting state + let state = + from_json::(&query(deps.as_ref(), env, QueryMsg::VestingState {}).unwrap()) + .unwrap(); + + assert_eq!(state.total_granted, amount); + assert_eq!(state.total_released, Uint128::zero()); +} + +#[test] +fn test_query_timestamp() { + let (deps, env, _, _) = setup_contract(); + + let timestamp = + from_json::(&query(deps.as_ref(), env.clone(), QueryMsg::Timestamp {}).unwrap()) + .unwrap(); + + assert_eq!(timestamp, env.block.time.seconds()); +} + +// ========== EXTENSION TESTS ========== + +#[test] +fn test_with_managers_extension() { + let (mut deps, env, owner, _, vesting_managers) = setup_contract_with_extensions(); + + // Query vesting managers + let managers_addrs = from_json::>( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::WithManagersExtension { + msg: QueryMsgWithManagers::VestingManagers {}, + }, + ) + .unwrap(), + ) + .unwrap(); + let managers_strings: Vec = managers_addrs.iter().map(|a| a.to_string()).collect(); + assert_eq!(managers_strings, vesting_managers); + + // Add vesting managers + let new_managers = vec![deps.api.addr_make("manager3").into_string()]; + let info = message_info(&owner, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::WithManagersExtension { + msg: ExecuteMsgWithManagers::AddVestingManagers { + managers: new_managers.clone(), + }, + }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "add_vesting_managers"); + + // Remove vesting managers + let info = message_info(&owner, &[]); + let res = execute( + deps.as_mut(), + env, + info, + ExecuteMsg::WithManagersExtension { + msg: ExecuteMsgWithManagers::RemoveVestingManagers { + managers: vec![vesting_managers[0].clone()], + }, + }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "remove_vesting_managers"); +} + +#[test] +fn test_historical_extension() { + let (mut deps, env, _, token_info_manager, _) = setup_contract_with_extensions(); + + // Set vesting token + let vesting_token = deps.api.addr_make("vesting_token"); + let info = message_info(&token_info_manager, &[]); + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::SetVestingToken { + vesting_token: token_asset_info(vesting_token), + }, + ) + .unwrap(); + + // Query historical data + let _result = query( + deps.as_ref(), + env.clone(), + QueryMsg::HistoricalExtension { + msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: 1000u64 }, + }, + ) + .unwrap(); + + let user1 = deps.api.addr_make("user1"); + let _result = query( + deps.as_ref(), + env, + QueryMsg::HistoricalExtension { + msg: QueryMsgHistorical::UnclaimedAmountAtHeight { + address: user1.to_string(), + height: 1000u64, + }, + }, + ) + .unwrap(); +} + +#[test] +fn test_managed_extension() { + let (mut deps, env, owner, token_info_manager, _) = setup_contract_with_extensions(); + + // Set vesting token + let vesting_token = deps.api.addr_make("vesting_token"); + let info = message_info(&token_info_manager, &[]); + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::SetVestingToken { + vesting_token: token_asset_info(vesting_token.clone()), + }, + ) + .unwrap(); + + // Register vesting account first + let user1 = deps.api.addr_make("user1"); + let amount = Uint128::new(1000); + + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + amount, + None, + None, + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Remove vesting accounts + let clawback_account = deps.api.addr_make("clawback"); + let info = message_info(&owner, &[]); + let res = execute( + deps.as_mut(), + env, + info, + ExecuteMsg::ManagedExtension { + msg: ExecuteMsgManaged::RemoveVestingAccounts { + vesting_accounts: vec![user1.to_string()], + clawback_account: clawback_account.to_string(), + }, + }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "remove_vesting_accounts"); +} + +#[test] +fn test_extension_disabled_errors() { + let (mut deps, env, owner, _) = setup_contract(); + + // Test with_managers extension disabled + let err = query( + deps.as_ref(), + env.clone(), + QueryMsg::WithManagersExtension { + msg: QueryMsgWithManagers::VestingManagers {}, + }, + ) + .unwrap_err(); + assert_eq!(err, ext_unsupported_err("with_managers")); + + // Test historical extension disabled + let err = query( + deps.as_ref(), + env.clone(), + QueryMsg::HistoricalExtension { + msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: 1000u64 }, + }, + ) + .unwrap_err(); + assert_eq!(err, ext_unsupported_err("historical")); + + // Test managed extension disabled + let info = message_info(&owner, &[]); + let err = execute( + deps.as_mut(), + env, + info, + ExecuteMsg::ManagedExtension { + msg: ExecuteMsgManaged::RemoveVestingAccounts { + vesting_accounts: vec![], + clawback_account: String::from("clawback"), + }, + }, + ) + .unwrap_err(); + assert_eq!(err, ext_unsupported_err("managed").into()); +} + +// ========== EDGE CASE TESTS ========== + +#[test] +fn test_vesting_schedule_edge_cases() { + let (mut deps, env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + let amount = Uint128::new(1000); + + // Test schedule with same start and end time (instant vesting) + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + Uint128::new(500), + Some(env.block.time.seconds()), + Some(amount), + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + let err = execute(deps.as_mut(), env, info, ExecuteMsg::Receive(cw20_msg)).unwrap_err(); + + assert_eq!(err, ContractError::VestingScheduleError(user1.to_string())); +} + +#[test] +fn test_multiple_schedules_same_user() { + let (mut deps, env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + + // Register first schedule + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + Uint128::new(500), + None, + None, + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount: Uint128::new(500), + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Register second schedule for same user + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds() + 100, + Uint128::new(500), + None, + None, + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount: Uint128::new(500), + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Verify user has multiple schedules + let account = from_json::( + &query( + deps.as_ref(), + env, + QueryMsg::VestingAccount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + + assert_eq!(account.info.schedules.len(), 2); +} + +#[test] +fn test_zero_amount_claim() { + let (mut deps, env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + let amount = Uint128::new(1000); + + // Register vesting account with future start time + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds() + 1000, // Future start + amount, + None, + None, + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Try to claim before vesting starts (should result in zero claim) + let info = message_info(&user1, &[]); + let res = execute( + deps.as_mut(), + env, + info, + ExecuteMsg::Claim { + recipient: None, + amount: None, + }, + ) + .unwrap(); + + // Should have no messages since amount is zero + assert_eq!(res.messages.len(), 0); + assert_eq!(res.attributes[3].key, "claimed_amount"); + assert_eq!(res.attributes[3].value, "0"); +} + +#[test] +fn test_vesting_token_not_set_error() { + let (mut deps, env, _owner, _) = setup_contract(); + + let user1 = deps.api.addr_make("user1"); + + // Try to claim without vesting token set + let info = message_info(&user1, &[]); + let err = execute( + deps.as_mut(), + env, + info, + ExecuteMsg::Claim { + recipient: None, + amount: None, + }, + ) + .unwrap_err(); + + assert_eq!(err, ContractError::VestingTokenIsNotSet {}); +} + +#[test] +fn test_register_with_manager() { + let (mut deps, env, _owner, token_info_manager, _vesting_managers) = + setup_contract_with_extensions(); + let native_denom = "uatom"; + + // Set native token as vesting token (only owner or token_info_manager can do this) + let info = message_info(&token_info_manager, &[]); + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::SetVestingToken { + vesting_token: native_asset_info(native_denom.to_string()), + }, + ) + .unwrap(); + + let user1 = deps.api.addr_make("user1"); + let amount = Uint128::new(1000); + + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + amount, + None, + None, + )], + }]; + + // Verify manager is in the list and get the actual manager address + let managers = from_json::>( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::WithManagersExtension { + msg: QueryMsgWithManagers::VestingManagers {}, + }, + ) + .unwrap(), + ) + .unwrap(); + + // Use the first manager from the actual stored list + let manager = &managers[0]; + + // Manager can register vesting accounts + let info = message_info( + manager, + &[Coin { + denom: native_denom.to_string(), + amount, + }], + ); + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::RegisterVestingAccounts { vesting_accounts }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "register_vesting_accounts"); +} + +#[test] +fn test_full_vesting_lifecycle() { + let (mut deps, mut env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + let total_amount = Uint128::new(1000); + let start_amount = Uint128::new(200); + let vesting_duration = 100u64; + + // Register vesting account with linear vesting + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + start_amount, + Some(env.block.time.seconds() + vesting_duration), + Some(total_amount), + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount: total_amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Check initial available amount + let available = from_json::( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::AvailableAmount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(available, start_amount); + + // Claim initial amount + let info = message_info(&user1, &[]); + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Claim { + recipient: None, + amount: Some(start_amount), + }, + ) + .unwrap(); + + // Move time forward to middle of vesting + env.block.time = env.block.time.plus_seconds(vesting_duration / 2); + + let available = from_json::( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::AvailableAmount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + // Should be approximately half of the remaining amount vested + let expected = start_amount + (total_amount - start_amount) / Uint128::new(2); + assert_eq!(available, expected - start_amount); // Minus already claimed + + // Move to end of vesting + env.block.time = env.block.time.plus_seconds(vesting_duration / 2); + + let available = from_json::( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::AvailableAmount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(available, total_amount - start_amount); // Full amount minus already claimed + + // Claim remaining amount + let info = message_info(&user1, &[]); + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Claim { + recipient: None, + amount: None, + }, + ) + .unwrap(); + + // Check final state + let available = from_json::( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::AvailableAmount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(available, Uint128::zero()); + + let state = + from_json::(&query(deps.as_ref(), env, QueryMsg::VestingState {}).unwrap()) + .unwrap(); + assert_eq!(state.total_granted, total_amount); + assert_eq!(state.total_released, total_amount); +} + + +#[test] +fn test_force_claim_tokens_and_create_another_schedule_after() { + let (mut deps, mut env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + let amount = Uint128::new(1000); + + // Register vesting account with future vesting + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + Uint128::new(0), + Some(env.block.time.seconds() + 1000), + Some(amount), + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Move time forward a bit but not to full vesting + env.block.time = env.block.time.plus_seconds(200); + + // Force claim tokens + let info = message_info(&user1, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info.clone(), + ExecuteMsg::ForceClaim { recipient: None }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "force_claim"); + assert_eq!(res.attributes[1].key, "address"); + assert_eq!(res.attributes[1].value, user1.to_string()); + assert_eq!(res.attributes[3].key, "claimed_amount"); + assert_eq!(res.attributes[3].value, "600"); + + // Check that some amount was claimed (should be more than normal vesting due to force claim) + assert_eq!(res.messages.len(), 1); + match &res.messages[0].msg { + CosmosMsg::Wasm(WasmMsg::Execute { msg, .. }) => { + let transfer_msg: Cw20ExecuteMsg = from_json(msg).unwrap(); + match transfer_msg { + Cw20ExecuteMsg::Transfer { amount, .. } => { + assert_eq!(amount, Uint128::new(600)); // 200 + 800 / 2 = 600 (available to claim + 50% of the remaining) + } + _ => panic!("Expected Transfer message"), + } + } + _ => panic!("Expected Wasm message"), + } + + // User hasn't anything to claim anymore + let available = from_json::( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::AvailableAmount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(available, Uint128::zero()); + + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Claim { + recipient: None, + amount: None, + }, + ) + .unwrap(); + assert_eq!(res.messages.len(), 0); + assert_eq!(res.attributes[2].value, "0"); + assert_eq!(res.attributes[3].value, "0"); + + // Register vesting account with future vesting + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + Uint128::new(0), + Some(env.block.time.seconds() + 1000), + Some(amount), + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Move time forward a bit but not to full vesting + env.block.time = env.block.time.plus_seconds(200); + + // User has 200 tokens to claim from newly created vesting schedule + let available = from_json::( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::AvailableAmount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(available, Uint128::from(200u128)); + + // A user can't claim more tokens than it has + let info = message_info(&user1, &[]); + assert_eq!(execute( + deps.as_mut(), + env.clone(), + info.clone(), + ExecuteMsg::Claim { amount: Some(Uint128::new(500u128)), recipient: None }, + ).err().unwrap(), AmountIsNotAvailable {}); + + // A user can claim tokens for newly created vesting schedule + let info = message_info(&user1, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info.clone(), + ExecuteMsg::Claim { amount: None, recipient: None }, + ).unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "claim"); + assert_eq!(res.attributes[3].key, "claimed_amount"); + assert_eq!(res.attributes[3].value, "200"); + assert_eq!(res.attributes[1].key, "address"); + assert_eq!(res.attributes[1].value, user1.to_string()); + + // A user can force claim tokens for newly created vesting schedule + let info = message_info(&user1, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info.clone(), + ExecuteMsg::ForceClaim { recipient: None }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "force_claim"); + assert_eq!(res.attributes[3].key, "claimed_amount"); + assert_eq!(res.attributes[3].value, "400"); + assert_eq!(res.attributes[1].key, "address"); + assert_eq!(res.attributes[1].value, user1.to_string()); + + // Owner can get remove vesting accounts and get unclaimed amount + let clawback_account = deps.api.addr_make("clawback"); + let info = message_info(&owner, &[]); + let res = execute( + deps.as_mut(), + env, + info, + ExecuteMsg::ManagedExtension { + msg: ExecuteMsgManaged::RemoveVestingAccounts { + vesting_accounts: vec![user1.to_string()], + clawback_account: clawback_account.to_string(), + }, + }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "remove_vesting_accounts"); + assert_eq!(res.messages.len(), 1); + match &res.messages[0].msg { + CosmosMsg::Wasm(WasmMsg::Execute { msg, .. }) => { + let transfer_msg: Cw20ExecuteMsg = from_json(msg).unwrap(); + match transfer_msg { + Cw20ExecuteMsg::Transfer { amount, recipient } => { + assert_eq!(amount, Uint128::new(800)); // 800 - the remaining of the users vesting + assert_eq!(recipient, clawback_account.to_string()); + } + _ => panic!("Expected Transfer message"), + } + } + _ => panic!("Expected Wasm message"), + } +} + + +#[test] +fn test_force_claim_tokens_multiple_schedules() { + let (mut deps, mut env, owner, _, vesting_token) = setup_contract_with_token(); + + let user1 = deps.api.addr_make("user1"); + let amount = Uint128::new(100); + + // Register vesting account with future vesting + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds() + 50, + Uint128::new(0), + Some(env.block.time.seconds() + 100), + Some(amount), + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Force claim tokens before the start of a schedule + let info = message_info(&user1, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info.clone(), + ExecuteMsg::ForceClaim { recipient: None }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "force_claim"); + assert_eq!(res.attributes[1].key, "address"); + assert_eq!(res.attributes[1].value, user1.to_string()); + assert_eq!(res.attributes[3].key, "claimed_amount"); + assert_eq!(res.attributes[3].value, "50"); + + + // Register vesting account with future vesting + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + Uint128::new(0), + Some(env.block.time.seconds() + 100), + Some(amount), + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Move time forward a bit but not to full vesting + env.block.time = env.block.time.plus_seconds(5); + + // Сlaim tokens + let info = message_info(&user1, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info.clone(), + ExecuteMsg::Claim { + recipient: None, + amount: None, + }, + ) + .unwrap(); + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "claim"); + assert_eq!(res.attributes[1].key, "address"); + assert_eq!(res.attributes[1].value, user1.to_string()); + assert_eq!(res.attributes[3].key, "claimed_amount"); + assert_eq!(res.attributes[3].value, "5"); + + // Move time forward a bit but not to full vesting + env.block.time = env.block.time.plus_seconds(5); + + // Force claim tokens + let info = message_info(&user1, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info.clone(), + ExecuteMsg::ForceClaim { recipient: None }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "force_claim"); + assert_eq!(res.attributes[1].key, "address"); + assert_eq!(res.attributes[1].value, user1.to_string()); + assert_eq!(res.attributes[3].key, "claimed_amount"); + assert_eq!(res.attributes[3].value, "50"); + + let vesting_info = vesting_info(true); + let mut vesting_info = vesting_info.load(&deps.storage, info.sender.clone()).unwrap(); + vesting_info.schedules[0].force_claimed = Uint128::from(55u128); + + // Check that some amount was claimed (should be more than normal vesting due to force claim) + assert_eq!(res.messages.len(), 1); + match &res.messages[0].msg { + CosmosMsg::Wasm(WasmMsg::Execute { msg, .. }) => { + let transfer_msg: Cw20ExecuteMsg = from_json(msg).unwrap(); + match transfer_msg { + Cw20ExecuteMsg::Transfer { amount, .. } => { + assert_eq!(amount, Uint128::new(50)); + } + _ => panic!("Expected Transfer message"), + } + } + _ => panic!("Expected Wasm message"), + } + + // User hasn't anything to claim anymore + let available = from_json::( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::AvailableAmount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(available, Uint128::zero()); + + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Claim { + recipient: None, + amount: None, + }, + ) + .unwrap(); + assert_eq!(res.messages.len(), 0); + assert_eq!(res.attributes[2].value, "0"); + assert_eq!(res.attributes[3].value, "0"); + + env.block.time = env.block.time.plus_seconds(20); + + // Register vesting account with future vesting + let vesting_accounts = vec![VestingAccount { + address: user1.to_string(), + schedules: vec![create_vesting_schedule( + env.block.time.seconds(), + Uint128::new(0), + Some(env.block.time.seconds() + 100), + Some(amount), + )], + }]; + + let info = message_info(&vesting_token, &[]); + let cw20_msg = Cw20ReceiveMsg { + sender: owner.to_string(), + amount, + msg: to_json_binary(&Cw20HookMsg::RegisterVestingAccounts { vesting_accounts }).unwrap(), + }; + + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::Receive(cw20_msg), + ) + .unwrap(); + + // Move time forward a bit but not to full vesting + env.block.time = env.block.time.plus_seconds(60); + + // User has 60 tokens to claim from newly created vesting schedule + let available = from_json::( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::AvailableAmount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(available, Uint128::from(60u128)); + + // A user can't claim more tokens than it has + let info = message_info(&user1, &[]); + assert_eq!(execute( + deps.as_mut(), + env.clone(), + info.clone(), + ExecuteMsg::Claim { amount: Some(Uint128::new(100u128)), recipient: None }, + ).err().unwrap(), AmountIsNotAvailable {}); + + // A user can claim tokens for newly created vesting schedule + let info = message_info(&user1, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info.clone(), + ExecuteMsg::Claim { amount: None, recipient: None }, + ).unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "claim"); + assert_eq!(res.attributes[3].key, "claimed_amount"); + assert_eq!(res.attributes[3].value, "60"); + assert_eq!(res.attributes[1].key, "address"); + assert_eq!(res.attributes[1].value, user1.to_string()); + + // User has 0 tokens to claim from newly created vesting schedule + let available = from_json::( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::AvailableAmount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(available, Uint128::from(0u128)); + + // Move time forward a bit but not to full vesting + env.block.time = env.block.time.plus_seconds(15); + + // User has 15 more tokens to claim from newly created vesting schedule + let available = from_json::( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::AvailableAmount { + address: user1.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(available, Uint128::from(15u128)); + + // A user can force claim tokens for newly created vesting schedule + let info = message_info(&user1, &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info.clone(), + ExecuteMsg::ForceClaim { recipient: None }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "force_claim"); + assert_eq!(res.attributes[3].key, "claimed_amount"); + assert_eq!(res.attributes[3].value, "27"); + assert_eq!(res.attributes[1].key, "address"); + assert_eq!(res.attributes[1].value, user1.to_string()); + + // Owner can get remove vesting accounts and get unclaimed amount + let clawback_account = deps.api.addr_make("clawback"); + let info = message_info(&owner, &[]); + let res = execute( + deps.as_mut(), + env, + info, + ExecuteMsg::ManagedExtension { + msg: ExecuteMsgManaged::RemoveVestingAccounts { + vesting_accounts: vec![user1.to_string()], + clawback_account: clawback_account.to_string(), + }, + }, + ) + .unwrap(); + + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "remove_vesting_accounts"); + assert_eq!(res.messages.len(), 1); + match &res.messages[0].msg { + CosmosMsg::Wasm(WasmMsg::Execute { msg, .. }) => { + let transfer_msg: Cw20ExecuteMsg = from_json(msg).unwrap(); + match transfer_msg { + Cw20ExecuteMsg::Transfer { amount, recipient } => { + assert_eq!(amount, Uint128::new(108)); // 108 - the remaining of the users vesting + assert_eq!(recipient, clawback_account.to_string()); + } + _ => panic!("Expected Transfer message"), + } + } + _ => panic!("Expected Wasm message"), + } +} \ No newline at end of file diff --git a/packages/vesting-base/src/types.rs b/packages/vesting-base/src/types.rs index 6a1378b..5419fdd 100644 --- a/packages/vesting-base/src/types.rs +++ b/packages/vesting-base/src/types.rs @@ -1,4 +1,4 @@ -use astroport::asset::AssetInfo; +use crate::asset::AssetInfo; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Order, Uint128}; @@ -61,6 +61,10 @@ pub struct VestingSchedule { pub start_point: VestingSchedulePoint, /// The end point for the vesting schedule pub end_point: Option, + /// Is the schedule disabled (cannot be claimed by a user) or not + pub disabled: bool, + // How many tokens have been force claimed by a user. + pub force_claimed: Uint128, } /// This structure stores the parameters used to create a vesting schedule.