From 93771fa7804ccaf7d6e67939e7a531446fe81261 Mon Sep 17 00:00:00 2001 From: larry <26318510+larry0x@users.noreply.github.com> Date: Thu, 31 Aug 2023 13:27:52 +0100 Subject: [PATCH] Update vesting contract (#28) * bump dependencies * error handling; use cw-utils * global config * update schema and typescript types * fix migrate entry point function signature * bump rust-optimizer to latest * Refactor migration. Add tests. --------- Co-authored-by: piobab --- Cargo.lock | 230 ++++++++++-------- Cargo.toml | 13 +- Makefile.toml | 6 +- contracts/vesting/Cargo.toml | 2 + contracts/vesting/examples/schema.rs | 4 +- contracts/vesting/src/contract.rs | 165 ++++++------- contracts/vesting/src/error.rs | 22 ++ contracts/vesting/src/helpers.rs | 5 +- contracts/vesting/src/lib.rs | 2 + contracts/vesting/src/migrations/mod.rs | 1 + contracts/vesting/src/migrations/v1_3_0.rs | 43 ++++ contracts/vesting/src/msg.rs | 51 +++- contracts/vesting/src/state.rs | 18 +- contracts/vesting/tests/tests.rs | 144 ++++++++--- schemas/mars-delegator/mars-delegator.json | 2 +- schemas/mars-vesting/mars-vesting.json | 66 ++++- .../mars-delegator/MarsDelegator.client.ts | 9 +- .../MarsDelegator.react-query.ts | 20 +- .../mars-delegator/MarsDelegator.types.ts | 7 +- .../mars-vesting/MarsVesting.client.ts | 30 ++- .../mars-vesting/MarsVesting.react-query.ts | 48 ++-- .../mars-vesting/MarsVesting.types.ts | 12 +- 22 files changed, 553 insertions(+), 347 deletions(-) create mode 100644 contracts/vesting/src/error.rs create mode 100644 contracts/vesting/src/migrations/mod.rs create mode 100644 contracts/vesting/src/migrations/v1_3_0.rs diff --git a/Cargo.lock b/Cargo.lock index 9a475f2..65a1ad5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,9 +27,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64ct" -version = "1.5.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "block-buffer" @@ -42,13 +42,19 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] +[[package]] +name = "bnum" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" + [[package]] name = "byteorder" version = "1.4.3" @@ -63,17 +69,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "const-oid" -version = "0.9.1" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "cosmwasm-crypto" -version = "1.1.9" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "227315dc11f0bb22a273d0c43d3ba8ef52041c42cf959f09045388a89c57e661" +checksum = "871ce1d5a4b00ed1741f84b377eec19fadd81a904a227bc1e268d76539d26f5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "ed25519-zebra", "k256", "rand_core 0.6.4", @@ -82,18 +88,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.1.9" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fca30d51f7e5fbfa6440d8b10d7df0231bdf77e97fd3fe5d0cb79cc4822e50c" +checksum = "7ce8b44b45a7c8c6d6f770cd0a51458c2445c7c15b6115e1d215fa35c77b305c" dependencies = [ - "syn", + "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.1.9" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04135971e2c3b867eb793ca4e832543c077dbf72edaef7672699190f8fcdb619" +checksum = "99222fa0401ee36389550d8a065700380877a2299c3043d24c38d705708c9d9d" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -104,22 +110,23 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.1.9" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06c8f516a13ae481016aa35f0b5c4652459e8aee65b15b6fb51547a07cea5a0" +checksum = "4b74eaf9e585ef8e5e3486b240b13ee593cb0f658b5879696937d8c22243d4fb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "cosmwasm-std" -version = "1.1.9" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13d5a84d15cf7be17dc249a21588cdb0f7ef308907c50ce2723316a7d79c3dc" +checksum = "da78abcf059181e8cb01e95e5003cf64fe95dde6c72b3fe37e5cabc75cdba32a" dependencies = [ "base64", + "bnum", "cosmwasm-crypto", "cosmwasm-derive", "derivative", @@ -128,25 +135,19 @@ dependencies = [ "schemars", "serde", "serde-json-wasm", + "sha2 0.10.7", "thiserror", - "uint", ] [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-bigint" version = "0.4.9" @@ -184,26 +185,42 @@ dependencies = [ [[package]] name = "cw-storage-plus" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-utils" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053a5083c258acd68386734f428a5a171b29f7d733151ae83090c6fcc9417ffa" +checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", + "cw2", "schemars", + "semver", "serde", + "thiserror", ] [[package]] name = "cw2" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb70cee2cf0b4a8ff7253e6bc6647107905e8eb37208f87d54f67810faa62f8" +checksum = "29ac2dc7a55ad64173ca1e0a46697c31b7a5c51342f55a1e84a724da4eb99908" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", "schemars", "serde", + "thiserror", ] [[package]] @@ -224,7 +241,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -238,20 +255,20 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.3", + "block-buffer 0.10.4", "crypto-common", "subtle", ] [[package]] name = "dyn-clone" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60" +checksum = "bbfc4744c1b8f2a09adc0e55242f60b1af195d88596bd8700be74418c056c555" [[package]] name = "ecdsa" @@ -289,7 +306,7 @@ dependencies = [ "base16ct", "crypto-bigint", "der", - "digest 0.10.6", + "digest 0.10.7", "ff", "generic-array", "group", @@ -318,9 +335,9 @@ checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -328,9 +345,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", @@ -369,14 +386,14 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "k256" @@ -387,18 +404,18 @@ dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] name = "libc" -version = "0.2.139" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "mars-delegator" -version = "1.2.0" +version = "1.3.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -409,20 +426,22 @@ dependencies = [ [[package]] name = "mars-vesting" -version = "1.2.0" +version = "1.3.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", + "cw-utils", "cw2", "serde", + "thiserror", ] [[package]] name = "once_cell" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -442,18 +461,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.50" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -486,15 +505,15 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "schemars" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307" +checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" dependencies = [ "dyn-clone", "schemars_derive", @@ -504,14 +523,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9" +checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 1.0.109", ] [[package]] @@ -528,33 +547,39 @@ dependencies = [ "zeroize", ] +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + [[package]] name = "serde" -version = "1.0.152" +version = "1.0.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "9f5db24220c009de9bd45e69fb2938f4b6d2df856aa9304ce377b3180f83b7c1" dependencies = [ "serde_derive", ] [[package]] name = "serde-json-wasm" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" +checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "5ad697f7e0b65af4983a4ce8f56ed5b357e8d3c36651bf6a7e13639c17b8e670" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.29", ] [[package]] @@ -565,14 +590,14 @@ checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", @@ -594,13 +619,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -609,7 +634,7 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -624,22 +649,27 @@ dependencies = [ ] [[package]] -name = "static_assertions" -version = "1.1.0" +name = "subtle" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] -name = "subtle" -version = "2.4.1" +name = "syn" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] [[package]] name = "syn" -version = "1.0.107" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -648,22 +678,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.29", ] [[package]] @@ -672,23 +702,11 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "version_check" @@ -704,6 +722,6 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "zeroize" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/Cargo.toml b/Cargo.toml index 6846318..e6d443b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,20 +2,21 @@ members = ["contracts/*"] [workspace.package] -version = "1.2.0" +version = "1.3.0" authors = ["Larry Engineer "] edition = "2021" -rust-version = "1.65" +rust-version = "1.69" license = "GPL-3.0-or-later" homepage = "https://marsprotocol.io" repository = "https://github.com/mars-protocol/periphery" documentation = "https://github.com/mars-protocol/periphery#readme" [workspace.dependencies] -cosmwasm-schema = "1.1" -cosmwasm-std = "1.1" -cw2 = "1.0" -cw-storage-plus = "1.0" +cosmwasm-schema = "1.3" +cosmwasm-std = "1.3" +cw2 = "1.1" +cw-storage-plus = "1.1" +cw-utils = "1.0" serde = "1.0" thiserror = "1.0" diff --git a/Makefile.toml b/Makefile.toml index 3ef473e..814a612 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -48,12 +48,12 @@ alias = "coverage-grcov-lcov" [tasks.rust-optimizer] script = """ if [[ $(arch) == "arm64" ]]; then - image="cosmwasm/workspace-optimizer-arm64:0.12.11" + image="cosmwasm/workspace-optimizer-arm64:0.14.0" else - image="cosmwasm/workspace-optimizer:0.12.11" + image="cosmwasm/workspace-optimizer:0.14.0" fi docker run --rm -v "$(pwd)":/code \ - --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ ${image} """ diff --git a/contracts/vesting/Cargo.toml b/contracts/vesting/Cargo.toml index 9668791..d014886 100644 --- a/contracts/vesting/Cargo.toml +++ b/contracts/vesting/Cargo.toml @@ -22,6 +22,8 @@ cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true, features = ["stargate"] } cw2 = { workspace = true } cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] cosmwasm-schema = { workspace = true } diff --git a/contracts/vesting/examples/schema.rs b/contracts/vesting/examples/schema.rs index 13eeb6c..d923d03 100644 --- a/contracts/vesting/examples/schema.rs +++ b/contracts/vesting/examples/schema.rs @@ -1,9 +1,9 @@ use cosmwasm_schema::write_api; -use mars_vesting::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_vesting::msg::{Config, ExecuteMsg, QueryMsg}; fn main() { write_api! { - instantiate: InstantiateMsg, + instantiate: Config, execute: ExecuteMsg, query: QueryMsg, } diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs index 2f415d1..b0956e8 100644 --- a/contracts/vesting/src/contract.rs +++ b/contracts/vesting/src/contract.rs @@ -1,23 +1,25 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - coins, to_binary, Addr, BankMsg, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Order, - Response, StdError, StdResult, Uint128, + coins, to_binary, Addr, BankMsg, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, + Order, Response, Uint128, }; use cw2::set_contract_version; use cw_storage_plus::Bound; +use cw_utils::must_pay; use crate::{ + error::{Error, Result}, helpers::{compute_position_response, compute_withdrawable}, + migrations::v1_3_0, msg::{ - ConfigResponse, ExecuteMsg, InstantiateMsg, PositionResponse, QueryMsg, Schedule, - VotingPowerResponse, VEST_DENOM, + Config, ExecuteMsg, Position, PositionResponse, QueryMsg, Schedule, VotingPowerResponse, }, - state::{Position, OWNER, POSITIONS, UNLOCK_SCHEDULE}, + state::{CONFIG, POSITIONS}, }; -const CONTRACT_NAME: &str = "crates.io:mars-vesting"; -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const CONTRACT_NAME: &str = "crates.io:mars-vesting"; +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; @@ -31,12 +33,12 @@ pub fn instantiate( deps: DepsMut, _env: Env, _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { + cfg: Config, +) -> Result { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - OWNER.save(deps.storage, &deps.api.addr_validate(&msg.owner)?)?; - UNLOCK_SCHEDULE.save(deps.storage, &msg.unlock_schedule)?; + let cfg = cfg.check(deps.api)?; + CONFIG.save(deps.storage, &cfg)?; Ok(Response::new()) } @@ -46,9 +48,12 @@ pub fn instantiate( //-------------------------------------------------------------------------------------------------- #[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> Result { let api = deps.api; match msg { + ExecuteMsg::UpdateConfig { + new_cfg, + } => update_config(deps, info, new_cfg), ExecuteMsg::CreatePosition { user, vest_schedule, @@ -57,50 +62,45 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S user, } => terminate_position(deps, env, info, api.addr_validate(&user)?), ExecuteMsg::Withdraw {} => withdraw(deps, env.block.time.seconds(), info.sender), - ExecuteMsg::TransferOwnership(new_owner) => { - transfer_ownership(deps, info.sender, api.addr_validate(&new_owner)?) - } } } +pub fn update_config( + deps: DepsMut, + info: MessageInfo, + new_cfg: Config, +) -> Result { + let cfg = CONFIG.load(deps.storage)?; + + // only owner can update config + if info.sender != cfg.owner { + return Err(Error::NotOwner); + } + + let new_cfg = new_cfg.check(deps.api)?; + CONFIG.save(deps.storage, &new_cfg)?; + + Ok(Response::new().add_attribute("action", "mars/vesting/update_config")) +} + pub fn create_position( deps: DepsMut, info: MessageInfo, user_addr: Addr, vest_schedule: Schedule, -) -> StdResult { - // only owner can create allocations - let owner_addr = OWNER.load(deps.storage)?; - if info.sender != owner_addr { - return Err(StdError::generic_err("only owner can create allocations")); - } +) -> Result { + let cfg = CONFIG.load(deps.storage)?; - // must send exactly one coin - if info.funds.len() != 1 { - return Err(StdError::generic_err(format!( - "wrong number of coins: expecting 1, received {}", - info.funds.len() - ))); - } - - // the coin must be the vesting coin - let coin = &info.funds[0]; - if coin.denom != VEST_DENOM { - return Err(StdError::generic_err(format!( - "wrong denom: expecting {}, received {}", - VEST_DENOM, coin.denom - ))); + // only owner can create allocations + if info.sender != cfg.owner { + return Err(Error::NotOwner); } - // the amount must be greater than zero - let total = coin.amount; - if total.is_zero() { - return Err(StdError::generic_err("wrong amount: must be greater than zero")); - } + let total = must_pay(&info, &cfg.denom)?; POSITIONS.update(deps.storage, &user_addr, |position| { if position.is_some() { - return Err(StdError::generic_err("user has a vesting position")); + return Err(Error::PositionExists); } Ok(Position { total, @@ -123,16 +123,15 @@ pub fn terminate_position( env: Env, info: MessageInfo, user_addr: Addr, -) -> StdResult { +) -> Result { + let cfg = CONFIG.load(deps.storage)?; let current_time = env.block.time.seconds(); // only owner can terminate allocations - let owner_addr = OWNER.load(deps.storage)?; - if info.sender != owner_addr { - return Err(StdError::generic_err("only owner can terminate allocations")); + if info.sender != cfg.owner { + return Err(Error::NotOwner); } - let unlock_schedule = UNLOCK_SCHEDULE.load(deps.storage)?; let mut position = POSITIONS.load(deps.storage, &user_addr)?; let (vested, _, _) = compute_withdrawable( @@ -140,7 +139,7 @@ pub fn terminate_position( position.total, position.withdrawn, &position.vest_schedule, - &unlock_schedule, + &cfg.unlock_schedule, ); // unvested tokens are to be reclaimed by the owner @@ -153,8 +152,8 @@ pub fn terminate_position( Ok(Response::new() .add_message(CosmosMsg::Bank(BankMsg::Send { - to_address: owner_addr.into(), - amount: coins(reclaim.u128(), VEST_DENOM), + to_address: cfg.owner.into(), + amount: coins(reclaim.u128(), cfg.denom), })) .add_attribute("action", "mars/vesting/terminate_position") .add_attribute("user", user_addr) @@ -162,8 +161,8 @@ pub fn terminate_position( .add_attribute("relaimed", reclaim)) } -pub fn withdraw(deps: DepsMut, time: u64, user_addr: Addr) -> StdResult { - let unlock_schedule = UNLOCK_SCHEDULE.load(deps.storage)?; +pub fn withdraw(deps: DepsMut, time: u64, user_addr: Addr) -> Result { + let cfg = CONFIG.load(deps.storage)?; let mut position = POSITIONS.load(deps.storage, &user_addr)?; let (_, _, withdrawable) = compute_withdrawable( @@ -171,11 +170,11 @@ pub fn withdraw(deps: DepsMut, time: u64, user_addr: Addr) -> StdResult StdResult StdResult StdResult { - let owner_addr = OWNER.load(deps.storage)?; - if sender_addr != owner_addr { - return Err(StdError::generic_err("only owner can transfer ownership")); - } - - OWNER.save(deps.storage, &new_owner_addr)?; - - Ok(Response::new() - .add_attribute("action", "mars/vesting/transfer_ownership") - .add_attribute("previous_owner", owner_addr) - .add_attribute("new_owner", new_owner_addr)) -} - //-------------------------------------------------------------------------------------------------- // Queries //-------------------------------------------------------------------------------------------------- #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { let api = deps.api; match msg { QueryMsg::Config {} => to_binary(&query_config(deps)?), @@ -234,20 +215,19 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { limit, } => to_binary(&query_positions(deps, env.block.time.seconds(), start_after, limit)?), } + .map_err(Into::into) } -pub fn query_config(deps: Deps) -> StdResult { - Ok(ConfigResponse { - owner: OWNER.load(deps.storage)?.into(), - unlock_schedule: UNLOCK_SCHEDULE.load(deps.storage)?, - }) +pub fn query_config(deps: Deps) -> Result> { + let cfg = CONFIG.load(deps.storage)?; + Ok(cfg.into()) } -pub fn query_voting_power(deps: Deps, user_addr: Addr) -> StdResult { +pub fn query_voting_power(deps: Deps, user_addr: Addr) -> Result { let voting_power = match POSITIONS.may_load(deps.storage, &user_addr) { Ok(Some(position)) => position.total - position.withdrawn, Ok(None) => Uint128::zero(), - Err(err) => return Err(err), + Err(err) => return Err(err.into()), }; Ok(VotingPowerResponse { @@ -256,18 +236,18 @@ pub fn query_voting_power(deps: Deps, user_addr: Addr) -> StdResult StdResult { - let unlock_schedule = UNLOCK_SCHEDULE.load(deps.storage)?; +pub fn query_position(deps: Deps, time: u64, user_addr: Addr) -> Result { + let cfg = CONFIG.load(deps.storage)?; let position = POSITIONS.load(deps.storage, &user_addr)?; - Ok(compute_position_response(time, user_addr, &position, &unlock_schedule)) + Ok(compute_position_response(time, user_addr, &position, &cfg.unlock_schedule)) } pub fn query_voting_powers( deps: Deps, start_after: Option, limit: Option, -) -> StdResult> { +) -> Result> { let addr: Addr; let start = match &start_after { Some(addr_str) => { @@ -297,8 +277,8 @@ pub fn query_positions( time: u64, start_after: Option, limit: Option, -) -> StdResult> { - let unlock_schedule = UNLOCK_SCHEDULE.load(deps.storage)?; +) -> Result> { + let cfg = CONFIG.load(deps.storage)?; let addr: Addr; let start = match &start_after { @@ -316,7 +296,16 @@ pub fn query_positions( .take(limit) .map(|res| { let (user_addr, position) = res?; - Ok(compute_position_response(time, user_addr, &position, &unlock_schedule)) + Ok(compute_position_response(time, user_addr, &position, &cfg.unlock_schedule)) }) .collect() } + +//-------------------------------------------------------------------------------------------------- +// Migration +//-------------------------------------------------------------------------------------------------- + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _: Env, _: Empty) -> Result { + v1_3_0::migrate(deps) +} diff --git a/contracts/vesting/src/error.rs b/contracts/vesting/src/error.rs new file mode 100644 index 0000000..bcfff08 --- /dev/null +++ b/contracts/vesting/src/error.rs @@ -0,0 +1,22 @@ +#[derive(Debug, PartialEq, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Std(#[from] cosmwasm_std::StdError), + + #[error(transparent)] + Payment(#[from] cw_utils::PaymentError), + + #[error("caller is not owner")] + NotOwner, + + #[error("a vesting position already exists for this user")] + PositionExists, + + #[error("withdrawable amount is zero")] + ZeroWithdrawable, + + #[error("{0}")] + Version(#[from] cw2::VersionError), +} + +pub(crate) type Result = core::result::Result; diff --git a/contracts/vesting/src/helpers.rs b/contracts/vesting/src/helpers.rs index 726f69a..df459d9 100644 --- a/contracts/vesting/src/helpers.rs +++ b/contracts/vesting/src/helpers.rs @@ -2,10 +2,7 @@ use std::cmp::min; use cosmwasm_std::Uint128; -use crate::{ - msg::{PositionResponse, Schedule}, - state::Position, -}; +use crate::msg::{Position, PositionResponse, Schedule}; /// The return value is a three-tuple consists of: the vested amount, the unlocked amount, and the /// withdrawable amount diff --git a/contracts/vesting/src/lib.rs b/contracts/vesting/src/lib.rs index 16273b3..014b0d9 100644 --- a/contracts/vesting/src/lib.rs +++ b/contracts/vesting/src/lib.rs @@ -1,4 +1,6 @@ pub mod contract; +pub mod error; pub mod helpers; +pub mod migrations; pub mod msg; pub mod state; diff --git a/contracts/vesting/src/migrations/mod.rs b/contracts/vesting/src/migrations/mod.rs new file mode 100644 index 0000000..bd6dc60 --- /dev/null +++ b/contracts/vesting/src/migrations/mod.rs @@ -0,0 +1 @@ +pub mod v1_3_0; diff --git a/contracts/vesting/src/migrations/v1_3_0.rs b/contracts/vesting/src/migrations/v1_3_0.rs new file mode 100644 index 0000000..4178691 --- /dev/null +++ b/contracts/vesting/src/migrations/v1_3_0.rs @@ -0,0 +1,43 @@ +use cosmwasm_std::{DepsMut, Response}; +use cw2::set_contract_version; + +use crate::{ + contract::{CONTRACT_NAME, CONTRACT_VERSION}, + error::Result, + msg::Config, + state::CONFIG, +}; + +const FROM_VERSION: &str = "1.2.0"; + +pub mod v1_2_0_state { + use cosmwasm_std::Addr; + use cw_storage_plus::Item; + + use crate::msg::Schedule; + + pub const OWNER: Item = Item::new("owner"); + pub const VEST_DENOM: &str = "umars"; + pub const UNLOCK_SCHEDULE: Item = Item::new("unlock_schedule"); +} + +pub fn migrate(deps: DepsMut) -> Result { + // make sure we're migrating the correct contract and from the correct version + cw2::assert_contract_version(deps.as_ref().storage, CONTRACT_NAME, FROM_VERSION)?; + + // CONFIG updated, re-initializing + let cfg = Config { + owner: v1_2_0_state::OWNER.load(deps.storage)?, + denom: v1_2_0_state::VEST_DENOM.into(), + unlock_schedule: v1_2_0_state::UNLOCK_SCHEDULE.load(deps.storage)?, + }; + + CONFIG.save(deps.storage, &cfg)?; + + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + Ok(Response::new() + .add_attribute("action", "migrate") + .add_attribute("from_version", FROM_VERSION) + .add_attribute("to_version", CONTRACT_VERSION)) +} diff --git a/contracts/vesting/src/msg.rs b/contracts/vesting/src/msg.rs index d8e9fbe..601251c 100644 --- a/contracts/vesting/src/msg.rs +++ b/contracts/vesting/src/msg.rs @@ -1,8 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Uint128; - -/// Denomination of the token to be vested -pub const VEST_DENOM: &str = "umars"; +use cosmwasm_std::{Addr, Api, StdResult, Uint128}; #[cw_serde] pub struct Schedule { @@ -16,15 +13,51 @@ pub struct Schedule { } #[cw_serde] -pub struct InstantiateMsg { +pub struct Position { + /// Total amount of MARS allocated + pub total: Uint128, + /// Amount of MARS already withdrawn + pub withdrawn: Uint128, + /// The user's vesting schedule + pub vest_schedule: Schedule, +} + +#[cw_serde] +pub struct Config { /// The contract's owner - pub owner: String, + pub owner: T, + /// Denomination of the token to be vested + pub denom: String, /// Schedule for token unlocking; this schedule is the same for all users pub unlock_schedule: Schedule, } +impl Config { + pub fn check(self, api: &dyn Api) -> StdResult> { + Ok(Config { + owner: api.addr_validate(&self.owner)?, + denom: self.denom, + unlock_schedule: self.unlock_schedule, + }) + } +} + +impl From> for Config { + fn from(cfg: Config) -> Self { + Config { + owner: cfg.owner.into(), + denom: cfg.denom, + unlock_schedule: cfg.unlock_schedule, + } + } +} + #[cw_serde] pub enum ExecuteMsg { + /// Update the contract's configurations + UpdateConfig { + new_cfg: Config, + }, /// Create a new vesting position for a user CreatePosition { user: String, @@ -36,15 +69,13 @@ pub enum ExecuteMsg { }, /// Withdraw vested and unlocked MARS tokens Withdraw {}, - /// Transfer the contract's ownership to another account - TransferOwnership(String), } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { /// The contract's configurations - #[returns(ConfigResponse)] + #[returns(Config)] Config {}, /// Amount of MARS tokens of a vesting recipient current locked in the contract #[returns(VotingPowerResponse)] @@ -76,8 +107,6 @@ pub enum QueryMsg { }, } -pub type ConfigResponse = InstantiateMsg; - #[cw_serde] pub struct VotingPowerResponse { /// Address of the user diff --git a/contracts/vesting/src/state.rs b/contracts/vesting/src/state.rs index fbb085b..d0702b9 100644 --- a/contracts/vesting/src/state.rs +++ b/contracts/vesting/src/state.rs @@ -1,20 +1,8 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Uint128}; +use cosmwasm_std::Addr; use cw_storage_plus::{Item, Map}; -use crate::msg::Schedule; +use crate::msg::{Config, Position}; -#[cw_serde] -pub struct Position { - /// Total amount of MARS allocated - pub total: Uint128, - /// Amount of MARS already withdrawn - pub withdrawn: Uint128, - /// The user's vesting schedule - pub vest_schedule: Schedule, -} +pub const CONFIG: Item> = Item::new("config"); pub const POSITIONS: Map<&Addr, Position> = Map::new("positions"); - -pub const OWNER: Item = Item::new("owner"); -pub const UNLOCK_SCHEDULE: Item = Item::new("unlock_schedule"); diff --git a/contracts/vesting/tests/tests.rs b/contracts/vesting/tests/tests.rs index 91667c0..a7a129a 100644 --- a/contracts/vesting/tests/tests.rs +++ b/contracts/vesting/tests/tests.rs @@ -1,17 +1,30 @@ use cosmwasm_std::{ - coin, coins, from_binary, + attr, coin, coins, from_binary, testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage}, - Addr, BankMsg, CosmosMsg, Deps, Empty, Env, OwnedDeps, StdError, SubMsg, Timestamp, Uint128, + Addr, BankMsg, CosmosMsg, Deps, Empty, Env, OwnedDeps, SubMsg, Timestamp, Uint128, }; +use cw2::{set_contract_version, ContractVersion, VersionError}; +use cw_utils::PaymentError; use mars_vesting::{ - contract::{execute, instantiate, query}, + contract::{execute, instantiate, migrate, query}, + error::Error, + migrations::v1_3_0::v1_2_0_state, msg::{ - ConfigResponse, ExecuteMsg, InstantiateMsg, PositionResponse, QueryMsg, Schedule, - VotingPowerResponse, + Config, ExecuteMsg, Position, PositionResponse, QueryMsg, Schedule, VotingPowerResponse, }, - state::{Position, POSITIONS}, + state::{CONFIG, POSITIONS}, }; +pub const MOCK_DENOM: &str = "umars"; + +fn mock_unlock_schedule() -> Schedule { + Schedule { + start_time: 1662033600, // 2022-09-01 + cliff: 0, + duration: 63072000, // two years (365 * 24 * 60 * 60 * 2) + } +} + fn mock_env_at_timestamp(seconds: u64) -> Env { let mut env = mock_env(); env.block.time = Timestamp::from_seconds(seconds); @@ -29,13 +42,10 @@ fn setup_test() -> OwnedDeps { deps.as_mut(), mock_env(), mock_info("deployer", &[]), - InstantiateMsg { + Config { owner: "owner".to_string(), - unlock_schedule: Schedule { - start_time: 1662033600, // 2022-09-01 - cliff: 0, - duration: 63072000, // two years (365 * 24 * 60 * 60 * 2) - }, + denom: MOCK_DENOM.into(), + unlock_schedule: mock_unlock_schedule(), }, ) .unwrap(); @@ -47,45 +57,52 @@ fn setup_test() -> OwnedDeps { fn proper_instantiation() { let deps = setup_test(); - let config: ConfigResponse = query_helper(deps.as_ref(), mock_env(), QueryMsg::Config {}); + let config: Config = query_helper(deps.as_ref(), mock_env(), QueryMsg::Config {}); assert_eq!( config, - ConfigResponse { + Config { owner: "owner".to_string(), - unlock_schedule: Schedule { - start_time: 1662033600, - cliff: 0, - duration: 63072000, - }, + denom: MOCK_DENOM.into(), + unlock_schedule: mock_unlock_schedule(), }, ); } #[test] -fn transferring_ownership() { +fn updating_ownership() { let mut deps = setup_test(); + let new_cfg = Config { + owner: "new_owner".into(), + denom: MOCK_DENOM.into(), + unlock_schedule: mock_unlock_schedule(), + }; + // non-owner cannot transfer ownership let err = execute( deps.as_mut(), mock_env(), mock_info("non_owner", &[]), - ExecuteMsg::TransferOwnership("new_owner".to_string()), + ExecuteMsg::UpdateConfig { + new_cfg: new_cfg.clone(), + }, ) .unwrap_err(); - assert_eq!(err, StdError::generic_err("only owner can transfer ownership")); + assert_eq!(err, Error::NotOwner); // owner can propose a transfer let res = execute( deps.as_mut(), mock_env(), mock_info("owner", &[]), - ExecuteMsg::TransferOwnership("new_owner".to_string()), + ExecuteMsg::UpdateConfig { + new_cfg, + }, ) .unwrap(); assert_eq!(res.messages.len(), 0); - let config: ConfigResponse = query_helper(deps.as_ref(), mock_env(), QueryMsg::Config {}); + let config: Config = query_helper(deps.as_ref(), mock_env(), QueryMsg::Config {}); assert_eq!(config.owner, "new_owner".to_string()); } @@ -105,11 +122,11 @@ fn creating_positions() { // non-owner cannot create positions let err = execute(deps.as_mut(), mock_env(), mock_info("non_owner", &[]), msg.clone()).unwrap_err(); - assert_eq!(err, StdError::generic_err("only owner can create allocations")); + assert_eq!(err, Error::NotOwner); // cannot create a position without sending a coin let err = execute(deps.as_mut(), mock_env(), mock_info("owner", &[]), msg.clone()).unwrap_err(); - assert_eq!(err, StdError::generic_err("wrong number of coins: expecting 1, received 0")); + assert_eq!(err, PaymentError::NoFunds {}.into()); // cannot create a position sending more than one coin let err = execute( @@ -119,7 +136,7 @@ fn creating_positions() { msg.clone(), ) .unwrap_err(); - assert_eq!(err, StdError::generic_err("wrong number of coins: expecting 1, received 2")); + assert_eq!(err, PaymentError::MultipleDenoms {}.into()); // cannot create a position with the wrong coin let err = execute( @@ -129,13 +146,7 @@ fn creating_positions() { msg.clone(), ) .unwrap_err(); - assert_eq!(err, StdError::generic_err("wrong denom: expecting umars, received uosmo")); - - // cannot create a position with the correct coin but with zero amount - let err = - execute(deps.as_mut(), mock_env(), mock_info("owner", &[coin(0, "umars")]), msg.clone()) - .unwrap_err(); - assert_eq!(err, StdError::generic_err("wrong amount: must be greater than zero")); + assert_eq!(err, PaymentError::MissingDenom(MOCK_DENOM.into()).into()); // properly create a position let res = execute(deps.as_mut(), mock_env(), mock_info("owner", &[coin(12345, "umars")]), msg) @@ -203,7 +214,7 @@ fn terminating_positions() { // non-owner can't terminate allocation let err = execute(deps.as_mut(), env.clone(), mock_info("non_owner", &[]), msg.clone()).unwrap_err(); - assert_eq!(err, StdError::generic_err("only owner can terminate allocations")); + assert_eq!(err, Error::NotOwner); // owner properly terminates position let res = execute(deps.as_mut(), env, mock_info("owner", &[]), msg).unwrap(); @@ -271,7 +282,7 @@ fn withdrawing() { ExecuteMsg::Withdraw {}, ) .unwrap_err(); - assert_eq!(err, StdError::generic_err("withdrawable amount is zero")); + assert_eq!(err, Error::ZeroWithdrawable); // 2022-05-01 // after the cliff period, but unlock hasn't start yet, withdrawable amount is zero @@ -282,7 +293,7 @@ fn withdrawing() { ExecuteMsg::Withdraw {}, ) .unwrap_err(); - assert_eq!(err, StdError::generic_err("withdrawable amount is zero")); + assert_eq!(err, Error::ZeroWithdrawable); // 2022-10-01 // vested: 12345 * (1664625600 - 1614600000) / 126144000 = 4895 @@ -316,7 +327,7 @@ fn withdrawing() { ExecuteMsg::Withdraw {}, ) .unwrap_err(); - assert_eq!(err, StdError::generic_err("withdrawable amount is zero")); + assert_eq!(err, Error::ZeroWithdrawable); // 2023-10-01 // vested: 12345 * (1696161600 - 1614600000) / 126144000 = 7981 @@ -573,3 +584,60 @@ fn querying_positions() { ], ); } + +#[test] +fn invalid_contract_version() { + let mut deps = mock_dependencies(); + let env = mock_env(); + + let old_contract_version = ContractVersion { + contract: "crates.io:mars-vesting".to_string(), + version: "1.0.0".to_string(), + }; + + set_contract_version( + deps.as_mut().storage, + old_contract_version.contract.clone(), + old_contract_version.version, + ) + .unwrap(); + + let err = migrate(deps.as_mut(), env, Empty {}).unwrap_err(); + assert_eq!( + Error::Version(VersionError::WrongVersion { + expected: "1.2.0".to_string(), + found: "1.0.0".to_string() + }), + err + ); +} + +#[test] +fn proper_migration() { + let mut deps = mock_dependencies(); + cw2::set_contract_version(deps.as_mut().storage, "crates.io:mars-vesting", "1.2.0").unwrap(); + + let old_owner = "spiderman_246"; + v1_2_0_state::OWNER.save(deps.as_mut().storage, &Addr::unchecked(old_owner)).unwrap(); + + let old_schedule = Schedule { + start_time: 1614600000, + cliff: 31536000, + duration: 126144000, + }; + v1_2_0_state::UNLOCK_SCHEDULE.save(deps.as_mut().storage, &old_schedule).unwrap(); + + let res = migrate(deps.as_mut(), mock_env(), Empty {}).unwrap(); + + assert_eq!(res.messages, vec![]); + assert!(res.data.is_none()); + assert_eq!( + res.attributes, + vec![attr("action", "migrate"), attr("from_version", "1.2.0"), attr("to_version", "1.3.0"),] + ); + + let config = CONFIG.load(deps.as_ref().storage).unwrap(); + assert_eq!(config.denom, v1_2_0_state::VEST_DENOM.to_string()); + assert_eq!(config.owner.to_string(), old_owner.to_string()); + assert_eq!(config.unlock_schedule, old_schedule); +} diff --git a/schemas/mars-delegator/mars-delegator.json b/schemas/mars-delegator/mars-delegator.json index ecb221c..9fe4217 100644 --- a/schemas/mars-delegator/mars-delegator.json +++ b/schemas/mars-delegator/mars-delegator.json @@ -1,6 +1,6 @@ { "contract_name": "mars-delegator", - "contract_version": "1.2.0", + "contract_version": "1.3.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-vesting/mars-vesting.json b/schemas/mars-vesting/mars-vesting.json index 0bb2db4..8da6bc9 100644 --- a/schemas/mars-vesting/mars-vesting.json +++ b/schemas/mars-vesting/mars-vesting.json @@ -1,16 +1,21 @@ { "contract_name": "mars-vesting", - "contract_version": "1.2.0", + "contract_version": "1.3.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "InstantiateMsg", "type": "object", "required": [ + "denom", "owner", "unlock_schedule" ], "properties": { + "denom": { + "description": "Denomination of the token to be vested", + "type": "string" + }, "owner": { "description": "The contract's owner", "type": "string" @@ -61,6 +66,28 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", "oneOf": [ + { + "description": "Update the contract's configurations", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "new_cfg" + ], + "properties": { + "new_cfg": { + "$ref": "#/definitions/Config_for_String" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Create a new vesting position for a user", "type": "object", @@ -122,22 +149,36 @@ } }, "additionalProperties": false - }, - { - "description": "Transfer the contract's ownership to another account", + } + ], + "definitions": { + "Config_for_String": { "type": "object", "required": [ - "transfer_ownership" + "denom", + "owner", + "unlock_schedule" ], "properties": { - "transfer_ownership": { + "denom": { + "description": "Denomination of the token to be vested", + "type": "string" + }, + "owner": { + "description": "The contract's owner", "type": "string" + }, + "unlock_schedule": { + "description": "Schedule for token unlocking; this schedule is the same for all users", + "allOf": [ + { + "$ref": "#/definitions/Schedule" + } + ] } }, "additionalProperties": false - } - ], - "definitions": { + }, "Schedule": { "type": "object", "required": [ @@ -298,13 +339,18 @@ "responses": { "config": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", + "title": "Config_for_String", "type": "object", "required": [ + "denom", "owner", "unlock_schedule" ], "properties": { + "denom": { + "description": "Denomination of the token to be vested", + "type": "string" + }, "owner": { "description": "The contract's owner", "type": "string" diff --git a/scripts/types/generated/mars-delegator/MarsDelegator.client.ts b/scripts/types/generated/mars-delegator/MarsDelegator.client.ts index 428f713..fe7e585 100644 --- a/scripts/types/generated/mars-delegator/MarsDelegator.client.ts +++ b/scripts/types/generated/mars-delegator/MarsDelegator.client.ts @@ -7,7 +7,7 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from "@cosmjs/cosmwasm-stargate"; import { Coin, StdFee } from "@cosmjs/amino"; -import { InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg, Config } from "./MarsDelegator.types"; +import { InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg, SudoMsg, Config } from "./MarsDelegator.types"; export interface MarsDelegatorReadOnlyInterface { contractAddress: string; config: () => Promise; @@ -31,7 +31,6 @@ export class MarsDelegatorQueryClient implements MarsDelegatorReadOnlyInterface export interface MarsDelegatorInterface extends MarsDelegatorReadOnlyInterface { contractAddress: string; sender: string; - bond: (fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; unbond: (fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; refund: (fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; } @@ -45,16 +44,10 @@ export class MarsDelegatorClient extends MarsDelegatorQueryClient implements Mar this.client = client; this.sender = sender; this.contractAddress = contractAddress; - this.bond = this.bond.bind(this); this.unbond = this.unbond.bind(this); this.refund = this.refund.bind(this); } - bond = async (fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { - return await this.client.execute(this.sender, this.contractAddress, { - bond: {} - }, fee, memo, funds); - }; unbond = async (fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { return await this.client.execute(this.sender, this.contractAddress, { unbond: {} diff --git a/scripts/types/generated/mars-delegator/MarsDelegator.react-query.ts b/scripts/types/generated/mars-delegator/MarsDelegator.react-query.ts index 71b8a93..3559542 100644 --- a/scripts/types/generated/mars-delegator/MarsDelegator.react-query.ts +++ b/scripts/types/generated/mars-delegator/MarsDelegator.react-query.ts @@ -8,7 +8,7 @@ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from "@tanstack/react-query"; import { ExecuteResult } from "@cosmjs/cosmwasm-stargate"; import { StdFee, Coin } from "@cosmjs/amino"; -import { InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg, Config } from "./MarsDelegator.types"; +import { InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg, SudoMsg, Config } from "./MarsDelegator.types"; import { MarsDelegatorQueryClient, MarsDelegatorClient } from "./MarsDelegator.client"; export const marsDelegatorQueryKeys = { contract: ([{ @@ -72,22 +72,4 @@ export function useMarsDelegatorUnbondMutation(options?: Omit client.unbond(fee, memo, funds), options); -} -export interface MarsDelegatorBondMutation { - client: MarsDelegatorClient; - args?: { - fee?: number | StdFee | "auto"; - memo?: string; - funds?: Coin[]; - }; -} -export function useMarsDelegatorBondMutation(options?: Omit, "mutationFn">) { - return useMutation(({ - client, - args: { - fee, - memo, - funds - } = {} - }) => client.bond(fee, memo, funds), options); } \ No newline at end of file diff --git a/scripts/types/generated/mars-delegator/MarsDelegator.types.ts b/scripts/types/generated/mars-delegator/MarsDelegator.types.ts index d28b2e9..1627075 100644 --- a/scripts/types/generated/mars-delegator/MarsDelegator.types.ts +++ b/scripts/types/generated/mars-delegator/MarsDelegator.types.ts @@ -10,8 +10,6 @@ export interface InstantiateMsg { ending_time: number; } export type ExecuteMsg = { - bond: {}; -} | { unbond: {}; } | { refund: {}; @@ -22,6 +20,11 @@ export type QueryMsg = { export interface MigrateMsg { [k: string]: unknown; } +export type SudoMsg = { + bond: {}; +} | { + force_unbond: {}; +}; export interface Config { bond_denom: string; ending_time: number; diff --git a/scripts/types/generated/mars-vesting/MarsVesting.client.ts b/scripts/types/generated/mars-vesting/MarsVesting.client.ts index c3555b1..823548f 100644 --- a/scripts/types/generated/mars-vesting/MarsVesting.client.ts +++ b/scripts/types/generated/mars-vesting/MarsVesting.client.ts @@ -7,10 +7,10 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from "@cosmjs/cosmwasm-stargate"; import { Coin, StdFee } from "@cosmjs/amino"; -import { InstantiateMsg, Schedule, ExecuteMsg, QueryMsg, Uint128, PositionResponse, ArrayOfPositionResponse, VotingPowerResponse, ArrayOfVotingPowerResponse } from "./MarsVesting.types"; +import { InstantiateMsg, Schedule, ExecuteMsg, ConfigForString, QueryMsg, Uint128, PositionResponse, ArrayOfPositionResponse, VotingPowerResponse, ArrayOfVotingPowerResponse } from "./MarsVesting.types"; export interface MarsVestingReadOnlyInterface { contractAddress: string; - config: () => Promise; + config: () => Promise; votingPower: ({ user }: { @@ -50,7 +50,7 @@ export class MarsVestingQueryClient implements MarsVestingReadOnlyInterface { this.positions = this.positions.bind(this); } - config = async (): Promise => { + config = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { config: {} }); @@ -109,6 +109,11 @@ export class MarsVestingQueryClient implements MarsVestingReadOnlyInterface { export interface MarsVestingInterface extends MarsVestingReadOnlyInterface { contractAddress: string; sender: string; + updateConfig: ({ + newCfg + }: { + newCfg: ConfigForString; + }, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; createPosition: ({ user, vestSchedule @@ -122,7 +127,6 @@ export interface MarsVestingInterface extends MarsVestingReadOnlyInterface { user: string; }, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; withdraw: (fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; - transferOwnership: (fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; } export class MarsVestingClient extends MarsVestingQueryClient implements MarsVestingInterface { client: SigningCosmWasmClient; @@ -134,12 +138,23 @@ export class MarsVestingClient extends MarsVestingQueryClient implements MarsVes this.client = client; this.sender = sender; this.contractAddress = contractAddress; + this.updateConfig = this.updateConfig.bind(this); this.createPosition = this.createPosition.bind(this); this.terminatePosition = this.terminatePosition.bind(this); this.withdraw = this.withdraw.bind(this); - this.transferOwnership = this.transferOwnership.bind(this); } + updateConfig = async ({ + newCfg + }: { + newCfg: ConfigForString; + }, fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + update_config: { + new_cfg: newCfg + } + }, fee, memo, funds); + }; createPosition = async ({ user, vestSchedule @@ -170,9 +185,4 @@ export class MarsVestingClient extends MarsVestingQueryClient implements MarsVes withdraw: {} }, fee, memo, funds); }; - transferOwnership = async (fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { - return await this.client.execute(this.sender, this.contractAddress, { - transfer_ownership: {} - }, fee, memo, funds); - }; } \ No newline at end of file diff --git a/scripts/types/generated/mars-vesting/MarsVesting.react-query.ts b/scripts/types/generated/mars-vesting/MarsVesting.react-query.ts index f16c277..e4f03d5 100644 --- a/scripts/types/generated/mars-vesting/MarsVesting.react-query.ts +++ b/scripts/types/generated/mars-vesting/MarsVesting.react-query.ts @@ -8,7 +8,7 @@ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from "@tanstack/react-query"; import { ExecuteResult } from "@cosmjs/cosmwasm-stargate"; import { StdFee, Coin } from "@cosmjs/amino"; -import { InstantiateMsg, Schedule, ExecuteMsg, QueryMsg, Uint128, PositionResponse, ArrayOfPositionResponse, VotingPowerResponse, ArrayOfVotingPowerResponse } from "./MarsVesting.types"; +import { InstantiateMsg, Schedule, ExecuteMsg, ConfigForString, QueryMsg, Uint128, PositionResponse, ArrayOfPositionResponse, VotingPowerResponse, ArrayOfVotingPowerResponse } from "./MarsVesting.types"; import { MarsVestingQueryClient, MarsVestingClient } from "./MarsVesting.client"; export const marsVestingQueryKeys = { contract: ([{ @@ -112,33 +112,15 @@ export function useMarsVestingVotingPowerQuery({ enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }); } -export interface MarsVestingConfigQuery extends MarsVestingReactQuery {} -export function useMarsVestingConfigQuery({ +export interface MarsVestingConfigQuery extends MarsVestingReactQuery {} +export function useMarsVestingConfigQuery({ client, options }: MarsVestingConfigQuery) { - return useQuery(marsVestingQueryKeys.config(client?.contractAddress), () => client ? client.config() : Promise.reject(new Error("Invalid client")), { ...options, + return useQuery(marsVestingQueryKeys.config(client?.contractAddress), () => client ? client.config() : Promise.reject(new Error("Invalid client")), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }); } -export interface MarsVestingTransferOwnershipMutation { - client: MarsVestingClient; - args?: { - fee?: number | StdFee | "auto"; - memo?: string; - funds?: Coin[]; - }; -} -export function useMarsVestingTransferOwnershipMutation(options?: Omit, "mutationFn">) { - return useMutation(({ - client, - args: { - fee, - memo, - funds - } = {} - }) => client.transferOwnership(fee, memo, funds), options); -} export interface MarsVestingWithdrawMutation { client: MarsVestingClient; args?: { @@ -201,4 +183,26 @@ export function useMarsVestingCreatePositionMutation(options?: Omit client.createPosition(msg, fee, memo, funds), options); +} +export interface MarsVestingUpdateConfigMutation { + client: MarsVestingClient; + msg: { + newCfg: ConfigForString; + }; + args?: { + fee?: number | StdFee | "auto"; + memo?: string; + funds?: Coin[]; + }; +} +export function useMarsVestingUpdateConfigMutation(options?: Omit, "mutationFn">) { + return useMutation(({ + client, + msg, + args: { + fee, + memo, + funds + } = {} + }) => client.updateConfig(msg, fee, memo, funds), options); } \ No newline at end of file diff --git a/scripts/types/generated/mars-vesting/MarsVesting.types.ts b/scripts/types/generated/mars-vesting/MarsVesting.types.ts index 4d83db5..32d04bc 100644 --- a/scripts/types/generated/mars-vesting/MarsVesting.types.ts +++ b/scripts/types/generated/mars-vesting/MarsVesting.types.ts @@ -6,6 +6,7 @@ */ export interface InstantiateMsg { + denom: string; owner: string; unlock_schedule: Schedule; } @@ -15,6 +16,10 @@ export interface Schedule { start_time: number; } export type ExecuteMsg = { + update_config: { + new_cfg: ConfigForString; + }; +} | { create_position: { user: string; vest_schedule: Schedule; @@ -25,9 +30,12 @@ export type ExecuteMsg = { }; } | { withdraw: {}; -} | { - transfer_ownership: string; }; +export interface ConfigForString { + denom: string; + owner: string; + unlock_schedule: Schedule; +} export type QueryMsg = { config: {}; } | {