From 1df1edbb0a7ce32b602ba2d7cb600aa0ee145718 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Wed, 31 Jan 2024 05:46:57 +0200 Subject: [PATCH] v8 --- Cargo.lock | 521 +++++++++++---- Cargo.toml | 33 +- LICENSE.md | 206 ++---- contracts/coin-flip/Cargo.toml | 5 + contracts/coin-flip/schema/coin-flip.json | 380 ++++++++++- contracts/coin-flip/src/contract.rs | 594 ++++++++++++----- contracts/coin-flip/src/error.rs | 56 +- contracts/coin-flip/src/helpers.rs | 2 +- contracts/coin-flip/src/msg.rs | 122 +++- contracts/coin-flip/src/state.rs | 12 +- contracts/coin-flip/src/sudo.rs | 319 ++++++++- contracts/coin-flip/src/testing/mod.rs | 5 + .../coin-flip/src/testing/test_contract.rs | 46 +- .../coin-flip/src/testing/test_distribute.rs | 35 +- .../coin-flip/src/testing/test_migration.rs | 211 ++++++ .../src/testing/test_multiple_denoms.rs | 191 ++++++ .../coin-flip/src/testing/test_queries.rs | 41 +- .../coin-flip/src/testing/test_streak.rs | 605 ++++++++++++++++++ contracts/coin-flip/src/testing/test_sudo.rs | 181 ++++++ contracts/coin-flip/src/testing/test_types.rs | 48 ++ .../coin-flip/src/testing/utils/executes.rs | 160 ++++- .../coin-flip/src/testing/utils/helpers.rs | 21 +- .../coin-flip/src/testing/utils/queries.rs | 59 +- .../coin-flip/src/testing/utils/setup.rs | 183 +++++- contracts/coin-flip/src/types.rs | 49 +- devtools/add_denom.sh | 15 + devtools/distribute.sh | 8 + devtools/init_main.sh | 25 + devtools/migrate.sh | 18 +- devtools/optimizer.sh | 2 +- devtools/prop.sh | 60 ++ devtools/upload.sh | 2 +- 32 files changed, 3680 insertions(+), 535 deletions(-) create mode 100644 contracts/coin-flip/src/testing/test_migration.rs create mode 100644 contracts/coin-flip/src/testing/test_multiple_denoms.rs create mode 100644 contracts/coin-flip/src/testing/test_streak.rs create mode 100644 contracts/coin-flip/src/testing/test_sudo.rs create mode 100644 contracts/coin-flip/src/testing/test_types.rs create mode 100755 devtools/add_denom.sh create mode 100755 devtools/distribute.sh create mode 100755 devtools/init_main.sh create mode 100755 devtools/prop.sh diff --git a/Cargo.lock b/Cargo.lock index 79e1450..b12a495 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,9 +15,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "base16ct" @@ -25,11 +25,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" -version = "0.13.1" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "base64ct" @@ -37,6 +43,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "block-buffer" version = "0.9.0" @@ -55,6 +67,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bnum" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9008b6bb9fc80b5277f2fe481c09e828743d9151203e804583eb4c9e15b31d" + [[package]] name = "bumpalo" version = "3.12.0" @@ -82,24 +100,51 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "coin-flip" version = "0.7.2" +source = "git+https://github.com/Cosmos-Coin-Flip/Cosmos-Coin-Flip.git?tag=v0.7.2#2442f84bac6747bff2d75c8a8905433cd6e55f43" +dependencies = [ + "anyhow", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw721", + "cw721-base", + "getrandom", + "schemars", + "serde", + "sg-std 0.22.11", + "sg721 0.22.11", + "sg721-base 0.22.11", + "sha256", + "thiserror", +] + +[[package]] +name = "coin-flip" +version = "0.8.2" dependencies = [ "anyhow", + "coin-flip 0.7.2", "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-multi-test", - "cw-storage-plus 1.0.1", - "cw-utils 1.0.1", - "cw2 1.0.1", + "cw-multi-test 0.19.0", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", "cw721", "cw721-base", "getrandom", "schemars", "serde", "sg-multi-test", - "sg-std", - "sg721", - "sg721-base", + "sg-std 0.22.11", + "sg-std 2.3.0", + "sg721 2.3.0", + "sg721-base 0.22.11", + "sg721-base 2.3.0", "sha256", "thiserror", ] @@ -112,31 +157,32 @@ checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" [[package]] name = "cosmwasm-crypto" -version = "1.2.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f22add0f9b2a5416df98c1d0248a8d8eedb882c38fbf0c5052b64eebe865df6d" +checksum = "d8bb3c77c3b7ce472056968c745eb501c440fbc07be5004eba02782c35bfbbe3" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", + "ecdsa 0.16.9", "ed25519-zebra", - "k256", + "k256 0.13.2", "rand_core 0.6.4", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "1.2.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e64f710a18ef90d0a632cf27842e98ffc2d005a38a6f76c12fd0bc03bc1a2d" +checksum = "fea73e9162e6efde00018d55ed0061e93a108b5d6ec4548b4f8ce3c706249687" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.2.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe5ad2e23a971b9e4cd57b20cee3e2e79c33799bed4b128e473aca3702bfe5dd" +checksum = "0df41ea55f2946b6b43579659eec048cc2f66e8c8e2e3652fc5e5e476f673856" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -147,9 +193,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2926d159a9bb1a716a592b40280f1663f2491a9de3b6da77c0933cee2a2655b8" +checksum = "43609e92ce1b9368aa951b334dd354a2d0dd4d484931a5f83ae10e12a26c8ba9" dependencies = [ "proc-macro2", "quote", @@ -158,11 +204,13 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.2.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76fee88ff5bf7bef55bd37ac0619974701b99bf6bd4b16cf56ee8810718abd71" +checksum = "04d6864742e3a7662d024b51a94ea81c9af21db6faea2f9a6d2232bb97c6e53e" dependencies = [ "base64", + "bech32", + "bnum", "cosmwasm-crypto", "cosmwasm-derive", "derivative", @@ -171,16 +219,16 @@ dependencies = [ "schemars", "serde", "serde-json-wasm", - "sha2 0.10.6", + "sha2 0.10.8", + "static_assertions", "thiserror", - "uint", ] [[package]] name = "cosmwasm-storage" -version = "1.2.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "639bc36408bc1ac45e3323166ceeb8f0b91b55a941c4ad59d389829002fbbd94" +checksum = "bd2b4ae72a03e8f56c85df59d172d51d2d7dc9cec6e2bc811e3fb60c588032a4" dependencies = [ "cosmwasm-std", "serde", @@ -196,16 +244,22 @@ dependencies = [ ] [[package]] -name = "crunchy" -version = "0.2.2" +name = "crypto-bigint" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] [[package]] name = "crypto-bigint" -version = "0.4.9" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -244,17 +298,37 @@ checksum = "c2eb84554bbfa6b66736abcd6a9bfdf237ee0ecb83910f746dff7f799093c80a" dependencies = [ "anyhow", "cosmwasm-std", - "cw-storage-plus 1.0.1", - "cw-utils 1.0.1", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", "derivative", - "itertools", - "k256", - "prost", + "itertools 0.10.5", + "k256 0.11.6", + "prost 0.9.0", "schemars", "serde", "thiserror", ] +[[package]] +name = "cw-multi-test" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561604d987be2ef3e34db1f01f0c98544106d84d8be2af92c0737bb199af452c" +dependencies = [ + "anyhow", + "bech32", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "derivative", + "itertools 0.12.0", + "prost 0.12.3", + "schemars", + "serde", + "sha2 0.10.8", + "thiserror", +] + [[package]] name = "cw-storage-plus" version = "0.16.0" @@ -268,9 +342,9 @@ dependencies = [ [[package]] name = "cw-storage-plus" -version = "1.0.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053a5083c258acd68386734f428a5a171b29f7d733151ae83090c6fcc9417ffa" +checksum = "d5ff29294ee99373e2cd5fd21786a3c0ced99a52fec2ca347d565489c61b723c" dependencies = [ "cosmwasm-std", "schemars", @@ -294,13 +368,13 @@ dependencies = [ [[package]] name = "cw-utils" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" +checksum = "1c4a657e5caacc3a0d00ee96ca8618745d050b8f757c709babafb81208d4239c" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2 1.0.1", + "cw2 1.1.2", "schemars", "semver", "serde", @@ -322,15 +396,17 @@ dependencies = [ [[package]] name = "cw2" -version = "1.0.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb70cee2cf0b4a8ff7253e6bc6647107905e8eb37208f87d54f67810faa62f8" +checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.2.0", "schemars", + "semver", "serde", + "thiserror", ] [[package]] @@ -373,6 +449,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "derivative" version = "2.2.0" @@ -395,11 +481,12 @@ 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.4", + "const-oid", "crypto-common", "subtle", ] @@ -416,10 +503,24 @@ version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ - "der", - "elliptic-curve", - "rfc6979", - "signature", + "der 0.6.1", + "elliptic-curve 0.12.3", + "rfc6979 0.3.1", + "signature 1.6.4", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der 0.7.8", + "digest 0.10.7", + "elliptic-curve 0.13.8", + "rfc6979 0.4.0", + "signature 2.2.0", + "spki 0.7.3", ] [[package]] @@ -449,16 +550,35 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ - "base16ct", - "crypto-bigint", - "der", - "digest 0.10.6", - "ff", + "base16ct 0.1.1", + "crypto-bigint 0.4.9", + "der 0.6.1", + "digest 0.10.7", + "ff 0.12.1", "generic-array", - "group", - "pkcs8", + "group 0.12.1", + "pkcs8 0.9.0", "rand_core 0.6.4", - "sec1", + "sec1 0.3.0", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint 0.5.5", + "digest 0.10.7", + "ff 0.13.0", + "generic-array", + "group 0.13.0", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "sec1 0.7.1", "subtle", "zeroize", ] @@ -473,6 +593,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -496,6 +626,7 @@ checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -517,7 +648,18 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ - "ff", + "ff 0.12.1", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", "rand_core 0.6.4", "subtle", ] @@ -543,7 +685,7 @@ 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]] @@ -565,6 +707,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.6" @@ -587,9 +738,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if", - "ecdsa", - "elliptic-curve", - "sha2 0.10.6", + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", + "sha2 0.10.8", +] + +[[package]] +name = "k256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b" +dependencies = [ + "cfg-if", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "once_cell", + "sha2 0.10.8", + "signature 2.2.0", ] [[package]] @@ -609,9 +774,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" @@ -631,15 +796,25 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ - "der", - "spki", + "der 0.6.1", + "spki 0.6.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.8", + "spki 0.7.3", ] [[package]] name = "proc-macro2" -version = "1.0.53" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -651,7 +826,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.9.0", +] + +[[package]] +name = "prost" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +dependencies = [ + "bytes", + "prost-derive 0.12.3", ] [[package]] @@ -661,17 +846,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", "syn 1.0.109", ] +[[package]] +name = "prost-derive" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "quote" -version = "1.0.26" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -697,11 +895,21 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.4.9", "hmac", "zeroize", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ryu" version = "1.0.13" @@ -710,9 +918,9 @@ checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "schemars" -version = "0.8.12" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" dependencies = [ "dyn-clone", "schemars_derive", @@ -722,9 +930,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.12" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" dependencies = [ "proc-macro2", "quote", @@ -738,25 +946,39 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ - "base16ct", - "der", + "base16ct 0.1.1", + "der 0.6.1", "generic-array", - "pkcs8", + "pkcs8 0.9.0", + "subtle", + "zeroize", +] + +[[package]] +name = "sec1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48518a2b5775ba8ca5b46596aae011caa431e6ce7e4a67ead66d92f08884220e" +dependencies = [ + "base16ct 0.2.0", + "der 0.7.8", + "generic-array", + "pkcs8 0.10.2", "subtle", "zeroize", ] [[package]] name = "semver" -version = "1.0.17" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "serde" -version = "1.0.158" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] @@ -772,13 +994,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.158" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.10", + "syn 2.0.39", ] [[package]] @@ -805,16 +1027,16 @@ dependencies = [ [[package]] name = "sg-multi-test" -version = "0.22.11" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7704c3607bcac9db6c2fc7810d4647268f29c7ea4368648846cbe6bef2bec2be" +checksum = "126d5b5e90687a0937386b42595b703aa70f285fa297e24c5dd3565b47a25aa2" dependencies = [ "anyhow", "cosmwasm-std", - "cw-multi-test", + "cw-multi-test 0.16.2", "schemars", "serde", - "sg-std", + "sg-std 2.3.0", ] [[package]] @@ -832,6 +1054,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "sg-std" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6b7bed04a43538e04005584fb95674f181b2eed8132e13887ca6ad86a62cde" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 0.16.0", + "cw721", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "sg721" version = "0.22.11" @@ -846,6 +1083,20 @@ dependencies = [ "thiserror", ] +[[package]] +name = "sg721" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f284472b4f580af7b64ade14af42623581534512d8a681642d79cfbd0c3a254" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 0.16.0", + "cw721-base", + "serde", + "thiserror", +] + [[package]] name = "sg721-base" version = "0.22.11" @@ -860,8 +1111,28 @@ dependencies = [ "cw721", "cw721-base", "serde", - "sg-std", - "sg721", + "sg-std 0.22.11", + "sg721 0.22.11", + "thiserror", + "url", +] + +[[package]] +name = "sg721-base" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f638b1d6db71e7e61c796e510833f09eb6a4e2a14aecd368636b90a7c7b8c7" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", + "cw2 0.16.0", + "cw721", + "cw721-base", + "serde", + "sg-std 2.3.0", + "sg721 2.3.0", "thiserror", "url", ] @@ -881,13 +1152,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -906,7 +1177,17 @@ 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", +] + +[[package]] +name = "signature" +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", ] @@ -917,7 +1198,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", - "der", + "der 0.6.1", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.8", ] [[package]] @@ -945,9 +1236,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.10" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aad1363ed6d37b84299588d62d3a7d95b5a5c2d9aad5c85609fda12afaa1f40" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -956,22 +1247,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.10", + "syn 2.0.39", ] [[package]] @@ -995,18 +1286,6 @@ 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-bidi" version = "0.3.13" @@ -1107,6 +1386,6 @@ checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "zeroize" -version = "1.5.7" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index a8b4a3d..b84e7f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ resolver = "2" [workspace.package] edition = "2021" -version = "0.7.2" +version = "0.8.2" license = "Apache-2.0" repository = "https://github.com/Art3miX/cosmos-coin-flip" @@ -17,25 +17,30 @@ exclude = [ ] [workspace.dependencies] -cosmwasm-schema = "1.1" -cosmwasm-std = "1.1" -cosmwasm-storage = "1.1" -cw-storage-plus = "1.0" -cw-utils = "1.0" -cw2 = "1.0" +cosmwasm-schema = "1.5.0" +cosmwasm-std = "1.5.0" +cosmwasm-storage = "1.5.0" +cw-storage-plus = "1.2.0" +cw-utils = "1.0.3" +cw2 = "1.1.2" cw721 = "0.16.0" cw721-base = { version = "0.16.0", features = ["library"] } -schemars = "0.8.11" +schemars = "0.8.16" serde = { version = "1.0.147", default-features = false, features = ["derive"] } -thiserror = "1.0.31" +thiserror = "1.0.50" anyhow = "1" -sg721 = { version = "0.22.11" } -sg721-base = { version = "0.22.11", features = ["library"] } -sg-std = { version = "0.22.11" } +sg721 = { version = "2.3.0" } +sg721-base = { version = "2.3.0", features = ["library"] } +sg-std = { version = "2.3.0" } + +#v07 +coin-flip-v07 = { package = "coin-flip", version = "0.7.2", git = "https://github.com/Cosmos-Coin-Flip/Cosmos-Coin-Flip.git", tag = "v0.7.2" } +sg721-base-v07 = { package = "sg721-base", version = "0.22.11", features = ["library"] } +sg-std-v07 = { package = "sg-std", version = "0.22.11" } # dev deps -cw-multi-test = "0.16" -sg-multi-test = { version = "0.22.11" } +cw-multi-test = "0.19.0" +sg-multi-test = { version = "2.3.0" } [profile.release.package.coin-flip] codegen-units = 1 diff --git a/LICENSE.md b/LICENSE.md index 2fb9394..33b819c 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,157 +1,93 @@ -# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International - -Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. - -**Using Creative Commons Public Licenses** - -Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. - -* __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors). - -* __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees). - -## Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International Public License +# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International Public License By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. -### Section 1 – Definitions. - -a. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. - -b. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. - -e. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. - -f. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. - -h. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License. - -i. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. - -h. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License. - -i. __NonCommercial__ means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange. - -j. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. - -k. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. - -l. __You__ means the individual or entity exercising the Licensed Rights under this Public License. __Your__ has a corresponding meaning. - -### Section 2 – Scope. - -a. ___License grant.___ - - 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: - - A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and - - B. produce and reproduce, but not Share, Adapted Material for NonCommercial purposes only. - - 2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. - - 3. __Term.__ The term of this Public License is specified in Section 6(a). - - 4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. - - 5. __Downstream recipients.__ - - A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. - - B. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. - - 6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). - -b. ___Other rights.___ - - 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. - - 2. Patent and trademark rights are not licensed under this Public License. - - 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes. - -### Section 3 – License Conditions. +## Section 1 – Definitions. + +Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. +Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. +Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. +Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. +Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. +Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. +Licensor means the individual(s) or entity(ies) granting rights under this Public License. +NonCommercial means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange. +Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. +Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. +You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. +Section 2 – Scope. + +License grant. +Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: +reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and +produce and reproduce, but not Share, Adapted Material for NonCommercial purposes only. +Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. +Term. The term of this Public License is specified in Section 6(a). +Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. +Downstream recipients. +Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. +No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. +No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). +Other rights. + +Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. +Patent and trademark rights are not licensed under this Public License. +To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes. + +## Section 3 – License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. -a. ___Attribution.___ - - 1. If You Share the Licensed Material, You must: - - A. retain the following if it is supplied by the Licensor with the Licensed Material: - - i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); - - ii. a copyright notice; +Attribution. - iii. a notice that refers to this Public License; +If You Share the Licensed Material, You must: - iv. a notice that refers to the disclaimer of warranties; +retain the following if it is supplied by the Licensor with the Licensed Material: +identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); +a copyright notice; +a notice that refers to this Public License; +a notice that refers to the disclaimer of warranties; +a URI or hyperlink to the Licensed Material to the extent reasonably practicable; +indicate if You modified the Licensed Material and retain an indication of any previous modifications; and +indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. +For the avoidance of doubt, You do not have permission under this Public License to Share Adapted Material. +You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. +If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. - v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; - - B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and - - C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. - - For the avoidance of doubt, You do not have permission under this Public License to Share Adapted Material. - - 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. - - 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. - -### Section 4 – Sui Generis Database Rights. +## Section 4 – Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: -a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only and provided You do not Share Adapted Material; - -b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and - -c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. - +for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only and provided You do not Share Adapted Material; +if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and +You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. -### Section 5 – Disclaimer of Warranties and Limitation of Liability. - -a. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__ - -b. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__ - -c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. - -### Section 6 – Term and Termination. - -a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. - -b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: - - 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or - - 2. upon express reinstatement by the Licensor. - - For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. - -c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. - -d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. - -### Section 7 – Other Terms and Conditions. +## Section 5 – Disclaimer of Warranties and Limitation of Liability. -a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. +Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. +To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. +The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. -b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. +## Section 6 – Term and Termination. -### Section 8 – Interpretation. +This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. +Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: -a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. +automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or +upon express reinstatement by the Licensor. +For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. +For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. +Sections 1, 5, 6, 7, and 8 survive termination of this Public License. -b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. +## Section 7 – Other Terms and Conditions. -c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. +The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. +Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. -d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. +## Section 8 – Interpretation. -> Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. -> -> Creative Commons may be contacted at [creativecommons.org](http://creativecommons.org). +For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. +To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. +No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. +Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. diff --git a/contracts/coin-flip/Cargo.toml b/contracts/coin-flip/Cargo.toml index 02de29d..4c6d920 100644 --- a/contracts/coin-flip/Cargo.toml +++ b/contracts/coin-flip/Cargo.toml @@ -42,6 +42,11 @@ cw721 = { workspace = true } cw721-base = { workspace = true } getrandom = { version = "0.2.8", features = ["js"] } +# v07 +coin-flip-v07 = { workspace = true } +sg-std-v07 = { workspace = true } +sg721-base-v07 = { workspace = true } + [dev-dependencies] cw-multi-test = { workspace = true } sg-multi-test = { workspace = true } diff --git a/contracts/coin-flip/schema/coin-flip.json b/contracts/coin-flip/schema/coin-flip.json index 7dfae10..62e71be 100644 --- a/contracts/coin-flip/schema/coin-flip.json +++ b/contracts/coin-flip/schema/coin-flip.json @@ -1,20 +1,26 @@ { "contract_name": "coin-flip", - "contract_version": "0.6.0", + "contract_version": "0.8.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "InstantiateMsg", "type": "object", "required": [ - "admin", + "allowed_to_send_nft", "denoms", "fees", + "nft_pool_max", + "streak_nft_winning_amount", + "streak_rewards", "wallets" ], "properties": { - "admin": { - "type": "string" + "allowed_to_send_nft": { + "type": "array", + "items": { + "type": "string" + } }, "bank_limit": { "anyOf": [ @@ -43,12 +49,48 @@ "format": "uint64", "minimum": 0.0 }, + "max_bet_limit": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "min_bet_limit": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "nft_pool_max": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, "sg721_addr": { "type": [ "string", "null" ] }, + "streak_nft_winning_amount": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "streak_rewards": { + "type": "array", + "items": { + "$ref": "#/definitions/StreakReward" + } + }, "wallets": { "$ref": "#/definitions/Wallets" } @@ -87,6 +129,24 @@ }, "additionalProperties": false }, + "StreakReward": { + "type": "object", + "required": [ + "reward", + "streak" + ], + "properties": { + "reward": { + "$ref": "#/definitions/Uint128" + }, + "streak": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -113,6 +173,32 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", "oneOf": [ + { + "description": "Receive NFTs to add to our NFTs pool of streak mini game", + "type": "object", + "required": [ + "receive_nft" + ], + "properties": { + "receive_nft": { + "$ref": "#/definitions/Cw721ReceiveMsg" + } + }, + "additionalProperties": false + }, + { + "description": "Streak mini game msgs", + "type": "object", + "required": [ + "streak" + ], + "properties": { + "streak": { + "$ref": "#/definitions/StreakExecuteMsg" + } + }, + "additionalProperties": false + }, { "description": "Flip msgs", "type": "object", @@ -141,6 +227,31 @@ } ], "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Cw721ReceiveMsg": { + "description": "Cw721ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "msg", + "sender", + "token_id" + ], + "properties": { + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + }, "Fees": { "type": "object", "required": [ @@ -176,6 +287,7 @@ "FlipExecuteMsg": { "oneOf": [ { + "description": "Register the flip", "type": "object", "required": [ "start_flip" @@ -201,6 +313,7 @@ "additionalProperties": false }, { + "description": "Does the actual flip", "type": "object", "required": [ "do_flips" @@ -222,9 +335,46 @@ "tails" ] }, + "StreakExecuteMsg": { + "oneOf": [ + { + "description": "Claim streak reward if the sender streak matches one of the rewards e.g - streak reward is at 8 streaks, if sender have 8 streaks, he can claim the reward if sender have 9 streaks, he can't claim rewards for 8 streak,\n\nClaim only highest reward, so if sender have 10 streak, and streak rewards are at 8 and 10, only 10th will be claimed, and streak will be reset.\n\nNFT claims will be automatically claimed on 12th streak (highest reward) but if NFT pool is empty, last reward will be automatically sent", + "type": "object", + "required": [ + "claim" + ], + "properties": { + "claim": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "StreakReward": { + "type": "object", + "required": [ + "reward", + "streak" + ], + "properties": { + "reward": { + "$ref": "#/definitions/Uint128" + }, + "streak": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, "SudoMsg": { "oneOf": [ { + "description": "Distribute the collected fees so far", "type": "object", "required": [ "distribute" @@ -238,6 +388,7 @@ "additionalProperties": false }, { + "description": "Update fees", "type": "object", "required": [ "update_fees" @@ -280,6 +431,7 @@ "additionalProperties": false }, { + "description": "Update the bank limit", "type": "object", "required": [ "update_bank_limit" @@ -301,6 +453,33 @@ "additionalProperties": false }, { + "description": "Update the bet limit (min and max)", + "type": "object", + "required": [ + "update_bet_limit" + ], + "properties": { + "update_bet_limit": { + "type": "object", + "required": [ + "max_bet", + "min_bet" + ], + "properties": { + "max_bet": { + "$ref": "#/definitions/Uint128" + }, + "min_bet": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Pause the contract in case of emergency", "type": "object", "required": [ "update_pause" @@ -311,6 +490,134 @@ } }, "additionalProperties": false + }, + { + "description": "Update streak related config stuff", + "type": "object", + "required": [ + "update_streak" + ], + "properties": { + "update_streak": { + "type": "object", + "properties": { + "allowed_to_send_nft": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "nft_pool_max": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "streak_nft_winning_amount": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "streak_rewards": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/StreakReward" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Withdraw all or a single NFT from the pool only to the team wallet", + "type": "object", + "required": [ + "withdraw_nft_from_pool" + ], + "properties": { + "withdraw_nft_from_pool": { + "type": "object", + "properties": { + "all": { + "type": [ + "boolean", + "null" + ] + }, + "index": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Send excess funds to the reserve wallet This function checks the bank limit - fees and send the excess to the reserve wallet", + "type": "object", + "required": [ + "send_excess_funds" + ], + "properties": { + "send_excess_funds": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "This transfer the NFT to the team wallet This function will check that the NFT is not in the pool In case NFT will be transfered by mistake we will be able to handle it", + "type": "object", + "required": [ + "transfer_nft" + ], + "properties": { + "transfer_nft": { + "type": "object", + "required": [ + "contract", + "token_id" + ], + "properties": { + "contract": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, @@ -403,6 +710,7 @@ "additionalProperties": false }, { + "description": "Do dry ditribution to see results", "type": "object", "required": [ "dry_distribution" @@ -414,6 +722,20 @@ } }, "additionalProperties": false + }, + { + "description": "Get the NFT pool", + "type": "object", + "required": [ + "get_nft_pool" + ], + "properties": { + "get_nft_pool": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, @@ -485,6 +807,10 @@ "fees", "flips_per_block_limit", "is_paused", + "max_bet_limit", + "min_bet_limit", + "nft_pool_max", + "streak_nft_winning_amount", "wallets" ], "properties": { @@ -511,6 +837,17 @@ "is_paused": { "type": "boolean" }, + "max_bet_limit": { + "$ref": "#/definitions/Uint128" + }, + "min_bet_limit": { + "$ref": "#/definitions/Uint128" + }, + "nft_pool_max": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, "sg721_addr": { "anyOf": [ { @@ -521,6 +858,11 @@ } ] }, + "streak_nft_winning_amount": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, "wallets": { "$ref": "#/definitions/Wallets" } @@ -682,6 +1024,36 @@ } } }, + "get_nft_pool": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_NftReward", + "type": "array", + "items": { + "$ref": "#/definitions/NftReward" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "NftReward": { + "type": "object", + "required": [ + "contract_addr", + "token_id" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, "get_score": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "FlipScore", diff --git a/contracts/coin-flip/src/contract.rs b/contracts/coin-flip/src/contract.rs index 8a4cc38..30106de 100644 --- a/contracts/coin-flip/src/contract.rs +++ b/contracts/coin-flip/src/contract.rs @@ -1,29 +1,29 @@ +use std::collections::{HashMap, HashSet}; + #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ensure_eq, Binary, Deps, DepsMut, Env, MessageInfo, StdResult, Uint128}; use cw2::set_contract_version; use sg_std::Response; +use coin_flip_v07 as ccf_v07; + use crate::error::ContractError; -use crate::helpers::{ensure_admin, ensure_not_paused}; -use crate::msg::{ExecuteMsg, FlipExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg}; -use crate::state::{CONFIG, FEES, FLIPS, TODO_FLIPS}; -use crate::types::{Config, Wallets}; +use crate::helpers::ensure_not_paused; +use crate::msg::{ + ExecuteMsg, FlipExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StreakExecuteMsg, +}; +use crate::state::{ + ALLOWED_SEND_NFT, CONFIG, FEES, FLIPS, NFT_REWARDS, STREAK_REWARDS, TODO_FLIPS, +}; +use crate::types::{Config, DenomLimit, Fees, Wallets}; -use crate::sudo; +use crate::sudo::handle_sudo_msg; // version info for migration info const CONTRACT_NAME: &str = "cosmos-coin-flip"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -/// Minimum amount of tokens we need to have in the contract -pub const MIN_BANK_AMOUNT: Uint128 = Uint128::new(30_000_000_000); - -/// Min bet people are allow to bet -pub const MIN_BET: Uint128 = Uint128::new(5_000_000); -/// Max bet people are allow to bet -pub const MAX_BET: Uint128 = Uint128::new(25_000_000); - #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, @@ -42,16 +42,20 @@ pub fn instantiate( None => None, }; + let mut denom_limits: HashMap = HashMap::with_capacity(msg.denoms.len()); + + for (denom, min, max, bank) in msg.denom_limits { + denom_limits.insert(denom, DenomLimit { min, max, bank }); + } + // Save config CONFIG.save( deps.storage, &Config { admin: info.sender.to_string(), - denoms: msg.denoms, - bank_limit: msg.bank_limit.unwrap_or(MIN_BANK_AMOUNT), - min_bet_limit: msg.min_bet_limit.unwrap_or(MIN_BET), - max_bet_limit: msg.max_bet_limit.unwrap_or(MAX_BET), - flips_per_block_limit: msg.flips_per_block_limit.unwrap_or(10), // 10 flips per block + denoms: msg.denoms.clone(), + denom_limits, + flips_per_block_limit: msg.flips_per_block_limit.unwrap_or(10), wallets: Wallets { team: msg.wallets.team, reserve: msg.wallets.reserve, @@ -59,14 +63,28 @@ pub fn instantiate( fees: msg.fees, sg721_addr, is_paused: false, + streak_nft_winning_amount: msg.streak_nft_winning_amount, + nft_pool_max: msg.nft_pool_max, }, )?; // Init fees to be 0 - FEES.save(deps.storage, &Uint128::zero())?; + for denom in msg.denoms { + FEES.save(deps.storage, denom, &Uint128::zero())?; + } + FLIPS.save(deps.storage, &vec![])?; TODO_FLIPS.save(deps.storage, &vec![])?; + STREAK_REWARDS.save(deps.storage, &msg.streak_rewards)?; + let allowed_send_nft_addrs = msg + .allowed_to_send_nft + .into_iter() + .map(|addr| deps.api.addr_validate(&addr)) + .collect::>>()?; + ALLOWED_SEND_NFT.save(deps.storage, &allowed_send_nft_addrs)?; + NFT_REWARDS.save(deps.storage, &vec![])?; + Ok(Response::new().add_attribute("method", "instantiate")) } @@ -80,6 +98,15 @@ pub fn execute( let config = CONFIG.load(deps.storage)?; match msg { + ExecuteMsg::ReceiveNft(cw721::Cw721ReceiveMsg { + sender, + token_id, + msg: _, + }) => streak_execute::receive_nft(deps, info, &config, sender, token_id), + ExecuteMsg::Streak(StreakExecuteMsg::Claim {}) => { + ensure_not_paused(&config)?; + streak_execute::execute_claim(deps, info) + } ExecuteMsg::Flip(FlipExecuteMsg::StartFlip { pick, amount }) => { ensure_not_paused(&config)?; flip_execute::execute_start_flip(deps, env, info, &config, pick, amount) @@ -88,42 +115,21 @@ pub fn execute( ensure_not_paused(&config)?; flip_execute::execute_do_flips(deps, env, &config) } - ExecuteMsg::Sudo(SudoMsg::Distribute {}) => { - ensure_admin(&config, &info)?; - sudo::distribute(deps, env, &config) - } - ExecuteMsg::Sudo(SudoMsg::UpdateFees { fees }) => { - ensure_admin(&config, &info)?; - sudo::update_fees(deps, config, fees) - } - ExecuteMsg::Sudo(SudoMsg::UpdateBankLimit { limit }) => { - ensure_admin(&config, &info)?; - sudo::update_bank_limit(deps, config, limit) - } - ExecuteMsg::Sudo(SudoMsg::UpdateSg721 { addr }) => { - ensure_admin(&config, &info)?; - sudo::update_sg721(deps, config, addr) - } - ExecuteMsg::Sudo(SudoMsg::UpdatePause(is_paused)) => { - ensure_admin(&config, &info)?; - sudo::update_pause(deps, config, is_paused) - } - ExecuteMsg::Sudo(SudoMsg::UpdateBetLimit { min_bet, max_bet }) => { - ensure_admin(&config, &info)?; - sudo::update_bet_limit(deps, config, min_bet, max_bet) - } + ExecuteMsg::Sudo(sudo_msg) => handle_sudo_msg(deps, info, env, config, sudo_msg), } } mod flip_execute { + use std::collections::HashMap; use cw_utils::must_pay; - use cosmwasm_std::{coin, ensure, BankMsg, Event, Uint128}; + use cosmwasm_std::{coin, ensure, to_json_binary, BankMsg, CosmosMsg, Event, Uint128, WasmMsg}; + use sg_std::StargazeMsgWrapper; use sha256::Sha256Digest; use crate::helpers::ensure_correct_funds; - use crate::state::{get_next_flip_id, FEES, FLIPS, FLIP_ID, SCORES}; + use crate::state::{get_next_flip_id, FEES, FLIPS, FLIP_ID, NFT_REWARDS, SCORES}; use crate::types::{Flip, FlipScore, PickTypes, TodoFlip}; use super::*; @@ -136,28 +142,11 @@ mod flip_execute { pick: PickTypes, amount: Uint128, ) -> Result { - // Make sure that the sent amount is not above our max - ensure!( - amount <= config.max_bet_limit, - ContractError::OverTheLimitBet { - max_limit: (config.max_bet_limit / Uint128::new(1000000)).to_string() - } - ); - ensure!( - amount >= config.min_bet_limit, - ContractError::UnderTheLimitBet { - min_limit: (config.min_bet_limit / Uint128::new(1000000)).to_string() - } - ); - let mut todo_flips = TODO_FLIPS.load(deps.storage)?; // Make sure the user doesn't have flip waiting already ensure!( - todo_flips - .clone() - .into_iter() - .all(|x| x.wallet != info.sender), + todo_flips.iter().all(|x| x.wallet != info.sender), ContractError::AlreadyStartedFlip ); @@ -174,12 +163,30 @@ mod flip_execute { let funds = info.funds[0].clone(); // Verify the sent funds is in supported denom. - let denom = if config.denoms.clone().into_iter().any(|x| *x == funds.denom) { + let denom = if config.denoms.iter().any(|x| x == &funds.denom) { funds.denom } else { return Err(ContractError::WrongDenom { denom: funds.denom }); }; + // Make sure that the sent amount is within our limits + let Some(bet_limits) = config.denom_limits.get(&denom) else { + return Err(ContractError::NoBetLimits { denom}); + }; + + ensure!( + amount <= bet_limits.max, + ContractError::OverTheLimitBet { + max_limit: (bet_limits.max / Uint128::new(1000000)).to_string() + } + ); + ensure!( + amount >= bet_limits.min, + ContractError::UnderTheLimitBet { + min_limit: (bet_limits.min / Uint128::new(1000000)).to_string() + } + ); + // Make sure the paid amount is correct (funds sent is the amount + fee) let fee_amount = ensure_correct_funds(funds.amount, amount, config.fees.flip_bps)?; let should_pay_amount = amount.checked_add(fee_amount)?; @@ -192,18 +199,18 @@ mod flip_execute { ); // Make sure we have funds to pay for the flip - let mut fees = FEES.load(deps.storage)?; + let mut fees = FEES.load(deps.storage, denom.clone())?; let balance = deps .querier .query_balance(&env.contract.address, denom.clone())?; ensure!( balance.amount - fees >= amount * Uint128::new(2), - ContractError::ContractMissingFunds + ContractError::ContractMissingFunds(denom) ); // Save fees fees = fees.checked_add(fee_amount)?; - FEES.save(deps.storage, &fees)?; + FEES.save(deps.storage, denom.clone(), &fees)?; let id = get_next_flip_id(deps.storage); FLIP_ID.save(deps.storage, &id)?; @@ -229,83 +236,172 @@ mod flip_execute { config: &Config, ) -> Result { let todo_flips = TODO_FLIPS.load(deps.storage)?; + // Make sure we have flips + ensure!(!todo_flips.is_empty(), ContractError::NoFlipsToDo); - // Make sure we have funds to pay for all the flips - let fees = FEES.load(deps.storage)?; - let total_amount_to_pay = todo_flips - .iter() - .fold(Uint128::zero(), |acc, x| acc + x.amount.amount); - let balance = deps - .querier - .query_balance(&env.contract.address, config.denoms[0].clone())?; + let mut save_todo_flips: Vec = vec![]; + let mut flip_denoms: HashMap = HashMap::with_capacity(1); + + // Make sure we have flips to do + let filtered_todo_flips: Vec = todo_flips + .into_iter() + .filter(|todo_flip| { + if todo_flip.block >= env.block.height { + save_todo_flips.push(todo_flip.clone()); + return false; + } + + match flip_denoms.get(todo_flip.amount.denom.as_str()) { + Some(amount) => flip_denoms.insert( + todo_flip.amount.denom.clone(), + *amount + todo_flip.amount.amount, + ), + None => { + flip_denoms.insert(todo_flip.amount.denom.clone(), todo_flip.amount.amount) + } + }; + + true + }) + .collect(); + + // Make sure that we have flips to do, else error ensure!( - balance.amount - fees >= total_amount_to_pay * Uint128::new(2), - ContractError::ContractMissingFunds + !filtered_todo_flips.is_empty(), + ContractError::NoFlipsToDoThisBlock ); - let mut msgs = vec![]; + // Save the todo flips that are not ready to be flipped yet + TODO_FLIPS.save(deps.storage, &save_todo_flips)?; + + // Make sure we have funds to pay for all the flips + for (denom, total_amount) in flip_denoms { + let fees = FEES.load(deps.storage, denom.clone())?; + + let contract_balance = deps + .querier + .query_balance(&env.contract.address, denom.clone())?; + + ensure!( + contract_balance.amount - fees >= total_amount * Uint128::new(2), + ContractError::ContractMissingFunds(denom) + ); + } + + let mut msgs: Vec> = vec![]; let mut response = Response::default(); let rand = get_random(&env); let mut last_flips = FLIPS.load(deps.storage)?; - let save_todo_flips: Vec = todo_flips - .into_iter() - .filter_map(|todo_flip| { - // Filter out the flips that are not ready to be flipped. - if todo_flip.block >= env.block.height { - Some(todo_flip) - } else { - // This flip is ready to be flipped. - let flip_result = do_a_flip(&todo_flip, rand); - - // Handle score and save it (needed the streak info in Flip) - let score = match SCORES.load(deps.storage, &todo_flip.wallet) { - Ok(mut score) => score.update(flip_result, env.clone()), - Err(_) => FlipScore::new(flip_result, env.clone()), - }; - SCORES - .save(deps.storage, &todo_flip.wallet, &score) - .unwrap(); - - // Create new flip and save it - let flip = Flip { - wallet: todo_flip.wallet.clone(), - amount: todo_flip.amount.clone(), - result: flip_result, - streak: score.streak, - timestamp: env.block.time, - }; - - // Update last flips vector - if last_flips.len() >= 5 { - last_flips.remove(0); - } - last_flips.push(flip); - // Send funds if they won - if flip_result { - let pay = todo_flip.amount.amount * Uint128::new(2); // double the amount - msgs.push(BankMsg::Send { + for todo_flip in filtered_todo_flips { + // Get flip result (won or lost) + let flip_result = do_a_flip(&todo_flip, rand); + + // Handle score (needed the streak info in Flip) + let mut score = match SCORES.load(deps.storage, &todo_flip.wallet) { + Ok(mut score) => score.update(flip_result, env.clone()), + Err(_) => FlipScore::new(flip_result, env.clone()), + }; + + // Create new flip + let flip = Flip { + wallet: todo_flip.wallet.clone(), + amount: todo_flip.amount.clone(), + result: flip_result, + streak: score.streak.clone(), + timestamp: env.block.time, + }; + + // check if flipper did enough streak to win NFT (12) + if is_streak_nft_winner(config, &score) { + let mut nft_pool = NFT_REWARDS.load(deps.storage)?; + let mut streak_event = Event::new("streak-claim") + .add_attribute("flipper", todo_flip.wallet.to_string()) + .add_attribute("flip_id", todo_flip.id.to_string()); + + if nft_pool.is_empty() { + let rewards = STREAK_REWARDS.load(deps.storage)?; + let to_send = rewards[rewards.len() - 1].clone(); + + msgs.push( + BankMsg::Send { to_address: todo_flip.wallet.to_string(), - amount: vec![coin(pay.u128(), todo_flip.amount.denom.clone())], - }); - } + amount: vec![coin(to_send.reward.u128(), "ustars".to_string())], + } + .into(), + ); - response = response.clone().add_event( - Event::new("flip") - .add_attribute("flipper", todo_flip.wallet) - .add_attribute("flip_id", todo_flip.id.to_string()) - .add_attribute("flip_amount", todo_flip.amount.to_string()) - .add_attribute("flip_pick", format!("{:?}", todo_flip.pick)) - .add_attribute("result", if flip_result { "won" } else { "lost" }), + //add claim amount to event + streak_event = streak_event + .clone() + .add_attribute("claim", format!("{}ustars", to_send.reward.u128())); + } else { + // get the random index of the NFT to send + let winning_nft_index = rand % nft_pool.len() as u64; + let nft_to_send = nft_pool[winning_nft_index as usize].clone(); + + // Send the NFT to the winner + msgs.push( + WasmMsg::Execute { + contract_addr: nft_to_send.contract_addr.to_string(), + msg: to_json_binary(&cw721::Cw721ExecuteMsg::TransferNft { + recipient: todo_flip.wallet.to_string().clone(), + token_id: nft_to_send.token_id.clone(), + })?, + funds: vec![], + } + .into(), ); - None + nft_pool.remove(winning_nft_index as usize); + NFT_REWARDS.save(deps.storage, &nft_pool)?; + + //add claim amount to event + streak_event = streak_event.clone().add_attribute( + "claim", + format!("{}/{}", nft_to_send.contract_addr, nft_to_send.token_id), + ); } - }) - .collect(); - FLIPS.save(deps.storage, &last_flips).unwrap(); - TODO_FLIPS.save(deps.storage, &save_todo_flips)?; + // Reset the streak score of the flipper + score.streak.reset(); + + // Add event to response + response = response.add_event(streak_event); + } + + // Save the score + SCORES.save(deps.storage, &todo_flip.wallet, &score)?; + + // Update last flips vector + if last_flips.len() >= 5 { + last_flips.remove(0); + } + last_flips.push(flip); + + // Send funds if they won + if flip_result { + let pay = todo_flip.amount.amount * Uint128::new(2); // double the amount + msgs.push( + BankMsg::Send { + to_address: todo_flip.wallet.to_string(), + amount: vec![coin(pay.u128(), todo_flip.amount.denom.clone())], + } + .into(), + ); + } + + response = response.clone().add_event( + Event::new("flip") + .add_attribute("flipper", todo_flip.wallet) + .add_attribute("flip_id", todo_flip.id.to_string()) + .add_attribute("flip_amount", todo_flip.amount.to_string()) + .add_attribute("flip_pick", format!("{:?}", todo_flip.pick)) + .add_attribute("result", if flip_result { "won" } else { "lost" }), + ); + } + + FLIPS.save(deps.storage, &last_flips)?; Ok(response .add_attribute("flip_action", "do_flips") @@ -321,9 +417,9 @@ mod flip_execute { let sha256 = Sha256Digest::digest(format!( "{}{}{}", + tx_index, env.block.height, env.block.time.nanos(), - tx_index, )); sha256.as_bytes().iter().fold(0, |acc, x| acc + *x as u64) @@ -346,41 +442,145 @@ mod flip_execute { // Return true if one of them is true (won) else return false (lost) won_heads || won_tails } + + fn is_streak_nft_winner(config: &Config, score: &FlipScore) -> bool { + score.streak.amount == config.streak_nft_winning_amount + } +} + +mod streak_execute { + use cosmwasm_std::{coins, BankMsg, Event}; + + use crate::{ + state::{ALLOWED_SEND_NFT, NFT_REWARDS, SCORES, STREAK_REWARDS}, + types::NftReward, + }; + + use super::*; + + pub(crate) fn receive_nft( + deps: DepsMut, + info: MessageInfo, + config: &Config, + sender: String, + token_id: String, + ) -> Result { + // Verify the sender can send NFTs to the contract + let allowed_to_send_nfts = ALLOWED_SEND_NFT.load(deps.storage)?; + + if !allowed_to_send_nfts.contains(&deps.api.addr_validate(&sender)?) { + return Err(ContractError::UnauthorizedToSendNft); + } + + // Verify our NFT rewards pool is not full + let mut nft_rewards = NFT_REWARDS.load(deps.storage)?; + + if nft_rewards.len() as u32 >= config.nft_pool_max { + return Err(ContractError::MaxNFTStreakRewardsReached); + } + + // info.sender is the NFT contract address, token_id is the NFT id + // Add the NFT to the pool of NFTs + nft_rewards.push(NftReward::new(info.sender.clone(), token_id.clone())); + NFT_REWARDS.save(deps.storage, &nft_rewards)?; + + Ok(Response::default() + .add_attribute("action", "add_nft") + .add_attribute("nft_contract", info.sender) + .add_attribute("token_id", token_id) + .add_attribute("nft_pool_size", nft_rewards.len().to_string())) + } + + pub(crate) fn execute_claim( + deps: DepsMut, + info: MessageInfo, + ) -> Result { + let streak_rewards = STREAK_REWARDS.load(deps.storage)?; + let mut score = SCORES.load(deps.storage, &info.sender)?; + + // make sure score is higher or equal to the lowest streak reward + if score.streak.amount < streak_rewards[0].streak { + return Err(ContractError::LowStreak(streak_rewards[0].streak)); + } + + // find the matching reward to claim + match streak_rewards + .iter() + .find(|r| r.streak == score.streak.amount) + { + Some(reward) => { + score.streak.reset(); + SCORES.save(deps.storage, &info.sender, &score)?; + + Ok(Response::default() + .add_event( + Event::new("streak-claim") + .add_attribute("flipper", info.sender.to_string()) + .add_attribute("streak", reward.streak.to_string()) + .add_attribute("claim", format!("{}ustars", reward.reward)), + ) + .add_message(BankMsg::Send { + to_address: info.sender.to_string(), + amount: coins(reward.reward.u128(), "ustars"), + })) + } + None => Err(ContractError::NotEligibleForStreakReward( + score.streak.amount, + )), + } + } } #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::GetLast5 {} => query::get_last_5(deps), - QueryMsg::GetFeesAmount {} => query::get_fees(deps), + QueryMsg::GetFeesAmount { denom } => query::get_fees(deps, denom), + QueryMsg::GetAllFeesAmount {} => query::get_all_fees(deps), QueryMsg::GetScore { address } => query::get_score(deps, address), QueryMsg::GetConfig {} => query::get_config(deps), QueryMsg::ShouldDoFlips {} => query::should_do_flips(deps, env), - QueryMsg::DryDistribution {} => query::dry_distribution(deps, env), + QueryMsg::DryDistribution { denom } => query::dry_distribution(deps, env, denom), + QueryMsg::GetNftPool {} => query::get_nft_pool(deps), } } mod query { - use cosmwasm_std::{to_binary, Binary, Decimal, Deps, Env, StdError, StdResult, Uint128}; + use cosmwasm_std::{ + to_json_binary, Binary, Coin, Decimal, Deps, Env, StdError, StdResult, Uint128, + }; use crate::{ msg::DryDistributionResponse, - state::{CONFIG, FEES, FLIPS, SCORES, TODO_FLIPS}, + state::{CONFIG, FEES, FLIPS, NFT_REWARDS, SCORES, TODO_FLIPS}, sudo::{calculate_fees_to_pay, get_holders_list, verify_contract_balance}, - types::FeesToPay, + types::{FeesToPay, NftReward}, }; - pub fn get_fees(deps: Deps) -> StdResult { - to_binary(&FEES.load(deps.storage)?) + pub fn get_fees(deps: Deps, denom: String) -> StdResult { + to_json_binary(&FEES.load(deps.storage, denom)?) + } + + pub fn get_all_fees(deps: Deps) -> StdResult { + let all_fees = FEES + .range(deps.storage, None, None, cosmwasm_std::Order::Ascending) + .map(|res| { + let (denom, amount) = res?; + + Ok(Coin { denom, amount }) + }) + .collect::, StdError>>()?; + + to_json_binary(&all_fees) } pub fn get_config(deps: Deps) -> StdResult { - to_binary(&CONFIG.load(deps.storage)?) + to_json_binary(&CONFIG.load(deps.storage)?) } pub fn get_score(deps: Deps, address: String) -> StdResult { let address = deps.api.addr_validate(&address)?; - to_binary(&SCORES.load(deps.storage, &address)?) + to_json_binary(&SCORES.load(deps.storage, &address)?) } pub fn should_do_flips(deps: Deps, env: Env) -> StdResult { @@ -389,21 +589,24 @@ mod query { let res = todo_flips .iter() .any(|todo_flip| env.block.height > todo_flip.block); - to_binary(&res) + to_json_binary(&res) } pub fn get_last_5(deps: Deps) -> StdResult { let flips = FLIPS.load(deps.storage)?; - to_binary(&flips) + to_json_binary(&flips) } - pub fn dry_distribution(deps: Deps, env: Env) -> StdResult { + pub fn dry_distribution(deps: Deps, env: Env, denom: String) -> StdResult { let config = CONFIG.load(deps.storage)?; - let total_fees = FEES.load(deps.storage).unwrap_or_default(); + let total_fees = FEES.load(deps.storage, denom.clone()).unwrap_or_default(); + let bank_limit = config + .denom_limits + .get(&denom) + .expect("denom limit should exist in the map") + .bank; - // TODO: Currently we only accept 1 denom - let denom = config.denoms[0].clone(); let ( sg721_addr, FeesToPay { @@ -414,15 +617,9 @@ mod query { ) = calculate_fees_to_pay(&config, total_fees) .map_err(|x| StdError::generic_err(x.to_string()))?; - let reserve_fees_to_send = verify_contract_balance( - deps, - env, - denom, - total_fees, - reserve_fees, - config.bank_limit, - ) - .map_err(|x| StdError::generic_err(x.to_string()))?; + let reserve_fees_to_send = + verify_contract_balance(deps, env, denom, total_fees, reserve_fees, bank_limit) + .map_err(|x| StdError::generic_err(x.to_string()))?; let mut paid_to_holders = Uint128::zero(); let mut total_shares = Decimal::zero(); @@ -449,7 +646,7 @@ mod query { } } - to_binary(&DryDistributionResponse { + to_json_binary(&DryDistributionResponse { total_fees, team_total_fee: team_fees_to_send, reserve_total_fee: reserve_fees_to_send, @@ -460,10 +657,93 @@ mod query { number_of_holders, }) } + + pub fn get_nft_pool(deps: Deps) -> StdResult { + let nft_pool: Vec = NFT_REWARDS.load(deps.storage)?; + + to_json_binary(&nft_pool) + } } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + match msg { + MigrateMsg::Basic {} => {} + MigrateMsg::FromV07 { + nft_pool_max, + streak_nft_winning_amount, + streak_rewards, + allowed_to_send_nft, + } => { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + if allowed_to_send_nft.is_empty() { + return Err(ContractError::EmptyAllowedToSendNft); + } + + if streak_rewards.len() < 3 { + return Err(ContractError::LowStreakAmount); + } + + if streak_rewards[streak_rewards.len() - 1].streak != streak_nft_winning_amount { + return Err(ContractError::NftWinNotMatchLastStreakReward); + } + + // convert old config to new config + let old_config = ccf_v07::state::CONFIG.load(deps.storage)?; + let old_denom = old_config.denoms[0].clone(); + + let mut denom_limits: HashMap = HashMap::new(); + denom_limits.insert( + old_denom.clone(), + DenomLimit { + min: old_config.min_bet_limit, + max: old_config.max_bet_limit, + bank: old_config.bank_limit, + }, + ); + + CONFIG.save( + deps.storage, + &Config { + admin: old_config.admin, + denoms: HashSet::from_iter(old_config.denoms), + denom_limits, + flips_per_block_limit: old_config.flips_per_block_limit, + wallets: Wallets { + team: old_config.wallets.team, + reserve: old_config.wallets.reserve, + }, + fees: Fees { + team_bps: old_config.fees.team_bps, + holders_bps: old_config.fees.holders_bps, + reserve_bps: old_config.fees.reserve_bps, + flip_bps: old_config.fees.flip_bps, + }, + sg721_addr: old_config.sg721_addr, + is_paused: old_config.is_paused, + // New fields + nft_pool_max, + streak_nft_winning_amount, + }, + )?; + + STREAK_REWARDS.save(deps.storage, &streak_rewards)?; + let allowed_send_nft_addrs = allowed_to_send_nft + .into_iter() + .map(|addr| deps.api.addr_validate(&addr)) + .collect::>>()?; + ALLOWED_SEND_NFT.save(deps.storage, &allowed_send_nft_addrs)?; + NFT_REWARDS.save(deps.storage, &vec![])?; + + // Update fee storage + let old_fees = ccf_v07::state::FEES.load(deps.storage)?; + ccf_v07::state::FEES.remove(deps.storage); + FEES.save(deps.storage, old_denom, &old_fees)?; + } + }; + Ok(Response::default()) } diff --git a/contracts/coin-flip/src/error.rs b/contracts/coin-flip/src/error.rs index 9acd879..345b72f 100644 --- a/contracts/coin-flip/src/error.rs +++ b/contracts/coin-flip/src/error.rs @@ -56,8 +56,8 @@ pub enum ContractError { #[error("NFT contract is not set.")] Sg721NotSet, - #[error("Contract doesn't have enough funds to pay for the bet")] - ContractMissingFunds, + #[error("Contract doesn't have enough funds to pay for the bet: denom = {0}")] + ContractMissingFunds(String), #[error("You cannot bet above our limit = {max_limit}")] OverTheLimitBet { max_limit: String }, @@ -67,4 +67,56 @@ pub enum ContractError { #[error("Operation is paused at this moment! Please try again later.")] Paused, + + #[error("There are no flips to do")] + NoFlipsToDo, + + #[error("There are no flips to do this block")] + NoFlipsToDoThisBlock, + + // Streak errors + #[error("This address is not allowed to send NFTs to the contract")] + UnauthorizedToSendNft, + + #[error("NFTs rewards pool is full")] + MaxNFTStreakRewardsReached, + + #[error("Expecting an 'index' or 'all' parameter")] + EmptyWithdrawParams, + + #[error("Index does not exists in the NFT rewards pool")] + NftIndexOutOfRange, + + #[error("Minimum streak is {0}")] + LowStreak(u32), + + #[error("Streak ({0}) not eligible for reward")] + NotEligibleForStreakReward(u32), + + #[error("At least 1 address must be provided to allowed_send_nft")] + EmptyAllowedToSendNft, + + #[error("Can't transfer NFT, it's in the pool")] + NftInPool, + + #[error("No excess funds to send")] + NoExcessFunds, + + #[error("Streak rewards must be higher then 3")] + LowStreakAmount, + + #[error("NFT winning streak amount must match the last streak reward streak amount")] + NftWinNotMatchLastStreakReward, + + #[error("Bet Limits doesn't exists for this denom: {denom}")] + NoBetLimits { denom: String }, + + #[error("Denom is not in the list of denoms: {denom}")] + DenomNotFound { denom: String }, + + #[error("Denom Is already exists on the contract")] + DenomAlreadyExists, + + #[error("Denom still have fees that are not distributed, denom : {0}")] + DenomStillHaveFees(String), } diff --git a/contracts/coin-flip/src/helpers.rs b/contracts/coin-flip/src/helpers.rs index 93fb164..e5178fe 100644 --- a/contracts/coin-flip/src/helpers.rs +++ b/contracts/coin-flip/src/helpers.rs @@ -32,7 +32,7 @@ pub fn ensure_correct_funds( ) -> Result { let fee = bps_to_decimal(fee_bps); let fee_to_pay = calc_flip_fee(Decimal::from_atomics(amount, 0)?, fee)?; - let total_amount = amount.checked_add(fee_to_pay).unwrap(); + let total_amount = amount.checked_add(fee_to_pay)?; if funds != total_amount { return Err(ContractError::WrongPaidAmount {}); } diff --git a/contracts/coin-flip/src/msg.rs b/contracts/coin-flip/src/msg.rs index 68a12ca..a9f8e18 100644 --- a/contracts/coin-flip/src/msg.rs +++ b/contracts/coin-flip/src/msg.rs @@ -1,23 +1,35 @@ +use std::collections::HashSet; + use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Decimal, Uint128}; +use cosmwasm_std::{Coin, Decimal, Uint128}; -use crate::types::{Config, Fees, Flip, FlipScore, PickTypes, Wallets}; +use crate::types::{ + Config, DenomLimit, Fees, Flip, FlipScore, NftReward, PickTypes, StreakReward, Wallets, +}; #[cw_serde] pub struct InstantiateMsg { - pub admin: String, - pub denoms: Vec, + pub denoms: HashSet, pub wallets: Wallets, pub fees: Fees, - pub bank_limit: Option, - pub min_bet_limit: Option, - pub max_bet_limit: Option, + /// Limit of each denom, (denom, min_bet, max_bet, bank_limit) + pub denom_limits: Vec<(String, Uint128, Uint128, Uint128)>, pub flips_per_block_limit: Option, pub sg721_addr: Option, + + // streak + pub nft_pool_max: u32, + pub streak_nft_winning_amount: u32, + pub streak_rewards: Vec, + pub allowed_to_send_nft: Vec, } #[cw_serde] pub enum ExecuteMsg { + /// Receive NFTs to add to our NFTs pool of streak mini game + ReceiveNft(cw721::Cw721ReceiveMsg), + /// Streak mini game msgs + Streak(StreakExecuteMsg), /// Flip msgs Flip(FlipExecuteMsg), /// Only call-able by admin (mutlisig) @@ -32,7 +44,9 @@ pub enum QueryMsg { GetConfig {}, /// Get fees total #[returns(Uint128)] - GetFeesAmount {}, + GetFeesAmount { denom: String }, + #[returns(Vec)] + GetAllFeesAmount {}, /// Get last 10 flips #[returns(Vec)] GetLast5 {}, @@ -43,29 +57,109 @@ pub enum QueryMsg { /// this is to prevent sending unnecessary txs #[returns(bool)] ShouldDoFlips {}, + /// Do dry ditribution to see results #[returns(DryDistributionResponse)] - DryDistribution {}, + DryDistribution { denom: String }, + /// Get the NFT pool + #[returns(Vec)] + GetNftPool {}, +} + +#[cw_serde] +pub enum StreakExecuteMsg { + /// Claim streak reward if the sender streak matches one of the rewards + /// e.g - streak reward is at 8 streaks, if sender have 8 streaks, he can claim the reward + /// if sender have 9 streaks, he can't claim rewards for 8 streak, + /// + /// Claim only highest reward, so if sender have 10 streak, + /// and streak rewards are at 8 and 10, only 10th will be claimed, + /// and streak will be reset. + /// + /// NFT claims will be automatically claimed on 12th streak (highest reward) + /// but if NFT pool is empty, last reward will be automatically sent + Claim {}, } #[cw_serde] pub enum FlipExecuteMsg { + /// Register the flip StartFlip { pick: PickTypes, amount: Uint128 }, + /// Does the actual flip DoFlips {}, } #[cw_serde] pub enum SudoMsg { - Distribute {}, - UpdateFees { fees: Fees }, - UpdateSg721 { addr: String }, - UpdateBankLimit { limit: Uint128 }, - UpdateBetLimit { min_bet: Uint128, max_bet: Uint128 }, + /// Distribute the collected fees so far + Distribute { + denom: String, + }, + /// Add new denom to allow flipping with + AddNewDenom { + denom: String, + limits: DenomLimit, + }, + /// Remove denoms + RemoveDenoms { + denoms: HashSet, + }, + /// Update fees + UpdateFees { + fees: Fees, + }, + // Update the collection address + UpdateSg721 { + addr: String, + }, + /// Update the bank limit + UpdateBankLimit { + denom: String, + limit: Uint128, + }, + /// Update the bet limit (min and max) + UpdateBetLimit { + denom: String, + min_bet: Uint128, + max_bet: Uint128, + }, + /// Pause the contract in case of emergency UpdatePause(bool), + /// Update streak related config stuff + UpdateStreak { + nft_pool_max: Option, + streak_nft_winning_amount: Option, + streak_rewards: Option>, + allowed_to_send_nft: Option>, + }, + /// Withdraw all or a single NFT from the pool + /// only to the team wallet + WithdrawNftFromPool { + index: Option, + all: Option, + }, + /// Send excess funds to the reserve wallet + /// This function checks the bank limit - fees and send the excess to the reserve wallet + SendExcessFunds { + denom: String, + }, + /// This transfer the NFT to the team wallet + /// This function will check that the NFT is not in the pool + /// In case NFT will be transfered by mistake we will be able to handle it + TransferNft { + contract: String, + token_id: String, + }, } #[cw_serde] pub enum MigrateMsg { Basic {}, + FromV07 { + nft_pool_max: u32, + streak_nft_winning_amount: u32, + streak_rewards: Vec, + allowed_to_send_nft: Vec, + }, } #[cw_serde] diff --git a/contracts/coin-flip/src/state.rs b/contracts/coin-flip/src/state.rs index 0029f96..b3077f5 100644 --- a/contracts/coin-flip/src/state.rs +++ b/contracts/coin-flip/src/state.rs @@ -1,12 +1,13 @@ use cosmwasm_std::{Addr, StdError, Storage, Uint128}; use cw_storage_plus::{Item, Map}; -use crate::types::{Config, Flip, FlipScore, TodoFlip}; +use crate::types::{Config, Flip, FlipScore, NftReward, StreakReward, TodoFlip}; /// Our config holds admin and fees % pub const CONFIG: Item = Item::new("config"); /// Fees that we collected since last distribution. -pub const FEES: Item = Item::new("total_fees"); +/// A map of denom -> amount +pub const FEES: Map = Map::new("total_fees"); /// Score per address, basically how much wins/loses/streaks, etc. pub const SCORES: Map<&Addr, FlipScore> = Map::new("scores"); /// Last Flip id @@ -15,6 +16,13 @@ pub const FLIP_ID: Item = Item::new("flip_id"); pub const FLIPS: Item> = Item::new("last_flips"); pub const TODO_FLIPS: Item> = Item::new("todo_flips"); +/// streak rewards list +pub const STREAK_REWARDS: Item> = Item::new("streak_rewards"); +/// list of addresses that are allowed to send NFTs to the contract +pub const ALLOWED_SEND_NFT: Item> = Item::new("allowed_send_nft"); +/// NFT rewards pool +pub const NFT_REWARDS: Item> = Item::new("nft_rewards"); + /// Get the current flip id pub fn get_flip_id(store: &dyn Storage) -> Result { FLIP_ID.load(store) diff --git a/contracts/coin-flip/src/sudo.rs b/contracts/coin-flip/src/sudo.rs index aeeaff7..dd0c2ac 100644 --- a/contracts/coin-flip/src/sudo.rs +++ b/contracts/coin-flip/src/sudo.rs @@ -1,19 +1,78 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; -use cosmwasm_std::{coins, Addr, BankMsg, Decimal, Deps, DepsMut, Env, Uint128}; -use sg_std::Response; +use cosmwasm_std::{ + coins, to_json_binary, Addr, BankMsg, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, + MessageInfo, Uint128, WasmMsg, +}; +use sg_std::{Response, StargazeMsgWrapper}; use crate::error::ContractError; -use crate::state::{CONFIG, FEES}; -use crate::types::{Config, Fees, FeesToPay}; +use crate::helpers::ensure_admin; +use crate::msg::SudoMsg; +use crate::state::{ALLOWED_SEND_NFT, CONFIG, FEES, NFT_REWARDS, STREAK_REWARDS}; +use crate::types::{Config, DenomLimit, Fees, FeesToPay, StreakReward}; + +pub fn handle_sudo_msg( + deps: DepsMut, + info: MessageInfo, + env: Env, + config: Config, + msg: SudoMsg, +) -> Result { + ensure_admin(&config, &info)?; + + match msg { + SudoMsg::Distribute { denom } => distribute(deps, env, denom, &config), + SudoMsg::AddNewDenom { denom, limits } => add_new_denom(deps, config, denom, limits), + SudoMsg::RemoveDenoms { denoms } => remove_denoms(deps, config, denoms), + SudoMsg::UpdateFees { fees } => update_fees(deps, config, fees), + SudoMsg::UpdateBankLimit { denom, limit } => update_bank_limit(deps, config, denom, limit), + SudoMsg::UpdateSg721 { addr } => update_sg721(deps, config, addr), + SudoMsg::UpdatePause(is_paused) => update_pause(deps, config, is_paused), + SudoMsg::UpdateStreak { + nft_pool_max, + streak_nft_winning_amount, + streak_rewards, + allowed_to_send_nft, + } => update_streak_config( + deps, + config, + nft_pool_max, + streak_nft_winning_amount, + streak_rewards, + allowed_to_send_nft, + ), + SudoMsg::UpdateBetLimit { + denom, + min_bet, + max_bet, + } => update_bet_limit(deps, config, denom, min_bet, max_bet), + SudoMsg::WithdrawNftFromPool { index, all } => { + withdraw_nft_from_pool(deps, &config, index, all) + } + SudoMsg::SendExcessFunds { denom } => send_excess_funds(deps, env, &config, denom), + SudoMsg::TransferNft { contract, token_id } => { + transfer_nft(deps, &config, contract, token_id) + } + } +} /// Update the bank limit in the config pub fn update_bank_limit( deps: DepsMut, mut config: Config, + denom: String, limit: Uint128, ) -> Result { - config.bank_limit = limit; + let mut bank_limit = config + .denom_limits + .get(&denom) + .expect("denom limit should exist in the map") + .clone(); + + bank_limit.bank = limit; + config.denom_limits.insert(denom, bank_limit); + CONFIG.save(deps.storage, &config)?; Ok(Response::default().add_attribute("method", "update_bank_limit")) @@ -44,11 +103,20 @@ pub fn update_sg721( pub fn update_bet_limit( deps: DepsMut, mut config: Config, - min_bet: Uint128, - max_bet: Uint128, + denom: String, + min: Uint128, + max: Uint128, ) -> Result { - config.min_bet_limit = min_bet; - config.max_bet_limit = max_bet; + let mut denom_limit = config + .denom_limits + .get(&denom) + .expect("denom limit should exist in the map") + .clone(); + + denom_limit.min = min; + denom_limit.max = max; + config.denom_limits.insert(denom, denom_limit); + CONFIG.save(deps.storage, &config)?; Ok(Response::default().add_attribute("method", "update_bet_limit")) @@ -65,11 +133,96 @@ pub fn update_pause( Ok(Response::default().add_attribute("method", "update_pause")) } -pub fn distribute(deps: DepsMut, env: Env, config: &Config) -> Result { - let total_fees = FEES.load(deps.storage).unwrap_or_default(); +pub fn update_streak_config( + deps: DepsMut, + mut config: Config, + nft_pool_max: Option, + streak_nft_winning_amount: Option, + streak_rewards: Option>, + allowed_to_send_nft: Option>, +) -> Result { + if let Some(nft_pool_max) = nft_pool_max { + config.nft_pool_max = nft_pool_max; + } + + if let Some(streak_nft_winning_amount) = streak_nft_winning_amount { + config.streak_nft_winning_amount = streak_nft_winning_amount; + } + + CONFIG.save(deps.storage, &config)?; + + if let Some(streak_rewards) = streak_rewards { + STREAK_REWARDS.save(deps.storage, &streak_rewards)?; + } + + if let Some(allowed_to_send_nft) = allowed_to_send_nft { + let addrs = allowed_to_send_nft + .iter() + .map(|addr| deps.api.addr_validate(addr)) + .collect::, _>>()?; + ALLOWED_SEND_NFT.save(deps.storage, &addrs)?; + } + + Ok(Response::default().add_attribute("method", "update_streak_config")) +} + +pub fn add_new_denom( + deps: DepsMut, + mut config: Config, + denom: String, + limits: DenomLimit, +) -> Result { + FEES.update(deps.storage, denom.clone(), |denom| match denom { + Some(_) => Err(ContractError::DenomAlreadyExists), + None => Ok(Uint128::zero()), + })?; + + config.denom_limits.insert(denom.clone(), limits); + config.denoms.insert(denom); + + CONFIG.save(deps.storage, &config)?; + + Ok(Response::default().add_attribute("method", "add_new_denom")) +} + +pub fn remove_denoms( + deps: DepsMut, + mut config: Config, + denoms: HashSet, +) -> Result { + for denom in denoms { + if !config.denoms.contains(&denom) { + return Err(ContractError::DenomNotFound { denom }); + } + + let fees = FEES.load(deps.storage, denom.clone())?; + if !fees.is_zero() { + return Err(ContractError::DenomStillHaveFees(denom)); + } + + config.denom_limits.remove(&denom); + config.denoms.remove(&denom); + } + + CONFIG.save(deps.storage, &config)?; + + Ok(Response::default().add_attribute("method", "remove_denoms")) +} - // TODO: Currently we only accept 1 denom - let denom = config.denoms[0].clone(); +pub fn distribute( + deps: DepsMut, + env: Env, + denom: String, + config: &Config, +) -> Result { + let total_fees = FEES.load(deps.storage, denom.clone()).unwrap_or_default(); + let bank_limit = config + .denom_limits + .get(&denom) + .expect("denom limit should exist in the map") + .bank; + + // We do distribution per a single denom let ( sg721_addr, FeesToPay { @@ -85,7 +238,7 @@ pub fn distribute(deps: DepsMut, env: Env, config: &Config) -> Result Result Result Result, + all: Option, +) -> Result { + let nft_attr: String; + let mut msgs: Vec> = vec![]; + + if all.is_some() { + // loop over all NFTs in pool, and send them to team wallet. + let nft_rewards = NFT_REWARDS.load(deps.storage)?; + let nft_len = nft_rewards.len(); + + nft_rewards.into_iter().for_each(|nft| { + msgs.push( + WasmMsg::Execute { + contract_addr: nft.contract_addr.to_string(), + msg: to_json_binary(&cw721::Cw721ExecuteMsg::TransferNft { + recipient: config.wallets.team.clone(), + token_id: nft.token_id, + }) + .unwrap(), + funds: vec![], + } + .into(), + ); + }); + + NFT_REWARDS.save(deps.storage, &vec![])?; + + nft_attr = format!("All NFTs withdrawn from pool ({nft_len})"); + } else if let Some(index) = index { + let mut nft_rewards = NFT_REWARDS.load(deps.storage)?; + + if index >= nft_rewards.len() as u32 { + return Err(ContractError::NftIndexOutOfRange); + } + // Get NFT by index from our pool and send it to the team wallet. + let nft_to_send = nft_rewards[index as usize].clone(); + + msgs.push( + WasmMsg::Execute { + contract_addr: nft_to_send.contract_addr.to_string(), + msg: to_json_binary(&cw721::Cw721ExecuteMsg::TransferNft { + recipient: config.wallets.team.clone(), + token_id: nft_to_send.token_id.clone(), + })?, + funds: vec![], + } + .into(), + ); + + nft_rewards.remove(index as usize); + NFT_REWARDS.save(deps.storage, &nft_rewards)?; + + nft_attr = format!( + "withdraw: {} / {}", + nft_to_send.contract_addr, nft_to_send.token_id + ); + } else { + // Return error because none of the options are provided. + return Err(ContractError::EmptyWithdrawParams); + } + + Ok(Response::new() + .add_messages(msgs) + .add_attribute("action", "withdraw_nft_from_pool") + .add_attribute("withdraw", nft_attr)) +} + +pub fn send_excess_funds( + deps: DepsMut, + env: Env, + config: &Config, + denom: String, +) -> Result { + let Coin { + amount: bank_amount, + denom, + } = deps.querier.query_balance(env.contract.address, denom)?; + + let fees_amount = FEES.load(deps.storage, denom.clone())?; + let bank_balance = bank_amount - fees_amount; + let bank_limit = config + .denom_limits + .get(&denom) + .expect("denom limit should exist in the map") + .bank; + + if bank_balance > bank_limit { + let to_send = bank_balance - bank_limit; + + let msg = BankMsg::Send { + to_address: config.wallets.reserve.clone(), + amount: coins(to_send.into(), denom.clone()), + }; + Ok(Response::default() + .add_message(msg) + .add_attribute("action", "send_excess_funds") + .add_attribute("amount", format!("{to_send}{denom}"))) + } else { + Err(ContractError::NoExcessFunds) + } +} + +pub fn transfer_nft( + deps: DepsMut, + config: &Config, + contract: String, + token_id: String, +) -> Result { + let nft_pool = NFT_REWARDS.load(deps.storage)?; + + if nft_pool + .iter() + .any(|nft| nft.contract_addr == contract && nft.token_id == token_id) + { + return Err(ContractError::NftInPool); + } + + let msg = WasmMsg::Execute { + contract_addr: contract, + msg: to_json_binary(&cw721::Cw721ExecuteMsg::TransferNft { + recipient: config.wallets.team.clone(), + token_id, + })?, + funds: vec![], + }; + Ok(Response::default().add_message(msg)) +} + pub fn calculate_fees_to_pay( config: &Config, total_fees: Uint128, diff --git a/contracts/coin-flip/src/testing/mod.rs b/contracts/coin-flip/src/testing/mod.rs index f38c9ca..48c5288 100644 --- a/contracts/coin-flip/src/testing/mod.rs +++ b/contracts/coin-flip/src/testing/mod.rs @@ -2,4 +2,9 @@ mod utils; mod test_contract; mod test_distribute; +mod test_migration; +mod test_multiple_denoms; mod test_queries; +mod test_streak; +mod test_sudo; +mod test_types; diff --git a/contracts/coin-flip/src/testing/test_contract.rs b/contracts/coin-flip/src/testing/test_contract.rs index f8d58f3..98a50f6 100644 --- a/contracts/coin-flip/src/testing/test_contract.rs +++ b/contracts/coin-flip/src/testing/test_contract.rs @@ -2,10 +2,13 @@ use cosmwasm_std::{coin, coins, Addr, Timestamp, Uint128}; use cw_multi_test::Executor; use crate::{ - contract::{MAX_BET, MIN_BET}, error::ContractError, msg::{ExecuteMsg, FlipExecuteMsg, SudoMsg}, - testing::utils::{executes::sudo_update_pause, helpers::MIN_FUNDS}, + testing::utils::{ + executes::sudo_update_pause, + helpers::MIN_FUNDS, + setup::{MAX_BET, MIN_BET}, + }, types::{Fees, Flip, FlipScore, PickTypes, Streak}, }; @@ -29,6 +32,7 @@ fn test_do_single_flip() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -50,7 +54,7 @@ fn test_do_single_flip() { ); // lets match the score and make sure its correct. - let score = query_score(&app, contract_addr, FLIPPER_ADDR.to_string()).unwrap(); + let score = query_score(&app, contract_addr, FLIPPER_ADDR).unwrap(); // because we set the block in setup, we know our flip is a lose on current block. assert_eq!( @@ -75,6 +79,7 @@ fn test_multiple_flips() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -85,6 +90,7 @@ fn test_multiple_flips() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR2), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -100,6 +106,7 @@ fn test_multiple_flips() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -117,6 +124,7 @@ fn test_2_flips_in_a_row() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -127,6 +135,7 @@ fn test_2_flips_in_a_row() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap_err(); @@ -139,11 +148,12 @@ fn test_update_bank_limit() { let (mut app, contract_addr) = setup_base_contract(); let new_limit = Uint128::new(150000000); - sudo_update_bank_limit(&mut app, contract_addr.clone(), new_limit).unwrap(); + sudo_update_bank_limit(&mut app, contract_addr.clone(), NATIVE_DENOM, new_limit).unwrap(); let config = query_config(&app, contract_addr).unwrap(); + let bank_limit = config.denom_limits.get(NATIVE_DENOM).unwrap().bank; - assert_eq!(config.bank_limit, new_limit); + assert_eq!(bank_limit, new_limit); } #[test] @@ -175,6 +185,7 @@ fn test_flips_per_block_limit() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap_err(); @@ -192,6 +203,7 @@ fn test_wrong_funds() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, MIN_FUNDS.checked_add(Uint128::one()).unwrap(), ) .unwrap_err(); @@ -262,6 +274,7 @@ fn test_max_min_amounts() { PickTypes::Heads, Uint128::new(100000), Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, Uint128::new(103500), ) .unwrap_err(); @@ -282,6 +295,7 @@ fn test_max_min_amounts() { PickTypes::Heads, MAX_BET.checked_add(Uint128::one()).unwrap(), Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap_err(); @@ -310,16 +324,18 @@ fn test_no_flip_same_block() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); - unwrap_execute(app.execute_contract( + let err = unwrap_execute(app.execute_contract( Addr::unchecked(FLIPPER_ADDR), contract_addr.clone(), &ExecuteMsg::Flip(FlipExecuteMsg::DoFlips {}), &[], )) - .unwrap(); + .unwrap_err(); + assert_eq!(err, ContractError::NoFlipsToDoThisBlock); let flips = query_last_flips(&app, contract_addr.clone()).unwrap(); assert_eq!(flips.len(), 0); @@ -351,10 +367,14 @@ fn test_missing_funds() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap_err(); - assert_eq!(err, ContractError::ContractMissingFunds); + assert_eq!( + err, + ContractError::ContractMissingFunds(NATIVE_DENOM.to_string()) + ); } #[test] @@ -372,7 +392,10 @@ fn test_missing_funds_on_do_funds() { .unwrap(); let err = execute_do_flips(&mut app, contract_addr).unwrap_err(); - assert_eq!(err, ContractError::ContractMissingFunds); + assert_eq!( + err, + ContractError::ContractMissingFunds(NATIVE_DENOM.to_string()) + ); } #[test] @@ -387,6 +410,7 @@ fn test_contract_is_paused() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR2), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap_err(); @@ -403,6 +427,7 @@ fn test_contract_is_paused() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR2), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -419,6 +444,7 @@ fn test_update_bet_limit() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR2), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -427,6 +453,7 @@ fn test_update_bet_limit() { sudo_update_bet_limit( &mut app, contract_addr.clone(), + NATIVE_DENOM, MIN_BET + Uint128::new(1000000), MAX_BET + Uint128::new(1000000), ) @@ -438,6 +465,7 @@ fn test_update_bet_limit() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR2), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap_err(); diff --git a/contracts/coin-flip/src/testing/test_distribute.rs b/contracts/coin-flip/src/testing/test_distribute.rs index 6c59227..5cefbee 100644 --- a/contracts/coin-flip/src/testing/test_distribute.rs +++ b/contracts/coin-flip/src/testing/test_distribute.rs @@ -28,7 +28,7 @@ fn test_distribute() { execute_do_flips(&mut app, contract_addr.clone()).unwrap(); - let total_fee_amount_to_pay = query_fees(&app, contract_addr.clone()).unwrap(); + let total_fee_amount_to_pay = query_fees(&app, contract_addr.clone(), NATIVE_DENOM).unwrap(); // We did 10 flips, so fee should be 17500000 assert_eq!( total_fee_amount_to_pay, @@ -36,7 +36,7 @@ fn test_distribute() { ); //do dry distribute query - let dry_dist = query_dry_distribution(&app, contract_addr.clone()).unwrap(); + let dry_dist = query_dry_distribution(&app, contract_addr.clone(), NATIVE_DENOM).unwrap(); assert_eq!( dry_dist, DryDistributionResponse { @@ -52,10 +52,11 @@ fn test_distribute() { ); // With current set up (10 flips), here is how much should be distributed. - let res = sudo_distribute(&mut app, contract_addr.clone()).unwrap(); + query_dry_distribution(&app, contract_addr.clone(), NATIVE_DENOM).unwrap(); + let res = sudo_distribute(&mut app, contract_addr.clone(), NATIVE_DENOM).unwrap(); let res_data = get_dist_result(res); - let total_fee_amount_left = query_fees(&app, contract_addr.clone()).unwrap(); + let total_fee_amount_left = query_fees(&app, contract_addr.clone(), NATIVE_DENOM).unwrap(); // We distributed all fees except rounding assert_eq!( total_fee_amount_left, @@ -118,12 +119,16 @@ fn test_distribute_without_collection() { // Update fees to have uneven number (for rounding tests) update_storage(&mut app, contract_addr.as_bytes(), &mut |storage| { - let fees = FEES.load(storage).unwrap(); - FEES.save(storage, &fees.checked_add(Uint128::new(1)).unwrap()) - .unwrap(); + let fees = FEES.load(storage, NATIVE_DENOM.to_string()).unwrap(); + FEES.save( + storage, + NATIVE_DENOM.to_string(), + &fees.checked_add(Uint128::new(1)).unwrap(), + ) + .unwrap(); }); - let total_fee_amount_to_pay = query_fees(&app, contract_addr.clone()).unwrap(); + let total_fee_amount_to_pay = query_fees(&app, contract_addr.clone(), NATIVE_DENOM).unwrap(); // We did 10 flips of minimum bet, so fee should be 17500001 assert_eq!( total_fee_amount_to_pay, @@ -134,11 +139,12 @@ fn test_distribute_without_collection() { .unwrap() ); - let res = sudo_distribute(&mut app, contract_addr.clone()).unwrap(); + query_dry_distribution(&app, contract_addr.clone(), NATIVE_DENOM).unwrap(); + let res = sudo_distribute(&mut app, contract_addr.clone(), NATIVE_DENOM).unwrap(); let res_data = get_dist_result(res); // There should be 1 left because of rounding - let total_fee_amount = query_fees(&app, contract_addr.clone()).unwrap(); + let total_fee_amount = query_fees(&app, contract_addr.clone(), NATIVE_DENOM).unwrap(); assert_eq!( total_fee_amount, total_fee_amount_to_pay @@ -167,7 +173,8 @@ fn test_failing_distribute() { let (mut app, contract_addr) = setup_contract(); // try distribute without any flips done (no fees) - let err = sudo_distribute(&mut app, contract_addr.clone()).unwrap_err(); + query_dry_distribution(&app, contract_addr.clone(), NATIVE_DENOM).unwrap_err(); + let err = sudo_distribute(&mut app, contract_addr.clone(), NATIVE_DENOM).unwrap_err(); assert_eq!(err, ContractError::NoFeesToPay); add_10_todo_flips(&mut app, contract_addr.clone()); @@ -182,7 +189,8 @@ fn test_failing_distribute() { ) .unwrap(); - let res = sudo_distribute(&mut app, contract_addr.clone()).unwrap(); + query_dry_distribution(&app, contract_addr.clone(), NATIVE_DENOM).unwrap(); + let res = sudo_distribute(&mut app, contract_addr.clone(), NATIVE_DENOM).unwrap(); let res_data = get_dist_result(res); // Make sure team balance is correct @@ -208,6 +216,7 @@ fn test_failing_distribute() { ) .unwrap(); - let err = sudo_distribute(&mut app, contract_addr).unwrap_err(); + query_dry_distribution(&app, contract_addr.clone(), NATIVE_DENOM).unwrap_err(); + let err = sudo_distribute(&mut app, contract_addr, NATIVE_DENOM).unwrap_err(); assert_eq!(err, ContractError::NotEnoughFundsToPayFees); } diff --git a/contracts/coin-flip/src/testing/test_migration.rs b/contracts/coin-flip/src/testing/test_migration.rs new file mode 100644 index 0000000..5792e7c --- /dev/null +++ b/contracts/coin-flip/src/testing/test_migration.rs @@ -0,0 +1,211 @@ +use std::collections::{HashMap, HashSet}; + +use coin_flip_v07 as ccf07; +use cosmwasm_std::{ + testing::{mock_dependencies, mock_env, mock_info}, + Addr, Uint128, +}; +use sg_std::NATIVE_DENOM; + +use crate::{ + contract::migrate, + error::ContractError, + state::{ALLOWED_SEND_NFT, CONFIG, FEES, STREAK_REWARDS}, + testing::utils::setup::{MAX_BET, MIN_BANK_AMOUNT, MIN_BET, USDC_DENOM}, + types::{Config, DenomLimit, Fees, StreakReward, Wallets}, +}; + +use super::utils::setup::{CREATOR_ADDR, RESERVE_ADDR, TEAM_ADDR}; + +#[test] +fn test_07_to_08() { + let mut deps = mock_dependencies(); + let info = mock_info(CREATOR_ADDR, &[]); + + ccf07::contract::instantiate( + deps.as_mut(), + mock_env(), + info, + ccf07::msg::InstantiateMsg { + admin: CREATOR_ADDR.into(), + denoms: vec![NATIVE_DENOM.into()], + wallets: ccf07::types::Wallets { + team: TEAM_ADDR.into(), + reserve: RESERVE_ADDR.into(), + }, + fees: ccf07::types::Fees { + team_bps: 1500, + holders_bps: 7000, + reserve_bps: 1500, + flip_bps: 350, + }, + bank_limit: None, + min_bet_limit: None, + max_bet_limit: None, + flips_per_block_limit: None, + sg721_addr: None, + }, + ) + .unwrap(); + + let old_config = ccf07::state::CONFIG.load(deps.as_ref().storage).unwrap(); + + migrate( + deps.as_mut(), + mock_env(), + crate::msg::MigrateMsg::FromV07 { + nft_pool_max: 4, + streak_nft_winning_amount: 5, + streak_rewards: vec![ + StreakReward::new(2, Uint128::new(200000)), + StreakReward::new(4, Uint128::new(400000)), + StreakReward::new(5, Uint128::new(500000)), + ], + allowed_to_send_nft: vec![TEAM_ADDR.into(), CREATOR_ADDR.into()], + }, + ) + .unwrap(); + + let new_config = CONFIG.load(deps.as_ref().storage).unwrap(); + let mut new_denom_limits: HashMap = HashMap::new(); + + new_denom_limits.insert( + NATIVE_DENOM.to_string(), + DenomLimit { + min: MIN_BET, + max: MAX_BET, + bank: MIN_BANK_AMOUNT, + }, + ); + + assert_eq!( + new_config, + Config { + admin: old_config.admin, + denoms: HashSet::from_iter(old_config.denoms), + denom_limits: new_denom_limits, + flips_per_block_limit: old_config.flips_per_block_limit, + wallets: Wallets { + team: old_config.wallets.team, + reserve: old_config.wallets.reserve + }, + fees: Fees { + team_bps: old_config.fees.team_bps, + holders_bps: old_config.fees.holders_bps, + reserve_bps: old_config.fees.reserve_bps, + flip_bps: old_config.fees.flip_bps + }, + sg721_addr: old_config.sg721_addr, + is_paused: old_config.is_paused, + nft_pool_max: 4, + streak_nft_winning_amount: 5, + } + ); + + let streak_rewards = STREAK_REWARDS.load(deps.as_ref().storage).unwrap(); + assert_eq!( + streak_rewards, + vec![ + StreakReward::new(2, Uint128::new(200000)), + StreakReward::new(4, Uint128::new(400000)), + StreakReward::new(5, Uint128::new(500000)), + ] + ); + + let allowed_to_send_nft = ALLOWED_SEND_NFT.load(deps.as_ref().storage).unwrap(); + assert_eq!( + allowed_to_send_nft, + vec![Addr::unchecked(TEAM_ADDR), Addr::unchecked(CREATOR_ADDR)] + ); + + let fees = FEES + .load(deps.as_ref().storage, NATIVE_DENOM.to_string()) + .unwrap(); + assert!(fees.is_zero()); + + // Should error because usdc fees doesn't exists + FEES.load(deps.as_ref().storage, USDC_DENOM.to_string()) + .unwrap_err(); +} + +#[test] +fn test_07_to_08_failing() { + let mut deps = mock_dependencies(); + let info = mock_info(CREATOR_ADDR, &[]); + + ccf07::contract::instantiate( + deps.as_mut(), + mock_env(), + info, + ccf07::msg::InstantiateMsg { + admin: CREATOR_ADDR.into(), + denoms: vec![NATIVE_DENOM.into()], + wallets: ccf07::types::Wallets { + team: TEAM_ADDR.into(), + reserve: RESERVE_ADDR.into(), + }, + fees: ccf07::types::Fees { + team_bps: 1500, + holders_bps: 7000, + reserve_bps: 1500, + flip_bps: 350, + }, + bank_limit: None, + min_bet_limit: None, + max_bet_limit: None, + flips_per_block_limit: None, + sg721_addr: None, + }, + ) + .unwrap(); + + let err = migrate( + deps.as_mut(), + mock_env(), + crate::msg::MigrateMsg::FromV07 { + nft_pool_max: 4, + streak_nft_winning_amount: 5, + streak_rewards: vec![ + StreakReward::new(2, Uint128::new(200000)), + StreakReward::new(4, Uint128::new(400000)), + StreakReward::new(5, Uint128::new(500000)), + ], + allowed_to_send_nft: vec![], + }, + ) + .unwrap_err(); + assert_eq!(err, ContractError::EmptyAllowedToSendNft); + + let err = migrate( + deps.as_mut(), + mock_env(), + crate::msg::MigrateMsg::FromV07 { + nft_pool_max: 4, + streak_nft_winning_amount: 5, + streak_rewards: vec![ + StreakReward::new(2, Uint128::new(200000)), + StreakReward::new(4, Uint128::new(400000)), + ], + allowed_to_send_nft: vec![TEAM_ADDR.into(), CREATOR_ADDR.into()], + }, + ) + .unwrap_err(); + assert_eq!(err, ContractError::LowStreakAmount); + + let err = migrate( + deps.as_mut(), + mock_env(), + crate::msg::MigrateMsg::FromV07 { + nft_pool_max: 4, + streak_nft_winning_amount: 6, + streak_rewards: vec![ + StreakReward::new(2, Uint128::new(200000)), + StreakReward::new(4, Uint128::new(400000)), + StreakReward::new(5, Uint128::new(500000)), + ], + allowed_to_send_nft: vec![TEAM_ADDR.into(), CREATOR_ADDR.into()], + }, + ) + .unwrap_err(); + assert_eq!(err, ContractError::NftWinNotMatchLastStreakReward); +} diff --git a/contracts/coin-flip/src/testing/test_multiple_denoms.rs b/contracts/coin-flip/src/testing/test_multiple_denoms.rs new file mode 100644 index 0000000..d426546 --- /dev/null +++ b/contracts/coin-flip/src/testing/test_multiple_denoms.rs @@ -0,0 +1,191 @@ +use std::collections::HashSet; + +use cosmwasm_std::Addr; + +use crate::{ + error::ContractError, + testing::utils::{ + executes::{sudo_add_new_denom, sudo_distribute, sudo_remove_denoms}, + setup::{MIN_BANK_AMOUNT, NATIVE_DENOM}, + }, + types::{DenomLimit, PickTypes}, +}; + +use super::utils::{ + executes::{execute_do_flips, execute_start_flip}, + helpers::MIN_FUNDS, + queries::{query_all_fees, query_config, query_fees}, + setup::{setup_base_contract, setup_with_multiple_denoms, FLIPPER_ADDR, MIN_BET, USDC_DENOM}, +}; + +#[test] +fn test_update_denoms() { + let (mut app, contract_addr) = setup_base_contract(); + + let mut to_remove = HashSet::new(); + to_remove.insert(USDC_DENOM.to_string()); + + sudo_add_new_denom( + &mut app, + contract_addr.clone(), + USDC_DENOM, + DenomLimit { + min: MIN_BET, + max: MIN_BET, + bank: MIN_BANK_AMOUNT, + }, + ) + .unwrap(); + + let config = query_config(&app, contract_addr.clone()).unwrap(); + assert_eq!(config.denoms.len(), 2); + assert!(config.denoms.contains(NATIVE_DENOM)); + assert!(config.denoms.contains(USDC_DENOM)); + + // remove usdc + sudo_remove_denoms(&mut app, contract_addr.clone(), to_remove).unwrap(); + + let config = query_config(&app, contract_addr).unwrap(); + assert_eq!(config.denoms.len(), 1); + assert!(config.denoms.contains(NATIVE_DENOM)); +} + +#[test] +fn test_happy_path() { + let (mut app, contract_addr) = setup_with_multiple_denoms(); + + // Make sure fees are zero before we start + let native_fees = query_fees(&app, contract_addr.clone(), NATIVE_DENOM).unwrap(); + let usdc_fees = query_fees(&app, contract_addr.clone(), USDC_DENOM).unwrap(); + + assert!(native_fees.is_zero()); + assert!(usdc_fees.is_zero()); + + // Do some flips with each token + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + USDC_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + // Make sure the fees are not zero now + let native_fees = query_fees(&app, contract_addr.clone(), NATIVE_DENOM).unwrap(); + let usdc_fees = query_fees(&app, contract_addr.clone(), USDC_DENOM).unwrap(); + let all_fees = query_all_fees(&app, contract_addr.clone()).unwrap(); + + assert!(!native_fees.is_zero()); + assert!(!usdc_fees.is_zero()); + assert!(all_fees.len() == 2); + + sudo_distribute(&mut app, contract_addr.clone(), NATIVE_DENOM).unwrap(); + + // We only distributed native, usdc should not be empty + let native_fees = query_fees(&app, contract_addr.clone(), NATIVE_DENOM).unwrap(); + let usdc_fees = query_fees(&app, contract_addr.clone(), USDC_DENOM).unwrap(); + + assert!(native_fees.is_zero()); + assert!(!usdc_fees.is_zero()); + + sudo_distribute(&mut app, contract_addr.clone(), USDC_DENOM).unwrap(); + + // We distributed all fees, so they both should be zero now. + let native_fees = query_fees(&app, contract_addr.clone(), NATIVE_DENOM).unwrap(); + let usdc_fees = query_fees(&app, contract_addr, USDC_DENOM).unwrap(); + + assert!(native_fees.is_zero()); + assert!(usdc_fees.is_zero()); +} + +#[test] +fn test_non_existing_denom() { + let (mut app, contract_addr) = setup_with_multiple_denoms(); + + execute_start_flip( + &mut app, + contract_addr, + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + "random", + MIN_FUNDS, + ) + .unwrap_err(); +} + +#[test] +fn test_add_existing_denom() { + let (mut app, contract_addr) = setup_with_multiple_denoms(); + + let err = sudo_add_new_denom( + &mut app, + contract_addr, + USDC_DENOM, + DenomLimit { + min: MIN_BET, + max: MIN_BET, + bank: MIN_BANK_AMOUNT, + }, + ) + .unwrap_err(); + + assert_eq!(err, ContractError::DenomAlreadyExists) +} + +#[test] +fn test_remove_non_existing_denom() { + let (mut app, contract_addr) = setup_with_multiple_denoms(); + + let mut to_remove = HashSet::new(); + to_remove.insert("random".to_string()); + + let err = sudo_remove_denoms(&mut app, contract_addr, to_remove).unwrap_err(); + assert_eq!( + err, + ContractError::DenomNotFound { + denom: "random".to_string() + } + ) +} + +#[test] +fn test_remove_denom_with_fees() { + let (mut app, contract_addr) = setup_with_multiple_denoms(); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + + let mut to_remove = HashSet::new(); + to_remove.insert(NATIVE_DENOM.to_string()); + + let err = sudo_remove_denoms(&mut app, contract_addr, to_remove).unwrap_err(); + assert_eq!( + err, + ContractError::DenomStillHaveFees(NATIVE_DENOM.to_string()) + ) +} diff --git a/contracts/coin-flip/src/testing/test_queries.rs b/contracts/coin-flip/src/testing/test_queries.rs index a965945..bd57880 100644 --- a/contracts/coin-flip/src/testing/test_queries.rs +++ b/contracts/coin-flip/src/testing/test_queries.rs @@ -1,11 +1,14 @@ use cosmwasm_std::Addr; -use crate::{contract::MIN_BET, types::PickTypes}; +use crate::{ + testing::utils::setup::{next_block, MIN_BET, NATIVE_DENOM}, + types::PickTypes, +}; use super::utils::{ executes::{execute_do_flips, execute_start_flip}, helpers::{add_balance, add_balances, FLIPPER_PREFIX, MIN_FEES, MIN_FUNDS}, - queries::{query_fees, query_last_flips}, + queries::{query_fees, query_last_flips, query_should_flip}, setup::{setup_base_contract, FLIPPER_ADDR, FLIPPER_ADDR2}, }; @@ -19,12 +22,13 @@ fn test_get_fees() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); execute_do_flips(&mut app, contract_addr.clone()).unwrap(); - let fees = query_fees(&app, contract_addr).unwrap(); + let fees = query_fees(&app, contract_addr, NATIVE_DENOM).unwrap(); assert_eq!(fees, MIN_FEES) } @@ -45,6 +49,7 @@ fn test_get_last_flips() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -60,6 +65,7 @@ fn test_get_last_flips() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -69,6 +75,7 @@ fn test_get_last_flips() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR2), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -80,6 +87,7 @@ fn test_get_last_flips() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -89,6 +97,7 @@ fn test_get_last_flips() { PickTypes::Heads, MIN_BET, Addr::unchecked(FLIPPER_ADDR2), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -104,6 +113,7 @@ fn test_get_last_flips() { PickTypes::Tails, MIN_BET, Addr::unchecked(FLIPPER_ADDR2), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -119,6 +129,7 @@ fn test_get_last_flips() { PickTypes::Tails, MIN_BET, Addr::unchecked(FLIPPER_ADDR2), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -144,6 +155,7 @@ fn test_randomness() { PickTypes::Heads, MIN_BET, Addr::unchecked(format!("{FLIPPER_PREFIX}{i}")), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -169,6 +181,7 @@ fn test_get_flip_todo() { PickTypes::Tails, MIN_BET, Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -180,6 +193,7 @@ fn test_get_flip_todo() { PickTypes::Tails, MIN_BET, Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -190,6 +204,7 @@ fn test_get_flip_todo() { PickTypes::Tails, MIN_BET, Addr::unchecked(FLIPPER_ADDR2), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); @@ -197,3 +212,23 @@ fn test_get_flip_todo() { let flips = query_last_flips(&app, contract_addr).unwrap(); assert_eq!(flips.len(), 1); } + +#[test] +fn test_should_flip() { + let (mut app, contract_addr) = setup_base_contract(); + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Tails, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + + app.update_block(next_block); + + let res = query_should_flip(&app, contract_addr).unwrap(); + assert!(res) +} diff --git a/contracts/coin-flip/src/testing/test_streak.rs b/contracts/coin-flip/src/testing/test_streak.rs new file mode 100644 index 0000000..d6f1120 --- /dev/null +++ b/contracts/coin-flip/src/testing/test_streak.rs @@ -0,0 +1,605 @@ +use cosmwasm_std::{coin, Addr, Uint128}; +use cw_multi_test::Executor; + +use crate::{ + error::ContractError, + testing::utils::{ + executes::{execute_send_nft_to_pool, execute_streak_claim, unwrap_execute}, + helpers::add_balance, + queries::query_nft_owner, + setup::{setup_with_nft_pool, CREATOR_ADDR, MIN_BET, NATIVE_DENOM, TEST_STREAK_REWARDS}, + }, + types::PickTypes, +}; + +use super::utils::{ + executes::{execute_do_flips, execute_start_flip}, + helpers::MIN_FUNDS, + queries::{query_nft_pool, query_score}, + setup::{setup_base_contract, FLIPPER_ADDR}, +}; + +#[test] +fn test_receive_nft() { + let (mut app, contract_addr, nft_addr1, nft_addr2) = setup_with_nft_pool(); + + let pool = query_nft_pool(&app, contract_addr.clone()).unwrap(); + + assert_eq!(pool.len(), 4); + assert_eq!(pool[0].contract_addr, nft_addr1); + assert_eq!(pool[0].token_id, 1.to_string()); + assert_eq!(pool[1].contract_addr, nft_addr2); + assert_eq!(pool[1].token_id, 2.to_string()); + + // Try to send another NFT (can't cause reached the limit) + let err = execute_send_nft_to_pool( + &mut app, + CREATOR_ADDR, + contract_addr.clone(), + nft_addr2, + 4.to_string(), + ) + .unwrap_err(); + assert_eq!(err, ContractError::MaxNFTStreakRewardsReached); + + // try send not from team or owner + unwrap_execute(app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + nft_addr1.clone(), + &cw721::Cw721ExecuteMsg::TransferNft { + recipient: FLIPPER_ADDR.to_string(), + token_id: "4".to_string(), + }, + &[], + )) + .unwrap(); + + let err = execute_send_nft_to_pool( + &mut app, + FLIPPER_ADDR, + contract_addr, + nft_addr1, + 4.to_string(), + ) + .unwrap_err(); + assert_eq!(err, ContractError::UnauthorizedToSendNft); +} + +#[test] +fn test_streak_claim() { + let (mut app, contract_addr) = setup_base_contract(); + add_balance(&mut app, contract_addr.clone(), 30000000000); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + // We have streak of 3, try to claim (can't because no reward for 3 streaks) + let err = execute_streak_claim(&mut app, FLIPPER_ADDR, contract_addr.clone()).unwrap_err(); + assert_eq!(err, ContractError::NotEligibleForStreakReward(3)); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Tails, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + // Verify the streak is 4 + let score = query_score(&app, contract_addr.clone(), FLIPPER_ADDR).unwrap(); + assert_eq!(score.streak.amount, 4); + + // Verify we got paid the 2nd reward + let res = execute_streak_claim(&mut app, FLIPPER_ADDR, contract_addr.clone()).unwrap(); + assert_eq!( + res.events[1].attributes[3].value, + coin( + TEST_STREAK_REWARDS[1].reward.into(), + NATIVE_DENOM.to_string() + ) + .to_string() + ); + + // Verify we resset the score after claiming + let score = query_score(&app, contract_addr, FLIPPER_ADDR).unwrap(); + assert_eq!(score.streak.amount, 0); +} + +#[test] +fn test_streak_claim_win_streak() { + let (mut app, contract_addr) = setup_base_contract(); + add_balance(&mut app, contract_addr.clone(), 30000000000); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Tails, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Tails, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Tails, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + // Verify the streak is 4 + let score = query_score(&app, contract_addr.clone(), FLIPPER_ADDR).unwrap(); + assert_eq!(score.streak.amount, 4); + + // Verify we got paid the 2nd reward + let res = execute_streak_claim(&mut app, FLIPPER_ADDR, contract_addr.clone()).unwrap(); + assert_eq!( + res.events[1].attributes[3].value, + coin( + TEST_STREAK_REWARDS[1].reward.into(), + NATIVE_DENOM.to_string() + ) + .to_string() + ); + + // Verify we resset the score after claiming + let score = query_score(&app, contract_addr, FLIPPER_ADDR).unwrap(); + assert_eq!(score.streak.amount, 0); +} + +#[test] +fn test_streak_nft_rewards() { + let (mut app, contract_addr, _, _) = setup_with_nft_pool(); + add_balance(&mut app, contract_addr.clone(), 30000000000); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Tails, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Tails, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + + let res = execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + let nft_info: Vec<&str> = res + .events + .iter() + .find(|event| event.ty == "wasm-streak-claim") + .unwrap() + .attributes[3] + .value + .split('/') + .collect(); + let nft_contract = Addr::unchecked(nft_info[0]); + let token_id = nft_info[1]; + + let owner: cw721::OwnerOfResponse = + query_nft_owner(&app, nft_contract, token_id.to_string()).unwrap(); + assert_eq!(owner.owner, Addr::unchecked(FLIPPER_ADDR)); + + // Verify we resset the score after claiming + let score = query_score(&app, contract_addr, FLIPPER_ADDR).unwrap(); + assert_eq!(score.streak.amount, 0); +} + +#[test] +fn test_streak_nft_rewards_win_streak() { + let (mut app, contract_addr, _, _) = setup_with_nft_pool(); + add_balance(&mut app, contract_addr.clone(), 30000000000); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Tails, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Tails, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Tails, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + + let res = execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + let nft_info: Vec<&str> = res + .events + .iter() + .find(|event| event.ty == "wasm-streak-claim") + .unwrap() + .attributes[3] + .value + .split('/') + .collect(); + let nft_contract = Addr::unchecked(nft_info[0]); + let token_id = nft_info[1]; + + let owner: cw721::OwnerOfResponse = + query_nft_owner(&app, nft_contract, token_id.to_string()).unwrap(); + assert_eq!(owner.owner, Addr::unchecked(FLIPPER_ADDR)); + + // Verify we resset the score after claiming + let score = query_score(&app, contract_addr, FLIPPER_ADDR).unwrap(); + assert_eq!(score.streak.amount, 0); +} + +#[test] +fn test_no_nft_pool() { + let (mut app, contract_addr) = setup_base_contract(); + add_balance(&mut app, contract_addr.clone(), 30000000000); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Tails, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Tails, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + + let old_balance = app + .wrap() + .query_balance(FLIPPER_ADDR, NATIVE_DENOM) + .unwrap(); + let res = execute_do_flips(&mut app, contract_addr).unwrap(); + + let rewards = res + .events + .iter() + .find(|event| event.ty == "wasm-streak-claim") + .unwrap() + .attributes[3] + .value + .clone(); + assert_eq!( + rewards, + coin( + TEST_STREAK_REWARDS[TEST_STREAK_REWARDS.len() - 1] + .reward + .u128(), + NATIVE_DENOM.to_string() + ) + .to_string() + ); + + //Verify balance actually changed for the flipper + let new_balance = app + .wrap() + .query_balance(FLIPPER_ADDR, NATIVE_DENOM) + .unwrap(); + assert_eq!( + new_balance.amount, + old_balance.amount + TEST_STREAK_REWARDS[TEST_STREAK_REWARDS.len() - 1].reward + ); +} + +#[test] +fn test_no_nft_pool_win_streak() { + let (mut app, contract_addr) = setup_base_contract(); + add_balance(&mut app, contract_addr.clone(), 30000000000); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Tails, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Tails, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Tails, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Heads, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + + let old_balance = app + .wrap() + .query_balance(FLIPPER_ADDR, NATIVE_DENOM) + .unwrap(); + let res = execute_do_flips(&mut app, contract_addr).unwrap(); + + let rewards = res + .events + .iter() + .find(|event| event.ty == "wasm-streak-claim") + .unwrap() + .attributes[3] + .value + .clone(); + assert_eq!( + rewards, + coin( + TEST_STREAK_REWARDS[TEST_STREAK_REWARDS.len() - 1] + .reward + .u128(), + NATIVE_DENOM.to_string() + ) + .to_string() + ); + + //Verify balance actually changed for the flipper + let new_balance = app + .wrap() + .query_balance(FLIPPER_ADDR, NATIVE_DENOM) + .unwrap(); + assert_eq!( + new_balance.amount, + old_balance.amount + + TEST_STREAK_REWARDS[TEST_STREAK_REWARDS.len() - 1].reward + + MIN_BET * Uint128::new(2) // add win amount + ); +} + +#[test] +fn test_no_streak_claim() { + let (mut app, contract_addr) = setup_base_contract(); + add_balance(&mut app, contract_addr.clone(), 30000000000); + + execute_start_flip( + &mut app, + contract_addr.clone(), + PickTypes::Tails, + MIN_BET, + Addr::unchecked(FLIPPER_ADDR), + NATIVE_DENOM, + MIN_FUNDS, + ) + .unwrap(); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + let err = execute_streak_claim(&mut app, FLIPPER_ADDR, contract_addr).unwrap_err(); + assert_eq!(err, ContractError::LowStreak(TEST_STREAK_REWARDS[0].streak)); +} diff --git a/contracts/coin-flip/src/testing/test_sudo.rs b/contracts/coin-flip/src/testing/test_sudo.rs new file mode 100644 index 0000000..bdbdaff --- /dev/null +++ b/contracts/coin-flip/src/testing/test_sudo.rs @@ -0,0 +1,181 @@ +use std::vec; + +use cosmwasm_std::{to_json_binary, Addr, Uint128, WasmMsg}; +use cw_multi_test::Executor; +use sg_std::NATIVE_DENOM; + +use crate::{ + error::ContractError, + testing::utils::{ + executes::{sudo_transfer_nft, sudo_withdraw_nft_from_pool}, + queries::query_nft_owner, + setup::{CREATOR_ADDR, MIN_BANK_AMOUNT, TEAM_ADDR}, + }, + types::StreakReward, +}; + +use super::utils::{ + executes::{execute_do_flips, sudo_update_streak_config, sudo_withdraw_excess}, + helpers::{add_10_todo_flips, add_balance}, + queries::{query_fees, query_nft_pool}, + setup::{setup_base_contract, setup_with_nft_pool, RESERVE_ADDR}, +}; + +#[test] +fn test_withdraw_nft_from_pool() { + let (mut app, contract_addr, _, _) = setup_with_nft_pool(); + let pool = query_nft_pool(&app, contract_addr.clone()).unwrap(); + + assert_eq!(pool.len(), 4); + + // Make sure the owner of the NFTs is the coin flip contract + for nft in &pool { + let owner = query_nft_owner(&app, nft.contract_addr.clone(), nft.token_id.clone()).unwrap(); + assert_eq!(owner.owner, contract_addr) + } + + // Try withdraw NFT out of index + let err = + sudo_withdraw_nft_from_pool(&mut app, contract_addr.clone(), Some(100), None).unwrap_err(); + assert_eq!(err, ContractError::NftIndexOutOfRange); + + // Must include either an index, or the all flag + let err = sudo_withdraw_nft_from_pool(&mut app, contract_addr.clone(), None, None).unwrap_err(); + assert_eq!(err, ContractError::EmptyWithdrawParams); + + // Lets withdraw first NFT and make sure the owner is the team wallet + sudo_withdraw_nft_from_pool(&mut app, contract_addr.clone(), Some(0), None).unwrap(); + + let owner = query_nft_owner( + &app, + pool[0].contract_addr.clone(), + pool[0].token_id.clone(), + ) + .unwrap(); + assert_eq!(owner.owner, TEAM_ADDR); + + // Lets withdraw all NFTs and make sure the owner is the team wallet + let pool = query_nft_pool(&app, contract_addr.clone()).unwrap(); + assert_eq!(pool.len(), 3); + + sudo_withdraw_nft_from_pool(&mut app, contract_addr.clone(), None, Some(true)).unwrap(); + + for nft in &pool { + let owner = query_nft_owner(&app, nft.contract_addr.clone(), nft.token_id.clone()).unwrap(); + assert_eq!(owner.owner, TEAM_ADDR) + } + + // Make sure the pool is empty + let pool = query_nft_pool(&app, contract_addr).unwrap(); + assert_eq!(pool.len(), 0); +} + +#[test] +fn test_withdraw_excess_funds() { + let (mut app, contract_addr) = setup_base_contract(); + add_balance(&mut app, contract_addr.clone(), 35000000000); + + // do some flips + add_10_todo_flips(&mut app, contract_addr.clone()); + execute_do_flips(&mut app, contract_addr.clone()).unwrap(); + + let fees_amount = query_fees(&app, contract_addr.clone(), NATIVE_DENOM).unwrap(); + let old_contract_balance = app + .wrap() + .query_balance(contract_addr.clone(), NATIVE_DENOM) + .unwrap(); + let old_reserve_balance = app + .wrap() + .query_balance(RESERVE_ADDR, NATIVE_DENOM) + .unwrap(); + let excess_funds = old_contract_balance.amount - fees_amount - MIN_BANK_AMOUNT; + + // Withdraw excess funds + sudo_withdraw_excess(&mut app, contract_addr.clone(), NATIVE_DENOM).unwrap(); + + let new_contract_balance = app + .wrap() + .query_balance(contract_addr.clone(), NATIVE_DENOM) + .unwrap(); + let new_reserve_balance = app + .wrap() + .query_balance(RESERVE_ADDR, NATIVE_DENOM) + .unwrap(); + + // Make sure the contract balance is correct (old balance - excess funds) + assert_eq!( + new_contract_balance.amount, + old_contract_balance.amount - excess_funds + ); + + // Make sure the reserve now holds the excess funds + assert_eq!( + new_reserve_balance.amount, + old_reserve_balance.amount + excess_funds + ); + + // Try withdraw when there are no excess funds + let err = sudo_withdraw_excess(&mut app, contract_addr, NATIVE_DENOM).unwrap_err(); + assert_eq!(err, ContractError::NoExcessFunds) +} + +#[test] +fn test_transfer_nft() { + let (mut app, contract_addr, nft_contract1, _) = setup_with_nft_pool(); + let pool = query_nft_pool(&app, contract_addr.clone()).unwrap(); + assert_eq!(pool.len(), 4); + + // Try to transfer an NFt that is in the pool + let err = sudo_transfer_nft( + &mut app, + contract_addr.clone(), + pool[0].contract_addr.as_str(), + pool[0].token_id.as_str(), + ) + .unwrap_err(); + + assert_eq!(err, ContractError::NftInPool); + + // Try transfer NFT that was sent by mistake + let token_id = "3"; + app.execute( + Addr::unchecked(CREATOR_ADDR), + WasmMsg::Execute { + contract_addr: nft_contract1.to_string(), + msg: to_json_binary(&cw721::Cw721ExecuteMsg::TransferNft { + recipient: contract_addr.to_string(), + token_id: token_id.into(), + }) + .unwrap(), + funds: vec![], + } + .into(), + ) + .unwrap(); + + let owner = query_nft_owner(&app, nft_contract1.clone(), token_id.into()).unwrap(); + assert_eq!(owner.owner, contract_addr); + + sudo_transfer_nft(&mut app, contract_addr, nft_contract1.as_str(), token_id).unwrap(); + + let owner = query_nft_owner(&app, nft_contract1.clone(), token_id.into()).unwrap(); + assert_eq!(owner.owner, TEAM_ADDR); +} + +#[test] +fn test_update_streak() { + let (mut app, contract_addr) = setup_base_contract(); + + sudo_update_streak_config( + &mut app, + contract_addr, + Some(10), + Some(100), + Some(vec![ + StreakReward::new(1, Uint128::new(1000)), + StreakReward::new(1, Uint128::new(2000)), + ]), + Some(vec![TEAM_ADDR.into(), RESERVE_ADDR.into()]), + ) + .unwrap(); +} diff --git a/contracts/coin-flip/src/testing/test_types.rs b/contracts/coin-flip/src/testing/test_types.rs new file mode 100644 index 0000000..59d5b7c --- /dev/null +++ b/contracts/coin-flip/src/testing/test_types.rs @@ -0,0 +1,48 @@ +use crate::types::Streak; + +#[cfg(test)] +#[test] +fn test_streak_reward() { + use cosmwasm_std::Uint128; + + use crate::types::StreakReward; + + let streak_reward = StreakReward::new(1, Uint128::new(100)); + assert_eq!( + streak_reward, + StreakReward { + streak: 1, + reward: Uint128::new(100) + } + ); +} + +#[test] +fn test_streak_update() { + let mut streak = Streak::new(true); + assert_eq!( + streak, + Streak { + amount: 1, + result: true + } + ); + + streak.update(true); + assert_eq!( + streak, + Streak { + amount: 2, + result: true + } + ); + + streak.update(false); + assert_eq!( + streak, + Streak { + amount: 1, + result: false + } + ); +} diff --git a/contracts/coin-flip/src/testing/utils/executes.rs b/contracts/coin-flip/src/testing/utils/executes.rs index 898293e..64f56a6 100644 --- a/contracts/coin-flip/src/testing/utils/executes.rs +++ b/contracts/coin-flip/src/testing/utils/executes.rs @@ -1,13 +1,15 @@ -use cosmwasm_std::{coins, Addr, Uint128}; +use std::collections::HashSet; + +use cosmwasm_std::{coins, to_json_binary, Addr, Empty, Uint128}; use cw_multi_test::{AppResponse, Executor}; use crate::{ error::ContractError, - msg::{ExecuteMsg, FlipExecuteMsg, SudoMsg}, - types::{Fees, PickTypes}, + msg::{ExecuteMsg, FlipExecuteMsg, StreakExecuteMsg, SudoMsg}, + types::{DenomLimit, Fees, PickTypes, StreakReward}, }; -use super::setup::{next_block, BaseApp, CREATOR_ADDR, FLIPPER_ADDR, NATIVE_DENOM}; +use super::setup::{next_block, BaseApp, CREATOR_ADDR, FLIPPER_ADDR}; pub(crate) fn unwrap_execute( res: Result, @@ -24,9 +26,10 @@ pub fn execute_start_flip( pick: PickTypes, flip_amount: Uint128, flipper: Addr, + denom: &str, funds: Uint128, ) -> Result { - let funds = coins(funds.u128(), NATIVE_DENOM); + let funds = coins(funds.u128(), denom); unwrap_execute(app.execute_contract( flipper, contract_addr, @@ -64,16 +67,51 @@ pub fn sudo_update_fees( )) } +pub fn sudo_add_new_denom( + app: &mut BaseApp, + contract_addr: Addr, + denom: &str, + limits: DenomLimit, +) -> Result { + unwrap_execute(app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + contract_addr, + &ExecuteMsg::Sudo(SudoMsg::AddNewDenom { + denom: denom.to_string(), + limits, + }), + &[], + )) +} + +pub fn sudo_remove_denoms( + app: &mut BaseApp, + contract_addr: Addr, + denoms: HashSet, +) -> Result { + unwrap_execute(app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + contract_addr, + &ExecuteMsg::Sudo(SudoMsg::RemoveDenoms { denoms }), + &[], + )) +} + pub fn sudo_update_bet_limit( app: &mut BaseApp, contract_addr: Addr, + denom: &str, min_bet: Uint128, max_bet: Uint128, ) -> Result { unwrap_execute(app.execute_contract( Addr::unchecked(CREATOR_ADDR), contract_addr, - &ExecuteMsg::Sudo(SudoMsg::UpdateBetLimit { min_bet, max_bet }), + &ExecuteMsg::Sudo(SudoMsg::UpdateBetLimit { + denom: denom.to_string(), + min_bet, + max_bet, + }), &[], )) } @@ -107,12 +145,37 @@ pub fn sudo_update_sg721( pub fn sudo_update_bank_limit( app: &mut BaseApp, contract_addr: Addr, + denom: &str, limit: Uint128, ) -> Result { unwrap_execute(app.execute_contract( Addr::unchecked(CREATOR_ADDR), contract_addr, - &ExecuteMsg::Sudo(SudoMsg::UpdateBankLimit { limit }), + &ExecuteMsg::Sudo(SudoMsg::UpdateBankLimit { + denom: denom.to_string(), + limit, + }), + &[], + )) +} + +pub fn sudo_update_streak_config( + app: &mut BaseApp, + contract_addr: Addr, + nft_pool_max: Option, + streak_nft_winning_amount: Option, + streak_rewards: Option>, + allowed_to_send_nft: Option>, +) -> Result { + unwrap_execute(app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + contract_addr, + &ExecuteMsg::Sudo(SudoMsg::UpdateStreak { + nft_pool_max, + streak_nft_winning_amount, + streak_rewards, + allowed_to_send_nft, + }), &[], )) } @@ -120,11 +183,92 @@ pub fn sudo_update_bank_limit( pub fn sudo_distribute( app: &mut BaseApp, contract_addr: Addr, + denom: &str, ) -> Result { unwrap_execute(app.execute_contract( Addr::unchecked(CREATOR_ADDR), contract_addr, - &ExecuteMsg::Sudo(SudoMsg::Distribute {}), + &ExecuteMsg::Sudo(SudoMsg::Distribute { + denom: denom.to_string(), + }), + &[], + )) +} + +pub fn sudo_withdraw_nft_from_pool( + app: &mut BaseApp, + contract_addr: Addr, + index: Option, + all: Option, +) -> Result { + unwrap_execute(app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + contract_addr, + &ExecuteMsg::Sudo(SudoMsg::WithdrawNftFromPool { index, all }), + &[], + )) +} + +pub fn sudo_withdraw_excess( + app: &mut BaseApp, + contract_addr: Addr, + denom: &str, +) -> Result { + unwrap_execute(app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + contract_addr, + &ExecuteMsg::Sudo(SudoMsg::SendExcessFunds { + denom: denom.into(), + }), + &[], + )) +} + +pub fn sudo_transfer_nft( + app: &mut BaseApp, + contract_addr: Addr, + nft_contract: &str, + token_id: &str, +) -> Result { + unwrap_execute(app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + contract_addr, + &ExecuteMsg::Sudo(SudoMsg::TransferNft { + contract: nft_contract.into(), + token_id: token_id.into(), + }), + &[], + )) +} + +pub fn execute_send_nft_to_pool( + app: &mut BaseApp, + sender: &str, + contract_addr: Addr, + nft_contract: Addr, + token_id: String, +) -> Result { + unwrap_execute(app.execute_contract( + Addr::unchecked(sender), + nft_contract, + &cw721::Cw721ExecuteMsg::SendNft { + contract: contract_addr.into(), + token_id, + msg: to_json_binary(&Empty {}).unwrap(), + }, + &[], + )) +} + +pub fn execute_streak_claim( + app: &mut BaseApp, + sender: &str, + contract_addr: Addr, +) -> Result { + unwrap_execute(app.execute_contract( + Addr::unchecked(sender), + contract_addr, + &ExecuteMsg::Streak(StreakExecuteMsg::Claim {}), &[], )) } diff --git a/contracts/coin-flip/src/testing/utils/helpers.rs b/contracts/coin-flip/src/testing/utils/helpers.rs index 935c2e3..57e0972 100644 --- a/contracts/coin-flip/src/testing/utils/helpers.rs +++ b/contracts/coin-flip/src/testing/utils/helpers.rs @@ -1,14 +1,14 @@ use std::str::FromStr; -use cosmwasm_std::{coins, Addr, Decimal, Empty, Uint128}; +use cosmwasm_std::{coin, coins, Addr, Decimal, Empty, Uint128}; use cosmwasm_storage::PrefixedStorage; use cw_multi_test::{AppResponse, Executor}; -use crate::{contract::MIN_BET, types::PickTypes}; +use crate::types::PickTypes; use super::{ executes::{execute_start_flip, unwrap_execute}, - setup::{BaseApp, NATIVE_DENOM}, + setup::{BaseApp, MIN_BET, NATIVE_DENOM, USDC_DENOM}, }; pub const FLIPPER_PREFIX: &str = "flipper-"; @@ -35,7 +35,11 @@ pub fn add_balance(app: &mut BaseApp, addr: Addr, amount: u128) { app.init_modules(|router, _, storage| { router .bank - .init_balance(storage, &addr, coins(amount, NATIVE_DENOM)) + .init_balance( + storage, + &addr, + vec![coin(amount, NATIVE_DENOM), coin(amount, USDC_DENOM)], + ) .unwrap(); }); } @@ -65,23 +69,24 @@ pub fn add_10_todo_flips(app: &mut BaseApp, contract_addr: Addr) { PickTypes::Heads, MIN_BET, Addr::unchecked(format!("{FLIPPER_PREFIX}{i}")), + NATIVE_DENOM, MIN_FUNDS, ) .unwrap(); } } -pub fn mint_777_nfts(app: &mut BaseApp, nft_contract_addr: Addr, sender: Addr) { - add_balances(app, 777); +pub fn mint_nfts(app: &mut BaseApp, nft_contract_addr: Addr, sender: Addr, to_mint: u64) { + add_balances(app, to_mint); - for i in 1..=777 { + for i in 1..=to_mint { unwrap_execute(app.execute_contract( sender.clone(), nft_contract_addr.clone(), &sg721::ExecuteMsg::Mint::(cw721_base::MintMsg { token_id: i.to_string(), owner: format!("{FLIPPER_PREFIX}{i}").to_string(), - token_uri: None, + token_uri: Some("ipfs://sdfsdf.com".to_string()), extension: Empty {}, }), &[], diff --git a/contracts/coin-flip/src/testing/utils/queries.rs b/contracts/coin-flip/src/testing/utils/queries.rs index 0aa4c2a..47dd726 100644 --- a/contracts/coin-flip/src/testing/utils/queries.rs +++ b/contracts/coin-flip/src/testing/utils/queries.rs @@ -1,8 +1,9 @@ -use cosmwasm_std::{Addr, StdError, Uint128}; +use cosmwasm_std::{Addr, Coin, StdError, Uint128}; +use cw721::OwnerOfResponse; use crate::{ msg::{DryDistributionResponse, QueryMsg}, - types::{Config, Flip, FlipScore}, + types::{Config, Flip, FlipScore, NftReward}, }; use super::setup::BaseApp; @@ -12,9 +13,18 @@ pub fn query_config(app: &BaseApp, contract_addr: Addr) -> Result Result { +pub fn query_fees(app: &BaseApp, contract_addr: Addr, denom: &str) -> Result { + app.wrap().query_wasm_smart( + contract_addr, + &QueryMsg::GetFeesAmount { + denom: denom.to_string(), + }, + ) +} + +pub fn query_all_fees(app: &BaseApp, contract_addr: Addr) -> Result, StdError> { app.wrap() - .query_wasm_smart(contract_addr, &QueryMsg::GetFeesAmount {}) + .query_wasm_smart(contract_addr, &QueryMsg::GetAllFeesAmount {}) } pub fn query_last_flips(app: &BaseApp, contract_addr: Addr) -> Result, StdError> { @@ -25,16 +35,49 @@ pub fn query_last_flips(app: &BaseApp, contract_addr: Addr) -> Result, pub fn query_score( app: &BaseApp, contract_addr: Addr, - address: String, + address: &str, ) -> Result { - app.wrap() - .query_wasm_smart(contract_addr, &QueryMsg::GetScore { address }) + app.wrap().query_wasm_smart( + contract_addr, + &QueryMsg::GetScore { + address: address.to_string(), + }, + ) } pub fn query_dry_distribution( app: &BaseApp, contract_addr: Addr, + denom: &str, ) -> Result { + app.wrap().query_wasm_smart( + contract_addr, + &QueryMsg::DryDistribution { + denom: denom.to_string(), + }, + ) +} + +pub fn query_nft_pool(app: &BaseApp, contract_addr: Addr) -> Result, StdError> { + app.wrap() + .query_wasm_smart(contract_addr, &QueryMsg::GetNftPool {}) +} + +pub fn query_should_flip(app: &BaseApp, contract_addr: Addr) -> Result { app.wrap() - .query_wasm_smart(contract_addr, &QueryMsg::DryDistribution {}) + .query_wasm_smart(contract_addr, &QueryMsg::ShouldDoFlips {}) +} + +pub fn query_nft_owner( + app: &BaseApp, + contract_addr: Addr, + token_id: String, +) -> Result { + app.wrap().query_wasm_smart( + contract_addr, + &cw721::Cw721QueryMsg::OwnerOf { + token_id, + include_expired: None, + }, + ) } diff --git a/contracts/coin-flip/src/testing/utils/setup.rs b/contracts/coin-flip/src/testing/utils/setup.rs index 727076b..7136456 100644 --- a/contracts/coin-flip/src/testing/utils/setup.rs +++ b/contracts/coin-flip/src/testing/utils/setup.rs @@ -1,5 +1,7 @@ +use std::collections::HashSet; + use cosmwasm_std::{ - coin, coins, testing::MockApi, Addr, BlockInfo, Empty, MemoryStorage, Timestamp, + coin, coins, testing::MockApi, Addr, BlockInfo, Empty, MemoryStorage, Timestamp, Uint128, }; use cw_multi_test::{ App, BankKeeper, BasicAppBuilder, Contract, ContractWrapper, Executor, FailingModule, @@ -11,12 +13,12 @@ use sg_std::StargazeMsgWrapper; use crate::{ msg::InstantiateMsg, - types::{Fees, Wallets}, + types::{DenomLimit, Fees, StreakReward, Wallets}, }; use super::{ - executes::sudo_update_sg721, - helpers::{add_balance, mint_777_nfts}, + executes::{execute_send_nft_to_pool, sudo_add_new_denom, sudo_update_sg721, unwrap_execute}, + helpers::{add_balance, mint_nfts}, }; pub type BaseApp = App< @@ -30,7 +32,20 @@ pub type BaseApp = App< pub const FLIPPER_ADDR: &str = "some_flipper"; pub const FLIPPER_ADDR2: &str = "some_flipper2"; pub const CREATOR_ADDR: &str = "creator"; -pub const NATIVE_DENOM: &str = "native_denom"; +pub const NATIVE_DENOM: &str = "ustars"; +pub const USDC_DENOM: &str = "uusdc"; +pub const TEST_STREAK_REWARDS: [StreakReward; 3] = [ + StreakReward::new(2, Uint128::new(100000)), + StreakReward::new(4, Uint128::new(200000)), + StreakReward::new(5, Uint128::new(300000)), +]; + +/// Min bet people are allow to bet +pub const MIN_BET: Uint128 = Uint128::new(5_000_000); +/// Max bet people are allow to bet +pub const MAX_BET: Uint128 = Uint128::new(25_000_000); +/// Minimum amount of tokens we need to have in the contract +pub const MIN_BANK_AMOUNT: Uint128 = Uint128::new(30_000_000_000); //Wallets pub const TEAM_ADDR: &str = "team_wallet"; @@ -63,7 +78,7 @@ pub fn next_block(block: &mut BlockInfo) { /// Basic setup for unit test on a single contract pub fn setup_base_contract() -> (BaseApp, Addr) { - let mut app: BaseApp = BasicAppBuilder::::new_custom() + let mut app: BaseApp = BasicAppBuilder::::new_custom() .with_block(BlockInfo { height: 1, time: Timestamp::from_seconds(123456789), @@ -77,6 +92,7 @@ pub fn setup_base_contract() -> (BaseApp, Addr) { &Addr::unchecked(FLIPPER_ADDR), vec![ coin(999999999999999, NATIVE_DENOM), + coin(999999999999999, USDC_DENOM), coin(999999999999999, "random"), ], ) @@ -94,9 +110,9 @@ pub fn setup_base_contract() -> (BaseApp, Addr) { let code_id = app.store_code(flip_contract()); + let denoms = vec![NATIVE_DENOM.to_string()]; let init_msg = &InstantiateMsg { - admin: CREATOR_ADDR.to_string(), - denoms: vec![NATIVE_DENOM.to_string()], + denoms: HashSet::from_iter(denoms), wallets: Wallets { team: TEAM_ADDR.to_string(), reserve: RESERVE_ADDR.to_string(), @@ -107,11 +123,13 @@ pub fn setup_base_contract() -> (BaseApp, Addr) { reserve_bps: 1500, flip_bps: 350, }, - bank_limit: None, - min_bet_limit: None, - max_bet_limit: None, + denom_limits: vec![(NATIVE_DENOM.to_string(), MIN_BET, MAX_BET, MIN_BANK_AMOUNT)], flips_per_block_limit: None, sg721_addr: None, + nft_pool_max: 4, + streak_nft_winning_amount: 5, + streak_rewards: TEST_STREAK_REWARDS.into(), + allowed_to_send_nft: vec![TEAM_ADDR.to_string(), CREATOR_ADDR.to_string()], }; let contract_addr = app @@ -158,9 +176,150 @@ pub fn setup_contract() -> (BaseApp, Addr) { ) .unwrap(); - mint_777_nfts(&mut app, nft_addr.clone(), contract_addr.clone()); + mint_nfts(&mut app, nft_addr.clone(), contract_addr.clone(), 777); sudo_update_sg721(&mut app, contract_addr.clone(), nft_addr.to_string()).unwrap(); (app, contract_addr) } + +pub fn setup_nft_contracts(app: &mut BaseApp, contract_addr: Addr) -> (Addr, Addr) { + // Create two NFT contracts + let nft_code_id = app.store_code(nft_contract()); + let nft_addr1 = app + .instantiate_contract( + nft_code_id, + contract_addr.clone(), + &sg721::InstantiateMsg { + name: "Test NFT".to_string(), + symbol: "TEST".to_string(), + minter: Addr::unchecked(CREATOR_ADDR).into(), + collection_info: CollectionInfo:: { + description: "Test NFT".to_string(), + image: "https://example.net".to_string(), + creator: CREATOR_ADDR.to_string(), + external_link: None, + explicit_content: Some(false), + start_trading_time: None, + royalty_info: None, + }, + }, + &[], + "nft1 contract", + None, + ) + .unwrap(); + + let nft_addr2 = app + .instantiate_contract( + nft_code_id, + contract_addr, + &sg721::InstantiateMsg { + name: "Test NFT2".to_string(), + symbol: "TEST2".to_string(), + minter: Addr::unchecked(CREATOR_ADDR).into(), + collection_info: CollectionInfo:: { + description: "Test NFT2".to_string(), + image: "https://example.net".to_string(), + creator: CREATOR_ADDR.to_string(), + external_link: None, + explicit_content: Some(false), + start_trading_time: None, + royalty_info: None, + }, + }, + &[], + "nft2 contract", + None, + ) + .unwrap(); + + // mint 5 nfts for each collection + for i in 1..=5 { + unwrap_execute(app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + nft_addr1.clone(), + &sg721::ExecuteMsg::Mint::(cw721_base::MintMsg { + token_id: i.to_string(), + owner: CREATOR_ADDR.to_string(), + token_uri: Some("ipfs://sdfsdf.com".to_string()), + extension: Empty {}, + }), + &[], + )) + .unwrap(); + + unwrap_execute(app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + nft_addr2.clone(), + &sg721::ExecuteMsg::Mint::(cw721_base::MintMsg { + token_id: i.to_string(), + owner: CREATOR_ADDR.to_string(), + token_uri: Some("ipfs://sdfsdf.com".to_string()), + extension: Empty {}, + }), + &[], + )) + .unwrap(); + } + + (nft_addr1, nft_addr2) +} + +pub fn setup_with_nft_pool() -> (BaseApp, Addr, Addr, Addr) { + let (mut app, contract_addr) = setup_base_contract(); + let (nft_addr1, nft_addr2) = setup_nft_contracts(&mut app, contract_addr.clone()); + + execute_send_nft_to_pool( + &mut app, + CREATOR_ADDR, + contract_addr.clone(), + nft_addr1.clone(), + 1.to_string(), + ) + .unwrap(); + execute_send_nft_to_pool( + &mut app, + CREATOR_ADDR, + contract_addr.clone(), + nft_addr2.clone(), + 2.to_string(), + ) + .unwrap(); + execute_send_nft_to_pool( + &mut app, + CREATOR_ADDR, + contract_addr.clone(), + nft_addr2.clone(), + 1.to_string(), + ) + .unwrap(); + execute_send_nft_to_pool( + &mut app, + CREATOR_ADDR, + contract_addr.clone(), + nft_addr1.clone(), + 5.to_string(), + ) + .unwrap(); + + (app, contract_addr, nft_addr1, nft_addr2) +} + +pub fn setup_with_multiple_denoms() -> (BaseApp, Addr) { + let (mut app, contract_addr) = setup_base_contract(); + + sudo_add_new_denom( + &mut app, + contract_addr.clone(), + USDC_DENOM, + DenomLimit { + min: MIN_BET, + max: MAX_BET, + bank: MIN_BANK_AMOUNT, + }, + ) + .unwrap(); + + (app, contract_addr) +} diff --git a/contracts/coin-flip/src/types.rs b/contracts/coin-flip/src/types.rs index 740ccb7..70b9180 100644 --- a/contracts/coin-flip/src/types.rs +++ b/contracts/coin-flip/src/types.rs @@ -1,3 +1,5 @@ +use std::collections::{HashMap, HashSet}; + use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Coin, Decimal, Env, Timestamp, Uint128}; @@ -6,15 +8,50 @@ use crate::helpers::bps_to_decimal; #[cw_serde] pub struct Config { pub admin: String, - pub denoms: Vec, - pub bank_limit: Uint128, - pub min_bet_limit: Uint128, - pub max_bet_limit: Uint128, + pub denoms: HashSet, + pub denom_limits: HashMap, pub flips_per_block_limit: u64, pub wallets: Wallets, pub fees: Fees, pub sg721_addr: Option, pub is_paused: bool, + + pub nft_pool_max: u32, + pub streak_nft_winning_amount: u32, +} + +#[cw_serde] +pub struct DenomLimit { + pub min: Uint128, + pub max: Uint128, + pub bank: Uint128, +} + +#[cw_serde] +pub struct StreakReward { + pub streak: u32, + pub reward: Uint128, +} + +impl StreakReward { + pub const fn new(streak: u32, reward: Uint128) -> StreakReward { + StreakReward { streak, reward } + } +} + +#[cw_serde] +pub struct NftReward { + pub token_id: String, + pub contract_addr: Addr, +} + +impl NftReward { + pub fn new(contract_addr: Addr, token_id: String) -> NftReward { + NftReward { + token_id, + contract_addr, + } + } } #[cw_serde] @@ -121,6 +158,10 @@ impl Streak { self.result = result; } } + + pub fn reset(&mut self) { + self.amount = 0; + } } #[cw_serde] diff --git a/devtools/add_denom.sh b/devtools/add_denom.sh new file mode 100755 index 0000000..e5f2123 --- /dev/null +++ b/devtools/add_denom.sh @@ -0,0 +1,15 @@ +add_denom_msg=$(jq -n \ + '{ + "sudo": { + "add_new_denom": { + "denom": "factory/stars1zjjqxfqm33tz27phd0z4jyg53fv0yq7m3945le/art3mix", + "limits": { + "min": "1000000", + "max": "10000000", + "bank": "100000000" + } + } + } + }') + +starsd tx wasm execute stars16fdgsjm60yrknend2kwl0gw90tstyzvvgr5dre0mkfuktl83q7ascv28xj "$add_denom_msg" --amount 100000000factory/stars1zjjqxfqm33tz27phd0z4jyg53fv0yq7m3945le/art3mix --from main --gas-prices 1ustars --gas-adjustment 1.4 --gas auto -b block -y diff --git a/devtools/distribute.sh b/devtools/distribute.sh new file mode 100755 index 0000000..ca9ba54 --- /dev/null +++ b/devtools/distribute.sh @@ -0,0 +1,8 @@ + +# starsd query wasm contract-state smart stars1vy9z85pp3zymdz6vw2gyqg27gh264t7gas6ufg9gzx72pxz9s6nq34y5c9 \ +# '{"dry_distribution":{}}' \ +# --node https://stargaze-rpc.polkachu.com:443 --chain-id stargaze-1 + +starsd tx wasm execute stars1vy9z85pp3zymdz6vw2gyqg27gh264t7gas6ufg9gzx72pxz9s6nq34y5c9 \ +'{"sudo":{"distribute": {}}}' --gas-prices 1ustars --gas auto --gas-adjustment 1.4 --from main \ +--node https://stargaze-rpc.polkachu.com:443 --chain-id stargaze-1 -b block -y diff --git a/devtools/init_main.sh b/devtools/init_main.sh new file mode 100755 index 0000000..ed59321 --- /dev/null +++ b/devtools/init_main.sh @@ -0,0 +1,25 @@ +code_id=46 +init_msg=$( jq -n \ + '{ + admin: "", + denoms: ["ustars"], + fees: { + flip_bps: 300, + holders_bps: 7000, + reserve_bps: 1500, + team_bps: 1500, + }, + flips_per_block_limit: 10, + bank_limit: "30000000000", + min_bet_limit: "50000000", + max_bet_limit: "250000000", + wallets: { + reserve: "stars1ngygw5sw0dfchq9ffm5p2hf2z7ndq4lu9dh6fm", + team: "stars12d0k89lxp224xke3fe4mzfxpnxjshwqcvtxnd8", + }, + }') + +starsd tx wasm instantiate $code_id "$init_msg" --label "coin_flip" \ +--admin stars1zjjqxfqm33tz27phd0z4jyg53fv0yq7m3945le \ +--gas-prices 0.025ustars --gas auto --gas-adjustment 1.9 --from main \ +--node https://stargaze-rpc.polkachu.com:443 --chain-id stargaze-1 -b block -y diff --git a/devtools/migrate.sh b/devtools/migrate.sh index c96c1dc..31533f0 100755 --- a/devtools/migrate.sh +++ b/devtools/migrate.sh @@ -1 +1,17 @@ -starsd tx wasm migrate stars1qlv5p0gm4ndd65rq4le2h0x77swprxl83lyhufp0uay9thzqtjuqzmetum 1670 '{"basic":{}}' --from main --gas-prices 0ustars --gas-adjustment 1.3 --gas auto -b block -y +migrate_msg=$( jq -n \ + '{ + "from_v07": { + "nft_pool_max": 5, + "streak_nft_winning_amount": 3, + "streak_rewards": [{ + "streak": 1, + "reward": "500000"}, + {"streak": 2, + "reward": "1000000"}, + {"streak": 3, + "reward": "5000000"}], + "allowed_to_send_nft": ["stars1zjjqxfqm33tz27phd0z4jyg53fv0yq7m3945le"] + } + }') + +starsd tx wasm migrate stars16fdgsjm60yrknend2kwl0gw90tstyzvvgr5dre0mkfuktl83q7ascv28xj 3420 "$migrate_msg" --from main --gas-prices 1ustars --gas-adjustment 1.4 --gas auto -b block -y diff --git a/devtools/optimizer.sh b/devtools/optimizer.sh index 08f11b9..30407c6 100755 --- a/devtools/optimizer.sh +++ b/devtools/optimizer.sh @@ -1,4 +1,4 @@ docker run --rm -v "$(pwd)":/code --platform linux/amd64 \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/workspace-optimizer:0.12.13 + cosmwasm/workspace-optimizer:0.14.0 diff --git a/devtools/prop.sh b/devtools/prop.sh new file mode 100755 index 0000000..c944d98 --- /dev/null +++ b/devtools/prop.sh @@ -0,0 +1,60 @@ +read -r -d '' desc << EOM +## Summary - Store WASM Code + +Cosmos Coinflip is a coinflip game for Cosmos IBC enabled tokens. We are launching first on Stargaze where users are able to flip head or tails on a set amount of stars to double or nothing. This all comes with an exclusive Pixel art NFT collection on stargaze that has some benefits of their own! + +The stargaze team has reviewed the contract and provided feedback which was addressed. + +Github Repo for review: https://github.com/Cosmos-Coin-Flip/Cosmos-Coin-Flip + +Testnet Website: https://testnet.cosmoscoinflip.com + +Twitter: https://twitter.com/CosmosCoinFlip + +Discord: https://discord.gg/qaNZCpTfhE + +## Compile Instructions + +Source code: https://github.com/Cosmos-Coin-Flip/Cosmos-Coin-Flip/tree/v0.7.2 + +docker run --rm -v "$(pwd)":/code --platform linux/amd64 \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/workspace-optimizer:0.12.13 + +Or clone the repo and run: " ./devtools/optimizer.sh " + +This results in the following SHA256 checksum: + +86077c3caf35eb66d58b6380f69efaaf1a979c09d3b08af118ad398c160fdec9 + +## Verify On-chain Contract + +starsd q gov proposal $id --output json \\ +| jq -r '.content.wasm_byte_code' \\ +| base64 -d \\ +| gzip -dc \\ +| sha256sum + +## Verify Local Contract +sha256sum artifacts/coin_flip.wasm +EOM + +title="Upload Cosmos Coin Flip game smart contract" + +starsd tx gov submit-proposal wasm-store ../artifacts/coin_flip.wasm \ +--title "$title" \ +--description "$desc" \ +--run-as "stars1zjjqxfqm33tz27phd0z4jyg53fv0yq7m3945le" \ +--builder "cosmwasm/workspace-optimizer:0.12.13" \ +--code-hash "86077c3caf35eb66d58b6380f69efaaf1a979c09d3b08af118ad398c160fdec9" \ +--code-source-url "https://github.com/Cosmos-Coin-Flip/Cosmos-Coin-Flip/tree/v0.7.2" \ +--instantiate-anyof-addresses "stars1zjjqxfqm33tz27phd0z4jyg53fv0yq7m3945le" \ +--deposit 20000000000ustars \ +--chain-id "stargaze-1" \ +--node "https://rpc.stargaze-apis.com:443" \ +--from main \ +--gas-prices 0ustars \ +--gas-adjustment 1.3 \ +--gas auto \ +-b block -y diff --git a/devtools/upload.sh b/devtools/upload.sh index 9b1533e..a184c29 100755 --- a/devtools/upload.sh +++ b/devtools/upload.sh @@ -1 +1 @@ -starsd tx wasm store ../artifacts/coin_flip.wasm --from main --gas-prices 0ustars --gas-adjustment 1.3 --gas auto -b block -y +starsd tx wasm store ../artifacts/coin_flip.wasm --from main --gas-prices 1ustars --gas-adjustment 1.4 --gas auto -b block -y