From 4007fa0280f2d5fe55f33062fb0dd569cc5a8bb8 Mon Sep 17 00:00:00 2001 From: itshaseebsaeed Date: Mon, 7 Oct 2024 00:29:36 +0500 Subject: [PATCH] pushing changes --- Cargo.toml | 3 + contracts/galactic_pools/exp/.editorconfig | 11 + contracts/galactic_pools/exp/.gitignore | 17 + contracts/galactic_pools/exp/Cargo.toml | 55 + contracts/galactic_pools/exp/Developing.md | 142 + contracts/galactic_pools/exp/Importing.md | 62 + contracts/galactic_pools/exp/LICENSE | 202 + contracts/galactic_pools/exp/Makefile | 68 + contracts/galactic_pools/exp/NOTICE | 13 + contracts/galactic_pools/exp/Publishing.md | 115 + contracts/galactic_pools/exp/README.md | 10 + .../galactic_pools/exp/examples/schema.rs | 19 + contracts/galactic_pools/exp/rustfmt.toml | 15 + contracts/galactic_pools/exp/src/contract.rs | 1260 ++++ contracts/galactic_pools/exp/src/error.rs | 58 + contracts/galactic_pools/exp/src/lib.rs | 6 + contracts/galactic_pools/exp/src/msg.rs | 254 + .../galactic_pools/exp/src/multi_test.rs | 238 + contracts/galactic_pools/exp/src/state.rs | 82 + contracts/galactic_pools/exp/src/unit_test.rs | 2290 ++++++ .../galactic_pools/pools/native/.cargo/config | 5 + .../galactic_pools/pools/native/.editorconfig | 12 + .../galactic_pools/pools/native/.gitignore | 28 + .../galactic_pools/pools/native/Cargo.toml | 65 + .../galactic_pools/pools/native/Developing.md | 142 + .../galactic_pools/pools/native/Importing.md | 62 + contracts/galactic_pools/pools/native/LICENSE | 202 + .../galactic_pools/pools/native/Makefile | 71 + contracts/galactic_pools/pools/native/NOTICE | 13 + .../galactic_pools/pools/native/Publishing.md | 115 + .../galactic_pools/pools/native/README.md | 3 + .../pools/native/rust-toolchain | 1 + .../galactic_pools/pools/native/secretjs/.env | 8 + .../pools/native/secretjs/1_init/init.js | 169 + .../secretjs/2_execute/admins/end_lottery.js | 62 + .../2_execute/admins/end_lottery_loop.js | 57 + .../2_execute/admins/rebalance_val_set.js | 59 + .../secretjs/2_execute/admins/unbond_batch.js | 59 + .../2_execute/admins/unbond_batch_loop.js | 67 + .../native/secretjs/2_execute/allinone.js | 16 + .../pools/native/secretjs/2_execute/main.js | 152 + .../secretjs/2_execute/sponsors/sponsor.js | 66 + .../sponsors/sponsor_request_withdraw.js | 60 + .../2_execute/sponsors/sponsor_withdraw.js | 66 + .../secretjs/2_execute/users/claim_rewards.js | 60 + .../secretjs/2_execute/users/deposit.js | 109 + .../2_execute/users/request_withdraw.js | 61 + .../secretjs/2_execute/users/withdraw.js | 65 + .../native/secretjs/2_execute/users/wrap.js | 57 + .../pools/native/secretjs/3_query/main.js | 120 + .../private_queries/delegated.js | 68 + .../pool_contract/private_queries/records.js | 66 + .../private_queries/unbondings.js | 76 + .../private_queries/withdrawable.js | 67 + .../public_queries/contract_config.js | 61 + .../public_queries/contract_status.js | 35 + .../public_queries/current_rewards.js | 36 + .../pool_contract/public_queries/example.js | 47 + .../public_queries/lottery_info.js | 59 + .../pool_contract/public_queries/pool_info.js | 44 + .../public_queries/pool_liquidity_stats.js | 36 + .../pool_liquidity_stats_specific.js | 42 + .../public_queries/reward_stats.js | 42 + .../pool_contract/public_queries/sponsors.js | 38 + .../public_queries/sponsors_msg_req_check.js | 38 + .../secretjs/3_query/sscrt/sscrt_balance.js | 81 + .../pools/native/secretjs/Errors/error_1.js | 128 + .../pools/native/secretjs/deploy.js | 227 + .../pools/native/src/constants.rs | 32 + .../pools/native/src/contract.rs | 5032 +++++++++++++ .../galactic_pools/pools/native/src/helper.rs | 391 + .../galactic_pools/pools/native/src/lib.rs | 11 + .../galactic_pools/pools/native/src/msg.rs | 744 ++ .../pools/native/src/multi_test.rs | 698 ++ .../galactic_pools/pools/native/src/rand.rs | 75 + .../pools/native/src/staking.rs | 194 + .../galactic_pools/pools/native/src/state.rs | 342 + .../pools/native/src/unit_test.rs | 6589 +++++++++++++++++ .../galactic_pools/pools/native/src/utils.rs | 15 + .../pools/native/src/viewing_key.rs | 64 + contracts/liquidity_book/lb_pair/Cargo.toml | 2 +- packages/shade_protocol/Cargo.toml | 2 +- 82 files changed, 22332 insertions(+), 2 deletions(-) create mode 100644 contracts/galactic_pools/exp/.editorconfig create mode 100644 contracts/galactic_pools/exp/.gitignore create mode 100644 contracts/galactic_pools/exp/Cargo.toml create mode 100644 contracts/galactic_pools/exp/Developing.md create mode 100644 contracts/galactic_pools/exp/Importing.md create mode 100644 contracts/galactic_pools/exp/LICENSE create mode 100644 contracts/galactic_pools/exp/Makefile create mode 100644 contracts/galactic_pools/exp/NOTICE create mode 100644 contracts/galactic_pools/exp/Publishing.md create mode 100644 contracts/galactic_pools/exp/README.md create mode 100644 contracts/galactic_pools/exp/examples/schema.rs create mode 100644 contracts/galactic_pools/exp/rustfmt.toml create mode 100644 contracts/galactic_pools/exp/src/contract.rs create mode 100644 contracts/galactic_pools/exp/src/error.rs create mode 100644 contracts/galactic_pools/exp/src/lib.rs create mode 100644 contracts/galactic_pools/exp/src/msg.rs create mode 100644 contracts/galactic_pools/exp/src/multi_test.rs create mode 100644 contracts/galactic_pools/exp/src/state.rs create mode 100644 contracts/galactic_pools/exp/src/unit_test.rs create mode 100644 contracts/galactic_pools/pools/native/.cargo/config create mode 100644 contracts/galactic_pools/pools/native/.editorconfig create mode 100644 contracts/galactic_pools/pools/native/.gitignore create mode 100644 contracts/galactic_pools/pools/native/Cargo.toml create mode 100644 contracts/galactic_pools/pools/native/Developing.md create mode 100644 contracts/galactic_pools/pools/native/Importing.md create mode 100644 contracts/galactic_pools/pools/native/LICENSE create mode 100644 contracts/galactic_pools/pools/native/Makefile create mode 100644 contracts/galactic_pools/pools/native/NOTICE create mode 100644 contracts/galactic_pools/pools/native/Publishing.md create mode 100644 contracts/galactic_pools/pools/native/README.md create mode 100644 contracts/galactic_pools/pools/native/rust-toolchain create mode 100644 contracts/galactic_pools/pools/native/secretjs/.env create mode 100644 contracts/galactic_pools/pools/native/secretjs/1_init/init.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/2_execute/admins/end_lottery.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/2_execute/admins/end_lottery_loop.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/2_execute/admins/rebalance_val_set.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/2_execute/admins/unbond_batch.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/2_execute/admins/unbond_batch_loop.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/2_execute/allinone.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/2_execute/main.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/2_execute/sponsors/sponsor.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/2_execute/sponsors/sponsor_request_withdraw.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/2_execute/sponsors/sponsor_withdraw.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/2_execute/users/claim_rewards.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/2_execute/users/deposit.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/2_execute/users/request_withdraw.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/2_execute/users/withdraw.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/2_execute/users/wrap.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/3_query/main.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/private_queries/delegated.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/private_queries/records.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/private_queries/unbondings.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/private_queries/withdrawable.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/contract_config.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/contract_status.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/current_rewards.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/example.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/lottery_info.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/pool_info.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/pool_liquidity_stats.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/pool_liquidity_stats_specific.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/reward_stats.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/sponsors.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/sponsors_msg_req_check.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/3_query/sscrt/sscrt_balance.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/Errors/error_1.js create mode 100644 contracts/galactic_pools/pools/native/secretjs/deploy.js create mode 100644 contracts/galactic_pools/pools/native/src/constants.rs create mode 100644 contracts/galactic_pools/pools/native/src/contract.rs create mode 100644 contracts/galactic_pools/pools/native/src/helper.rs create mode 100644 contracts/galactic_pools/pools/native/src/lib.rs create mode 100644 contracts/galactic_pools/pools/native/src/msg.rs create mode 100644 contracts/galactic_pools/pools/native/src/multi_test.rs create mode 100644 contracts/galactic_pools/pools/native/src/rand.rs create mode 100644 contracts/galactic_pools/pools/native/src/staking.rs create mode 100644 contracts/galactic_pools/pools/native/src/state.rs create mode 100644 contracts/galactic_pools/pools/native/src/unit_test.rs create mode 100644 contracts/galactic_pools/pools/native/src/utils.rs create mode 100644 contracts/galactic_pools/pools/native/src/viewing_key.rs diff --git a/Cargo.toml b/Cargo.toml index dc7e7613d..8ad2304cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,9 @@ members = [ "contracts/liquidity_book/lb_staking", "contracts/liquidity_book/tests", + "contracts/galactic_pools/pools/native", + + # Staking #"contracts/basic_staking", #"contracts/snip20_derivative", diff --git a/contracts/galactic_pools/exp/.editorconfig b/contracts/galactic_pools/exp/.editorconfig new file mode 100644 index 000000000..3d36f20b1 --- /dev/null +++ b/contracts/galactic_pools/exp/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.rs] +indent_size = 4 diff --git a/contracts/galactic_pools/exp/.gitignore b/contracts/galactic_pools/exp/.gitignore new file mode 100644 index 000000000..2c314560c --- /dev/null +++ b/contracts/galactic_pools/exp/.gitignore @@ -0,0 +1,17 @@ +# Build results +/target +Cargo.lock +contract.wasm* + +# Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) +.cargo-ok + +# Text file backups +**/*.rs.bk + +# macOS +.DS_Store + +# IDEs +*.iml +.idea diff --git a/contracts/galactic_pools/exp/Cargo.toml b/contracts/galactic_pools/exp/Cargo.toml new file mode 100644 index 000000000..f753c52b6 --- /dev/null +++ b/contracts/galactic_pools/exp/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "experience_contract" +version = "1.0.0" +authors = ["Lumi - Trivium","Haseeb Saeed"] +edition = "2021" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[profile.release] +opt-level = 3 +debug = false +rpath = false +lto = true +debug-assertions = false +codegen-units = 1 +panic = 'abort' +incremental = false +overflow-checks = true + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +#backtraces = ["cosmwasm-std/backtraces"] +#debug-print = ["cosmwasm-std/debug-print"] + +[dependencies] +snafu = { version = "0.6.3" } +serde = { version = "1.0.103", default-features = false, features = ["derive"] } +schemars = "0.8.8" +shade-protocol = { version = "0.1.0", path = "./../../../packages/shade_protocol" } + +thiserror = { version = "1.0.31" } + +rand_chacha = { version = "0.2.2", default-features = false } +#rand_core = { version = "0.5.1", default-features = false } +rand = { version = "0.7.3" } +sha2 = { version = "0.9.1", default-features = false } +[dev-dependencies] +cosmwasm-schema = { git = "https://github.com/scrtlabs/cosmwasm/", branch = "secret" } +shade-multi-test = { path = "./../../../packages/multi_test", features = [ + "admin", + "lb_pair", + "lb_token", + "snip20", +] } diff --git a/contracts/galactic_pools/exp/Developing.md b/contracts/galactic_pools/exp/Developing.md new file mode 100644 index 000000000..c7cd8ff21 --- /dev/null +++ b/contracts/galactic_pools/exp/Developing.md @@ -0,0 +1,142 @@ +# Developing + +If you have recently created a contract with this template, you probably could use some +help on how to build and test the contract, as well as prepare it for production. This +file attempts to provide a brief overview, assuming you have installed a recent +version of Rust already (eg. 1.41+). + +## Prerequisites + +Before starting, make sure you have [rustup](https://rustup.rs/) along with a +recent `rustc` and `cargo` version installed. Currently, we are testing on 1.41+. + +And you need to have the `wasm32-unknown-unknown` target installed as well. + +You can check that via: + +```sh +rustc --version +cargo --version +rustup target list --installed +# if wasm32 is not listed above, run this +rustup target add wasm32-unknown-unknown +``` + +## Compiling and running tests + +Now that you created your custom contract, make sure you can compile and run it before +making any changes. Go into the + +```sh +# this will produce a wasm build in ./target/wasm32-unknown-unknown/release/YOUR_NAME_HERE.wasm +cargo wasm + +# this runs unit tests with helpful backtraces +RUST_BACKTRACE=1 cargo unit-test + +# this runs integration tests with cranelift backend (uses rust stable) +cargo integration-test + +# this runs integration tests with singlepass backend (needs rust nightly) +cargo integration-test --no-default-features --features singlepass + +# auto-generate json schema +cargo schema +``` + +The wasmer engine, embedded in `cosmwasm-vm` supports multiple backends: +singlepass and cranelift. Singlepass has fast compile times and slower run times, +and supportes gas metering. It also requires rust `nightly`. This is used as default +when embedding `cosmwasm-vm` in `go-cosmwasm` and is needed to use if you want to +check the gas usage. + +However, when just building contacts, if you don't want to worry about installing +two rust toolchains, you can run all tests with cranelift. The integration tests +may take a small bit longer, but the results will be the same. The only difference +is that you can not check gas usage here, so if you wish to optimize gas, you must +switch to nightly and run with cranelift. + +### Understanding the tests + +The main code is in `src/contract.rs` and the unit tests there run in pure rust, +which makes them very quick to execute and give nice output on failures, especially +if you do `RUST_BACKTRACE=1 cargo unit-test`. + +However, we don't just want to test the logic rust, but also the compiled Wasm artifact +inside a VM. You can look in `tests/integration.rs` to see some examples there. They +load the Wasm binary into the vm and call the contract externally. Effort has been +made that the syntax is very similar to the calls in the native rust contract and +quite easy to code. In fact, usually you can just copy a few unit tests and modify +a few lines to make an integration test (this should get even easier in a future release). + +To run the latest integration tests, you need to explicitely rebuild the Wasm file with +`cargo wasm` and then run `cargo integration-test`. + +We consider testing critical for anything on a blockchain, and recommend to always keep +the tests up to date. While doing active development, it is often simplest to disable +the integration tests completely and iterate rapidly on the code in `contract.rs`, +both the logic and the tests. Once the code is finalized, you can copy over some unit +tests into the integration.rs and make the needed changes. This ensures the compiled +Wasm also behaves as desired in the real system. + +## Generating JSON Schema + +While the Wasm calls (`init`, `handle`, `query`) accept JSON, this is not enough +information to use it. We need to expose the schema for the expected messages to the +clients. You can generate this schema by calling `cargo schema`, which will output +4 files in `./schema`, corresponding to the 3 message types the contract accepts, +as well as the internal `State`. + +These files are in standard json-schema format, which should be usable by various +client side tools, either to auto-generate codecs, or just to validate incoming +json wrt. the defined schema. + +## Preparing the Wasm bytecode for production + +Before we upload it to a chain, we need to ensure the smallest output size possible, +as this will be included in the body of a transaction. We also want to have a +reproducible build process, so third parties can verify that the uploaded Wasm +code did indeed come from the claimed rust code. + +To solve both these issues, we have produced `rust-optimizer`, a docker image to +produce an extremely small build output in a consistent manner. The suggest way +to run it is this: + +```sh +docker run --rm -v "$$(pwd)":/contract \ + --mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/contract/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + enigmampc/secret-contract-optimizer:1.0.3 +``` + +We must mount the contract code to `/contract`. You can use an absolute path instead +of `$(pwd)` if you don't want to `cd` to the directory first. The other two +volumes are nice for speedup. Mounting `/contract/target` in particular is useful +to avoid docker overwriting your local dev files with root permissions. +Note the `/contract/target` cache is unique for each contract being compiled to limit +interference, while the registry cache is global. + +This is rather slow compared to local compilations, especially the first compile +of a given contract. The use of the two volume caches is very useful to speed up +following compiles of the same contract. + +This produces a `contract.wasm` file in the current directory (which must be the root +directory of your rust project, the one with `Cargo.toml` inside). As well as +`hash.txt` containing the Sha256 hash of `contract.wasm`, and it will rebuild +your schema files as well. + +### Testing production build + +Once we have this compressed `contract.wasm`, we may want to ensure it is actually +doing everything it is supposed to (as it is about 4% of the original size). +If you update the "WASM" line in `tests/integration.rs`, it will run the integration +steps on the optimized build, not just the normal build. I have never seen a different +behavior, but it is nice to verify sometimes. + +```rust +static WASM: &[u8] = include_bytes!("../contract.wasm"); +``` + +Note that this is the same (deterministic) code you will be uploading to +a blockchain to test it out, as we need to shrink the size and produce a +clear mapping from wasm hash back to the source code. diff --git a/contracts/galactic_pools/exp/Importing.md b/contracts/galactic_pools/exp/Importing.md new file mode 100644 index 000000000..e367b6582 --- /dev/null +++ b/contracts/galactic_pools/exp/Importing.md @@ -0,0 +1,62 @@ +# Importing + +In [Publishing](./Publishing.md), we discussed how you can publish your contract to the world. +This looks at the flip-side, how can you use someone else's contract (which is the same +question as how they will use your contract). Let's go through the various stages. + +## Verifying Artifacts + +Before using remote code, you most certainly want to verify it is honest. + +The simplest audit of the repo is to simply check that the artifacts in the repo +are correct. This involves recompiling the claimed source with the claimed builder +and validating that the locally compiled code (hash) matches the code hash that was +uploaded. This will verify that the source code is the correct preimage. Which allows +one to audit the original (Rust) source code, rather than looking at wasm bytecode. + +We have a script to do this automatic verification steps that can +easily be run by many individuals. Please check out +[`cosmwasm-verify`](https://github.com/CosmWasm/cosmwasm-verify/blob/master/README.md) +to see a simple shell script that does all these steps and easily allows you to verify +any uploaded contract. + +## Reviewing + +Once you have done the quick programatic checks, it is good to give at least a quick +look through the code. A glance at `examples/schema.rs` to make sure it is outputing +all relevant structs from `contract.rs`, and also ensure `src/lib.rs` is just the +default wrapper (nothing funny going on there). After this point, we can dive into +the contract code itself. Check the flows for the handle methods, any invariants and +permission checks that should be there, and a reasonable data storage format. + +You can dig into the contract as far as you want, but it is important to make sure there +are no obvious backdoors at least. + +## Decentralized Verification + +It's not very practical to do a deep code review on every dependency you want to use, +which is a big reason for the popularity of code audits in the blockchain world. We trust +some experts review in lieu of doing the work ourselves. But wouldn't it be nice to do this +in a decentralized manner and peer-review each other's contracts? Bringing in deeper domain +knowledge and saving fees. + +Luckily, there is an amazing project called [crev](https://github.com/crev-dev/cargo-crev/blob/master/cargo-crev/README.md) +that provides `A cryptographically verifiable code review system for the cargo (Rust) package manager`. + +I highly recommend that CosmWasm contract developers get set up with this. At minimum, we +can all add a review on a package that programmatically checked out that the json schemas +and wasm bytecode do match the code, and publish our claim, so we don't all rely on some +central server to say it validated this. As we go on, we can add deeper reviews on standard +packages. + +If you want to use `cargo-crev`, please follow their +[getting started guide](https://github.com/crev-dev/cargo-crev/blob/master/cargo-crev/src/doc/getting_started.md) +and once you have made your own *proof repository* with at least one *trust proof*, +please make a PR to the [`cawesome-wasm`]() repo with a link to your repo and +some public name or pseudonym that people know you by. This allows people who trust you +to also reuse your proofs. + +There is a [standard list of proof repos](https://github.com/crev-dev/cargo-crev/wiki/List-of-Proof-Repositories) +with some strong rust developers in there. This may cover dependencies like `serde` and `snafu` +but will not hit any CosmWasm-related modules, so we look to bootstrap a very focused +review community. diff --git a/contracts/galactic_pools/exp/LICENSE b/contracts/galactic_pools/exp/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/contracts/galactic_pools/exp/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/contracts/galactic_pools/exp/Makefile b/contracts/galactic_pools/exp/Makefile new file mode 100644 index 000000000..c94c77787 --- /dev/null +++ b/contracts/galactic_pools/exp/Makefile @@ -0,0 +1,68 @@ +.PHONY: check +check: + cargo check + +.PHONY: clippy +clippy: + cargo clippy + +PHONY: test +test: unit-test + +.PHONY: unit-test +unit-test: + cargo unit-test + +# This is a local build with debug-prints activated. Debug prints only show up +# in the local development chain (see the `start-server` command below) +# and mainnet won't accept contracts built with the feature enabled. +.PHONY: build _build +build: _build compress-wasm +_build: + RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown --features="debug-print" + +# This is a build suitable for uploading to mainnet. +# Calls to `debug_print` get removed by the compiler. +.PHONY: build-mainnet _build-mainnet +build-mainnet: _build-mainnet compress-wasm +_build-mainnet: + RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown + +# like build-mainnet, but slower and more deterministic +.PHONY: build-mainnet-reproducible +build-mainnet-reproducible: + docker run --rm -v "$$(pwd)":/contract \ + --mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/contract/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + enigmampc/secret-contract-optimizer:1.0.3 + +.PHONY: compress-wasm +compress-wasm: + cp ./target/wasm32-unknown-unknown/release/*.wasm ./contract.wasm + @## The following line is not necessary, may work only on linux (extra size optimization) + @# wasm-opt -Os ./contract.wasm -o ./contract.wasm + cat ./contract.wasm | gzip -9 > ./contract.wasm.gz + +.PHONY: schema +schema: + cargo run --example schema + +# Run local development chain with four funded accounts (named a, b, c, and d) +.PHONY: start-server +start-server: # CTRL+C to stop + docker run -it --rm \ + -p 26657:26657 -p 26656:26656 -p 1317:1317 -p 5000:5000 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.2.6 + +# This relies on running `start-server` in another console +# You can run other commands on the secretcli inside the dev image +# by using `docker exec secretdev secretcli`. +.PHONY: store-contract-local +store-contract-local: + docker exec secretdev secretcli tx compute store -y --from a --gas 1000000 /root/code/contract.wasm.gz + +.PHONY: clean +clean: + cargo clean + -rm -f ./contract.wasm ./contract.wasm.gz diff --git a/contracts/galactic_pools/exp/NOTICE b/contracts/galactic_pools/exp/NOTICE new file mode 100644 index 000000000..216c96976 --- /dev/null +++ b/contracts/galactic_pools/exp/NOTICE @@ -0,0 +1,13 @@ +Copyright {{ "now" | date: "%Y" }} {{authors}} + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/contracts/galactic_pools/exp/Publishing.md b/contracts/galactic_pools/exp/Publishing.md new file mode 100644 index 000000000..35f52129d --- /dev/null +++ b/contracts/galactic_pools/exp/Publishing.md @@ -0,0 +1,115 @@ +# Publishing Contracts + +This is an overview of how to publish the contract's source code in this repo. +We use Cargo's default registry [crates.io](https://crates.io/) for publishing contracts written in Rust. + +## Preparation + +Ensure the `Cargo.toml` file in the repo is properly configured. In particular, you want to +choose a name starting with `cw-`, which will help a lot finding CosmWasm contracts when +searching on crates.io. For the first publication, you will probably want version `0.1.0`. +If you have tested this on a public net already and/or had an audit on the code, +you can start with `1.0.0`, but that should imply some level of stability and confidence. +You will want entries like the following in `Cargo.toml`: + +```toml +name = "cw-escrow" +version = "0.1.0" +description = "Simple CosmWasm contract for an escrow with arbiter and timeout" +repository = "https://github.com/confio/cosmwasm-examples" +``` + +You will also want to add a valid [SPDX license statement](https://spdx.org/licenses/), +so others know the rules for using this crate. You can use any license you wish, +even a commercial license, but we recommend choosing one of the following, unless you have +specific requirements. + +* Permissive: [`Apache-2.0`](https://spdx.org/licenses/Apache-2.0.html#licenseText) or [`MIT`](https://spdx.org/licenses/MIT.html#licenseText) +* Copyleft: [`GPL-3.0-or-later`](https://spdx.org/licenses/GPL-3.0-or-later.html#licenseText) or [`AGPL-3.0-or-later`](https://spdx.org/licenses/AGPL-3.0-or-later.html#licenseText) +* Commercial license: `Commercial` (not sure if this works, I cannot find examples) + +It is also helpful to download the LICENSE text (linked to above) and store this +in a LICENSE file in your repo. Now, you have properly configured your crate for use +in a larger ecosystem. + +### Updating schema + +To allow easy use of the contract, we can publish the schema (`schema/*.json`) together +with the source code. + +```sh +cargo schema +``` + +Ensure you check in all the schema files, and make a git commit with the final state. +This commit will be published and should be tagged. Generally, you will want to +tag with the version (eg. `v0.1.0`), but in the `cosmwasm-examples` repo, we have +multiple contracts and label it like `escrow-0.1.0`. Don't forget a +`git push && git push --tags` + +### Note on build results + +Build results like Wasm bytecode or expected hash don't need to be updated since +the don't belong to the source publication. However, they are excluded from packaging +in `Cargo.toml` which allows you to commit them to your git repository if you like. + +```toml +exclude = ["contract.wasm", "hash.txt"] +``` + +A single source code can be built with multiple different optimizers, so +we should not make any strict assumptions on the tooling that will be used. + +## Publishing + +Now that your package is properly configured and all artifacts are committed, it +is time to share it with the world. +Please refer to the [complete instructions for any questions](https://rurust.github.io/cargo-docs-ru/crates-io.html), +but I will try to give a quick overview of the happy path here. + +### Registry + +You will need an account on [crates.io](https://crates.io) to publish a rust crate. +If you don't have one already, just click on "Log in with GitHub" in the top-right +to quickly set up a free account. Once inside, click on your username (top-right), +then "Account Settings". On the bottom, there is a section called "API Access". +If you don't have this set up already, create a new token and use `cargo login` +to set it up. This will now authenticate you with the `cargo` cli tool and allow +you to publish. + +### Uploading + +Once this is set up, make sure you commit the current state you want to publish. +Then try `cargo publish --dry-run`. If that works well, review the files that +will be published via `cargo package --list`. If you are satisfied, you can now +officially publish it via `cargo publish`. + +Congratulations, your package is public to the world. + +### Sharing + +Once you have published your package, people can now find it by +[searching for "cw-" on crates.io](https://crates.io/search?q=cw). +But that isn't exactly the simplest way. To make things easier and help +keep the ecosystem together, we suggest making a PR to add your package +to the [`cawesome-wasm`](https://github.com/cosmwasm/cawesome-wasm) list. + +### Organizations + +Many times you are writing a contract not as a solo developer, but rather as +part of an organization. You will want to allow colleagues to upload new +versions of the contract to crates.io when you are on holiday. +[These instructions show how]() you can set up your crate to allow multiple maintainers. + +You can add another owner to the crate by specifying their github user. Note, you will +now both have complete control of the crate, and they can remove you: + +`cargo owner --add ethanfrey` + +You can also add an existing github team inside your organization: + +`cargo owner --add github:confio:developers` + +The team will allow anyone who is currently in the team to publish new versions of the crate. +And this is automatically updated when you make changes on github. However, it will not allow +anyone in the team to add or remove other owners. diff --git a/contracts/galactic_pools/exp/README.md b/contracts/galactic_pools/exp/README.md new file mode 100644 index 000000000..e576daa5f --- /dev/null +++ b/contracts/galactic_pools/exp/README.md @@ -0,0 +1,10 @@ +# Secret Contract Skeleton Template + +Blank slate contract with viewing key functionality to be fleshed for your own contracts. CW v1.0 + + +## Viewing Key Functions + +`create_key()` + +`check_key()` \ No newline at end of file diff --git a/contracts/galactic_pools/exp/examples/schema.rs b/contracts/galactic_pools/exp/examples/schema.rs new file mode 100644 index 000000000..781059968 --- /dev/null +++ b/contracts/galactic_pools/exp/examples/schema.rs @@ -0,0 +1,19 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; + +use experience_contract::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use experience_contract::state::Config; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(InstantiateMsg), &out_dir); + export_schema(&schema_for!(ExecuteMsg), &out_dir); + export_schema(&schema_for!(QueryMsg), &out_dir); + export_schema(&schema_for!(Config), &out_dir); +} diff --git a/contracts/galactic_pools/exp/rustfmt.toml b/contracts/galactic_pools/exp/rustfmt.toml new file mode 100644 index 000000000..11a85e6a9 --- /dev/null +++ b/contracts/galactic_pools/exp/rustfmt.toml @@ -0,0 +1,15 @@ +# stable +newline_style = "unix" +hard_tabs = false +tab_spaces = 4 + +# unstable... should we require `rustup run nightly cargo fmt` ? +# or just update the style guide when they are stable? +#fn_single_line = true +#format_code_in_doc_comments = true +#overflow_delimited_expr = true +#reorder_impl_items = true +#struct_field_align_threshold = 20 +#struct_lit_single_line = true +#report_todo = "Always" + diff --git a/contracts/galactic_pools/exp/src/contract.rs b/contracts/galactic_pools/exp/src/contract.rs new file mode 100644 index 000000000..db0d07c47 --- /dev/null +++ b/contracts/galactic_pools/exp/src/contract.rs @@ -0,0 +1,1260 @@ +use std::ops::{Add, AddAssign, Sub}; + +use rand::distributions::Uniform; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaChaRng; +use s_toolkit::permit::{validate, Permit, TokenPermissions}; +use s_toolkit::utils::types::Contract; +use s_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; +use shade_protocol::c_std; + +use crate::error::{self, ContractError}; +use crate::msg::{ + AddContract, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, + ResponseStatus::Success, VerifiedContractRes, WeightUpdate, +}; +use crate::msg::{ConfigRes, Entropy, MintingSchedule}; +use crate::state::{ + sort_schedule, Config, ContractStored, Schedule, ScheduleUnit, SupplyPool, VerifiedContract, + XpSlot, CONFIG, EXP_ACCOUNTS, PREFIX_REVOKED_PERMITS, SUPPLY_POOL, VERIFIED_CONTRACTS, + XP_APPEND_STORE, XP_NONCE, +}; +use sha2::{Digest, Sha256}; + +use shade_protocol::{ + c_std::{ + entry_point, to_binary, Addr, Attribute, Binary, CosmosMsg, Deps, DepsMut, Env, + MessageInfo, Response, StdError, Storage, Uint128, WasmMsg, + }, + s_toolkit, +}; + +#[entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + // Initialize admins + let mut admins = Vec::new(); + if let Some(ad) = msg.admin { + for admin in ad { + admins.push(admin); + } + } else { + admins.push(info.sender) + } + + // Process minting schedules and calculate total XP + let mut total_xp: u128 = 0u128; + let mut schedules_vec: Vec = Vec::new(); + //we use schedule's max duration and turn it into season_duration + let mut max_duration: u64 = 0; + for schedule in &msg.schedules { + total_xp += schedule.duration as u128 * schedule.mint_per_block.u128(); + + schedules_vec.push(ScheduleUnit { + end_block: schedule.duration.add(env.block.height), + mint_per_block: schedule.mint_per_block, + duration: schedule.duration, + start_block: schedule + .start_after + .unwrap_or_default() + .add(env.block.height), + start_after: schedule.start_after, + }); + + if schedule.duration > max_duration { + max_duration = schedule.duration; + } + } + + // Create the contract configuration + let config = Config { + admins, + contract_address: env.contract.address, + total_weight: 0, + minting_schedule: schedules_vec, + season_counter: 1, + verified_contracts: Vec::new(), + season_duration: max_duration, + season_ending_block: env.block.height.add(max_duration), + season_starting_block: env.block.height, + grand_prize_contract: None, + }; + + //Save data to storage + CONFIG.save(deps.storage, &config)?; + + SUPPLY_POOL.save( + deps.storage, + config.season_counter, + &SupplyPool { + season_total_xp_cap: Uint128::from(total_xp), + xp_claimed_by_contracts: Uint128::default(), + xp_claimed_by_users: Uint128::default(), + }, + )?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Instantiate { status: Success })?)) +} + +//-------------------------------------------- HANDLES --------------------------------- +#[entry_point] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + //Admins + ExecuteMsg::AddAdmin { address } => try_add_admin(deps, env, info, address), + ExecuteMsg::RemoveAdmin { address } => try_remove_admin(deps, env, info, address), + ExecuteMsg::AddContract { contracts, .. } => try_add_contract(deps, env, info, contracts), + ExecuteMsg::RemoveContract { contracts } => try_remove_contract(deps, env, info, contracts), + ExecuteMsg::SetGrandPrizeContract { address } => { + try_set_grand_prize_contract(deps, env, info, address) + } + ExecuteMsg::SetSchedule { schedule } => try_set_schedule(deps, env, info, schedule), + ExecuteMsg::ResetSeason {} => try_reset_season(deps, env, info), + ExecuteMsg::UpdateWeights { weights } => try_update_weights(deps, env, info, weights), + + //Verified Contracts + ExecuteMsg::AddExp { address, exp } => try_add_exp(deps, env, info, address, exp), + ExecuteMsg::UpdateLastClaimed {} => try_update_last_claimed(deps, env, info), + + //Verified Contracts + Users + Grand prize contract + ExecuteMsg::CreateViewingKey { entropy } => try_create_key(deps, env, info, entropy), + ExecuteMsg::SetViewingKey { key, .. } => try_set_key(deps, info, &key), + + //Grand prize contract + Admin + ExecuteMsg::GetWinners { no_of_winners } => { + try_get_winners(deps.as_ref(), env, no_of_winners) + } + } +} + +#[entry_point] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { + match msg { + QueryMsg::ContractInfo {} => query_contract_info(deps, env), + QueryMsg::VerifiedContracts { + start_page, + page_size, + } => query_verified_contracts(deps, env, start_page, page_size), + + QueryMsg::WithPermit { permit, query } => permit_queries(deps, permit, query), + + _ => viewing_keys_queries(deps, env, msg), + } +} + +/// Returns Result +/// +/// Adds admin address +/// +/// # Arguments +/// +/// * `deps` - DepsMut containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - Carries the info of who sent the message and how much native funds were sent along +fn try_add_admin( + deps: DepsMut, + _env: Env, + info: MessageInfo, + address: Addr, +) -> Result { + let mut config = CONFIG.load(deps.storage)?; + enforce_admin(deps.as_ref(), &config, &info.sender)?; + + config.admins.push(address); + + CONFIG.save(deps.storage, &config)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::AddAdmin { status: Success })?)) +} + +/// Returns Result +/// +/// Removes admin address +/// +/// # Arguments +/// +/// * `deps` - DepsMut containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - Carries the info of who sent the message and how much native funds were sent along +fn try_remove_admin( + deps: DepsMut, + _env: Env, + info: MessageInfo, + address: Addr, +) -> Result { + let mut config = CONFIG.load(deps.storage)?; + enforce_admin(deps.as_ref(), &config, &info.sender)?; + + if !config.admins.contains(&address) { + return Err(ContractError::CustomError { + val: format!("Address not found in admins: {}", address), + }); + } + + config.admins.retain(|addr| addr != &address); + + if config.admins.is_empty() { + return Err(ContractError::CustomError { + val: "Cannot remove the last admin".to_string(), + }); + } + + CONFIG.save(deps.storage, &config)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RemoveAdmin { status: Success })?)) +} + +/// Returns Result +/// +/// Adds verified contracts +/// +/// # Arguments +/// +/// * `deps` - DepsMut containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - Carries the info of who sent the message and how much native funds were sent along +fn try_add_contract( + deps: DepsMut, + env: Env, + info: MessageInfo, + contracts: Vec, +) -> Result { + let mut config = CONFIG.load(deps.storage)?; + enforce_admin(deps.as_ref(), &config, &info.sender)?; + // Collect existing contracts and their rewards + let mut new_weight = 0u64; + let mut supply_pool = SUPPLY_POOL.load(deps.storage, config.season_counter)?; + + for contract in &config.verified_contracts { + let mut verf_contract = VERIFIED_CONTRACTS.load(deps.storage, contract)?; + let rewards = get_exp( + env.block.height, + config.total_weight, + &config.minting_schedule, + verf_contract.clone(), + ); + verf_contract.available_exp += Uint128::from(rewards); + supply_pool.xp_claimed_by_contracts += Uint128::from(rewards); + + verf_contract.last_claimed = env.block.height; + VERIFIED_CONTRACTS.save(deps.storage, &contract, &verf_contract)?; + } + + for contract in &contracts { + if config.verified_contracts.contains(&contract.address) { + continue; + } + + config.verified_contracts.push(contract.address.clone()); + + VERIFIED_CONTRACTS.save( + deps.storage, + &contract.address, + &VerifiedContract { + code_hash: contract.code_hash.clone(), + available_exp: Uint128::zero(), + weight: contract.weight, + last_claimed: env.block.height, + total_xp: Uint128::default(), + xp_claimed: Uint128::default(), + }, + )?; + + if let Some(final_value) = new_weight.checked_add(contract.weight) { + new_weight = final_value; + } else { + return Err(ContractError::CustomError { + val: "Overflow while adding weights".to_string(), + }); + } + } + + if let Some(final_weight) = config.total_weight.checked_add(new_weight) { + config.total_weight = final_weight; + let supply_pool = SUPPLY_POOL.load(deps.storage, config.season_counter)?; + let total_xp_remaining = + supply_pool.season_total_xp_cap - supply_pool.xp_claimed_by_contracts; + + for contract in &config.verified_contracts { + let mut verf_contract = VERIFIED_CONTRACTS.load(deps.storage, contract)?; + verf_contract.total_xp = verf_contract.xp_claimed; + + if config.total_weight > 0 { + verf_contract.total_xp += total_xp_remaining + * Uint128::from((verf_contract.weight / config.total_weight) as u128); + } + + VERIFIED_CONTRACTS.save(deps.storage, &contract, &verf_contract)?; + } + } else { + return Err(ContractError::CustomError { + val: "Overflow while adding weights".to_string(), + }); + } + + CONFIG.save(deps.storage, &config)?; + SUPPLY_POOL.save(deps.storage, config.season_counter, &supply_pool)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::AddContract { status: Success })?)) +} + +/// Returns Result +/// +/// Removes verified contracts +/// +/// # Arguments +/// +/// * `deps` - DepsMut containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - Carries the info of who sent the message and how much native funds were sent along +fn try_remove_contract( + deps: DepsMut, + env: Env, + info: MessageInfo, + contracts: Vec, +) -> Result { + let mut config = CONFIG.load(deps.storage)?; + enforce_admin(deps.as_ref(), &config, &info.sender)?; + // iterate through all the contracts and make their rewards available + // Check if contracts vector is empty + if contracts.is_empty() { + return Err(ContractError::CustomError { + val: "No contracts provided for removal".to_string(), + }); + } + + if config.verified_contracts.is_empty() { + return Err(ContractError::CustomError { + val: "Total weight of contracts is zero".to_string(), + }); + } + + // Iterate through all the contracts and make their rewards available + let mut supply_pool = SUPPLY_POOL.load(deps.storage, config.season_counter)?; + + for contract in &config.verified_contracts { + let mut verf_contract = VERIFIED_CONTRACTS.load(deps.storage, contract)?; + let rewards = get_exp( + env.block.height, + config.total_weight, + &config.minting_schedule, + verf_contract.clone(), + ); + if let Ok(xp) = verf_contract.available_exp.checked_add(rewards.into()) { + verf_contract.available_exp = xp; + } + if let Ok(xp) = supply_pool + .xp_claimed_by_contracts + .checked_add(rewards.into()) + { + supply_pool.xp_claimed_by_contracts = xp; + } + // supply_pool.exp_claimed_by_contracts = supply_pool + // .exp_claimed_by_contracts + // .checked_add(rewards.into())?; + // // supply_pool.exp_claimed_by_contracts += Uint128::from(rewards); + verf_contract.last_claimed = env.block.height; + VERIFIED_CONTRACTS.save(deps.storage, &contract, &verf_contract)?; + } + + let mut missing_contracts: Vec = vec![]; + let mut new_weight = 0u64; + + for contract in contracts { + if !VERIFIED_CONTRACTS.has(deps.storage, &contract) { + let fail = Attribute { + key: "Missing: ".to_string(), + value: contract.to_string(), + encrypted: false, + }; + missing_contracts.push(fail); + continue; + } + + let mut con = VERIFIED_CONTRACTS.load(deps.storage, &contract)?; + new_weight += con.weight; + con.weight = 0u64; + VERIFIED_CONTRACTS.save(deps.storage, &contract, &con)?; + } + + // config.total_weight.sub_assign(new_weight); + + if let Some(w) = config.total_weight.checked_sub(new_weight) { + config.total_weight = w; + } else { + return Err(ContractError::Overflow {}); + } + let supply_pool = SUPPLY_POOL.load(deps.storage, config.season_counter)?; + let total_xp_remaining = supply_pool.season_total_xp_cap - supply_pool.xp_claimed_by_contracts; + + for contract in &config.verified_contracts { + let mut verf_contract = VERIFIED_CONTRACTS.load(deps.storage, contract)?; + verf_contract.total_xp = verf_contract.xp_claimed + + total_xp_remaining + * Uint128::from((verf_contract.weight / config.total_weight) as u128); + + VERIFIED_CONTRACTS.save(deps.storage, &contract, &verf_contract)?; + } + CONFIG.save(deps.storage, &config)?; + SUPPLY_POOL.save(deps.storage, config.season_counter, &supply_pool)?; + + Ok(Response::new() + .add_attributes(missing_contracts) + .set_data(to_binary(&ExecuteAnswer::RemoveContract { + status: Success, + })?)) +} + +/// Returns Result +/// +/// Set grand prize contract +/// +/// # Arguments +/// +/// * `deps` - DepsMut containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - Carries the info of who sent the message and how much native funds were sent along +fn try_set_grand_prize_contract( + deps: DepsMut, + _env: Env, + info: MessageInfo, + address: Addr, +) -> Result { + let mut config = CONFIG.load(deps.storage)?; + enforce_admin(deps.as_ref(), &config, &info.sender)?; + + config.grand_prize_contract = Some(address); + + CONFIG.save(deps.storage, &config)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::SetGrandPrizeContract { + status: Success, + })?), + ) +} + +/// Returns Result +/// +/// Set minting schedule for xp +/// +/// # Arguments +/// +/// * `deps` - DepsMut containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - Carries the info of who sent the message and how much native funds were sent along +/// * `schedule` - Minting schedule +fn try_set_schedule( + deps: DepsMut, + env: Env, + info: MessageInfo, + schedule: MintingSchedule, +) -> Result { + let mut config = CONFIG.load(deps.storage)?; + enforce_admin(deps.as_ref(), &config, &info.sender)?; + let mut supply_pool = SUPPLY_POOL.load(deps.storage, config.season_counter)?; + + //get xp already minted + let already_mined_xp = get_exp( + env.block.height, + config.total_weight, + &config.minting_schedule, + VerifiedContract { + code_hash: String::new(), + available_exp: Uint128::default(), + weight: config.total_weight, + last_claimed: config.season_starting_block, + total_xp: Uint128::default(), + xp_claimed: Uint128::default(), // TODO check this + }, + ); + + //Should have the ability to + //1) update the duration of season + //2) should update the minting of a schedule + + let mut total_xp: u128 = 0u128; + let mut schedule_vec: Vec = Vec::new(); + //we use schedule's max duration and turn it into season_duration + let mut max_end_block: u64 = 0; + for sch in schedule { + let start_block = sch.start_after.unwrap_or_default().add(env.block.height); + + let mut end_block; + + if sch.continue_with_current_season { + let difference = (env.block.height).sub(config.season_starting_block); + end_block = sch.duration.add(env.block.height).sub(difference); + total_xp += (sch.duration - difference) as u128 * sch.mint_per_block.u128(); + } else { + end_block = sch.duration.add(env.block.height); + total_xp += sch.duration as u128 * sch.mint_per_block.u128(); + } + + end_block = end_block.add(sch.start_after.unwrap_or_default()); + + schedule_vec.push(ScheduleUnit { + end_block, + mint_per_block: sch.mint_per_block, + duration: sch.duration, + start_block, + start_after: sch.start_after, + }); + + if end_block > max_end_block { + max_end_block = end_block; + } + } + + config.season_ending_block = max_end_block; + config.season_duration = max_end_block.sub(config.season_starting_block); + + let mut s = schedule_vec; + sort_schedule(&mut s); + + config.minting_schedule = s; + + total_xp += already_mined_xp; + + supply_pool.season_total_xp_cap = Uint128::from(total_xp); + + // config.minting_schedule = s; + CONFIG.save(deps.storage, &config)?; + SUPPLY_POOL.save(deps.storage, config.season_counter, &supply_pool)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetSchedule { status: Success })?)) +} + +/// Returns Result +/// +/// Resets the seasons +/// +/// # Arguments +/// +/// * `deps` - DepsMut containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - Carries the info of who sent the message and how much native funds were sent along +fn try_reset_season(deps: DepsMut, env: Env, info: MessageInfo) -> Result { + let mut config = CONFIG.load(deps.storage)?; + enforce_admin(deps.as_ref(), &config, &info.sender)?; + + config.season_counter = config.season_counter.add(1u64); + + // TODO send transaction to grandprize contract to end round. + let mut max_end_block: u64 = 0; + let mut total_xp: u128 = 0u128; + + //RESETS the schedule using duraion. + + for schedule in &mut config.minting_schedule { + schedule.start_block = env + .block + .height + .add(schedule.start_after.unwrap_or_default()); + + schedule.end_block = env + .block + .height + .add(schedule.duration) + .add(schedule.start_after.unwrap_or_default()); + + total_xp += schedule.duration as u128 * schedule.mint_per_block.u128(); + + if schedule.end_block > max_end_block { + max_end_block = schedule.end_block; + } + } + + config.season_starting_block = env.block.height; + config.season_ending_block = max_end_block; + config.season_duration = max_end_block.sub(env.block.height); + + CONFIG.save(deps.storage, &config)?; + + let supply_pool = SupplyPool { + season_total_xp_cap: Uint128::from(total_xp), + xp_claimed_by_contracts: Uint128::zero(), + xp_claimed_by_users: Uint128::zero(), + }; + SUPPLY_POOL.save(deps.storage, config.season_counter, &supply_pool)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::EndRound { status: Success })?)) +} + +/// Returns Result +/// +/// Update the weights of already exiting contracts +/// +/// # Arguments +/// +/// * `deps` - DepsMut containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - Carries the info of who sent the message and how much native funds were sent along +fn try_update_weights( + deps: DepsMut, + env: Env, + info: MessageInfo, + contracts: Vec, +) -> Result { + // let mut state = config_read(&deps.storage).load()?; + let mut config = CONFIG.load(deps.storage)?; + enforce_admin(deps.as_ref(), &config, &info.sender)?; + + let mut new_weight_counter: u64 = 0; + let mut old_weight_counter: u64 = 0; + + // Update reward contracts one by one + for to_update in contracts { + let raw_contract = VERIFIED_CONTRACTS.load(deps.storage, &to_update.address); + let mut contract; + if raw_contract.is_ok() { + contract = raw_contract?; + } else { + return Err(ContractError::CustomError { + val: format!( + "Contract address {} is not a a verified contract. Add contract first", + to_update.address + ), + }); + } + + // There is no need to update a SPY twice in a block, and there is no need to update a SPY + // that had 0 weight until now + if contract.last_claimed < env.block.height && contract.weight > 0 { + // Calc amount to mint for this spy contract and push to messages + let rewards = get_exp( + env.block.height, + config.total_weight, + &config.minting_schedule, + contract.clone(), + ); + contract.available_exp += Uint128::from(rewards); + } + let old_weight = contract.weight; + let new_weight = to_update.weight; + + // Set new weight and update total counter + contract.weight = new_weight; + contract.last_claimed = env.block.height; + VERIFIED_CONTRACTS.save(deps.storage, &to_update.address, &contract)?; + + // Update counters to batch update after the loop + new_weight_counter = new_weight_counter + .checked_add(new_weight) + .ok_or(ContractError::Overflow {})?; + old_weight_counter = old_weight_counter + .checked_add(old_weight) + .ok_or(ContractError::Overflow {})?; + } + + config.total_weight = config + .total_weight + .checked_sub(old_weight_counter) + .and_then(|intermediate| intermediate.checked_add(new_weight_counter)) + .ok_or(ContractError::Underflow {})?; + + let supply_pool = SUPPLY_POOL.load(deps.storage, config.season_counter)?; + let total_xp_remaining = supply_pool.season_total_xp_cap - supply_pool.xp_claimed_by_contracts; + + for contract in &config.verified_contracts { + let mut verf_contract = VERIFIED_CONTRACTS.load(deps.storage, contract)?; + verf_contract.total_xp = verf_contract.xp_claimed; + + if config.total_weight > 0 { + verf_contract.total_xp += total_xp_remaining + * Uint128::from((verf_contract.weight / config.total_weight) as u128); + } + + VERIFIED_CONTRACTS.save(deps.storage, &contract, &verf_contract)?; + } + + CONFIG.save(deps.storage, &config)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::UpdateWeights { + status: Success, + })?), + ) +} + +/// Returns Result +/// +/// Adds exp to user account +/// +/// # Arguments +/// +/// * `deps` - DepsMut containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - Carries the info of who sent the message and how much native funds were sent along +fn try_add_exp( + deps: DepsMut, + _env: Env, + info: MessageInfo, + user_address: Addr, + exp: Uint128, +) -> Result { + if !VERIFIED_CONTRACTS.has(deps.storage, &info.sender) { + return Err(ContractError::CustomError { + val: "This function can only be called by a verified contract".to_string(), + }); + } + let config = CONFIG.load(deps.storage)?; + + let mut contract = VERIFIED_CONTRACTS.load(deps.storage, &info.sender).unwrap(); + + if exp > contract.available_exp { + return Err(ContractError::CustomError { + val: "Cannot assign more exp then available".to_string(), + }); + } + + contract.available_exp -= exp; + VERIFIED_CONTRACTS.save(deps.storage, &info.sender, &contract)?; + + if !EXP_ACCOUNTS.has(deps.storage, (&user_address, config.season_counter)) { + EXP_ACCOUNTS.save(deps.storage, (&user_address, config.season_counter), &exp)?; + } else { + let old_exp = EXP_ACCOUNTS + .load(deps.storage, (&user_address, config.season_counter)) + .unwrap(); + let new_exp = old_exp + exp; + EXP_ACCOUNTS.save( + deps.storage, + (&user_address, config.season_counter), + &new_exp, + )?; + } + //updating supply pool + let mut supply_pool = SUPPLY_POOL + .load(deps.storage, config.season_counter) + .unwrap(); + supply_pool.xp_claimed_by_users += exp; + SUPPLY_POOL.save(deps.storage, config.season_counter, &supply_pool)?; + + //Add XP slot + let mut xp_nonce = XP_NONCE + .load(deps.storage, config.season_counter) + .unwrap_or_default(); + + XP_APPEND_STORE + .add_suffix(&format!("{}", config.season_counter)) + .push( + deps.storage, + &XpSlot { + starting_slot: xp_nonce.add(Uint128::one()), + ending_slot: xp_nonce.add(exp), + user_address, + }, + )?; + xp_nonce.add_assign(exp); + + XP_NONCE.save(deps.storage, config.season_counter, &xp_nonce)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::AddExp { status: Success })?)) +} + +/// Returns Result +/// +/// update the contract's exp +/// +/// # Arguments +/// +/// * `deps` - DepsMut containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - Carries the info of who sent the message and how much native funds were sent along +fn try_update_last_claimed( + deps: DepsMut, + env: Env, + info: MessageInfo, +) -> Result { + let config = CONFIG.load(deps.storage)?; + + if env.block.height > config.season_ending_block {} + + if !VERIFIED_CONTRACTS.has(deps.storage, &info.sender) { + return Err(ContractError::CustomError { + val: "This function can only be called by a verified contract".to_string(), + }); + } + + let mut contract = VERIFIED_CONTRACTS.load(deps.storage, &info.sender).unwrap(); + + let available_exp = Uint128::from(get_exp( + env.block.height, + config.total_weight, + &config.minting_schedule, + contract.clone(), + )); + + contract.available_exp += available_exp; + + if env.block.height <= config.season_ending_block { + contract.last_claimed = env.block.height; + } else { + contract.last_claimed = config.season_ending_block; + } + + VERIFIED_CONTRACTS.save(deps.storage, &info.sender, &contract)?; + + let mut supply_pool = SUPPLY_POOL + .load(deps.storage, config.season_counter) + .unwrap(); + supply_pool.xp_claimed_by_contracts += available_exp; + + SUPPLY_POOL.save(deps.storage, config.season_counter, &supply_pool)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::UpdateLastClaimed { + status: Success, + })?), + ) +} + +/// Returns Result +/// +/// create a viewing key +/// +/// # Arguments +/// +/// * `deps` - DepsMut containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - Carries the info of who sent the message and how much native funds were sent along +/// * `entropy` - string to be used as an entropy source for randomization +fn try_create_key( + deps: DepsMut, + env: Env, + info: MessageInfo, + entropy: String, +) -> Result { + let key = ViewingKey::create( + deps.storage, + &info, + &env, + info.sender.as_str(), + entropy.as_bytes(), + ); + + Ok(Response::new() + .add_attribute("viewing_key", key) + .set_data(to_binary(&ExecuteAnswer::CreateViewingKey { + status: Success, + })?)) +} + +/// Returns Result +/// +/// sets the viewing key +/// +/// # Arguments +/// +/// * `deps` - DepsMut containing all the contract's external dependencies +/// * `info` - Carries the info of who sent the message and how much native funds were sent along +/// * `key` - string slice to be used as the viewing key +fn try_set_key(deps: DepsMut, info: MessageInfo, key: &str) -> Result { + ViewingKey::set(deps.storage, info.sender.as_str(), key); + + Ok(Response::new() + .add_attribute("viewing_key", key) + .set_data(to_binary(&ExecuteAnswer::SetViewingKey { + status: Success, + })?)) +} + +//An helper function +fn get_exp( + current_block: u64, + total_weight: u64, + schedule: &Schedule, + contract: VerifiedContract, +) -> u128 { + let mut multiplier = 0; + // Going serially assuming that schedule is not a big vector + for u in schedule.to_owned() { + if current_block >= u.start_block && contract.last_claimed < u.end_block { + if current_block >= u.end_block { + multiplier += + (u.end_block - contract.last_claimed) as u128 * u.mint_per_block.u128(); + + if contract.last_claimed < u.start_block { + multiplier -= + (u.start_block - contract.last_claimed) as u128 * u.mint_per_block.u128(); + } + + // last_update_block = u.end_block; + } else { + multiplier += + (current_block - contract.last_claimed) as u128 * u.mint_per_block.u128(); + + if contract.last_claimed < u.start_block { + multiplier -= + (u.start_block - contract.last_claimed) as u128 * u.mint_per_block.u128(); + } + + // last_update_block = current_block; + // break; // No need to go further up the schedule + } + } + } + + if total_weight.eq(&0u64) { + return 0u128; + } + + let xp = (multiplier * contract.weight as u128) / total_weight as u128; + + xp +} + +/// Enforces that an address is a admin address. +/// Takes in a Deps instance, a Config struct, and an Address, and checks if the provided +/// address is an admin in the given Config. If the address is not an admin, returns a +/// ContractError with a message indicating that the provided address is not an admin. +/// +/// # Arguments +/// +/// * `deps` - Deps containing all the contract's external dependencies +/// * `config` - The Config struct to check for admins +/// * `address` - The Address to check if it is an admin +fn enforce_admin(deps: Deps, config: &Config, address: &Addr) -> Result<(), ContractError> { + if !config.admins.contains(address) { + return Err(error::ContractError::CustomError { + val: format!("Not an admin: {}", address), + }); + } + + Ok(()) +} + +// ---------------------------------------- QUERIES -------------------------------------- + +/// Returns QueryResult from validating a permit and then using its creator's address when +/// performing the specified query +/// +/// # Arguments +/// +/// * `deps` - a reference to Extern containing all the contract's external dependencies +/// * `permit` - the permit used to authentic the query +/// * `query` - the query to perform +fn permit_queries( + deps: Deps, + permit: Permit, + query: QueryWithPermit, +) -> Result { + // Validate permit content + let config = CONFIG.load(deps.storage)?; + + let viewer = validate( + deps, + PREFIX_REVOKED_PERMITS.load(deps.storage).unwrap().as_str(), + &permit, + config.contract_address.to_string(), + None, + )?; + + let account = deps.api.addr_validate(&viewer)?; + + // Permit validated! We can now execute the query. + match query { + QueryWithPermit::UserExp { season } => { + if !permit.check_permission(&TokenPermissions::Balance) { + return Err(ContractError::Unauthorized {}); + } + + query_exp(deps, account, season) + } + } +} + +pub fn viewing_keys_queries(deps: Deps, env: Env, msg: QueryMsg) -> Result { + let (address, key) = msg.get_validation_params(); + + if !is_key_valid(deps.storage, &address, key) { + Err(ContractError::Unauthorized {}) + } else { + match msg { + // Base + QueryMsg::UserExp { + address, season, .. + } => query_exp(deps, address, season), + //Only be checked by admin and verified contracts + QueryMsg::CheckUserExp { + user_address, + address, + key: _, + season, + } => { + let config = CONFIG.load(deps.storage)?; + // let admin_bool = config.admins.to_string() == address; + + let _res = enforce_admin(deps, &config, &address); + + let is_admin = _res.is_ok(); + + let is_verified_contract = VERIFIED_CONTRACTS.has(deps.storage, &address); + + if !(is_admin || is_verified_contract) { + return Err(error::ContractError::Std(StdError::generic_err(format!( + "{} is not authorized to access this query", + address + )))); + } + + query_exp(deps, user_address, season) + } + + QueryMsg::GetWinner { no_of_winners, .. } => { + let config = CONFIG.load(deps.storage)?; + // let admin_bool = config.admins.to_string() == address; + + let _res = enforce_admin(deps, &config, &deps.api.addr_validate(&address)?); + + let is_admin = _res.is_ok(); + + let is_grand_prize; + + if let Some(address) = config.grand_prize_contract { + is_grand_prize = address == address; + } else { + is_grand_prize = false; + } + + if !(is_admin || is_grand_prize) { + return Err(error::ContractError::Std(StdError::generic_err(format!( + "{} is not authorized to access this query", + address + )))); + } + + query_get_winners(deps, env, no_of_winners) + } + + QueryMsg::Contract { address, .. } => query_contract(deps, env, address), + + _ => panic!("This query type does not require authentication"), + } + } +} + +fn try_get_winners( + deps: Deps, + env: Env, + no_of_winners: Option, +) -> Result { + let winners = get_winners(deps, env, no_of_winners.unwrap_or_default())?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::GetWinners { winners })?)) +} + +fn query_contract_info(deps: Deps, env: Env) -> Result { + let config = CONFIG.load(deps.storage)?; + let supply_pool = SUPPLY_POOL.load(deps.storage, config.season_counter)?; + + let info = ConfigRes { + admins: config.admins, + contract_address: config.contract_address, + total_weight: config.total_weight, + minting_schedule: config.minting_schedule, + verified_contracts: config.verified_contracts, + season_count: config.season_counter, + season_starting_block: config.season_starting_block, + season_ending_block: config.season_ending_block, + season_duration: config.season_duration, + season_total_xp_cap: supply_pool.season_total_xp_cap, + current_block: env.block.height, + }; + + Ok(to_binary(&QueryAnswer::ContractInfoResponse { info })?) +} + +fn query_verified_contracts( + deps: Deps, + _env: Env, + start_page: Option, + page_size: Option, +) -> Result { + // Check for defaults + let start = start_page.unwrap_or(0); + let size = page_size.unwrap_or(5); + let config = CONFIG.load(deps.storage)?; + + // Prep empty List of Listing Data for response + let mut contract_list: Vec = vec![]; + + for contract in config + .verified_contracts + .iter() + .skip((start as usize) * (size as usize)) + .take(size as usize) + { + let verf_contract = VERIFIED_CONTRACTS.load(deps.storage, &contract)?; + contract_list.push(VerifiedContractRes { + address: contract.clone(), + available_xp: verf_contract.available_exp, + weight: verf_contract.weight, + last_claimed: verf_contract.last_claimed, + code_hash: verf_contract.code_hash, + }); + } + + Ok(to_binary(&QueryAnswer::VerifiedContractsResponse { + contracts: contract_list, + })?) +} + +fn query_exp(deps: Deps, address: Addr, season: Option) -> Result { + let exp: Uint128; + let config = CONFIG.load(deps.storage)?; + if EXP_ACCOUNTS.has(deps.storage, (&address, config.season_counter)) { + exp = EXP_ACCOUNTS + .load( + deps.storage, + (&address, season.unwrap_or(config.season_counter)), + ) + .unwrap() + } else { + exp = Uint128::from(0_u128) + } + Ok(to_binary(&QueryAnswer::UserExp { exp })?) +} + +fn query_contract(deps: Deps, env: Env, address: Addr) -> Result { + let raw_contract = VERIFIED_CONTRACTS.load(deps.storage, &address); + + let contract; + if raw_contract.is_ok() { + contract = raw_contract.unwrap(); + } else { + contract = VerifiedContract { + available_exp: Uint128::default(), + weight: 0, + last_claimed: env.block.height, + code_hash: String::new(), + total_xp: Uint128::default(), + xp_claimed: Uint128::default(), + }; + } + + let config = CONFIG.load(deps.storage)?; + + let unclaimed_exp = Uint128::from(get_exp( + env.block.height, + config.total_weight, + &config.minting_schedule, + contract.clone(), + )); + + Ok(to_binary(&QueryAnswer::ContractResponse { + available_exp: contract.available_exp, + unclaimed_exp, + weight: contract.weight, + last_claimed: contract.last_claimed, + total_xp: contract.total_xp, + xp_claimed: contract.xp_claimed, + })?) +} + +//----------------------------------------- Helper functions---------------------------------- + +/// Returns bool result of validating an address' viewing key +/// +/// # Arguments +/// +/// * `storage` - a reference to the contract's storage +/// * `account` - a reference to the str whose key should be validated +/// * `viewing_key` - String key used for authentication +fn is_key_valid(storage: &dyn Storage, account: &str, viewing_key: String) -> bool { + ViewingKey::check(storage, account, &viewing_key).is_ok() +} + +fn query_get_winners( + deps: Deps, + env: Env, + no_of_winners: Option, +) -> Result { + let winners = get_winners(deps, env, no_of_winners.unwrap_or_default())?; + + Ok(to_binary(&QueryAnswer::GetWinnersResponse { winners })?) +} + +fn get_winners(deps: Deps, env: Env, no_of_winners: u64) -> Result, ContractError> { + let mut winners: Vec = Vec::new(); + let mut winner_xp_indexed: Vec = Vec::new(); + + let config = CONFIG.load(deps.storage)?; + + let xp_nonce = XP_NONCE + .load(deps.storage, config.season_counter) + .unwrap_or_default(); + + if xp_nonce == Uint128::zero() { + return Err(ContractError::CustomError { + val: String::from("Not enough users to return a winner"), + }); + } + + //run a loop for no_of_winners + //search user with such xp. + let len = XP_APPEND_STORE + .add_suffix(&format!("{}", config.season_counter)) + .get_len(deps.storage) + .unwrap_or_default(); + + let mut min: u32 = 0; + let mut max: u32 = len; + + for _ in 0..no_of_winners { + let drafted_xp = calculate_drafted_xp(env.clone(), xp_nonce.u128()); + + winner_xp_indexed.push(drafted_xp); + + while min <= max { + let mid = (min + max) / 2; + let value = XP_APPEND_STORE + .add_suffix(&format!("{}", config.season_counter)) + .get_at(deps.storage, mid) + .unwrap(); + + if drafted_xp >= value.starting_slot.u128() && drafted_xp <= value.ending_slot.u128() { + winners.push(value.user_address); + break; + } else if value.ending_slot < drafted_xp.into() { + min = mid + 1; + } else { + max = mid - 1; + } + } + } + + Ok(winners) +} + +use std::convert::TryInto; + +fn binary_to_u128(binary: &Binary) -> Option { + let bytes = binary.0.as_slice(); + // Choosing the last 16 bytes for this example + if bytes.len() >= 16 { + Some(u128::from_be_bytes( + bytes[(bytes.len() - 16)..].try_into().unwrap(), + )) + } else { + Some(u128::from_be_bytes(bytes.try_into().unwrap())) + } +} + +fn calculate_drafted_xp(env: Env, xp_nonce: u128) -> u128 { + if let Some(random_binary) = env.block.random { + let random_number = binary_to_u128(&random_binary).unwrap(); // Adjust based on the chosen method + return random_number % xp_nonce + 1; + } + 0 // Fallback value +} diff --git a/contracts/galactic_pools/exp/src/error.rs b/contracts/galactic_pools/exp/src/error.rs new file mode 100644 index 000000000..3bb02b730 --- /dev/null +++ b/contracts/galactic_pools/exp/src/error.rs @@ -0,0 +1,58 @@ +use thiserror::Error; + +use shade_protocol::c_std::StdError; +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("This contract is stopped")] + Stopped {}, + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("This address is unauthorized and/or viewing key is not valid")] + ViewingKeyOrUnauthorized {}, + + #[error("Submessage (id: {id:?}) reply cannot be parsed.")] + ParseReplyError { id: u64 }, + + #[error("Unknown reply id: {id:?}")] + UnexpectedReplyId { id: u64 }, + + /// Whenever UTF-8 bytes cannot be decoded into a unicode string, e.g. in String::from_utf8 or str::from_utf8. + #[error("Cannot decode UTF8 bytes into string: {msg}")] + InvalidUtf8 { msg: String }, + + #[error("Custom Error val: {val:?}")] + CustomError { val: String }, + + // Add any other custom errors you like here. + // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. + #[error("Overflow while adding")] + Overflow {}, + + #[error("Underflow while substracting")] + Underflow {}, +} + +impl ContractError { + pub fn invalid_utf8(msg: impl ToString) -> Self { + ContractError::InvalidUtf8 { + msg: msg.to_string(), + } + } +} + +impl From for ContractError { + fn from(source: std::str::Utf8Error) -> Self { + Self::invalid_utf8(source) + } +} + +impl From for ContractError { + fn from(source: std::string::FromUtf8Error) -> Self { + Self::invalid_utf8(source) + } +} diff --git a/contracts/galactic_pools/exp/src/lib.rs b/contracts/galactic_pools/exp/src/lib.rs new file mode 100644 index 000000000..6a5a1860f --- /dev/null +++ b/contracts/galactic_pools/exp/src/lib.rs @@ -0,0 +1,6 @@ +pub mod contract; +pub mod error; +pub mod msg; +mod multi_test; +pub mod state; +mod unit_test; diff --git a/contracts/galactic_pools/exp/src/msg.rs b/contracts/galactic_pools/exp/src/msg.rs new file mode 100644 index 000000000..540e6aba6 --- /dev/null +++ b/contracts/galactic_pools/exp/src/msg.rs @@ -0,0 +1,254 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use shade_protocol::{ + c_std::{to_binary, Addr, Coin, CosmosMsg, StdResult, Uint128, WasmMsg}, + s_toolkit::{permit::Permit, utils::types::Contract}, +}; + +use crate::state::Schedule; +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct InstantiateMsg { + pub admin: Option>, + pub entropy: String, + pub grand_prize_contract: Option, + pub schedules: MintingSchedule, + pub season_ending_block: u64, +} + +pub type MintingSchedule = Vec; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MintingScheduleUint { + pub continue_with_current_season: bool, + pub duration: u64, + pub mint_per_block: Uint128, + pub start_after: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + AddAdmin { address: Addr }, + AddContract { contracts: Vec }, + AddExp { address: Addr, exp: Uint128 }, + CreateViewingKey { entropy: String }, + GetWinners { no_of_winners: Option }, + RemoveAdmin { address: Addr }, + RemoveContract { contracts: Vec }, + ResetSeason {}, + SetGrandPrizeContract { address: Addr }, + SetSchedule { schedule: MintingSchedule }, + SetViewingKey { key: String }, + UpdateLastClaimed {}, + UpdateWeights { weights: Vec }, +} + +//////////////////////////////////////////////////////////////// Handle Answer //////////////////////////////////////////////////////////////// +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteAnswer { + // Alphabetically sorted + AddAdmin { status: ResponseStatus }, + AddContract { status: ResponseStatus }, + AddExp { status: ResponseStatus }, + BurnExp { status: ResponseStatus }, + CreateViewingKey { status: ResponseStatus }, + EndRound { status: ResponseStatus }, + GetWinners { winners: Vec }, + Instantiate { status: ResponseStatus }, + RemoveAdmin { status: ResponseStatus }, + RemoveContract { status: ResponseStatus }, + SetGrandPrizeContract { status: ResponseStatus }, + SetSchedule { status: ResponseStatus }, + SetViewingKey { status: ResponseStatus }, + UpdateLastClaimed { status: ResponseStatus }, + UpdateRng { status: ResponseStatus }, + UpdateWeights { status: ResponseStatus }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ResponseStatus { + Success, + Failure, +} + +impl ExecuteMsg { + /// Returns a StdResult used to execute a SNIP20 contract function + /// + /// # Arguments + /// + /// * `block_size` - pad the message to blocks of this size + /// * `callback_code_hash` - String holding the code hash of the contract being called + /// * `contract_addr` - address of the contract being called + /// * `send_amount` - Optional Uint128 amount of native coin to send with the callback message + /// NOTE: Only a Deposit message should have an amount sent with it + pub fn to_cosmos_msg( + &self, + mut block_size: usize, + code_hash: String, + contract_addr: String, + send_amount: Option, + ) -> StdResult { + // can not have block size of 0 + if block_size == 0 { + block_size = 1; + } + let mut msg = to_binary(self)?; + space_pad(&mut msg.0, block_size); + let mut funds = Vec::new(); + if let Some(amount) = send_amount { + funds.push(Coin { + amount, + denom: String::from("uscrt"), + }); + } + let execute = WasmMsg::Execute { + contract_addr, + code_hash, + msg, + funds, + }; + Ok(execute.into()) + } +} +/// Take a Vec and pad it up to a multiple of `block_size`, using spaces at the end. +pub fn space_pad(message: &mut Vec, block_size: usize) -> &mut Vec { + let len = message.len(); + let surplus = len % block_size; + if surplus == 0 { + return message; + } + + let missing = block_size - surplus; + message.reserve(missing); + message.extend(std::iter::repeat(b' ').take(missing)); + message +} +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + CheckUserExp { + address: Addr, + key: String, + user_address: Addr, + season: Option, + }, + Contract { + address: Addr, + key: String, + }, + ContractInfo {}, + GetWinner { + address: Addr, + key: String, + no_of_winners: Option, + }, + UserExp { + address: Addr, + key: String, + season: Option, + }, + VerifiedContracts { + page_size: Option, + start_page: Option, + }, + WithPermit { + permit: Permit, + query: QueryWithPermit, + }, +} + +impl QueryMsg { + pub fn get_validation_params(&self) -> (String, String) { + match self { + Self::CheckUserExp { address, key, .. } => (address.to_string(), key.clone()), + Self::Contract { address, key } => (address.to_string(), key.clone()), + Self::GetWinner { address, key, .. } => (address.to_string(), key.to_string()), + Self::UserExp { address, key, .. } => (address.to_string(), key.clone()), + + _ => panic!("This query type does not require authentication"), + } + } +} + +/// queries using permits instead of viewing keys +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryWithPermit { + UserExp { season: Option }, +} + +#[derive(Serialize, Deserialize, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum QueryAnswer { + ContractInfoResponse { + info: ConfigRes, + }, + ContractResponse { + available_exp: Uint128, + last_claimed: u64, + total_xp: Uint128, + unclaimed_exp: Uint128, + weight: u64, + xp_claimed: Uint128, + }, + GetWinnersResponse { + winners: Vec, + }, + UserExp { + exp: Uint128, + }, + VerifiedContractsResponse { + contracts: Vec, + }, + ViewingKeyError { + error: String, + }, +} + +#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)] +pub struct ConfigRes { + pub admins: Vec, + pub contract_address: Addr, + pub current_block: u64, + pub minting_schedule: Schedule, + pub season_count: u64, + pub season_duration: u64, + pub season_ending_block: u64, + pub season_starting_block: u64, + pub season_total_xp_cap: Uint128, + pub total_weight: u64, + pub verified_contracts: Vec, +} + +#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)] +pub struct VerifiedContractRes { + pub address: Addr, + pub available_xp: Uint128, + pub code_hash: String, + pub last_claimed: u64, + pub weight: u64, +} + +/// code hash and address of a contract +#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)] +pub struct AddContract { + /// contract's code hash string + pub address: Addr, + pub code_hash: String, + pub weight: u64, +} + +#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)] +pub struct WeightUpdate { + pub address: Addr, + pub weight: u64, +} + +#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)] +pub struct Entropy { + pub entropy: [u8; 32], + pub seed: [u8; 32], +} diff --git a/contracts/galactic_pools/exp/src/multi_test.rs b/contracts/galactic_pools/exp/src/multi_test.rs new file mode 100644 index 000000000..f98fe2461 --- /dev/null +++ b/contracts/galactic_pools/exp/src/multi_test.rs @@ -0,0 +1,238 @@ +#[cfg(test)] +mod tests { + + use std::ops::Add; + + use c_std::{Addr, BlockInfo, Coin, ContractInfo, Empty, StdResult, Uint128}; + + use rand::{distributions::Uniform, Rng, SeedableRng}; + use rand_chacha::ChaChaRng; + use sha2::{Digest, Sha256}; + use shade_protocol::{ + c_std, + multi_test::{App, AppBuilder, Contract, ContractWrapper, Executor}, + }; + + use crate::msg::{AddContract, Entropy, ExecuteMsg}; + + const ADMIN: &str = "admin00001"; + const DURATION: u64 = 2400000; + const MINT_PER_BLOCK: u64 = 1; + + pub fn _pool_info() -> Box> { + let contract = ContractWrapper::new_with_empty( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ); + Box::new(contract) + } + + fn mock_app(init_funds: &[Coin]) -> App { + AppBuilder::new().build(|router, _, storage| { + router + .bank + .init_balance(storage, &Addr::unchecked(ADMIN), init_funds.to_vec()) + .unwrap(); + }) + } + + pub fn exp_contract() -> Box> { + let contract = ContractWrapper::new_with_empty( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ); + Box::new(contract) + } + #[track_caller] + fn instantiate_exp( + app: &mut App, + address: &Addr, + rng_contract: Option, + ) -> StdResult { + let flex_id = app.store_code(exp_contract()); + let msg = crate::msg::InstantiateMsg { + entropy: "entropy lol 123".to_string(), + admin: Some(vec![address.clone()]), + schedules: vec![crate::msg::MintingScheduleUint { + mint_per_block: Uint128::from(MINT_PER_BLOCK), + duration: DURATION, + start_after: None, + continue_with_current_season: false, + }], + + season_ending_block: 0, + grand_prize_contract: None, + }; + Ok(app + .instantiate_contract(flex_id, Addr::unchecked(ADMIN), &msg, &[], "exp", None) + .unwrap()) + } + + //1)Init pool contract + + #[test] + fn test_initialize_exp_contract() -> StdResult<()> { + let mut app = mock_app(&[]); + instantiate_exp(&mut app, &Addr::unchecked(ADMIN.to_string()), None)?; + Ok(()) + } + + #[test] + fn test_get_winners() -> StdResult<()> { + //Init rng then exp + let mut app = mock_app(&[]); + let exp_contract = instantiate_exp(&mut app, &Addr::unchecked(ADMIN.to_string()), None)?; + + //add contracts + let _res = app + .execute_contract( + Addr::unchecked(&ADMIN.to_string()), + &exp_contract, + &ExecuteMsg::AddContract { + contracts: [AddContract { + address: Addr::unchecked("pool1".to_string()), + code_hash: "pool1 hash".to_string(), + weight: 50, + }] + .to_vec(), + }, + &[], + ) + .unwrap(); + + //xp give it to users. + app.set_block(BlockInfo { + height: app.block_info().height.add(DURATION), + time: app.block_info().time, + chain_id: app.block_info().chain_id, + random: None, + }); + + let msg = ExecuteMsg::UpdateLastClaimed {}; + let _res = app + .execute_contract( + Addr::unchecked(&"pool1".to_string()), + &exp_contract, + &msg, + &[], + ) + .unwrap(); + + //Creating a simulation. + + let total_xp = DURATION * MINT_PER_BLOCK; + let mut assigned_xp = 0; + let init_seed_arr = sha2::Sha256::digest("init_seed".as_bytes()); + let init_seed: [u8; 32] = init_seed_arr.as_slice().try_into().expect("Invalid"); + let init_entropy_arr = sha2::Sha256::digest("init_entropy".as_bytes()); + let init_entropy: [u8; 32] = init_entropy_arr.as_slice().try_into().expect("Invalid"); + let entropy = Entropy { + seed: init_seed, + entropy: init_entropy, + }; + while total_xp > assigned_xp { + //Get a random user here generate a number between 1 and 1000. + let generate_user_id = generate_random_number( + &entropy, + 1, + 100000, + String::from(format!("users {}", assigned_xp)), + ); + + //Get a random xp between 1 and 1000 + let mut generate_xp = generate_random_number( + &entropy, + 1, + 10000, + String::from(format!("xp {}", assigned_xp)), + ); + + let random = 10000 % generate_xp; + + generate_xp -= random; + + let msg = ExecuteMsg::AddExp { + address: Addr::unchecked(format!("user_id {}", generate_user_id).to_string()), + exp: Uint128::from(generate_xp as u128), + }; + + let _res = app.execute_contract(Addr::unchecked("pool1"), &exp_contract, &msg, &[]); + + assigned_xp += generate_xp; + } + + let _res = app + .execute_contract( + Addr::unchecked(&ADMIN.to_string()), + &exp_contract, + &ExecuteMsg::SetViewingKey { + key: String::from("vk_1"), + }, + &[], + ) + .unwrap(); + + //get winners + + let query_msg = crate::msg::QueryMsg::GetWinner { + no_of_winners: Some(1), + key: String::from("vk_1"), + address: Addr::unchecked(String::from(ADMIN)), + }; + let winners_res: crate::msg::QueryAnswer = app + .wrap() + .query_wasm_smart(&exp_contract.code_hash, &exp_contract.address, &query_msg) + .unwrap(); + + if let crate::msg::QueryAnswer::GetWinnersResponse { winners } = winners_res {} + + //Start new round + + let msg = ExecuteMsg::ResetSeason {}; + + let _res = app + .execute_contract( + Addr::unchecked(&ADMIN.to_string()), + &exp_contract, + &msg, + &[], + ) + .unwrap(); + let query_msg = crate::msg::QueryMsg::GetWinner { + no_of_winners: Some(1), + key: String::from("vk_1"), + address: Addr::unchecked(String::from(ADMIN)), + }; + let winners_res: Result = app + .wrap() + .query_wasm_smart(&exp_contract.code_hash, &exp_contract.address, &query_msg); + // println!("{:?}", winners_res.unwrap()); + assert!(winners_res.is_err()); + + Ok(()) + } + + pub fn generate_random_number( + entropy: &Entropy, + low: u64, + high: u64, + extra_entropy: String, + ) -> u64 { + let mut hasher = Sha256::new(); + hasher.update(&entropy.seed); + hasher.update(&entropy.entropy); + hasher.update(extra_entropy); + + let seed: [u8; 32] = hasher.finalize().into(); + + let rng = ChaChaRng::from_seed(seed); + + let range = Uniform::new_inclusive(low, high); + let mut digit_generator = rng.clone().sample_iter(&range); + let drafted_xp = digit_generator.next().unwrap_or(0u64); + + drafted_xp + } +} diff --git a/contracts/galactic_pools/exp/src/state.rs b/contracts/galactic_pools/exp/src/state.rs new file mode 100644 index 000000000..524e9b97c --- /dev/null +++ b/contracts/galactic_pools/exp/src/state.rs @@ -0,0 +1,82 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use shade_protocol::c_std::{Addr, Uint128}; + +use shade_protocol::secret_storage_plus::{AppendStore, Item, Map}; + +/// Basic configuration struct +pub const CONFIG_KEY: &str = "config"; + +pub const CONFIG: Item = Item::new(CONFIG_KEY); +/// Basic configuration struct +pub const SUPPLY_POOL: Map = Map::new("supply_pool"); +/// Revoked permits prefix key +pub const PREFIX_REVOKED_PERMITS: Item = Item::new("revoked"); +/// Map of exp amounts per address +pub const EXP_ACCOUNTS: Map<(&Addr, u64), Uint128> = Map::new("exp_accounts"); +/// List of verified contracts allowed to interact with manager +pub const VERIFIED_CONTRACTS: Map<&Addr, VerifiedContract> = Map::new("contracts"); +/// User XP Append Store +pub const XP_APPEND_STORE: AppendStore = AppendStore::new("xp_append_store"); +/// User XP Nonce +pub const XP_NONCE: Map = Map::new("xp_nonce"); + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct Config { + pub admins: Vec, + pub contract_address: Addr, + pub grand_prize_contract: Option, + pub minting_schedule: Schedule, + pub season_counter: u64, + pub season_duration: u64, + pub season_ending_block: u64, + pub season_starting_block: u64, + pub total_weight: u64, + pub verified_contracts: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct ContractStored { + pub address: Addr, + pub hash: String, +} + +pub fn sort_schedule(s: &mut Schedule) { + s.sort_by(|s1, s2| s1.end_block.cmp(&s2.end_block)) +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct ScheduleUnit { + pub duration: u64, + pub end_block: u64, + pub mint_per_block: Uint128, + pub start_after: Option, + pub start_block: u64, +} + +pub type Schedule = Vec; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct SupplyPool { + pub season_total_xp_cap: Uint128, + pub xp_claimed_by_contracts: Uint128, + pub xp_claimed_by_users: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct VerifiedContract { + pub available_exp: Uint128, + pub code_hash: String, + pub last_claimed: u64, + pub total_xp: Uint128, + pub weight: u64, + pub xp_claimed: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct XpSlot { + pub ending_slot: Uint128, + pub starting_slot: Uint128, + pub user_address: Addr, +} diff --git a/contracts/galactic_pools/exp/src/unit_test.rs b/contracts/galactic_pools/exp/src/unit_test.rs new file mode 100644 index 000000000..fccf4f29b --- /dev/null +++ b/contracts/galactic_pools/exp/src/unit_test.rs @@ -0,0 +1,2290 @@ +#[cfg(test)] +mod tests { + use std::{ + ops::{Add, Sub}, + vec, + }; + + use c_std::{ + coins, from_binary, + testing::{mock_dependencies_with_balance, MOCK_CONTRACT_ADDR}, + Addr, Api, BlockInfo, ContractInfo, Env, StdResult, Timestamp, TransactionInfo, Uint128, + }; + use s_toolkit::utils::types::Contract; + use shade_protocol::{c_std, s_toolkit}; + + use crate::{ + contract::execute, + error::{self, ContractError}, + msg::{ + AddContract, ExecuteMsg, InstantiateMsg, MintingScheduleUint, + QueryAnswer::{self, ContractInfoResponse}, + QueryMsg, WeightUpdate, + }, + state::{ScheduleUnit, CONFIG, SUPPLY_POOL}, + }; + + use crate::contract::{instantiate, query}; + + use c_std::testing::mock_info; + + const OWNER: &str = "admin0000000001"; + const DENOM: &str = "uscrt"; + + /// Returns a default enviroment with height, time, chain_id, and contract address + /// You can submit as is to most contracts, or modify height/time if you want to + /// test for expiration. + /// + /// This is intended for use in test code only. + pub fn custom_mock_env( + height: Option, + time: Option, + chain_id: Option<&str>, + transaction_index: Option, + contract_address: Option<&str>, + ) -> Env { + Env { + block: BlockInfo { + height: height.unwrap_or_default(), + time: Timestamp::from_seconds(time.unwrap_or_default()), + chain_id: chain_id.unwrap_or_default().to_string(), + random: None, + }, + transaction: Some(TransactionInfo { + index: transaction_index.unwrap_or_default(), + hash: String::new(), + }), + contract: ContractInfo { + address: Addr::unchecked(contract_address.unwrap_or(MOCK_CONTRACT_ADDR)), + code_hash: "".to_string(), + }, + } + } + + #[test] + fn test_instantiate_works() -> StdResult<()> { + let mut deps = mock_dependencies_with_balance(&[]); + + let msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![Addr::unchecked(OWNER.to_owned())]), + schedules: Vec::new(), + season_ending_block: 0, + grand_prize_contract: None, + }; + let message_info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + + let _res = instantiate(deps.as_mut(), env.clone(), message_info.clone(), msg).unwrap(); + + let query_msg = QueryMsg::ContractInfo {}; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap())?; + + match res { + ContractInfoResponse { info } => { + assert_eq!(info.admins, vec![OWNER]); + assert_eq!(&info.contract_address.to_string(), "exp_contract"); + assert_eq!(info.minting_schedule, []); + assert_eq!(info.total_weight, 0); + assert_eq!(info.season_total_xp_cap, Uint128::zero()); + } + _ => {} + } + + Ok(()) + } + + #[test] + fn test_add_admin() -> StdResult<()> { + let mut deps = mock_dependencies_with_balance(&[]); + + let msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![Addr::unchecked(OWNER.to_owned())]), + schedules: Vec::new(), + + grand_prize_contract: None, + season_ending_block: 0, + }; + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let config = CONFIG.load(&deps.storage)?; + assert_eq!(config.admins.len(), 1); + + // Test Case: Trying with non-admin address + let info = mock_info("NOT-OWNER", &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let msg = ExecuteMsg::AddAdmin { + address: Addr::unchecked(String::from("admin2")), + }; + + let res = execute(deps.as_mut(), env, info, msg); + + assert_eq!( + res.unwrap_err(), + error::ContractError::CustomError { + val: format!("Not an admin: {}", "NOT-OWNER"), + } + ); + + // Test Case: Executing with admin address + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let msg = ExecuteMsg::AddAdmin { + address: Addr::unchecked(String::from("admin2")), + }; + + let _res = execute(deps.as_mut(), env, info, msg).unwrap(); + + let config = CONFIG.load(&deps.storage)?; + assert_eq!(config.admins.len(), 2); + + Ok(()) + } + + #[test] + fn test_remove_admin() -> StdResult<()> { + let mut deps = mock_dependencies_with_balance(&[]); + + let msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![ + Addr::unchecked(OWNER.to_owned()), + Addr::unchecked("admin2".to_owned()), + ]), + schedules: Vec::new(), + + grand_prize_contract: None, + season_ending_block: 0, + }; + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let config = CONFIG.load(&deps.storage)?; + assert_eq!(config.admins.len(), 2); + + // Test Case: Trying with non-admin address + let info = mock_info("NOT-OWNER", &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let msg = ExecuteMsg::RemoveAdmin { + address: Addr::unchecked(String::from("admin2")), + }; + + let res = execute(deps.as_mut(), env, info, msg); + + assert_eq!( + res.unwrap_err(), + error::ContractError::CustomError { + val: format!("Not an admin: {}", "NOT-OWNER"), + } + ); + + // Test Case: Executing with admin address + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let msg = ExecuteMsg::RemoveAdmin { + address: Addr::unchecked(String::from("admin2")), + }; + + let _res = execute(deps.as_mut(), env, info, msg).unwrap(); + + let config = CONFIG.load(&deps.storage)?; + assert_eq!(config.admins.len(), 1); + + // Test Case: Attempt to remove the last admin + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let msg = ExecuteMsg::RemoveAdmin { + address: Addr::unchecked(String::from(OWNER)), + }; + + let res = execute(deps.as_mut(), env, info, msg); + + assert_eq!( + res.unwrap_err(), + error::ContractError::CustomError { + val: "Cannot remove the last admin".to_string(), + } + ); + + // Test Case: Trying with an invalid address + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let msg = ExecuteMsg::RemoveAdmin { + address: Addr::unchecked(String::from("**")), + }; + + let res = execute(deps.as_mut(), env, info, msg); + + assert_eq!( + res.unwrap_err(), + error::ContractError::CustomError { + val: format!("Address not found in admins: {}", "**"), + } + ); + + Ok(()) + } + + #[test] + fn test_add_contract() -> StdResult<()> { + let mut deps = mock_dependencies_with_balance(&[]); + + let msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![Addr::unchecked(OWNER.to_owned())]), + schedules: Vec::new(), + + grand_prize_contract: None, + season_ending_block: 0, + }; + + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let schedule = [ScheduleUnit { + end_block: env.block.height.add(100), + mint_per_block: Uint128::one(), + duration: 100, + start_block: env.block.height, + start_after: None, + }]; + + let sch = [MintingScheduleUint { + duration: 100, + mint_per_block: Uint128::one(), + continue_with_current_season: false, + start_after: None, + }]; + + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let msg = ExecuteMsg::AddContract { + contracts: [ + AddContract { + address: Addr::unchecked("pool1".to_string()), + code_hash: "pool1 hash".to_string(), + weight: 60, + }, + AddContract { + address: Addr::unchecked("pool2".to_string()), + code_hash: "pool2 hash".to_string(), + weight: 20, + }, + ] + .to_vec(), + }; + + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + //adding contract at block height h + let msg = ExecuteMsg::AddContract { + contracts: [AddContract { + address: Addr::unchecked("pool3".to_string()), + code_hash: "pool3 hash".to_string(), + weight: 20, + }] + .to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + // Get contract info query + let query_msg = QueryMsg::VerifiedContracts { + start_page: None, + page_size: None, + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + + if let crate::msg::QueryAnswer::VerifiedContractsResponse { contracts } = res { + assert_eq!(contracts[0].address, "pool1".to_string()); + assert_eq!(contracts[0].available_xp, Uint128::zero()); + assert_eq!(contracts[0].weight, 60); + assert_eq!(contracts[0].last_claimed, env.block.height); + assert_eq!(contracts[1].address, "pool2".to_string()); + assert_eq!(contracts[1].available_xp, Uint128::zero()); + assert_eq!(contracts[1].weight, 20); + assert_eq!(contracts[1].last_claimed, env.block.height); + assert_eq!(contracts[2].address, "pool3".to_string()); + assert_eq!(contracts[2].available_xp, Uint128::zero()); + assert_eq!(contracts[2].weight, 20); + assert_eq!(contracts[2].last_claimed, env.block.height); + } + + //adding contract at block height h + 100 + //setting block to block + 100 + let env = custom_mock_env( + Some(env.block.height.add(100)), + None, + None, + None, + Some("exp_contract"), + ); + + let msg = ExecuteMsg::AddContract { + contracts: [AddContract { + address: Addr::unchecked("pool4".to_string()), + code_hash: "pool4 hash".to_string(), + weight: 100, + }] + .to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + // Get contract info query + let query_msg = QueryMsg::VerifiedContracts { + start_page: None, + page_size: None, + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + + if let crate::msg::QueryAnswer::VerifiedContractsResponse { contracts } = res { + assert_eq!(contracts[0].address, "pool1".to_string()); + assert_eq!(contracts[0].available_xp, Uint128::from(60u128)); + assert_eq!(contracts[0].weight, 60); + assert_eq!(contracts[0].last_claimed, env.block.height); + assert_eq!(contracts[1].address, "pool2".to_string()); + assert_eq!(contracts[1].available_xp, Uint128::from(20u128)); + assert_eq!(contracts[1].weight, 20); + assert_eq!(contracts[1].last_claimed, env.block.height); + assert_eq!(contracts[2].address, "pool3".to_string()); + assert_eq!(contracts[2].available_xp, Uint128::from(20u128)); + assert_eq!(contracts[2].weight, 20); + assert_eq!(contracts[2].last_claimed, env.block.height); + assert_eq!(contracts[3].address, "pool4".to_string()); + assert_eq!(contracts[3].available_xp, Uint128::from(0u128)); + assert_eq!(contracts[3].weight, 100); + assert_eq!(contracts[3].last_claimed, env.block.height); + } + + let query_msg = QueryMsg::ContractInfo {}; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + ContractInfoResponse { info } => { + assert_eq!(info.admins, vec![OWNER]); + assert_eq!(&info.contract_address.to_string(), "exp_contract"); + assert_eq!(info.minting_schedule, schedule); + assert_eq!(info.total_weight, 200); + } + _ => {} + } + Ok(()) + } + + #[test] + fn test_remove_contract() -> StdResult<()> { + let mut deps = mock_dependencies_with_balance(&[]); + + let msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![Addr::unchecked(OWNER.to_owned())]), + schedules: Vec::new(), + + grand_prize_contract: None, + season_ending_block: 0, + }; + let info = mock_info(OWNER, &[]); + let mut env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _exp_obj = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + // Set schedule + + let schedule = [ScheduleUnit { + end_block: env.block.height.add(100), + mint_per_block: Uint128::one(), + duration: 100, + start_block: env.block.height, + start_after: None, + }]; + + let sch = [MintingScheduleUint { + duration: 100, + mint_per_block: Uint128::one(), + continue_with_current_season: false, + start_after: None, + }]; + + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + // Set weights + + let msg = ExecuteMsg::AddContract { + contracts: [ + AddContract { + address: Addr::unchecked("pool1".to_string()), + code_hash: "pool1 hash".to_string(), + weight: 60, + }, + AddContract { + address: Addr::unchecked("pool2".to_string()), + code_hash: "pool2 hash".to_string(), + weight: 40, + }, + ] + .to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + // Advance block height + env.block.height += 100; + + // Remove contract + let msg = ExecuteMsg::RemoveContract { + contracts: [Addr::unchecked("pool2".to_string())].to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + // Check verified contracts query + let query_msg = QueryMsg::VerifiedContracts { + start_page: None, + page_size: None, + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + crate::msg::QueryAnswer::VerifiedContractsResponse { contracts } => { + assert_eq!(contracts.len(), 2); + assert_eq!(contracts[0].address, "pool1".to_string()); + assert_eq!(contracts[0].available_xp, Uint128::from(60u128)); + assert_eq!(contracts[0].weight, 60); + assert_eq!(contracts[0].last_claimed, env.block.height); + assert_eq!(contracts[1].address, "pool2".to_string()); + assert_eq!(contracts[1].available_xp, Uint128::from(40u128)); + assert_eq!(contracts[1].weight, 0); + assert_eq!(contracts[1].last_claimed, env.block.height); + } + _ => {} + } + + // Check contract info query + let query_msg = QueryMsg::ContractInfo {}; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + ContractInfoResponse { info } => { + assert_eq!(info.admins, vec![OWNER]); + assert_eq!(&info.contract_address.to_string(), "exp_contract"); + assert_eq!(info.minting_schedule, schedule); + assert_eq!(info.total_weight, 60); + } + _ => {} + } + Ok(()) + } + + #[test] + fn test_set_grand_prize_contract() -> StdResult<()> { + let mut deps = mock_dependencies_with_balance(&[]); + + let msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![Addr::unchecked(OWNER.to_owned())]), + schedules: Vec::new(), + + grand_prize_contract: None, + season_ending_block: 0, + }; + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let config = CONFIG.load(&deps.storage)?; + assert_eq!(config.grand_prize_contract, None); + + // Trying with non-admin address + let info = mock_info("NOT-OWNER", &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let msg = ExecuteMsg::SetGrandPrizeContract { + address: Addr::unchecked(String::from("grand_prize_contract")), + }; + + let res = execute(deps.as_mut(), env, info, msg); + + assert_eq!( + res.unwrap_err(), + error::ContractError::CustomError { + val: format!("Not an admin: {}", "NOT-OWNER"), + } + ); + + // Executing with admin address + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let msg = ExecuteMsg::SetGrandPrizeContract { + address: Addr::unchecked(String::from("grand_prize_contract")), + }; + + let _res = execute(deps.as_mut(), env, info, msg).unwrap(); + + let config = CONFIG.load(&deps.storage)?; + assert_eq!( + config.grand_prize_contract.unwrap().to_string(), + String::from("grand_prize_contract") + ); + + // Trying with an invalid address + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let msg = ExecuteMsg::RemoveAdmin { + address: Addr::unchecked(String::from("**")), + }; + + let res = execute(deps.as_mut(), env, info, msg); + + assert_eq!( + res.unwrap_err(), + error::ContractError::CustomError { + val: format!("Address not found in admins: {}", "**"), + } + ); + Ok(()) + } + + #[test] + fn test_set_schedule() -> StdResult<()> { + let mut deps = mock_dependencies_with_balance(&[]); + + let msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![Addr::unchecked(OWNER.to_owned())]), + schedules: Vec::new(), + + grand_prize_contract: None, + season_ending_block: 0, + }; + + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let msg = ExecuteMsg::AddContract { + contracts: [ + AddContract { + address: Addr::unchecked("pool1".to_string()), + code_hash: "pool1 hash".to_string(), + weight: 60, + }, + AddContract { + address: Addr::unchecked("pool2".to_string()), + code_hash: "pool2 hash".to_string(), + weight: 40, + }, + ] + .to_vec(), + }; + + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let mut duration = 100u64; + let sch = [MintingScheduleUint { + duration, + mint_per_block: Uint128::one(), + continue_with_current_season: false, + start_after: None, + }]; + + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + let info = mock_info("Not-Owner", &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); + + if let Err(err) = _res { + assert_eq!( + err, + ContractError::CustomError { + val: format!("Not an admin: Not-Owner"), + } + ) + } + + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg); + + let schdule = vec![ScheduleUnit { + end_block: env.block.height.add(duration), + mint_per_block: Uint128::one(), + duration, + start_block: env.block.height, + start_after: None, + }]; + let query_msg = QueryMsg::ContractInfo {}; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + + match res { + ContractInfoResponse { info } => { + assert_eq!(info.admins, vec![OWNER]); + assert_eq!(&info.contract_address.to_string(), "exp_contract"); + assert_eq!(info.minting_schedule, schdule); + assert_eq!(info.season_count, 1); + assert_eq!(info.season_duration, 100); + assert_eq!(info.season_starting_block, env.block.height); + assert_eq!(info.season_ending_block, env.block.height.add(100)); + assert_eq!(info.total_weight, 100); + assert_eq!( + info.season_total_xp_cap, + Uint128::from(duration as u128 * 1u128) + ); + } + _ => {} + } + + //ability to extend or reduce the size of season. + //extend season + duration = 1000; + let sch = [MintingScheduleUint { + duration, + mint_per_block: Uint128::one(), + continue_with_current_season: false, + start_after: None, + }]; + + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); + + let query_msg = QueryMsg::ContractInfo {}; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + + let schdule = vec![ScheduleUnit { + end_block: env.block.height.add(duration), + mint_per_block: Uint128::one(), + duration, + start_block: env.block.height, + start_after: None, + }]; + match res { + ContractInfoResponse { info } => { + assert_eq!(info.minting_schedule, schdule); + assert_eq!(info.season_count, 1); + assert_eq!(info.season_duration, duration); + assert_eq!(info.season_starting_block, env.block.height); + assert_eq!(info.season_ending_block, env.block.height.add(duration)); + assert_eq!(info.total_weight, 100); + assert_eq!( + info.season_total_xp_cap, + Uint128::from(duration as u128 * 1u128) + ); + } + _ => {} + } + + //reduce season + duration = 10; + let sch = [MintingScheduleUint { + duration, + mint_per_block: Uint128::one(), + continue_with_current_season: false, + start_after: None, + }]; + + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + let info = mock_info(OWNER, &[]); + let mut env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); + + let query_msg = QueryMsg::ContractInfo {}; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + + let schdule = vec![ScheduleUnit { + end_block: env.block.height.add(duration), + mint_per_block: Uint128::one(), + duration, + start_block: env.block.height, + start_after: None, + }]; + match res { + ContractInfoResponse { info } => { + assert_eq!(info.minting_schedule, schdule); + assert_eq!(info.season_count, 1); + assert_eq!(info.season_duration, duration); + assert_eq!(info.season_starting_block, env.block.height); + assert_eq!(info.season_ending_block, env.block.height.add(duration)); + assert_eq!( + info.season_total_xp_cap, + Uint128::from(duration as u128 * 1u128) + ); + assert_eq!(info.total_weight, 100); + } + _ => {} + } + env.block.height = env.block.height.add(duration); + + duration = 1000; + let sch = [MintingScheduleUint { + duration, + mint_per_block: Uint128::one(), + continue_with_current_season: false, + start_after: None, + }]; + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + let info = mock_info(OWNER, &[]); + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap(); + + let query_msg = QueryMsg::ContractInfo {}; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + + let schdule = vec![ScheduleUnit { + end_block: env.block.height.add(duration), + mint_per_block: Uint128::one(), + duration, + start_block: env.block.height, + start_after: None, + }]; + let old_duration: u64 = 10; + match res { + ContractInfoResponse { info } => { + assert_eq!(info.minting_schedule, schdule); + assert_eq!(info.season_count, 1); + assert_eq!(info.season_duration, duration.add(old_duration)); + assert_eq!( + info.season_starting_block, + env.block.height - (old_duration) + ); + assert_eq!(info.season_ending_block, env.block.height.add(duration)); + assert_eq!( + info.season_total_xp_cap, + Uint128::from(duration as u128 * 1u128) + .add(Uint128::from(old_duration as u128 * 1u128)) + ); + assert_eq!(info.total_weight, 100); + } + _ => {} + } + + Ok(()) + } + + #[test] + fn test_update_weight() -> StdResult<()> { + let mut deps = mock_dependencies_with_balance(&[]); + + let msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![Addr::unchecked(OWNER.to_owned())]), + schedules: Vec::new(), + + grand_prize_contract: None, + season_ending_block: 0, + }; + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let msg = ExecuteMsg::AddContract { + contracts: [ + AddContract { + address: Addr::unchecked("pool1".to_string()), + code_hash: "pool1 hash".to_string(), + weight: 60, + }, + AddContract { + address: Addr::unchecked("pool2".to_string()), + code_hash: "pool2 hash".to_string(), + weight: 40, + }, + ] + .to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let query_msg = QueryMsg::VerifiedContracts { + start_page: None, + page_size: None, + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + + match res { + crate::msg::QueryAnswer::VerifiedContractsResponse { contracts } => { + assert_eq!(contracts[0].address, "pool1".to_string()); + assert_eq!(contracts[0].available_xp, Uint128::zero()); + assert_eq!(contracts[0].weight, 60); + assert_eq!(contracts[0].last_claimed, env.block.height); + assert_eq!(contracts[1].address, "pool2".to_string()); + assert_eq!(contracts[1].available_xp, Uint128::zero()); + assert_eq!(contracts[1].weight, 40); + assert_eq!(contracts[1].last_claimed, env.block.height); + } + _ => {} + } + + let query_msg = QueryMsg::ContractInfo {}; + let res: QueryAnswer = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap())?; + + if let QueryAnswer::ContractInfoResponse { info } = res { + assert_eq!(info.total_weight, 100); // Updated total weight after setting pool1 weight to zero + } + + //Updating contracts + + //updating without adding + let w = [WeightUpdate { + address: Addr::unchecked("pool3".to_string()), + weight: 60, + }]; + let msg = ExecuteMsg::UpdateWeights { + weights: w.to_vec(), + }; + let error_res = execute(deps.as_mut(), env.clone(), info.clone(), msg); + + if let Err(err) = error_res { + assert_eq!( + err, + ContractError::CustomError { + val: format!( + "Contract address pool3 is not a a verified contract. Add contract first", + ), + } + ) + } + + //updating + + // Updating the weight to zero + let w = [WeightUpdate { + address: Addr::unchecked("pool1".to_string()), + weight: 0, + }]; + let msg = ExecuteMsg::UpdateWeights { + weights: w.to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + //Total weight + let query_msg = QueryMsg::ContractInfo {}; + let res: QueryAnswer = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap())?; + + if let QueryAnswer::ContractInfoResponse { info } = res { + assert_eq!(info.total_weight, 40); // Updated total weight after setting pool1 weight to zero + } + + let query_msg = QueryMsg::VerifiedContracts { + start_page: None, + page_size: None, + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + crate::msg::QueryAnswer::VerifiedContractsResponse { contracts } => { + assert_eq!(contracts[0].address, "pool1".to_string()); + assert_eq!(contracts[0].weight, 0); // Updated weight for pool1 (zero) + assert_eq!(contracts[1].address, "pool2".to_string()); + assert_eq!(contracts[1].weight, 40); + } + _ => {} + } + + Ok(()) + } + + #[test] + fn test_add_exp() -> StdResult<()> { + let mut deps = mock_dependencies_with_balance(&[]); + let info = mock_info(OWNER, &[]); + + let msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![Addr::unchecked(OWNER.to_owned())]), + schedules: Vec::new(), + + grand_prize_contract: None, + season_ending_block: 0, + }; + + let mut env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _exp_obj = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let sch = [MintingScheduleUint { + duration: 100, + mint_per_block: Uint128::one(), + continue_with_current_season: false, + start_after: None, + }]; + + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let msg = ExecuteMsg::AddContract { + contracts: [ + AddContract { + address: Addr::unchecked("pool1".to_string()), + code_hash: "pool1 hash".to_string(), + weight: 60, + }, + AddContract { + address: Addr::unchecked("pool2".to_string()), + code_hash: "pool2 hash".to_string(), + weight: 40, + }, + ] + .to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + env.block.height += 100; + + let msg = ExecuteMsg::UpdateLastClaimed {}; + let _res = execute( + deps.as_mut(), + env.clone(), + mock_info("pool1", &coins(0, DENOM)), + msg, + ) + .unwrap(); + + let msg = ExecuteMsg::SetViewingKey { + key: "vk_1".to_string(), + }; + let _res = execute( + deps.as_mut(), + env.clone(), + mock_info("pool1", &coins(0, DENOM)), + msg, + ) + .unwrap(); + + let contract_exp_obj = query( + deps.as_ref(), + env.clone(), + QueryMsg::Contract { + address: Addr::unchecked("pool1".to_string()), + key: "vk_1".to_string(), + }, + ) + .unwrap(); + let res = from_binary(&contract_exp_obj).unwrap(); + + if let crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } = res + { + assert_eq!(available_exp, Uint128::from(60u128)); + assert_eq!(unclaimed_exp, Uint128::zero()); + } + + let msg = ExecuteMsg::AddExp { + address: Addr::unchecked("user1".to_string()), + exp: Uint128::from(20u128), + }; + let _res = execute( + deps.as_mut(), + env.clone(), + mock_info("pool1", &coins(0, DENOM)), + msg, + ) + .unwrap(); + + let msg = ExecuteMsg::SetViewingKey { + key: "vk_1".to_string(), + }; + let _res = execute( + deps.as_mut(), + env.clone(), + mock_info("user1", &coins(0, DENOM)), + msg, + ) + .unwrap(); + let user_exp_obj = query( + deps.as_ref(), + env.clone(), + crate::msg::QueryMsg::UserExp { + address: Addr::unchecked("user1".to_string()), + key: String::from("vk_1"), + season: None, + }, + ) + .unwrap(); + let query_answer = from_binary(&user_exp_obj).unwrap(); + + if let crate::msg::QueryAnswer::UserExp { exp } = query_answer { + assert_eq!(exp, Uint128::from(20u128)); + } + Ok(()) + } + + #[test] + fn test_update_last_claimed() -> StdResult<()> { + let mut deps = mock_dependencies_with_balance(&[]); + + let msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![Addr::unchecked(OWNER.to_owned())]), + schedules: Vec::new(), + + grand_prize_contract: None, + season_ending_block: 0, + }; + let info = mock_info(OWNER, &[]); + let mut env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _exp_obj = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + // Set schedule + + let sch = [MintingScheduleUint { + duration: 100, + mint_per_block: Uint128::one(), + continue_with_current_season: false, + start_after: None, + }]; + + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + // Set weights + + let msg = ExecuteMsg::AddContract { + contracts: [ + AddContract { + address: Addr::unchecked("pool1".to_string()), + code_hash: "pool1 hash".to_string(), + weight: 60, + }, + AddContract { + address: Addr::unchecked("pool2".to_string()), + code_hash: "pool2 hash".to_string(), + weight: 40, + }, + ] + .to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + // Advance block height + env.block.height += 100; + + // pool1 updates its last claimed + let msg = ExecuteMsg::UpdateLastClaimed {}; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool1", &[]), msg).unwrap(); + + // Set vk + let msg = ExecuteMsg::SetViewingKey { + key: "vk_1".to_string(), + }; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool1", &[]), msg).unwrap(); + + // Query contract exp + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool1".to_string()), + key: "vk_1".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } => { + assert_eq!(available_exp, Uint128::from(60u128)); + assert_eq!(unclaimed_exp, Uint128::zero()); + } + _ => {} + } + Ok(()) + } + + #[test] + fn test_get_xp() -> StdResult<()> { + let mut deps = mock_dependencies_with_balance(&[]); + + let init_msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![Addr::unchecked(OWNER.to_owned())]), + schedules: Vec::new(), + + grand_prize_contract: None, + season_ending_block: 0, + }; + + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _res = instantiate(deps.as_mut(), env.clone(), info.clone(), init_msg).unwrap(); + + let msg = ExecuteMsg::AddContract { + contracts: [ + AddContract { + address: Addr::unchecked("pool1".to_string()), + code_hash: "pool1 hash".to_string(), + weight: 50, + }, + AddContract { + address: Addr::unchecked("pool2".to_string()), + code_hash: "pool2 hash".to_string(), + weight: 50, + }, + ] + .to_vec(), + }; + + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + // Scenario 1: + + // Schedule: + // Schedule 1: start_block = 10, end_block = 20, mint_per_block = 2 + // Schedule 2: start_block = 25, end_block = 35, mint_per_block = 4 + // current_block = 15 + // last_claimed = 0 + // total_weight = 100 + // contract: weight = 50 + // Expected result: XP earned during Schedule 1 + let sch = [ + MintingScheduleUint { + duration: 10, + mint_per_block: Uint128::from(2u128), + continue_with_current_season: false, + start_after: Some(10u64), + }, + MintingScheduleUint { + duration: 10, + mint_per_block: Uint128::from(4u128), + continue_with_current_season: false, + start_after: Some(25u64), + }, + ]; + + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap(); + + //Set vk + let msg = ExecuteMsg::SetViewingKey { + key: "vk_1".to_string(), + }; + + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None, Some("pool1")), + mock_info("pool1", &[]), + msg, + ) + .unwrap(); + + let env = custom_mock_env(Some(15u64), None, None, None, Some("exp_contract")); + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool1".to_string()), + key: "vk_1".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + + match res { + crate::msg::QueryAnswer::ContractResponse { unclaimed_exp, .. } => { + assert_eq!(unclaimed_exp, Uint128::from(5u128)); // 10 / 2 + } + _ => {} + } + + // Scenario 2: + // Schedule: + // Schedule 1: start_block = 10, end_block = 20, mint_per_block = 2 + // Schedule 2: start_block = 25, end_block = 35, mint_per_block = 4 + // current_block = 30 + // last_claimed = 0 + // total_weight = 100 + // contract: weight = 50 + // Expected result: XP earned during Schedule 1 and a part of Schedule 2 + let env = custom_mock_env(Some(30u64), None, None, None, Some("exp_contract")); + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool1".to_string()), + key: "vk_1".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + + match res { + crate::msg::QueryAnswer::ContractResponse { unclaimed_exp, .. } => { + assert_eq!(unclaimed_exp, Uint128::from(20u128)); // (20 + 20)/2 + } + _ => {} + } + + // Scenario 3: + + // Schedule: + // Schedule 1: start_block = 10, end_block = 30, mint_per_block = 2 + // Schedule 2: start_block = 20, end_block = 40, mint_per_block = 4 + // current_block = 25 + // last_claimed = 0 + // total_weight = 100 + // contract: weight = 50 + // Expected result: XP earned during Schedule 1, and the overlapping part with Schedule 2 + let sch = [ + MintingScheduleUint { + duration: 20, + mint_per_block: Uint128::from(2u128), + continue_with_current_season: false, + start_after: Some(10u64), + }, + MintingScheduleUint { + duration: 20, + mint_per_block: Uint128::from(4u128), + continue_with_current_season: false, + start_after: Some(20u64), + }, + ]; + + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap(); + + let env = custom_mock_env(Some(25u64), None, None, None, Some("exp_contract")); + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool1".to_string()), + key: "vk_1".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + + match res { + crate::msg::QueryAnswer::ContractResponse { unclaimed_exp, .. } => { + assert_eq!(unclaimed_exp, Uint128::from(25u128)); // (30 + 20)/2 + } + _ => {} + } + + // Scenario 4: + + // Schedule: + // Schedule 1: start_block = 10, end_block = 20, mint_per_block = 2 + // Schedule 2: start_block = 25, end_block = 35, mint_per_block = 4 + // current_block = 40 + // last_claimed = 0 + // total_weight = 100 + // contract: weight = 50 + // Expected result: XP earned during Schedule 1 and Schedule 2 + let sch = [ + MintingScheduleUint { + duration: 10, + mint_per_block: Uint128::from(2u128), + continue_with_current_season: false, + start_after: Some(10u64), + }, + MintingScheduleUint { + duration: 10, + mint_per_block: Uint128::from(4u128), + continue_with_current_season: false, + start_after: Some(25u64), + }, + ]; + + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap(); + + let env = custom_mock_env(Some(40u64), None, None, None, Some("exp_contract")); + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool1".to_string()), + key: "vk_1".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + + match res { + crate::msg::QueryAnswer::ContractResponse { unclaimed_exp, .. } => { + assert_eq!(unclaimed_exp, Uint128::from(30u128)); // (30 + 20)/2 + } + _ => {} + } + + // Scenario 5: + + // Schedule: + // Schedule 1: start_block = 10, end_block = 30, mint_per_block = 2 + // Schedule 2: start_block = 20, end_block = 40, mint_per_block = 4 + // current_block = 45 + // last_claimed = 0 + // total_weight = 100 + // contract: weight = 50 + // Expected result: XP earned during Schedule 1 and Schedule 2 + let sch = [ + MintingScheduleUint { + duration: 20, + mint_per_block: Uint128::from(2u128), + continue_with_current_season: false, + start_after: Some(10u64), + }, + MintingScheduleUint { + duration: 20, + mint_per_block: Uint128::from(4u128), + continue_with_current_season: false, + start_after: Some(20u64), + }, + ]; + + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap(); + + let env = custom_mock_env(Some(45u64), None, None, None, Some("exp_contract")); + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool1".to_string()), + key: "vk_1".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + + match res { + crate::msg::QueryAnswer::ContractResponse { unclaimed_exp, .. } => { + assert_eq!(unclaimed_exp, Uint128::from(60u128)); // (30 + 20)/2 + } + _ => {} + } + + Ok(()) + } + + #[test] + fn test_query_verified_contracts() -> StdResult<()> { + // test_set_weight(); + Ok(()) + } + + #[test] + fn season_walkthrough() -> StdResult<()> { + // Initialized + let mut deps = mock_dependencies_with_balance(&[]); + let info = mock_info(OWNER, &[]); + + let msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![Addr::unchecked(OWNER.to_owned())]), + schedules: Vec::new(), + + grand_prize_contract: None, + season_ending_block: 0, + }; + + let mut env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _exp_obj = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + // Minting 1 xp/block. + let sch = [MintingScheduleUint { + duration: 100, + mint_per_block: Uint128::from(100u128), + continue_with_current_season: false, + start_after: None, + }]; + + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + // Adding a new contract pool1 @ b0 duration = b10, w = 50 + let msg = ExecuteMsg::AddContract { + contracts: [AddContract { + address: Addr::unchecked("pool1".to_string()), + code_hash: "pool1 hash".to_string(), + weight: 50, + }] + .to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + // Checking at b10. Claim some xp, assert xp claimed = 10 * 100. + // Advance block height + env.block.height += 10; + + // pool1 updates its last claimed + let msg = ExecuteMsg::UpdateLastClaimed {}; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool1", &[]), msg).unwrap(); + let config = CONFIG.load(&deps.storage)?; + let supply_pool = SUPPLY_POOL.load(&deps.storage, config.season_counter)?; + assert_eq!( + supply_pool.xp_claimed_by_contracts, + Uint128::from(10 * 100u128) + ); + + // Set vk + let msg = ExecuteMsg::SetViewingKey { + key: "vk_1".to_string(), + }; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool1", &[]), msg).unwrap(); + + // Query contract exp + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool1".to_string()), + key: "vk_1".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } => { + assert_eq!(available_exp, Uint128::from(1000u128)); + assert_eq!(unclaimed_exp, Uint128::zero()); + } + _ => {} + } + + // Adding a new contract @ b11, duration = b10, w = 50 + env.block.height += 1; + + let msg = ExecuteMsg::AddContract { + contracts: [AddContract { + address: Addr::unchecked("pool2".to_string()), + code_hash: "pool2 hash".to_string(), + weight: 50, + }] + .to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let config = CONFIG.load(&deps.storage)?; + assert_eq!(config.total_weight, 100u64); + + // Checking at b20. Claim some xp, assert xp claimed = 4.5 * 100, total xp claimed = 20 + // pool1 updates its last claimed + env.block.height += 9; + + let msg = ExecuteMsg::UpdateLastClaimed {}; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool2", &[]), msg).unwrap(); + let msg = ExecuteMsg::UpdateLastClaimed {}; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool1", &[]), msg).unwrap(); + let supply_pool = SUPPLY_POOL.load(&deps.storage, config.season_counter)?; + assert_eq!( + supply_pool.xp_claimed_by_contracts, + Uint128::from(20 * 100u128) + ); + + // Set vk + let msg = ExecuteMsg::SetViewingKey { + key: "vk_2".to_string(), + }; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool2", &[]), msg).unwrap(); + + // Query contract exp + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool2".to_string()), + key: "vk_2".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } => { + assert_eq!(available_exp, Uint128::from(450u128)); + assert_eq!(unclaimed_exp, Uint128::zero()); + } + _ => {} + } + + // Simulating at b100. assert pool 1 xp = (11+ 44.5) , assert pool2 xp (44.5) + env.block.height += 80; + + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool2".to_string()), + key: "vk_2".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } => { + assert_eq!(available_exp, Uint128::from(450u128)); + assert_eq!(unclaimed_exp, Uint128::from(4000u128)); + } + _ => {} + } + + let msg = ExecuteMsg::UpdateLastClaimed {}; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool1", &[]), msg).unwrap(); + + let msg = ExecuteMsg::UpdateLastClaimed {}; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool2", &[]), msg).unwrap(); + + // Query contract exp + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool1".to_string()), + key: "vk_1".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } => { + assert_eq!(available_exp, Uint128::from(5550u128)); + assert_eq!(unclaimed_exp, Uint128::zero()); + } + _ => {} + } + + // Query contract exp + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool2".to_string()), + key: "vk_2".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + last_claimed, + .. + } => { + assert_eq!(available_exp, Uint128::from(4450u128)); + assert_eq!(unclaimed_exp, Uint128::zero()); + assert_eq!(last_claimed, env.block.height); + } + _ => {} + } + + let supply_pool = SUPPLY_POOL.load(&deps.storage, config.season_counter)?; + assert_eq!( + supply_pool.season_total_xp_cap, + supply_pool.xp_claimed_by_contracts + ); + //Trying to add more xp after season ends. hence all stats remain constant. + env.block.height += 10; + let msg = ExecuteMsg::UpdateLastClaimed {}; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool1", &[]), msg).unwrap(); + + let msg = ExecuteMsg::UpdateLastClaimed {}; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool2", &[]), msg).unwrap(); + + // Query contract exp + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool1".to_string()), + key: "vk_1".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } => { + assert_eq!(available_exp, Uint128::from(5550u128)); + assert_eq!(unclaimed_exp, Uint128::zero()); + } + _ => {} + } + + // Query contract exp + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool2".to_string()), + key: "vk_2".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + last_claimed, + .. + } => { + assert_eq!(available_exp, Uint128::from(4450u128)); + assert_eq!(unclaimed_exp, Uint128::zero()); + assert_eq!(last_claimed, env.block.height.sub(10u64)); + } + _ => {} + } + + let supply_pool = SUPPLY_POOL.load(&deps.storage, config.season_counter)?; + assert_eq!( + supply_pool.season_total_xp_cap, + supply_pool.xp_claimed_by_contracts + ); + + // Adding pool @ b0. Remove Pool 2 @ b20, assert xp claimed = 10 * 100, total xp claimed = 20 + let mut deps = mock_dependencies_with_balance(&[]); + let info = mock_info(OWNER, &[]); + + let msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![Addr::unchecked(OWNER.to_owned())]), + schedules: Vec::new(), + + grand_prize_contract: None, + season_ending_block: 0, + }; + + let mut env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _exp_obj = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + // Minting 1 xp/block. + let sch = [MintingScheduleUint { + duration: 100, + mint_per_block: Uint128::from(100u128), + continue_with_current_season: false, + start_after: None, + }]; + + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + // Adding a new contract pool1 @ b0 duration = b10, w = 50 + let msg = ExecuteMsg::AddContract { + contracts: [ + AddContract { + address: Addr::unchecked("pool1".to_string()), + code_hash: "pool1 hash".to_string(), + weight: 50, + }, + AddContract { + address: Addr::unchecked("pool2".to_string()), + code_hash: "pool2 hash".to_string(), + weight: 50, + }, + ] + .to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + // Remove contract @ b20. + env.block.height += 20; + + let msg = ExecuteMsg::RemoveContract { + contracts: [Addr::unchecked("pool2".to_string())].to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + //Query verified contracts + let query_msg = QueryMsg::VerifiedContracts { + start_page: None, + page_size: None, + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + // We're not removing contracts to avoid any errors on the pool contract sides + match res { + crate::msg::QueryAnswer::VerifiedContractsResponse { contracts } => { + assert_eq!(contracts.len(), 2); + } + _ => {} + } + // Simulating at b100. + + env.block.height += 80; + + let msg = ExecuteMsg::UpdateLastClaimed {}; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool1", &[]), msg).unwrap(); + + let msg = ExecuteMsg::UpdateLastClaimed {}; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool2", &[]), msg).unwrap(); + + // Set vk + // Set vk + let msg = ExecuteMsg::SetViewingKey { + key: "vk_1".to_string(), + }; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool1", &[]), msg).unwrap(); + let msg = ExecuteMsg::SetViewingKey { + key: "vk_2".to_string(), + }; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool2", &[]), msg).unwrap(); + + // Query contract exp + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool1".to_string()), + key: "vk_1".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } => { + assert_eq!(available_exp, Uint128::from(9000u128)); + assert_eq!(unclaimed_exp, Uint128::zero()); + } + _ => {} + } + + // Query contract exp + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool2".to_string()), + key: "vk_2".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + last_claimed, + .. + } => { + assert_eq!(available_exp, Uint128::from(1000u128)); + assert_eq!(unclaimed_exp, Uint128::zero()); + assert_eq!(last_claimed, env.block.height); + } + _ => {} + } + + // Recap to b20. Update Pool 2 weightage to 10 and pool 1 weightage to 90 + let mut deps = mock_dependencies_with_balance(&[]); + let info = mock_info(OWNER, &[]); + + let msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![Addr::unchecked(OWNER.to_owned())]), + schedules: Vec::new(), + + grand_prize_contract: None, + season_ending_block: 0, + }; + + let mut env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _exp_obj = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + // Minting 1 xp/block. + let sch = [MintingScheduleUint { + duration: 100, + mint_per_block: Uint128::from(100u128), + continue_with_current_season: false, + start_after: None, + }]; + + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + // Adding a new contract pool1 @ b0 duration = b10, w = 50 + let msg = ExecuteMsg::AddContract { + contracts: [ + AddContract { + address: Addr::unchecked("pool1".to_string()), + code_hash: "pool1 hash".to_string(), + weight: 50, + }, + AddContract { + address: Addr::unchecked("pool2".to_string()), + code_hash: "pool2 hash".to_string(), + weight: 50, + }, + ] + .to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + env.block.height += 20; + + // Simulating at b100. assert pool 1 xp = (91.1) , assert pool2 xp (8.9) + + let msg = ExecuteMsg::SetViewingKey { + key: "vk_1".to_string(), + }; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool1", &[]), msg).unwrap(); + let msg = ExecuteMsg::SetViewingKey { + key: "vk_2".to_string(), + }; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool2", &[]), msg).unwrap(); + + // Query contract exp + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool1".to_string()), + key: "vk_1".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } => { + assert_eq!(available_exp, Uint128::from(0u128)); + assert_eq!(unclaimed_exp, Uint128::from(1000u128)); + } + _ => {} + } + + // Query contract exp + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool2".to_string()), + key: "vk_2".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } => { + assert_eq!(available_exp, Uint128::zero()); + assert_eq!(unclaimed_exp, Uint128::from(1000u128)); + } + _ => {} + } + + let msg = ExecuteMsg::UpdateWeights { + weights: [ + WeightUpdate { + address: Addr::unchecked("pool1".to_string()), + weight: 90, + }, + WeightUpdate { + address: Addr::unchecked("pool2".to_string()), + weight: 10, + }, + ] + .to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + env.block.height += 80; + + let msg = ExecuteMsg::UpdateLastClaimed {}; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool1", &[]), msg).unwrap(); + + let msg = ExecuteMsg::UpdateLastClaimed {}; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool2", &[]), msg).unwrap(); + + // Query contract exp + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool1".to_string()), + key: "vk_1".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } => { + assert_eq!(available_exp, Uint128::from(8200u128)); + assert_eq!(unclaimed_exp, Uint128::zero()); + } + _ => {} + } + + // Query contract exp + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool2".to_string()), + key: "vk_2".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + match res { + crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + last_claimed, + .. + } => { + assert_eq!(available_exp, Uint128::from(1800u128)); + assert_eq!(unclaimed_exp, Uint128::zero()); + assert_eq!(last_claimed, env.block.height); + } + _ => {} + } + + Ok(()) + } + + #[test] + fn test_query_contract_info() -> StdResult<()> { + let mut deps = mock_dependencies_with_balance(&[]); + let info = mock_info(OWNER, &[]); + + let msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![Addr::unchecked(OWNER.to_owned())]), + schedules: Vec::new(), + + grand_prize_contract: None, + season_ending_block: 0, + }; + + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let contract_info_obj = + query(deps.as_ref(), env.clone(), QueryMsg::ContractInfo {}).unwrap(); + let res = from_binary(&contract_info_obj).unwrap(); + if let crate::msg::QueryAnswer::ContractInfoResponse { info } = res { + assert_eq!(info.admins, vec![OWNER]); + assert_eq!( + info.contract_address.to_string(), + "exp_contract".to_string() + ); + assert_eq!(info.minting_schedule, []); + assert_eq!(info.total_weight, 0); + } + Ok(()) + } + + #[test] + fn test_query_contract_exp_available() -> StdResult<()> { + let mut deps = mock_dependencies_with_balance(&[]); + + let msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![Addr::unchecked(OWNER.to_owned())]), + schedules: Vec::new(), + + grand_prize_contract: None, + season_ending_block: 0, + }; + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _exp_obj = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let sch = [MintingScheduleUint { + duration: 100, + mint_per_block: Uint128::one(), + continue_with_current_season: false, + start_after: None, + }]; + + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let msg = ExecuteMsg::AddContract { + contracts: [ + AddContract { + address: Addr::unchecked("pool1".to_string()), + code_hash: "pool1 hash".to_string(), + weight: 60, + }, + AddContract { + address: Addr::unchecked("pool2".to_string()), + code_hash: "pool2 hash".to_string(), + weight: 40, + }, + ] + .to_vec(), + }; + + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + //Set vk + let msg = ExecuteMsg::SetViewingKey { + key: "vk_1".to_string(), + }; + + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None, Some("pool1")), + mock_info("pool1", &[]), + msg, + ) + .unwrap(); + + // Get contract info query + //querying at the same block as contract was added + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool1".to_string()), + key: "vk_1".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); + + match res { + crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } => { + assert_eq!(available_exp, Uint128::zero()); + assert_eq!(unclaimed_exp, Uint128::zero()); + } + _ => {} + } + + //setting block to block + 100 + let new_env = custom_mock_env( + Some(env.block.height.add(100)), + None, + None, + None, + Some("exp_contract"), + ); + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool1".to_string()), + key: "vk_1".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), new_env.clone(), query_msg).unwrap()).unwrap(); + + match res { + crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } => { + assert_eq!(unclaimed_exp, Uint128::from(60u128)); + assert_eq!(available_exp, Uint128::zero()); + } + _ => {} + } + + //Set vk + let msg = ExecuteMsg::SetViewingKey { + key: "vk_2".to_string(), + }; + let _res = execute(deps.as_mut(), env, mock_info("pool2", &[]), msg).unwrap(); + + // Get contract info query + //querying at the same block as contract was added + let query_msg = QueryMsg::Contract { + address: Addr::unchecked("pool2".to_string()), + key: "vk_2".to_string(), + }; + let res = from_binary(&query(deps.as_ref(), new_env.clone(), query_msg).unwrap()).unwrap(); + + match res { + crate::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } => { + assert_eq!(unclaimed_exp, Uint128::from(40u128)); + assert_eq!(available_exp, Uint128::zero()); + } + _ => {} + } + Ok(()) + } + + #[test] + fn test_query_user_exp() -> StdResult<()> { + let mut deps = mock_dependencies_with_balance(&[]); + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + + let msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![Addr::unchecked(OWNER.to_owned())]), + schedules: Vec::new(), + + grand_prize_contract: None, + season_ending_block: 0, + }; + + let _exp_obj = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let sch = [MintingScheduleUint { + duration: 100, + mint_per_block: Uint128::one(), + continue_with_current_season: false, + start_after: None, + }]; + + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let msg = ExecuteMsg::AddContract { + contracts: [ + AddContract { + address: Addr::unchecked("pool1".to_string()), + code_hash: "pool1 hash".to_string(), + weight: 60, + }, + AddContract { + address: Addr::unchecked("pool2".to_string()), + code_hash: "pool2 hash".to_string(), + weight: 20, + }, + ] + .to_vec(), + }; + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + //pool1 updating it's last claimed + //adding contract at block height h + 100 + let new_env = custom_mock_env( + Some(env.block.height.add(100)), + None, + None, + None, + Some("pool1"), + ); + let msg = ExecuteMsg::UpdateLastClaimed {}; + let _res = execute(deps.as_mut(), new_env.clone(), mock_info("pool1", &[]), msg).unwrap(); + + //Rewarding 20 exp to user1 + let msg = ExecuteMsg::AddExp { + address: Addr::unchecked("user1".to_string()), + exp: Uint128::from(20u128), + }; + let _res = execute(deps.as_mut(), new_env.clone(), mock_info("pool1", &[]), msg).unwrap(); + + //Get user exp + //set vk + let msg = ExecuteMsg::SetViewingKey { + key: "vk_1".to_string(), + }; + let _res = execute(deps.as_mut(), env.clone(), mock_info("user1", &[]), msg).unwrap(); + + //querying at the block + 100 + let query_msg = QueryMsg::UserExp { + address: Addr::unchecked("user1".to_string()), + key: "vk_1".to_string(), + season: None, + }; + let user_exp_obj = + from_binary(&query(deps.as_ref(), new_env.clone(), query_msg).unwrap()).unwrap(); + + if let crate::msg::QueryAnswer::UserExp { exp } = user_exp_obj { + assert_eq!(exp, Uint128::from(20u128)); + } + Ok(()) + } + + #[test] + fn test_query_user_exp_by_authoritize_address() -> StdResult<()> { + let mut deps = mock_dependencies_with_balance(&[]); + + let msg = InstantiateMsg { + // Your instantiate message fields here + entropy: "LOL".to_string(), + admin: Some(vec![Addr::unchecked(OWNER.to_owned())]), + schedules: Vec::new(), + + grand_prize_contract: None, + season_ending_block: 0, + }; + + let info = mock_info(OWNER, &[]); + let env = custom_mock_env(None, None, None, None, Some("exp_contract")); + let _exp_obj = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let sch = [MintingScheduleUint { + duration: 100, + mint_per_block: Uint128::one(), + continue_with_current_season: false, + start_after: None, + }]; + + let msg = ExecuteMsg::SetSchedule { + schedule: sch.to_vec(), + }; + + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + let msg = ExecuteMsg::AddContract { + contracts: [ + AddContract { + address: Addr::unchecked("pool1".to_string()), + code_hash: "pool1 hash".to_string(), + weight: 60, + }, + AddContract { + address: Addr::unchecked("pool2".to_string()), + code_hash: "pool2 hash".to_string(), + weight: 20, + }, + ] + .to_vec(), + }; + + let _res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + //pool1 updating it's last claimed + //adding contract at block height h + 100 + let new_env = custom_mock_env( + Some(env.block.height.add(100)), + None, + None, + None, + Some("pool1"), + ); + let msg = ExecuteMsg::UpdateLastClaimed {}; + let _res = execute(deps.as_mut(), new_env.clone(), mock_info("pool1", &[]), msg).unwrap(); + + //Rewarding 20 exp to user1 + let msg = ExecuteMsg::AddExp { + address: Addr::unchecked("user1".to_string()), + exp: Uint128::from(20u128), + }; + let _res = execute(deps.as_mut(), new_env.clone(), mock_info("pool1", &[]), msg).unwrap(); + + // Get user exp + // set vk + let msg = ExecuteMsg::SetViewingKey { + key: "vk_1".to_string(), + }; + let _res = execute(deps.as_mut(), env.clone(), mock_info("user1", &[]), msg).unwrap(); + + // querying at the block + 100 + let query_msg = QueryMsg::UserExp { + address: Addr::unchecked("user1".to_string()), + key: "vk_1".to_string(), + season: None, + }; + let user_exp_obj: crate::msg::QueryAnswer = + from_binary(&query(deps.as_ref(), new_env.clone(), query_msg).unwrap()).unwrap(); + + if let crate::msg::QueryAnswer::UserExp { exp } = user_exp_obj { + assert_eq!(exp, Uint128::from(20u128)); + } + + // Getting user exp by authoritized addresses + // Get user exp + // set vk + let msg = ExecuteMsg::SetViewingKey { + key: "vk_1".to_string(), + }; + let _res = execute(deps.as_mut(), env.clone(), mock_info("pool1", &[]), msg).unwrap(); + + //querying at the block + 100 + let query_msg = QueryMsg::CheckUserExp { + address: Addr::unchecked("pool1".to_string()), + key: "vk_1".to_string(), + user_address: Addr::unchecked("user1".to_string()), + season: None, + }; + let user_exp_obj = + from_binary(&query(deps.as_ref(), new_env.clone(), query_msg).unwrap()).unwrap(); + + if let crate::msg::QueryAnswer::UserExp { exp } = user_exp_obj { + assert_eq!(exp, Uint128::from(20u128)); + } + Ok(()) + } +} diff --git a/contracts/galactic_pools/pools/native/.cargo/config b/contracts/galactic_pools/pools/native/.cargo/config new file mode 100644 index 000000000..882fe08f6 --- /dev/null +++ b/contracts/galactic_pools/pools/native/.cargo/config @@ -0,0 +1,5 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib --features backtraces" +integration-test = "test --test integration" +schema = "run --example schema" diff --git a/contracts/galactic_pools/pools/native/.editorconfig b/contracts/galactic_pools/pools/native/.editorconfig new file mode 100644 index 000000000..5bd56478f --- /dev/null +++ b/contracts/galactic_pools/pools/native/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.rs] +indent_size = 4 + diff --git a/contracts/galactic_pools/pools/native/.gitignore b/contracts/galactic_pools/pools/native/.gitignore new file mode 100644 index 000000000..d042dbe7c --- /dev/null +++ b/contracts/galactic_pools/pools/native/.gitignore @@ -0,0 +1,28 @@ +# Build results +/target +contract.wasm +contract.wasm.gz + +secretjs/node_modules + +# Binaries +*.wasm +*.wasm.gz + +# Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) +.cargo-ok + +# Text file backups +**/*.rs.bk + +# macOS +.DS_Store + +# IDEs +*.iml +.idea + +# client +/client/node_modules + +/client/.env \ No newline at end of file diff --git a/contracts/galactic_pools/pools/native/Cargo.toml b/contracts/galactic_pools/pools/native/Cargo.toml new file mode 100644 index 000000000..4daba9410 --- /dev/null +++ b/contracts/galactic_pools/pools/native/Cargo.toml @@ -0,0 +1,65 @@ +[package] + +name = "native" +version = "0.1.0" +edition = "2018" +authors = ["Haseeb Saeed "] + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + + + +[lib] +crate-type = ["cdylib", "rlib"] +[profile.release] +opt-level = 3 +debug = false +rpath = false +lto = true +debug-assertions = false +codegen-units = 1 +panic = 'abort' +incremental = false +overflow-checks = true + + + +[features] +default = ["schema"] +schema = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = [] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../../../packages/shade_protocol" } + +rand_chacha = { version = "0.2.2", default-features = false } +#rand_core = { version = "0.5.1", default-features = false } +rand = { version = "0.7.3" } +experience_contract = {path = "../../exp"} + +sha2 = { version = "0.10.8", default-features = false } +serde = { version = "1.0.103", default-features = false, features = ["derive"] } +schemars = "0.8.10" +snafu = { version = "0.6.3" } +bincode2 = "2.0.1" +subtle = { version = "2.2.3", default-features = false } +base64 = "0.12.3" +hex = "0.4.2" +rust_decimal = { version = "1.25.0", default-features = false} + + +[dev-dependencies] +anyhow = "1" +shade-multi-test = { path = "../../../../packages/multi_test", features = [ + "admin", + "lb_pair", + "lb_token", + "snip20", +] } + diff --git a/contracts/galactic_pools/pools/native/Developing.md b/contracts/galactic_pools/pools/native/Developing.md new file mode 100644 index 000000000..8d5bdbe11 --- /dev/null +++ b/contracts/galactic_pools/pools/native/Developing.md @@ -0,0 +1,142 @@ +# Developing + +If you have recently created a contract with this template, you probably could use some +help on how to build and test the contract, as well as prepare it for production. This +file attempts to provide a brief overview, assuming you have installed a recent +version of Rust already (eg. 1.41+). + +## Prerequisites + +Before starting, make sure you have [rustup](https://rustup.rs/) along with a +recent `rustc` and `cargo` version installed. Currently, we are testing on 1.41+. + +And you need to have the `wasm32-unknown-unknown` target installed as well. + +You can check that via: + +```sh +rustc --version +cargo --version +rustup target list --installed +# if wasm32 is not listed above, run this +rustup target add wasm32-unknown-unknown +``` + +## Compiling and running tests + +Now that you created your custom contract, make sure you can compile and run it before +making any changes. Go into the + +```sh +# this will produce a wasm build in ./target/wasm32-unknown-unknown/release/YOUR_NAME_HERE.wasm +cargo wasm + +# this runs unit tests with helpful backtraces +RUST_BACKTRACE=1 cargo unit-test + +# this runs integration tests with cranelift backend (uses rust stable) +cargo integration-test + +# this runs integration tests with singlepass backend (needs rust nightly) +cargo integration-test --no-default-features --features singlepass + +# auto-generate json schema +cargo schema +``` + +The wasmer engine, embedded in `cosmwasm-vm` supports multiple backends: +singlepass and cranelift. Singlepass has fast compile times and slower run times, +and supportes gas metering. It also requires rust `nightly`. This is used as default +when embedding `cosmwasm-vm` in `go-cosmwasm` and is needed to use if you want to +check the gas usage. + +However, when just building contacts, if you don't want to worry about installing +two rust toolchains, you can run all tests with cranelift. The integration tests +may take a small bit longer, but the results will be the same. The only difference +is that you can not check gas usage here, so if you wish to optimize gas, you must +switch to nightly and run with cranelift. + +### Understanding the tests + +The main code is in `src/contract.rs` and the unit tests there run in pure rust, +which makes them very quick to execute and give nice output on failures, especially +if you do `RUST_BACKTRACE=1 cargo unit-test`. + +However, we don't just want to test the logic rust, but also the compiled Wasm artifact +inside a VM. You can look in `tests/integration.rs` to see some examples there. They +load the Wasm binary into the vm and call the contract externally. Effort has been +made that the syntax is very similar to the calls in the native rust contract and +quite easy to code. In fact, usually you can just copy a few unit tests and modify +a few lines to make an integration test (this should get even easier in a future release). + +To run the latest integration tests, you need to explicitely rebuild the Wasm file with +`cargo wasm` and then run `cargo integration-test`. + +We consider testing critical for anything on a blockchain, and recommend to always keep +the tests up to date. While doing active development, it is often simplest to disable +the integration tests completely and iterate rapidly on the code in `contract.rs`, +both the logic and the tests. Once the code is finalized, you can copy over some unit +tests into the integration.rs and make the needed changes. This ensures the compiled +Wasm also behaves as desired in the real system. + +## Generating JSON Schema + +While the Wasm calls (`init`, `handle`, `query`) accept JSON, this is not enough +information to use it. We need to expose the schema for the expected messages to the +clients. You can generate this schema by calling `cargo schema`, which will output +4 files in `./schema`, corresponding to the 3 message types the contract accepts, +as well as the internal `State`. + +These files are in standard json-schema format, which should be usable by various +client side tools, either to auto-generate codecs, or just to validate incoming +json wrt. the defined schema. + +## Preparing the Wasm bytecode for production + +Before we upload it to a chain, we need to ensure the smallest output size possible, +as this will be included in the body of a transaction. We also want to have a +reproducible build process, so third parties can verify that the uploaded Wasm +code did indeed come from the claimed rust code. + +To solve both these issues, we have produced `rust-optimizer`, a docker image to +produce an extremely small build output in a consistent manner. The suggest way +to run it is this: + +```sh +docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.8.0 +``` + +We must mount the contract code to `/code`. You can use a absolute path instead +of `$(pwd)` if you don't want to `cd` to the directory first. The other two +volumes are nice for speedup. Mounting `/code/target` in particular is useful +to avoid docker overwriting your local dev files with root permissions. +Note the `/code/target` cache is unique for each contract being compiled to limit +interference, while the registry cache is global. + +This is rather slow compared to local compilations, especially the first compile +of a given contract. The use of the two volume caches is very useful to speed up +following compiles of the same contract. + +This produces a `contract.wasm` file in the current directory (which must be the root +directory of your rust project, the one with `Cargo.toml` inside). As well as +`hash.txt` containing the Sha256 hash of `contract.wasm`, and it will rebuild +your schema files as well. + +### Testing production build + +Once we have this compressed `contract.wasm`, we may want to ensure it is actually +doing everything it is supposed to (as it is about 4% of the original size). +If you update the "WASM" line in `tests/integration.rs`, it will run the integration +steps on the optimized build, not just the normal build. I have never seen a different +behavior, but it is nice to verify sometimes. + +```rust +static WASM: &[u8] = include_bytes!("../contract.wasm"); +``` + +Note that this is the same (deterministic) code you will be uploading to +a blockchain to test it out, as we need to shrink the size and produce a +clear mapping from wasm hash back to the source code. diff --git a/contracts/galactic_pools/pools/native/Importing.md b/contracts/galactic_pools/pools/native/Importing.md new file mode 100644 index 000000000..e367b6582 --- /dev/null +++ b/contracts/galactic_pools/pools/native/Importing.md @@ -0,0 +1,62 @@ +# Importing + +In [Publishing](./Publishing.md), we discussed how you can publish your contract to the world. +This looks at the flip-side, how can you use someone else's contract (which is the same +question as how they will use your contract). Let's go through the various stages. + +## Verifying Artifacts + +Before using remote code, you most certainly want to verify it is honest. + +The simplest audit of the repo is to simply check that the artifacts in the repo +are correct. This involves recompiling the claimed source with the claimed builder +and validating that the locally compiled code (hash) matches the code hash that was +uploaded. This will verify that the source code is the correct preimage. Which allows +one to audit the original (Rust) source code, rather than looking at wasm bytecode. + +We have a script to do this automatic verification steps that can +easily be run by many individuals. Please check out +[`cosmwasm-verify`](https://github.com/CosmWasm/cosmwasm-verify/blob/master/README.md) +to see a simple shell script that does all these steps and easily allows you to verify +any uploaded contract. + +## Reviewing + +Once you have done the quick programatic checks, it is good to give at least a quick +look through the code. A glance at `examples/schema.rs` to make sure it is outputing +all relevant structs from `contract.rs`, and also ensure `src/lib.rs` is just the +default wrapper (nothing funny going on there). After this point, we can dive into +the contract code itself. Check the flows for the handle methods, any invariants and +permission checks that should be there, and a reasonable data storage format. + +You can dig into the contract as far as you want, but it is important to make sure there +are no obvious backdoors at least. + +## Decentralized Verification + +It's not very practical to do a deep code review on every dependency you want to use, +which is a big reason for the popularity of code audits in the blockchain world. We trust +some experts review in lieu of doing the work ourselves. But wouldn't it be nice to do this +in a decentralized manner and peer-review each other's contracts? Bringing in deeper domain +knowledge and saving fees. + +Luckily, there is an amazing project called [crev](https://github.com/crev-dev/cargo-crev/blob/master/cargo-crev/README.md) +that provides `A cryptographically verifiable code review system for the cargo (Rust) package manager`. + +I highly recommend that CosmWasm contract developers get set up with this. At minimum, we +can all add a review on a package that programmatically checked out that the json schemas +and wasm bytecode do match the code, and publish our claim, so we don't all rely on some +central server to say it validated this. As we go on, we can add deeper reviews on standard +packages. + +If you want to use `cargo-crev`, please follow their +[getting started guide](https://github.com/crev-dev/cargo-crev/blob/master/cargo-crev/src/doc/getting_started.md) +and once you have made your own *proof repository* with at least one *trust proof*, +please make a PR to the [`cawesome-wasm`]() repo with a link to your repo and +some public name or pseudonym that people know you by. This allows people who trust you +to also reuse your proofs. + +There is a [standard list of proof repos](https://github.com/crev-dev/cargo-crev/wiki/List-of-Proof-Repositories) +with some strong rust developers in there. This may cover dependencies like `serde` and `snafu` +but will not hit any CosmWasm-related modules, so we look to bootstrap a very focused +review community. diff --git a/contracts/galactic_pools/pools/native/LICENSE b/contracts/galactic_pools/pools/native/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/contracts/galactic_pools/pools/native/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/contracts/galactic_pools/pools/native/Makefile b/contracts/galactic_pools/pools/native/Makefile new file mode 100644 index 000000000..e07dab1af --- /dev/null +++ b/contracts/galactic_pools/pools/native/Makefile @@ -0,0 +1,71 @@ +.PHONY: check +check: + cargo check + +.PHONY: clippy +clippy: + cargo clippy + +PHONY: test +test: unit-test + +.PHONY: unit-test +unit-test: + cargo test + +# This is a local build with debug-prints activated. Debug prints only show up +# in the local development chain (see the `start-server` command below) +# and mainnet won't accept contracts built with the feature enabled. +.PHONY: build _build +build: _build compress-wasm +_build: + RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown --features="debug-print" + +# This is a build suitable for uploading to mainnet. +# Calls to `debug_print` get removed by the compiler. +.PHONY: build-mainnet _build-mainnet +build-mainnet: _build-mainnet compress-wasm +_build-mainnet: + RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown + +# like build-mainnet, but slower and more deterministic +.PHONY: build-mainnet-reproducible +build-mainnet-reproducible: + docker run --rm -v "$$(pwd)":/contract \ + --mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/contract/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + enigmampc/secret-contract-optimizer:1.0.3 + +.PHONY: compress-wasm +compress-wasm: + cp ../../../../../target/wasm32-unknown-unknown/release/galactic_pools_secret_network_native_manual_claim.wasm ./contract.wasm + @## The following line is not necessary, may work only on linux (extra size optimization) + @# wasm-opt -Os ./contract.wasm -o ./contract.wasm + cat ./contract.wasm | gzip -9 > ./contract.wasm.gz + +.PHONY: schema +schema: + cargo run --example schema + +# Run local development chain with four funded accounts (named a, b, c, and d) +.PHONY: start-server +start-server: # CTRL+C to stop + docker run -it --rm \ + -p 26657:26657 -p 26656:26656 -p 1317:1317 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +.PHONY: start-download +server-download: + docker pull securesecrets/sn-testnet:v0.2 +# This relies on running `start-server` in another console +# You can run other commands on the secretcli inside the dev image +# by using `docker exec secretdev secretcli`. +.PHONY: store-contract-local +store-contract-local: + docker exec secretdev secretcli tx compute store -y --from a --gas 1000000 /root/code/contract.wasm.gz + +.PHONY: clean +clean: + cargo clean + -rm -f ./contract.wasm ./contract.wasm.gz \ No newline at end of file diff --git a/contracts/galactic_pools/pools/native/NOTICE b/contracts/galactic_pools/pools/native/NOTICE new file mode 100644 index 000000000..f18150bd5 --- /dev/null +++ b/contracts/galactic_pools/pools/native/NOTICE @@ -0,0 +1,13 @@ +Copyright 2020 Itzik + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/contracts/galactic_pools/pools/native/Publishing.md b/contracts/galactic_pools/pools/native/Publishing.md new file mode 100644 index 000000000..35f52129d --- /dev/null +++ b/contracts/galactic_pools/pools/native/Publishing.md @@ -0,0 +1,115 @@ +# Publishing Contracts + +This is an overview of how to publish the contract's source code in this repo. +We use Cargo's default registry [crates.io](https://crates.io/) for publishing contracts written in Rust. + +## Preparation + +Ensure the `Cargo.toml` file in the repo is properly configured. In particular, you want to +choose a name starting with `cw-`, which will help a lot finding CosmWasm contracts when +searching on crates.io. For the first publication, you will probably want version `0.1.0`. +If you have tested this on a public net already and/or had an audit on the code, +you can start with `1.0.0`, but that should imply some level of stability and confidence. +You will want entries like the following in `Cargo.toml`: + +```toml +name = "cw-escrow" +version = "0.1.0" +description = "Simple CosmWasm contract for an escrow with arbiter and timeout" +repository = "https://github.com/confio/cosmwasm-examples" +``` + +You will also want to add a valid [SPDX license statement](https://spdx.org/licenses/), +so others know the rules for using this crate. You can use any license you wish, +even a commercial license, but we recommend choosing one of the following, unless you have +specific requirements. + +* Permissive: [`Apache-2.0`](https://spdx.org/licenses/Apache-2.0.html#licenseText) or [`MIT`](https://spdx.org/licenses/MIT.html#licenseText) +* Copyleft: [`GPL-3.0-or-later`](https://spdx.org/licenses/GPL-3.0-or-later.html#licenseText) or [`AGPL-3.0-or-later`](https://spdx.org/licenses/AGPL-3.0-or-later.html#licenseText) +* Commercial license: `Commercial` (not sure if this works, I cannot find examples) + +It is also helpful to download the LICENSE text (linked to above) and store this +in a LICENSE file in your repo. Now, you have properly configured your crate for use +in a larger ecosystem. + +### Updating schema + +To allow easy use of the contract, we can publish the schema (`schema/*.json`) together +with the source code. + +```sh +cargo schema +``` + +Ensure you check in all the schema files, and make a git commit with the final state. +This commit will be published and should be tagged. Generally, you will want to +tag with the version (eg. `v0.1.0`), but in the `cosmwasm-examples` repo, we have +multiple contracts and label it like `escrow-0.1.0`. Don't forget a +`git push && git push --tags` + +### Note on build results + +Build results like Wasm bytecode or expected hash don't need to be updated since +the don't belong to the source publication. However, they are excluded from packaging +in `Cargo.toml` which allows you to commit them to your git repository if you like. + +```toml +exclude = ["contract.wasm", "hash.txt"] +``` + +A single source code can be built with multiple different optimizers, so +we should not make any strict assumptions on the tooling that will be used. + +## Publishing + +Now that your package is properly configured and all artifacts are committed, it +is time to share it with the world. +Please refer to the [complete instructions for any questions](https://rurust.github.io/cargo-docs-ru/crates-io.html), +but I will try to give a quick overview of the happy path here. + +### Registry + +You will need an account on [crates.io](https://crates.io) to publish a rust crate. +If you don't have one already, just click on "Log in with GitHub" in the top-right +to quickly set up a free account. Once inside, click on your username (top-right), +then "Account Settings". On the bottom, there is a section called "API Access". +If you don't have this set up already, create a new token and use `cargo login` +to set it up. This will now authenticate you with the `cargo` cli tool and allow +you to publish. + +### Uploading + +Once this is set up, make sure you commit the current state you want to publish. +Then try `cargo publish --dry-run`. If that works well, review the files that +will be published via `cargo package --list`. If you are satisfied, you can now +officially publish it via `cargo publish`. + +Congratulations, your package is public to the world. + +### Sharing + +Once you have published your package, people can now find it by +[searching for "cw-" on crates.io](https://crates.io/search?q=cw). +But that isn't exactly the simplest way. To make things easier and help +keep the ecosystem together, we suggest making a PR to add your package +to the [`cawesome-wasm`](https://github.com/cosmwasm/cawesome-wasm) list. + +### Organizations + +Many times you are writing a contract not as a solo developer, but rather as +part of an organization. You will want to allow colleagues to upload new +versions of the contract to crates.io when you are on holiday. +[These instructions show how]() you can set up your crate to allow multiple maintainers. + +You can add another owner to the crate by specifying their github user. Note, you will +now both have complete control of the crate, and they can remove you: + +`cargo owner --add ethanfrey` + +You can also add an existing github team inside your organization: + +`cargo owner --add github:confio:developers` + +The team will allow anyone who is currently in the team to publish new versions of the crate. +And this is automatically updated when you make changes on github. However, it will not allow +anyone in the team to add or remove other owners. diff --git a/contracts/galactic_pools/pools/native/README.md b/contracts/galactic_pools/pools/native/README.md new file mode 100644 index 000000000..3ba5679fc --- /dev/null +++ b/contracts/galactic_pools/pools/native/README.md @@ -0,0 +1,3 @@ +# Secret native manual-claim pool +Staking secret contract, where the rewards are allocated to a random winner based on the weight of their stake. + diff --git a/contracts/galactic_pools/pools/native/rust-toolchain b/contracts/galactic_pools/pools/native/rust-toolchain new file mode 100644 index 000000000..07ade694b --- /dev/null +++ b/contracts/galactic_pools/pools/native/rust-toolchain @@ -0,0 +1 @@ +nightly \ No newline at end of file diff --git a/contracts/galactic_pools/pools/native/secretjs/.env b/contracts/galactic_pools/pools/native/secretjs/.env new file mode 100644 index 000000000..38f796017 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/.env @@ -0,0 +1,8 @@ +SECRET_REST_URL='https://api.pulsar.scrttestnet.com' +SECRET_CHAIN_ID='pulsar-2' +ADDRESS='secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7' +MNEMONIC='taste problem oblige accuse great pig turn unlock crunch shift glare obey' +# MNEMONIC='spread simple supply draw mutual lawsuit apology draw next arch human alien' +SCRT_TO_USCRT = 1000000 +SSCRT_ADDRESS = 'secret18vd8fpwxzck93qlwghaj6arh4p7c5n8978vsyg' +SSCRT_HASH = '9587D60B8E6B078ACE12014CEEEE089530B9FABCD76535D93666A6C127AD8813' \ No newline at end of file diff --git a/contracts/galactic_pools/pools/native/secretjs/1_init/init.js b/contracts/galactic_pools/pools/native/secretjs/1_init/init.js new file mode 100644 index 000000000..215e53aa3 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/1_init/init.js @@ -0,0 +1,169 @@ +#!/usr/bin/node + +import { Wallet, SecretNetworkClient } from "secretjs"; +const grpcWebUrl = "https://grpc.testnet.secretsaturn.net/"; + +import * as dotenv from "dotenv"; // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import +dotenv.config({ path: "../.env" }); +import fs from "fs"; + +const wallet = new Wallet(process.env.MNEMONIC); +const myAddress = wallet.address; + +// To create a signer secret.js client, also pass in a wallet +const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: "pulsar-2", + wallet: wallet, + walletAddress: myAddress, +}); + +const tx1 = await secretjs.tx.compute.storeCode( + { + sender: myAddress, + wasmByteCode: fs.readFileSync(`../../contract.wasm.gz`), + source: "", + builder: "", + }, + { + broadcastCheckIntervalMs: 100, + gasLimit: 4_000_000, + } +); +console.log(tx1); + +const codeId = Number( + tx1.arrayLog.find((log) => log.type === "message" && log.key === "code_id") + .value +); + +console.log("Uploaded....."); + +const { + codeInfo: { codeHash }, +} = await secretjs.query.compute.code(codeId); + +let common_divisor = 10000; + +let validator_vector = [ + { + address: "secretvaloper1p0re3rp685fqsngfdvxg34wkwu9am2p4ckeq2h", + weightage: (60 * common_divisor) / 100, + }, + { + address: "secretvaloper1wcxr6l4hk7cf7yjlz2v68wxqvdvtf5cwwyymu2", + weightage: (40 * common_divisor) / 100, + }, +]; + +let rewards_distribution = { + tier_0: { + total_number_of_winners: String(1), + percentage_of_rewards: (20 * 10000) / 100, + }, + tier_1: { + total_number_of_winners: String(3), + percentage_of_rewards: (10 * 10000) / 100, + }, + tier_2: { + total_number_of_winners: String(9), + percentage_of_rewards: (14 * 10000) / 100, + }, + tier_3: { + total_number_of_winners: String(27), + percentage_of_rewards: (12 * 10000) / 100, + }, + tier_4: { + total_number_of_winners: String(81), + percentage_of_rewards: (19 * 10000) / 100, + }, + tier_5: { + total_number_of_winners: String(243), + percentage_of_rewards: (25 * 10000) / 100, + }, +}; + +let admins = ["secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7"]; +let triggerers = [ + "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + "secret1dr6vh9zlcjj69vv3av8mmpemwdg9z5jjad8u94", +]; +let reviewers = ["secret1dr6vh9zlcjj69vv3av8mmpemwdg9z5jjad8u94"]; + +let initMsg = { + admin: admins, + triggerer: triggerers, + reviewer: reviewers, + triggerer_share_percentage: (1 * 10000) / 100, //dividing by 1/100 * common_divisor + denom: "uscrt", + prng_seed: "ZW5pZ21hLXJvY2tzCg==", + validator: validator_vector, + unbonding_duration: 60 * 14, //21 days/ 14 mins on testnet + // round_duration: 60 * 3, //7 days + round_duration: 60 * 30, //7 days - 30 mins + + rewards_distribution, + ticket_price: String(1 * 1000000), + rewards_expiry_duration: 3600 * 24, // 45 days - 24 hours + common_divisor, + total_admin_share: (10 * common_divisor) / 100, + shade_percentage_share: (60 * common_divisor) / 100, + galactic_pools_percentage_share: (40 * common_divisor) / 100, + shade_rewards_address: "secret1zrpgrff3p2pc3ptqu07pz9nq4ezhjr9xauyhjf", + galactic_pools_rewards_address: + "secret1pa8ng8z5lwcukvqht83r5pu2zfq5vv3zdu36uu", + reserve_percentage: (60 * common_divisor) / 100, + is_sponosorship_admin_controlled: false, + unbonding_batch_duration: 60 * 2, // 3 days / 2 minutes on testnet + grand_prize_address: "secret1pa8ng8z5lwcukvqht83r5pu2zfq5vv3zdu36uu", + number_of_tickers_per_transaction: String(1000000), + sponsor_msg_edit_fee: String(1000000), +}; + +const txInit = await secretjs.tx.compute.instantiateContract( + { + sender: myAddress, + codeId, + codeHash, + initMsg, + label: `label-${Date.now()}`, + initFunds: [], + }, + { + broadcastCheckIntervalMs: 100, + gasLimit: 4_000_000, + } +); + +console.log(txInit); + +const contractAddress = txInit.arrayLog.find( + (log) => log.type === "message" && log.key === "contract_address" +).value; +console.log(contractAddress); + +var contract = { address: contractAddress, hash: codeHash }; + +//2- make it JSON: + +var contract_details = JSON.stringify(contract); + +/* 3- save your json file and dont forget that fs.writeFile(...) +requires a third (or fourth) parameter which is a callback +function to be invoked when the operation completes. */ + +fs.writeFile( + "../contract_details.json", + contract_details, + function (err, result) { + if (err) console.log("error", err); + } +); + +try { + fs.unlink("../users_details.json", contract_details, function (err, result) { + if (err) console.log("error", err); + }); +} catch (e) { + //nothing +} diff --git a/contracts/galactic_pools/pools/native/secretjs/2_execute/admins/end_lottery.js b/contracts/galactic_pools/pools/native/secretjs/2_execute/admins/end_lottery.js new file mode 100644 index 000000000..9c85238d6 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/2_execute/admins/end_lottery.js @@ -0,0 +1,62 @@ +import { Wallet, SecretNetworkClient } from "secretjs"; +const grpcWebUrl = "https://grpc.testnet.secretsaturn.net/"; + +import * as dotenv from "dotenv"; // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import +dotenv.config({ path: "../.env" }); +import fs from "fs"; +import { debug } from "console"; + +async function end_lottery() { + const wallet = new Wallet(process.env.MNEMONIC); + const myAddress = wallet.address; + + // To create a signer secret.js client, also pass in a wallet + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: "pulsar-2", + wallet: wallet, + walletAddress: myAddress, + }); + + let bufferData = fs.readFileSync("./../contract_details.json"); + let stData = bufferData.toString(); + let data = JSON.parse(stData); + + const contractAddress = data.address; + + const codeHash = data.hash; + let amount = String(100 * process.env.SCRT_TO_USCRT); + + // let sim = await secretjs.tx.compute.executeContract.simulate({ + // sender: myAddress, + // contractAddress: contractAddress, + // codeHash: codeHash, // optional but way faster + // msg: { + // end_lottery: {} + // } + // }) + + // let gasLimit = Math.ceil(sim.gasInfo.gasUsed * 1.9) + let gasLimit = 1000000; + try { + const tx = await secretjs.tx.compute.executeContract( + { + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + end_round: {}, + }, + }, + { + gasLimit, + } + ); + console.log(tx); + console.log(tx.tx.body.messages); + } catch (err) { + console.log(err); + } +} + +export default end_lottery; diff --git a/contracts/galactic_pools/pools/native/secretjs/2_execute/admins/end_lottery_loop.js b/contracts/galactic_pools/pools/native/secretjs/2_execute/admins/end_lottery_loop.js new file mode 100644 index 000000000..fbd5ecc72 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/2_execute/admins/end_lottery_loop.js @@ -0,0 +1,57 @@ +import { Wallet, SecretNetworkClient } from "secretjs"; +const grpcWebUrl = "https://grpc.testnet.secretsaturn.net/"; + +import * as dotenv from "dotenv"; // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import +dotenv.config({ path: "../.env" }); +import fs from "fs"; +import { debug } from "console"; + +async function end_lottery_loop() { + const wallet = new Wallet(process.env.MNEMONIC); + const myAddress = wallet.address; + + // To create a signer secret.js client, also pass in a wallet + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: "pulsar-2", + wallet: wallet, + walletAddress: myAddress, + }); + + let bufferData = fs.readFileSync("./../contract_details.json"); + let stData = bufferData.toString(); + let data = JSON.parse(stData); + + const contractAddress = data.address; + + const codeHash = data.hash; + + // let gasLimit = Math.ceil(sim.gasInfo.gasUsed * 1.9) + let gasLimit = 1000000; + + setInterval(async () => { + console.log("STARTING"); + try { + const tx = await secretjs.tx.compute.executeContract( + { + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + end_round: {}, + }, + }, + { + gasLimit, + } + ); + console.log(tx); + console.log(tx.tx.body.messages); + } catch (err) { + console.log(err); + } + console.log("Waiting"); + }, 60 * 32 * 1000); +} + +export default end_lottery_loop; diff --git a/contracts/galactic_pools/pools/native/secretjs/2_execute/admins/rebalance_val_set.js b/contracts/galactic_pools/pools/native/secretjs/2_execute/admins/rebalance_val_set.js new file mode 100644 index 000000000..b56c68b9f --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/2_execute/admins/rebalance_val_set.js @@ -0,0 +1,59 @@ +import { Wallet, SecretNetworkClient } from 'secretjs' +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' +import * as dotenv from 'dotenv' +dotenv.config({ path: '../.env' }) +import fs from 'fs' + +async function unbond_batch () { + const wallet = new Wallet(process.env.MNEMONIC) + const myAddress = wallet.address + + // To create a signer secret.js client, also pass in a wallet + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2', + wallet: wallet, + walletAddress: myAddress + // encryptionSeed: new Uint8Array( + // Buffer.from('helloworld1234567891123456789123') + // ) + }) + + let bufferData = fs.readFileSync('./../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + + const contractAddress = data.address + + const codeHash = data.hash + + // let sim = await secretjs.tx.compute.executeContract.simulate({ + // sender: myAddress, + // contractAddress: contractAddress, + // codeHash: codeHash, // optional but way faster + // msg: { + // unbond_batch: {} + // } + // }) + + // let gasLimit = Math.ceil(sim.gasInfo.gasUsed * 1.9) + let gasLimit = 1000000 + console.log('gasLimit = ' + gasLimit) + + const tx = await secretjs.tx.compute.executeContract( + { + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + rebalance_validator_set: {} + } + }, + { + gasLimit + } + ) + console.log(tx) +} + +export default unbond_batch diff --git a/contracts/galactic_pools/pools/native/secretjs/2_execute/admins/unbond_batch.js b/contracts/galactic_pools/pools/native/secretjs/2_execute/admins/unbond_batch.js new file mode 100644 index 000000000..4d1569f8d --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/2_execute/admins/unbond_batch.js @@ -0,0 +1,59 @@ +import { Wallet, SecretNetworkClient } from 'secretjs' +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' +import * as dotenv from 'dotenv' +dotenv.config({ path: '../.env' }) +import fs from 'fs' + +async function unbond_batch () { + const wallet = new Wallet(process.env.MNEMONIC) + const myAddress = wallet.address + + // To create a signer secret.js client, also pass in a wallet + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2', + wallet: wallet, + walletAddress: myAddress + // encryptionSeed: new Uint8Array( + // Buffer.from('helloworld1234567891123456789123') + // ) + }) + + let bufferData = fs.readFileSync('./../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + + const contractAddress = data.address + + const codeHash = data.hash + + // let sim = await secretjs.tx.compute.executeContract.simulate({ + // sender: myAddress, + // contractAddress: contractAddress, + // codeHash: codeHash, // optional but way faster + // msg: { + // unbond_batch: {} + // } + // }) + + // let gasLimit = Math.ceil(sim.gasInfo.gasUsed * 1.9) + let gasLimit = 1000000 + console.log('gasLimit = ' + gasLimit) + + const tx = await secretjs.tx.compute.executeContract( + { + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + unbond_batch: {} + } + }, + { + gasLimit + } + ) + console.log(tx) +} + +export default unbond_batch diff --git a/contracts/galactic_pools/pools/native/secretjs/2_execute/admins/unbond_batch_loop.js b/contracts/galactic_pools/pools/native/secretjs/2_execute/admins/unbond_batch_loop.js new file mode 100644 index 000000000..c5df0b737 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/2_execute/admins/unbond_batch_loop.js @@ -0,0 +1,67 @@ +import { Wallet, SecretNetworkClient } from "secretjs"; +const grpcWebUrl = "https://grpc.testnet.secretsaturn.net/"; +import * as dotenv from "dotenv"; +dotenv.config({ path: "../.env" }); +import fs from "fs"; + +async function unbond_batch_loop() { + const wallet = new Wallet(process.env.MNEMONIC); + const myAddress = wallet.address; + + // To create a signer secret.js client, also pass in a wallet + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: "pulsar-2", + wallet: wallet, + walletAddress: myAddress, + // encryptionSeed: new Uint8Array( + // Buffer.from('helloworld1234567891123456789123') + // ) + }); + + let bufferData = fs.readFileSync("./../contract_details.json"); + let stData = bufferData.toString(); + let data = JSON.parse(stData); + + const contractAddress = data.address; + + const codeHash = data.hash; + + // let sim = await secretjs.tx.compute.executeContract.simulate({ + // sender: myAddress, + // contractAddress: contractAddress, + // codeHash: codeHash, // optional but way faster + // msg: { + // unbond_batch: {} + // } + // }) + + // let gasLimit = Math.ceil(sim.gasInfo.gasUsed * 1.9) + let gasLimit = 1000000; + console.log("gasLimit = " + gasLimit); + + setInterval(async () => { + console.log("STARTING"); + try { + const tx = await secretjs.tx.compute.executeContract( + { + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + unbond_batch: {}, + }, + }, + { + gasLimit, + } + ); + console.log(tx); + } catch (err) { + console.log(err); + } + console.log("Waiting"); + }, 60 * 3 * 1000); +} + +export default unbond_batch_loop; diff --git a/contracts/galactic_pools/pools/native/secretjs/2_execute/allinone.js b/contracts/galactic_pools/pools/native/secretjs/2_execute/allinone.js new file mode 100644 index 000000000..ce93bfedb --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/2_execute/allinone.js @@ -0,0 +1,16 @@ +import deposit from './users/deposit.js' +import claim_rewards from './users/claim_rewards.js' +import end_lottery from './admins/end_lottery.js' + +async function allinone () { + for (let i = 0; i < 100; i++) { + setTimeout(() => { + //C - 1 second later + }, 5000) + await deposit(10000) + await end_lottery() + await claim_rewards() + } +} + +export default allinone diff --git a/contracts/galactic_pools/pools/native/secretjs/2_execute/main.js b/contracts/galactic_pools/pools/native/secretjs/2_execute/main.js new file mode 100644 index 000000000..422ef70d7 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/2_execute/main.js @@ -0,0 +1,152 @@ +import deposit from "./users/deposit.js"; +import request_withdraw from "./users/request_withdraw.js"; +import withdraw from "./users/withdraw.js"; +import claim_rewards from "./users/claim_rewards.js"; + +import sponsor from "./sponsors/sponsor.js"; +import sponsor_request_withdraw from "./sponsors/sponsor_request_withdraw.js"; +import sponsor_withdraw from "./sponsors/sponsor_withdraw.js"; + +import end_lottery from "./admins/end_lottery.js"; +import end_lottery_loop from "./admins/end_lottery_loop.js"; +import unbond_batch_loop from "./admins/unbond_batch_loop.js"; +import unbond_batch from "./admins/unbond_batch.js"; +import rebalance_val_set from "./admins/rebalance_val_set.js"; + +import all_in_one from "./allinone.js"; + +const args = process.argv.slice(2); + +let passed = 0; +let failed = 0; +let starting_time = Date.now(); +//running test + +console.log(args); + +console.log("Running tests"); +let total_test = 1; +console.log( + "************************************************** User **************************************************" +); + +if (args.includes("--all") || args.includes("--allinone")) { + all_in_one(); + + total_test -= 1; +} + +if (args.includes("--all") || args.includes("--wrap")) { + if (args.includes("--amount")) { + analyse_status(await wrap(parseInt(args[2]))); + } else { + analyse_status(await wrap()); + } + total_test -= 1; +} + +if (args.includes("--all") || args.includes("--deposit")) { + if (args.includes("--amount")) { + console.log(args); + analyse_status(await deposit(parseInt(args[2]))); + } else { + analyse_status(await deposit()); + } + + total_test -= 1; +} + +if (args.includes("--all") || args.includes("--request_withdraw")) { + if (args.includes("--amount")) { + analyse_status(await request_withdraw(parseInt(args[2]))); + } else { + analyse_status(await request_withdraw()); + } + total_test -= 1; +} +if (args.includes("--all") || args.includes("--withdraw")) { + if (args.includes("--amount")) { + analyse_status(await withdraw(parseInt(args[2]))); + } else { + analyse_status(await withdraw()); + } + total_test -= 1; +} + +if (args.includes("--all") || args.includes("--claim_rewards")) { + analyse_status(await claim_rewards()); + total_test -= 1; +} + +console.log( + "************************************************** Sponsor ************************************************** " +); + +if (args.includes("--all") || args.includes("--sponsor")) { + if (args.includes("--amount")) { + analyse_status(await sponsor(parseInt(args[2]))); + } else { + analyse_status(await sponsor()); + } + + total_test -= 1; +} + +if (args.includes("--all") || args.includes("--sponsor_request_withdraw")) { + if (args.includes("--amount")) { + analyse_status(await sponsor_request_withdraw(parseInt(args[2]))); + } else { + analyse_status(await sponsor_request_withdraw()); + } + + total_test -= 1; +} +if (args.includes("--all") || args.includes("--sponsor_withdraw")) { + if (args.includes("--amount")) { + analyse_status(await sponsor_withdraw(parseInt(args[2]))); + } else { + analyse_status(await sponsor_withdraw()); + } + + total_test -= 1; +} + +console.log( + "************************************************** Admin ************************************************** " +); + +if (args.includes("--all") || args.includes("--end_lottery")) { + analyse_status(await end_lottery()); + total_test -= 1; +} + +if (args.includes("--all") || args.includes("--end_lottery_loop")) { + analyse_status(await end_lottery_loop()); + total_test -= 1; +} + +if (args.includes("--all") || args.includes("--unbond_batch")) { + analyse_status(await unbond_batch()); + total_test -= 1; +} +if (args.includes("--all") || args.includes("--unbond_batch_loop")) { + analyse_status(await unbond_batch_loop()); + total_test -= 1; +} +if (args.includes("--all") || args.includes("--rebalance_val_set")) { + analyse_status(await rebalance_val_set()); + total_test -= 1; +} + +let passed_time = (Date.now() - starting_time) / 1000; +console.log( + `test result: ok. ${passed} passed; ${failed} failed; ${total_test} ignored; 0 measured; 0 filtered out; finished in ${passed_time}s` +); + +function analyse_status(status) { + if (status === true) { + passed += 1; + } else { + failed += 1; + } +} diff --git a/contracts/galactic_pools/pools/native/secretjs/2_execute/sponsors/sponsor.js b/contracts/galactic_pools/pools/native/secretjs/2_execute/sponsors/sponsor.js new file mode 100644 index 000000000..b40de76bf --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/2_execute/sponsors/sponsor.js @@ -0,0 +1,66 @@ +import { Wallet, SecretNetworkClient } from 'secretjs' +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' + +import * as dotenv from 'dotenv' // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import +dotenv.config({ path: '../.env' }) +import fs from 'fs' +import { debug } from 'console' + +async function deposits (amt = 10) { + const wallet = new Wallet(process.env.MNEMONIC) + const myAddress = wallet.address + + // To create a signer secret.js client, also pass in a wallet + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2', + wallet: wallet, + walletAddress: myAddress + }) + + let bufferData = fs.readFileSync('./../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + + const contractAddress = data.address + + const codeHash = data.hash + let amount = String(amt * process.env.SCRT_TO_USCRT) + + let sim = await secretjs.tx.compute.executeContract.simulate({ + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + sponsor: {} + }, + sentFunds: [{ amount: amount, denom: 'uscrt' }] // optional + }) + + let gasLimit = Math.ceil(sim.gasInfo.gasUsed * 1.34) + try { + const tx = await secretjs.tx.compute.executeContract( + { + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + sponsor: {} + }, + sentFunds: [{ amount: amount, denom: 'uscrt' }] // optional + }, + { + gasLimit + } + ) + console.log( + `Deposited ${amount / process.env.SCRT_TO_USCRT} Scrt successfully` + ) + } catch (err) { + console.log(err) + } + + return true +} + +export default deposits diff --git a/contracts/galactic_pools/pools/native/secretjs/2_execute/sponsors/sponsor_request_withdraw.js b/contracts/galactic_pools/pools/native/secretjs/2_execute/sponsors/sponsor_request_withdraw.js new file mode 100644 index 000000000..580a86790 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/2_execute/sponsors/sponsor_request_withdraw.js @@ -0,0 +1,60 @@ +import { Wallet, SecretNetworkClient } from 'secretjs' +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' + +import * as dotenv from 'dotenv' // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import +dotenv.config({ path: '../.env' }) +import fs from 'fs' +import { debug } from 'console' + +async function request_withdraw (am = 10) { + const wallet = new Wallet(process.env.MNEMONIC) + const myAddress = wallet.address + + // To create a signer secret.js client, also pass in a wallet + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2', + wallet: wallet, + walletAddress: myAddress + }) + + let bufferData = fs.readFileSync('./../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + + const contractAddress = data.address + const codeHash = data.hash + let amount = String(am * process.env.SCRT_TO_USCRT) + + let sim = await secretjs.tx.compute.executeContract.simulate({ + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + sponsor_request_withdraw: { + amount + } + } + }) + + let gasLimit = Math.ceil(sim.gasInfo.gasUsed * 1.34) + console.log('gasLimit = ' + gasLimit) + + const tx = await secretjs.tx.compute.executeContract( + { + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + sponsor_request_withdraw: { amount } + } + }, + { + gasLimit + } + ) + console.log(tx) + return true +} + +export default request_withdraw diff --git a/contracts/galactic_pools/pools/native/secretjs/2_execute/sponsors/sponsor_withdraw.js b/contracts/galactic_pools/pools/native/secretjs/2_execute/sponsors/sponsor_withdraw.js new file mode 100644 index 000000000..83bc5d65b --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/2_execute/sponsors/sponsor_withdraw.js @@ -0,0 +1,66 @@ +import { Wallet, SecretNetworkClient } from 'secretjs' +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' + +import * as dotenv from 'dotenv' // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import +dotenv.config({ path: '../.env' }) +import fs from 'fs' +import { debug } from 'console' + +async function withdraw (am = 10) { + const wallet = new Wallet(process.env.MNEMONIC) + const myAddress = wallet.address + + // To create a signer secret.js client, also pass in a wallet + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2', + wallet: wallet, + walletAddress: myAddress + }) + + let bufferData = fs.readFileSync('./../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + + const contractAddress = data.address + + const codeHash = data.hash + let amount = String(am * process.env.SCRT_TO_USCRT) + + // let sim = await secretjs.tx.compute.executeContract.simulate({ + // sender: myAddress, + // contractAddress: contractAddress, + // codeHash: codeHash, // optional but way faster + // msg: { + // sponsor_withdraw: { amount: amount, wrapping_enabled: false } + // } + // }) + + // let gasLimit = Math.ceil(sim.gasInfo.gasUsed * 1.9) + + let gasLimit = 1000000 + + try { + const tx = await secretjs.tx.compute.executeContract( + { + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + sponsor_withdraw: { + amount: amount, + wrapping_enabled: false + } + } + }, + { + gasLimit + } + ) + console.log(tx) + } catch (err) { + console.log(err) + } +} + +export default withdraw diff --git a/contracts/galactic_pools/pools/native/secretjs/2_execute/users/claim_rewards.js b/contracts/galactic_pools/pools/native/secretjs/2_execute/users/claim_rewards.js new file mode 100644 index 000000000..c9f50df61 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/2_execute/users/claim_rewards.js @@ -0,0 +1,60 @@ +import { Wallet, SecretNetworkClient } from "secretjs"; +const grpcWebUrl = "https://grpc.testnet.secretsaturn.net/"; + +import * as dotenv from "dotenv"; // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import +dotenv.config({ path: "../.env" }); +import fs from "fs"; + +async function claim_rewards() { + const wallet = new Wallet(process.env.MNEMONIC); + const myAddress = wallet.address; + + // To create a signer secret.js client, also pass in a wallet + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: "pulsar-2", + wallet: wallet, + walletAddress: myAddress, + }); + + let bufferData = fs.readFileSync("./../contract_details.json"); + let stData = bufferData.toString(); + let data = JSON.parse(stData); + + const contractAddress = data.address; + + const codeHash = data.hash; + + // let sim = await secretjs.tx.compute.executeContract.simulate({ + // sender: myAddress, + // contractAddress: contractAddress, + // codeHash: codeHash, // optional but way faster + // msg: { + // claim_rewards: { + // wrapping_enabled: false + // } + // } + // }) + + // let gasLimit = Math.ceil(sim.gasInfo.gasUsed * 1.5) + let gasLimit = 1000000; + console.log("gasLimit = " + gasLimit); + + const tx = await secretjs.tx.compute.executeContract( + { + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + claim_rewards: { wrapping_enabled: false }, + }, + }, + { + gasLimit, + } + ); + console.log(tx); + return true; +} + +export default claim_rewards; diff --git a/contracts/galactic_pools/pools/native/secretjs/2_execute/users/deposit.js b/contracts/galactic_pools/pools/native/secretjs/2_execute/users/deposit.js new file mode 100644 index 000000000..05d6859f0 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/2_execute/users/deposit.js @@ -0,0 +1,109 @@ +import { Wallet, SecretNetworkClient } from "secretjs"; +const grpcWebUrl = "https://grpc.testnet.secretsaturn.net/"; + +import * as dotenv from "dotenv"; // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import +dotenv.config({ path: "../.env" }); +import fs from "fs"; +import { debug } from "console"; + +async function deposits(amt = 10) { + const wallet = new Wallet(process.env.MNEMONIC); + const myAddress = wallet.address; + + // To create a signer secret.js client, also pass in a wallet + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: "pulsar-2", + wallet: wallet, + walletAddress: myAddress, + }); + + let bufferData = fs.readFileSync("./../contract_details.json"); + let stData = bufferData.toString(); + let data = JSON.parse(stData); + + const contractAddress = data.address; + + const codeHash = data.hash; + let amount = String(amt * process.env.SCRT_TO_USCRT); + + let sim = await secretjs.tx.compute.executeContract.simulate({ + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + deposit: {}, + }, + sentFunds: [{ amount: amount, denom: "uscrt" }], // optional + }); + + let gasLimit = Math.ceil(sim.gasInfo.gasUsed * 1.34); + console.log(gasLimit); + try { + const tx = await secretjs.tx.compute.executeContract( + { + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + deposit: {}, + }, + sentFunds: [{ amount: amount, denom: "uscrt" }], // optional + }, + { + gasLimit, + } + ); + console.log( + `Deposited ${amount / process.env.SCRT_TO_USCRT} Scrt successfully` + ); + } catch (err) { + console.log(err); + } + + let user = { + address: myAddress, + deposits: amount, + }; + let usersbufferData; + try { + usersbufferData = fs.readFileSync("../users_details.json"); + } catch (err) { + let vec = []; + vec.push(user); + var user_details = JSON.stringify(vec); + console.log(user_details); + fs.writeFile("../users_details.json", user_details, function (err, result) { + if (err) console.log("error", err); + }); + } + if (usersbufferData != null) { + // if user exists + + let usersStData = usersbufferData.toString(); + let usersdata = JSON.parse(usersStData); + + var newUser = user; + + let exisits = false; + usersdata.forEach(function (userdata) { + if (newUser.address === userdata.address) { + userdata.deposits = String( + Number(userdata.deposits) + Number(newUser.deposits) + ); + exisits = true; + } + }); + if (!exisits) { + usersdata.push(user); + } + var user_details = JSON.stringify(usersdata); + + fs.writeFile("../users_details.json", user_details, function (err, result) { + if (err) console.log("error", err); + }); + } + return true; +} + +export default deposits; diff --git a/contracts/galactic_pools/pools/native/secretjs/2_execute/users/request_withdraw.js b/contracts/galactic_pools/pools/native/secretjs/2_execute/users/request_withdraw.js new file mode 100644 index 000000000..8908276bc --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/2_execute/users/request_withdraw.js @@ -0,0 +1,61 @@ +import { Wallet, SecretNetworkClient } from "secretjs"; +const grpcWebUrl = "https://grpc.testnet.secretsaturn.net/"; + +import * as dotenv from "dotenv"; // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import +dotenv.config({ path: "../.env" }); +import fs from "fs"; +import { debug } from "console"; + +async function request_withdraw(am = 10) { + const wallet = new Wallet(process.env.MNEMONIC); + const myAddress = wallet.address; + + // To create a signer secret.js client, also pass in a wallet + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: "pulsar-2", + wallet: wallet, + walletAddress: myAddress, + }); + + let bufferData = fs.readFileSync("./../contract_details.json"); + let stData = bufferData.toString(); + let data = JSON.parse(stData); + + const contractAddress = data.address; + const codeHash = data.hash; + // let amount = String(am * process.env.SCRT_TO_USCRT) + let amount = String(am); + + let sim = await secretjs.tx.compute.executeContract.simulate({ + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + request_withdraw: { + amount, + }, + }, + }); + + let gasLimit = Math.ceil(sim.gasInfo.gasUsed * 1.34); + console.log("gasLimit = " + gasLimit); + + const tx = await secretjs.tx.compute.executeContract( + { + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + request_withdraw: { amount }, + }, + }, + { + gasLimit, + } + ); + console.log(tx); + return true; +} + +export default request_withdraw; diff --git a/contracts/galactic_pools/pools/native/secretjs/2_execute/users/withdraw.js b/contracts/galactic_pools/pools/native/secretjs/2_execute/users/withdraw.js new file mode 100644 index 000000000..5dd4e994c --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/2_execute/users/withdraw.js @@ -0,0 +1,65 @@ +import { Wallet, SecretNetworkClient } from "secretjs"; +const grpcWebUrl = "https://grpc.testnet.secretsaturn.net/"; + +import * as dotenv from "dotenv"; // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import +dotenv.config({ path: "../.env" }); +import fs from "fs"; +import { debug } from "console"; + +async function withdraw(am = 10) { + const wallet = new Wallet(process.env.MNEMONIC); + const myAddress = wallet.address; + + // To create a signer secret.js client, also pass in a wallet + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: "pulsar-2", + wallet: wallet, + walletAddress: myAddress, + }); + + let bufferData = fs.readFileSync("./../contract_details.json"); + let stData = bufferData.toString(); + let data = JSON.parse(stData); + + const contractAddress = data.address; + + const codeHash = data.hash; + let amount = String(am * process.env.SCRT_TO_USCRT); + + // let sim = await secretjs.tx.compute.executeContract.simulate({ + // sender: myAddress, + // contractAddress: contractAddress, + // codeHash: codeHash, // optional but way faster + // msg: { + // withdraw: { amount: amount, wrapping_enabled: false } + // } + // }) + + // let gasLimit = Math.ceil(sim.gasInfo.gasUsed * 1.9) + let gasLimit = 218513; + + try { + const tx = await secretjs.tx.compute.executeContract( + { + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + withdraw: { + amount: amount, + wrapping_enabled: false, + }, + }, + }, + { + gasLimit, + } + ); + console.log(tx); + } catch (err) { + console.log(err); + } +} + +export default withdraw; diff --git a/contracts/galactic_pools/pools/native/secretjs/2_execute/users/wrap.js b/contracts/galactic_pools/pools/native/secretjs/2_execute/users/wrap.js new file mode 100644 index 000000000..2f1e1910c --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/2_execute/users/wrap.js @@ -0,0 +1,57 @@ +import { Wallet, SecretNetworkClient } from 'secretjs' +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' + +import * as dotenv from 'dotenv' // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import +dotenv.config({ path: '../.env' }) +import fs from 'fs' +import { debug } from 'console' +async function wrap (amt = 10) { + const wallet = new Wallet(process.env.MNEMONIC) + const myAddress = wallet.address + + // To create a signer secret.js client, also pass in a wallet + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2', + wallet: wallet, + walletAddress: myAddress + }) + + let bufferData = fs.readFileSync('./../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + + const contractAddress = process.env.SSCRT_ADDRESS + const codeHash = process.env.SSCRT_HASH + + let amount = String(amt * process.env.SCRT_TO_USCRT) + + let sim = await secretjs.tx.compute.executeContract.simulate({ + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + deposit: {} + }, + sentFunds: [{ amount, denom: 'uscrt' }] // optional + }) + + let gasLimit = Math.ceil(sim.gasInfo.gasUsed * 1.34) + + const tx = await secretjs.tx.compute.executeContract( + { + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + deposit: {} + }, + sentFunds: [{ amount, denom: 'uscrt' }] // optional + }, + { + gasLimit + } + ) + console.log(`Wrapped ${amount / process.env.SCRT_TO_USCRT} Scrt successfully`) +} +export default wrap diff --git a/contracts/galactic_pools/pools/native/secretjs/3_query/main.js b/contracts/galactic_pools/pools/native/secretjs/3_query/main.js new file mode 100644 index 000000000..3e38137ec --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/3_query/main.js @@ -0,0 +1,120 @@ +import contract_config from "./pool_contract/public_queries/contract_config.js"; +import contract_status from "./pool_contract/public_queries/contract_status.js"; + +import current_rewards from "./pool_contract/public_queries/current_rewards.js"; +import example from "./pool_contract/public_queries/example.js"; +import lottery_info from "./pool_contract/public_queries/lottery_info.js"; +import pool_info from "./pool_contract/public_queries/pool_info.js"; +import pool_liq_stats from "./pool_contract/public_queries/pool_liquidity_stats.js"; +import pool_liq_stats_specific from "./pool_contract/public_queries/pool_liquidity_stats_specific.js"; +import sponsors from "./pool_contract/public_queries/sponsors.js"; +import sponsors_msg_req_check from "./pool_contract/public_queries/sponsors_msg_req_check.js"; +import reward_stats from "./pool_contract/public_queries/reward_stats.js"; + +import delegated from "./pool_contract/private_queries/delegated.js"; +import withdrawable from "./pool_contract/private_queries/withdrawable.js"; +import unbondings from "./pool_contract/private_queries/unbondings.js"; +import records from "./pool_contract/private_queries/records.js"; + +import sscrt_balance from "./sscrt/sscrt_balance.js"; + +const args = process.argv.slice(2); + +let passed = 0; +let failed = 0; +let starting_time = Date.now(); +//running test + +console.log("Running tests"); +let total_test = 15; +console.log( + "**************************************************Test**************************************************" +); +if (args.includes("--all") || args.includes("--test")) { + analyse_status(await example()); + total_test -= 1; +} +console.log( + "************************************************** Public **************************************************" +); + +if (args.includes("--all") || args.includes("--contract_config")) { + analyse_status(await contract_config()); + total_test -= 1; +} +if (args.includes("--all") || args.includes("--contract_status")) { + analyse_status(await contract_status()); + total_test -= 1; +} + +if (args.includes("--all") || args.includes("--current_rewards")) { + analyse_status(await current_rewards()); + total_test -= 1; +} +if (args.includes("--all") || args.includes("--lottery_info")) { + analyse_status(await lottery_info()); + total_test -= 1; +} +if (args.includes("--all") || args.includes("--pool_info")) { + analyse_status(await pool_info()); + total_test -= 1; +} +if (args.includes("--all") || args.includes("--pool_liq_stats")) { + analyse_status(await pool_liq_stats()); + total_test -= 1; +} +if (args.includes("--all") || args.includes("--pool_liq_stats_specific")) { + analyse_status(await pool_liq_stats_specific()); + total_test -= 1; +} +if (args.includes("--all") || args.includes("--reward_stats")) { + analyse_status(await reward_stats()); + total_test -= 1; +} + +if (args.includes("--all") || args.includes("--sponsors")) { + analyse_status(await sponsors()); + total_test -= 1; +} +if (args.includes("--all") || args.includes("--sponsors_msg_req_check")) { + analyse_status(await sponsors_msg_req_check()); + total_test -= 1; +} + +console.log( + "************************************************** Private **************************************************" +); + +if (args.includes("--all") || args.includes("--sscrt_balance")) { + analyse_status(await sscrt_balance()); + total_test -= 1; +} +if (args.includes("--all") || args.includes("--delegated")) { + analyse_status(await delegated()); + total_test -= 1; +} +if (args.includes("--all") || args.includes("--withdrawable")) { + analyse_status(await withdrawable()); + total_test -= 1; +} +if (args.includes("--all") || args.includes("--unbondings")) { + analyse_status(await unbondings()); + total_test -= 1; +} +if (args.includes("--all") || args.includes("--records")) { + analyse_status(await records()); + total_test -= 1; +} + +let passed_time = (Date.now() - starting_time) / 1000; +console.log( + `test result: ok. ${passed} passed; ${failed} failed; ${total_test} ignored; 0 measured; 0 filtered out; finished in ${passed_time}s` +); + +function analyse_status(status) { + if (status === true) { + passed += 1; + } else { + failed += 1; + } +} diff --git a/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/private_queries/delegated.js b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/private_queries/delegated.js new file mode 100644 index 000000000..081ff7b94 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/private_queries/delegated.js @@ -0,0 +1,68 @@ +import pkg from "secretjs"; +const { Wallet, SecretNetworkClient, grpc, signAmino } = pkg; +const grpcWebUrl = "https://grpc.testnet.secretsaturn.net/"; +import fs from "fs"; +import { debug } from "console"; +import * as dotenv from "dotenv"; // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import +dotenv.config({ path: "../.env" }); + +async function delegated() { + const wallet = new Wallet(process.env.MNEMONIC); + const myAddress = wallet.address; + + // To create a readonly secret.js client, just pass in a gRPC-web endpoint + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: "pulsar-2", + wallet: wallet, + walletAddress: myAddress, + }); + + let bufferData = fs.readFileSync("../contract_details.json"); + let stData = bufferData.toString(); + let data = JSON.parse(stData); + const contract = data.address; + + const hash = data.hash; + + const allowedTokens = [data.address]; + const permissions = ["owner", "delegated"]; + const chainId = process.env.SECRET_CHAIN_ID; + let permitName = "Permit1"; + + let permit = await secretjs.utils.accessControl.permit.sign( + myAddress, + chainId, + permitName, + allowedTokens, + permissions, + false + ); + let delegated = await secretjs.query.compute.queryContract({ + contractAddress: contract, + codeHash: hash, // optional but way faster + query: { + with_permit: { + permit, + + query: { + delegated: {}, + }, + }, + }, + }); + + console.log( + "-------------------------------- Delegated Start --------------------------------" + ); + + console.log( + `Total Delegated: ${parseInt( + delegated.amount / process.env.SCRT_TO_USCRT + )} SCRT` + ); + console.log(`Total Delegated: ${parseInt(delegated.amount)} SCRT`); + return true; +} + +export default delegated; diff --git a/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/private_queries/records.js b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/private_queries/records.js new file mode 100644 index 000000000..9b5380827 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/private_queries/records.js @@ -0,0 +1,66 @@ +import pkg from 'secretjs' +const { Wallet, SecretNetworkClient, grpc, signAmino } = pkg +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' +import fs from 'fs' +import { debug } from 'console' +import * as dotenv from 'dotenv' // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import +dotenv.config({ path: '../.env' }) + +async function records () { + const wallet = new Wallet(process.env.MNEMONIC) + const myAddress = wallet.address + + // To create a readonly secret.js client, just pass in a gRPC-web endpoint + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2', + wallet: wallet, + walletAddress: myAddress + }) + + let bufferData = fs.readFileSync('../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + const contract = data.address + + const hash = data.hash + + const allowedTokens = [data.address] + const permissions = ['owner', 'delegated'] + const chainId = process.env.SECRET_CHAIN_ID + let permitName = 'Permit1' + + let permit = await secretjs.utils.accessControl.permit.sign( + myAddress, + chainId, + permitName, + allowedTokens, + permissions, + false + ) + let records = await secretjs.query.compute.queryContract({ + contractAddress: contract, + codeHash: hash, // optional but way faster + query: { + with_permit: { + permit, + + query: { + records: { + page_size: 50, + start_pag: 0 + } + } + } + } + }) + + console.log( + '-------------------------------- records Start --------------------------------' + ) + + debug(records) + return true +} + +export default records diff --git a/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/private_queries/unbondings.js b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/private_queries/unbondings.js new file mode 100644 index 000000000..cb391234e --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/private_queries/unbondings.js @@ -0,0 +1,76 @@ +import pkg from "secretjs"; +const { Wallet, SecretNetworkClient, grpc, signAmino } = pkg; +const grpcWebUrl = "https://grpc.testnet.secretsaturn.net/"; +import fs from "fs"; +import { debug } from "console"; +import * as dotenv from "dotenv"; // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import +dotenv.config({ path: "../.env" }); + +async function withdrawable() { + const wallet = new Wallet(process.env.MNEMONIC); + const myAddress = wallet.address; + + // To create a readonly secret.js client, just pass in a gRPC-web endpoint + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: "pulsar-2", + wallet: wallet, + walletAddress: myAddress, + }); + + let bufferData = fs.readFileSync("../contract_details.json"); + let stData = bufferData.toString(); + let data = JSON.parse(stData); + const contract = data.address; + + const hash = data.hash; + + const allowedTokens = [data.address]; + const permissions = ["owner", "delegated"]; + const chainId = process.env.SECRET_CHAIN_ID; + let permitName = "Permit1"; + + let permit = await secretjs.utils.accessControl.permit.sign( + myAddress, + chainId, + permitName, + allowedTokens, + permissions, + false + ); + let unbondings = await secretjs.query.compute.queryContract({ + contractAddress: contract, + codeHash: hash, // optional but way faster + query: { + with_permit: { + permit, + + query: { + unbondings: {}, + }, + }, + }, + }); + + console.log( + "-------------------------------- unbondings Start --------------------------------" + ); + for (const val of unbondings.vec) { + console.log("amount: " + val.amount); + console.log("unbonding_batch_index: " + val.unbonding_batch_index); + let time_left = 0; + if (Date.now() / 1000 < val.unbonding_time) { + time_left = parseInt( + Number(val.unbonding_time - Number(Date.now() / 1000)) + ); + } + + console.log(val); + + console.log("time left: " + time_left); + } + + return true; +} + +export default withdrawable; diff --git a/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/private_queries/withdrawable.js b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/private_queries/withdrawable.js new file mode 100644 index 000000000..1a1e4e9b0 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/private_queries/withdrawable.js @@ -0,0 +1,67 @@ +import pkg from 'secretjs' +const { Wallet, SecretNetworkClient, grpc, signAmino } = pkg +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' +import fs from 'fs' +import { debug } from 'console' +import * as dotenv from 'dotenv' // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import +dotenv.config({ path: '../.env' }) + +async function withdrawable () { + const wallet = new Wallet(process.env.MNEMONIC) + const myAddress = wallet.address + + // To create a readonly secret.js client, just pass in a gRPC-web endpoint + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2', + wallet: wallet, + walletAddress: myAddress + }) + + let bufferData = fs.readFileSync('../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + const contract = data.address + + const hash = data.hash + + const allowedTokens = [data.address] + const permissions = ['owner', 'delegated'] + const chainId = process.env.SECRET_CHAIN_ID + let permitName = 'Permit1' + + let permit = await secretjs.utils.accessControl.permit.sign( + myAddress, + chainId, + permitName, + allowedTokens, + permissions, + false + ) + let withdrawable = await secretjs.query.compute.queryContract({ + contractAddress: contract, + codeHash: hash, // optional but way faster + query: { + with_permit: { + permit, + + query: { + withdrawable: {} + } + } + } + }) + + console.log( + '-------------------------------- withdrawbable Start --------------------------------' + ) + + console.log( + `Total withdrawbable: ${parseInt( + withdrawable.amount / process.env.SCRT_TO_USCRT + )} SCRT` + ) + return true +} + +export default withdrawable diff --git a/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/contract_config.js b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/contract_config.js new file mode 100644 index 000000000..66d105b82 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/contract_config.js @@ -0,0 +1,61 @@ +import pkg from 'secretjs' +const { SecretNetworkClient, grpc } = pkg +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' +import fs from 'fs' +import { debug } from 'console' + +async function config () { + // To create a readonly secret.js client, just pass in a gRPC-web endpoint + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2' + }) + + let bufferData = fs.readFileSync('../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + + const contract = data.address + + const hash = data.hash + + let contract_config = await secretjs.query.compute.queryContract({ + contractAddress: contract, + codeHash: hash, // optional but way faster + query: { contract_config: {} } + }) + + let time_left = 0 + if (Date.now() / 1000 < contract_config.next_unbonding_batch_time) { + time_left = parseInt( + Number( + contract_config.next_unbonding_batch_time - Number(Date.now() / 1000) + ) + ) + } + + console.log( + '--------------------------------Contract Config Start --------------------------------' + ) + + console.log(`Admin: ${contract_config.admin}`) + console.log(`Triggerer: ${contract_config.triggerer}`) + console.log(`Denom: ${contract_config.denom}`) + console.log(`Contract Address: ${contract_config.contract_address}`) + for (const val of contract_config.validators) { + console.log(val) + } + console.log(`Time left to unbond: ${time_left}`) + + console.log( + `Next_unbonding_batch_amount: ${contract_config.next_unbonding_batch_amount}` + ) + console.log( + `unbonding_batch_duration: ${contract_config.unbonding_batch_duration}` + ) + console.log(`unbonding_duration: ${contract_config.unbonding_duration}`) + + return true +} + +export default config diff --git a/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/contract_status.js b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/contract_status.js new file mode 100644 index 000000000..85646f534 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/contract_status.js @@ -0,0 +1,35 @@ +import pkg from 'secretjs' +const { SecretNetworkClient, grpc } = pkg +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' +import fs from 'fs' +import { debug } from 'console' + +async function config () { + // To create a readonly secret.js client, just pass in a gRPC-web endpoint + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2' + }) + + let bufferData = fs.readFileSync('../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + + const contract = data.address + + const hash = data.hash + let contract_status = await secretjs.query.compute.queryContract({ + contractAddress: contract, + codeHash: hash, // optional but way faster + query: { contract_status: {} } + }) + + console.log( + '--------------------------------Contract Status Start --------------------------------' + ) + console.log(`contract_status: ${contract_status.status}`) + + return true +} + +export default config diff --git a/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/current_rewards.js b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/current_rewards.js new file mode 100644 index 000000000..cad36fb53 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/current_rewards.js @@ -0,0 +1,36 @@ +import pkg from 'secretjs' +const { SecretNetworkClient, grpc } = pkg +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' +import fs from 'fs' +import { debug } from 'console' + +async function current_rewards () { + // To create a readonly secret.js client, just pass in a gRPC-web endpoint + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2' + }) + + let bufferData = fs.readFileSync('../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + + const contract = data.address + + const hash = data.hash + + let current_rewards = await secretjs.query.compute.queryContract({ + contractAddress: contract, + codeHash: hash, // optional but way faster + query: { current_rewards: {} } + }) + + console.log( + '--------------------------------Contract Rewards Start --------------------------------' + ) + console.log(`current_rewards: ${current_rewards.rewards}`) + + return true +} + +export default current_rewards diff --git a/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/example.js b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/example.js new file mode 100644 index 000000000..3d2beeef9 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/example.js @@ -0,0 +1,47 @@ +import pkg from 'secretjs' +const { SecretNetworkClient, grpc } = pkg +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' + +async function example () { + // To create a readonly secret.js client, just pass in a gRPC-web endpoint + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2' + }) + + // const { + // balance: { amount } + // } = await secretjs.query.bank.balance( + // { + // address: 'secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7', + // denom: 'uscrt' + // } /*, + // // optional: query at a specific height (using an archive node) + // new grpc.Metadata({"x-cosmos-block-height": "2000000"}) + // */ + // ) + + // console.log(`I have ${Number(amount) / 1e6} SCRT!`); + + const sSCRT = 'secret18vd8fpwxzck93qlwghaj6arh4p7c5n8978vsyg' + const sScrtCodeHash = + '9587D60B8E6B078ACE12014CEEEE089530B9FABCD76535D93666A6C127AD8813' + + const { token_info } = await secretjs.query.compute.queryContract({ + contractAddress: sSCRT, + codeHash: sScrtCodeHash, // optional but way faster + query: { token_info: {} } + }) + + if (token_info.decimals == 6) { + console.log(`test example.js ... ok`) + return true + } else { + console.log(`test example.js ... FAILED`) + return false + } + + // console.log(`sSCRT has ${token_info.decimals} decimals!`); +} + +export default example diff --git a/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/lottery_info.js b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/lottery_info.js new file mode 100644 index 000000000..01c6268a8 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/lottery_info.js @@ -0,0 +1,59 @@ +import pkg from "secretjs"; +const { SecretNetworkClient, grpc } = pkg; +const grpcWebUrl = "https://grpc.testnet.secretsaturn.net/"; +import fs from "fs"; +import { debug } from "console"; + +async function config() { + // To create a readonly secret.js client, just pass in a gRPC-web endpoint + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: "pulsar-2", + }); + + let bufferData = fs.readFileSync("../contract_details.json"); + let stData = bufferData.toString(); + let data = JSON.parse(stData); + + const contract = data.address; + + const hash = data.hash; + + let round_obj = await secretjs.query.compute.queryContract({ + contractAddress: contract, + codeHash: hash, // optional but way faster + query: { round: {} }, + }); + + // debug(round_obj) + + console.log(round_obj.end_time); + console.log(Number(Date.now() / 1000)); + + let time_left = 0; + if (Date.now() / 1000 < round_obj.end_time) { + time_left = parseInt( + Number(round_obj.end_time - Number(Date.now() / 1000)) + ); + } + console.log( + "-------------------------------- Round Info Start --------------------------------" + ); + + console.log(`duration: ${round_obj.duration}`); + console.log(round_obj.rewards_distribution); + console.log(`current_round_index: ${round_obj.current_round_index}`); + console.log(`ticket_price: ${round_obj.ticket_price}`); + console.log(`rewards_expiry_duration: ${round_obj.rewards_expiry_duration}`); + console.log(round_obj.admin_share); + console.log( + `triggerer_share_percentage: ${round_obj.triggerer_share_percentage}` + ); + console.log(round_obj.unclaimed_distribution); + + console.log("Time left " + time_left + " for round"); + + return true; +} + +export default config; diff --git a/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/pool_info.js b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/pool_info.js new file mode 100644 index 000000000..9692aab01 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/pool_info.js @@ -0,0 +1,44 @@ +import pkg from 'secretjs' +const { SecretNetworkClient, grpc } = pkg +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' +import fs from 'fs' +import { debug } from 'console' + +async function pool_info () { + // To create a readonly secret.js client, just pass in a gRPC-web endpoint + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2' + }) + + let bufferData = fs.readFileSync('../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + + const contract = data.address + + const hash = data.hash + + let supply_pool_info = await secretjs.query.compute.queryContract({ + contractAddress: contract, + codeHash: hash, // optional but way faster + query: { supply_pool_info: {} } + }) + + console.log( + '-------------------------------- Pool Info Start --------------------------------' + ) + console.log(`total_delegated: ${supply_pool_info.total_delegated}`) + console.log( + `rewards_returned_to_contract: ${supply_pool_info.rewards_returned_to_contract}` + ) + console.log(`total_withdrawn: ${supply_pool_info.total_withdrawn}`) + console.log(`total_reserves: ${supply_pool_info.total_reserves}`) + console.log(`total_sponsored: ${supply_pool_info.total_sponsored}`) + + return true + + // debug(supply_pool_info) +} + +export default pool_info diff --git a/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/pool_liquidity_stats.js b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/pool_liquidity_stats.js new file mode 100644 index 000000000..f3d791b78 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/pool_liquidity_stats.js @@ -0,0 +1,36 @@ +import pkg from 'secretjs' +const { SecretNetworkClient, grpc } = pkg +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' +import fs from 'fs' +import { debug } from 'console' + +async function pool_info () { + // To create a readonly secret.js client, just pass in a gRPC-web endpoint + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2' + }) + + let bufferData = fs.readFileSync('../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + + const contract = data.address + + const hash = data.hash + + let supply_pool_liquidity_stats = await secretjs.query.compute.queryContract({ + contractAddress: contract, + codeHash: hash, // optional but way faster + query: { supply_pool_liquidity_stats: {} } + }) + + console.log( + '-------------------------------- Pool Liq Stats Start --------------------------------' + ) + console.log(`total_delegated: ${supply_pool_liquidity_stats.total_liquidity}`) + + return true +} + +export default pool_info diff --git a/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/pool_liquidity_stats_specific.js b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/pool_liquidity_stats_specific.js new file mode 100644 index 000000000..69d2f9bb5 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/pool_liquidity_stats_specific.js @@ -0,0 +1,42 @@ +import pkg from 'secretjs' +const { SecretNetworkClient, grpc } = pkg +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' +import fs from 'fs' +import { debug } from 'console' + +async function pool_liq_stats_specific () { + // To create a readonly secret.js client, just pass in a gRPC-web endpoint + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2' + }) + + let bufferData = fs.readFileSync('../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + + const contract = data.address + + const hash = data.hash + + let supply_pool_liquidity_stats_specific = await secretjs.query.compute.queryContract( + { + contractAddress: contract, + codeHash: hash, // optional but way faster + query: { + supply_pool_liquidity_stats_specific: { + round_index: 1 + } + } + } + ) + + console.log( + '-------------------------------- Pool Liq Stats Specific Start --------------------------------' + ) + console.log( + `total_liq: ${supply_pool_liquidity_stats_specific.total_liquidity}` + ) +} + +export default pool_liq_stats_specific diff --git a/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/reward_stats.js b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/reward_stats.js new file mode 100644 index 000000000..f54a853f5 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/reward_stats.js @@ -0,0 +1,42 @@ +import pkg from 'secretjs' +const { SecretNetworkClient, grpc } = pkg +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' +import fs from 'fs' +import { debug } from 'console' + +async function config () { + // To create a readonly secret.js client, just pass in a gRPC-web endpoint + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2' + }) + + let bufferData = fs.readFileSync('../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + + const contract = data.address + + const hash = data.hash + + let reward_stats = await secretjs.query.compute.queryContract({ + contractAddress: contract, + codeHash: hash, // optional but way faster + query: { rewards_stats: {} } + }) + + console.log( + '--------------------------------Reward Stats Start --------------------------------' + ) + + debug(reward_stats.distribution_per_tiers.tier_0.claimed) + debug(reward_stats.distribution_per_tiers.tier_1.claimed) + debug(reward_stats.distribution_per_tiers.tier_2.claimed) + debug(reward_stats.distribution_per_tiers.tier_3.claimed) + debug(reward_stats.distribution_per_tiers.tier_4.claimed) + debug(reward_stats.distribution_per_tiers.tier_5.claimed) + debug(reward_stats) + return true +} + +export default config diff --git a/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/sponsors.js b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/sponsors.js new file mode 100644 index 000000000..d0ca61a0b --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/sponsors.js @@ -0,0 +1,38 @@ +import pkg from 'secretjs' +const { SecretNetworkClient, grpc } = pkg +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' +import fs from 'fs' +import { debug } from 'console' + +async function sponsors () { + // To create a readonly secret.js client, just pass in a gRPC-web endpoint + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2' + }) + + let bufferData = fs.readFileSync('../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + + const contract = data.address + + const hash = data.hash + + let sponsors = await secretjs.query.compute.queryContract({ + contractAddress: contract, + codeHash: hash, // optional but way faster + query: { sponsors: {} } + }) + + console.log( + '-------------------------------- Sponsors Start --------------------------------' + ) + console.log(sponsors) + + return true + + // debug(supply_pool_info) +} + +export default sponsors diff --git a/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/sponsors_msg_req_check.js b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/sponsors_msg_req_check.js new file mode 100644 index 000000000..268661ab0 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/3_query/pool_contract/public_queries/sponsors_msg_req_check.js @@ -0,0 +1,38 @@ +import pkg from 'secretjs' +const { SecretNetworkClient, grpc } = pkg +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' +import fs from 'fs' +import { debug } from 'console' + +async function sponsors_msg_req () { + // To create a readonly secret.js client, just pass in a gRPC-web endpoint + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: 'pulsar-2' + }) + + let bufferData = fs.readFileSync('../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + + const contract = data.address + + const hash = data.hash + + let sponsors_msg_req = await secretjs.query.compute.queryContract({ + contractAddress: contract, + codeHash: hash, // optional but way faster + query: { sponsor_message_request_check: {} } + }) + + console.log( + '-------------------------------- sponsors_msg_req Start --------------------------------' + ) + console.log(sponsors_msg_req) + + return true + + // debug(supply_pool_info) +} + +export default sponsors_msg_req diff --git a/contracts/galactic_pools/pools/native/secretjs/3_query/sscrt/sscrt_balance.js b/contracts/galactic_pools/pools/native/secretjs/3_query/sscrt/sscrt_balance.js new file mode 100644 index 000000000..0000eafb9 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/3_query/sscrt/sscrt_balance.js @@ -0,0 +1,81 @@ +import pkg from 'secretjs' +const { Wallet, SecretNetworkClient, grpc, signAmino } = pkg +const grpcWebUrl = 'https://grpc.testnet.secretsaturn.net/' +import fs from 'fs' +import { debug } from 'console' +import * as dotenv from 'dotenv' // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import +dotenv.config({ path: '../.env' }) + +async function sscrt_balance () { + const wallet = new Wallet(process.env.MNEMONIC) + const myAddress = wallet.address + + // To create a readonly secret.js client, just pass in a gRPC-web endpoint + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: process.env.SECRET_CHAIN_ID, + wallet: wallet, + walletAddress: myAddress + }) + + let bufferData = fs.readFileSync('../contract_details.json') + let stData = bufferData.toString() + let data = JSON.parse(stData) + + const allowedTokens = [process.env.SSCRT_ADDRESS] + const permissions = ['owner', 'balance'] + const chainId = process.env.SECRET_CHAIN_ID + let permitName = 'Permit1' + + let permit = await secretjs.utils.accessControl.permit.sign( + myAddress, + chainId, + permitName, + allowedTokens, + permissions, + false + ) + + let query = await secretjs.query.snip20.getBalance({ + contract: { + address: process.env.SSCRT_ADDRESS, + codeHash: process.env.SSCRT_HASH + }, + address: myAddress, + auth: { permit } + }) + + // let supply_pool_info = await secretjs.query.compute.queryContract({ + // contractAddress: env.process.SSCRT_ADDRESS, + // codeHash: env.process.SSCRT_HASH, // optional but way faster + // query: { + // with_permit: { + // permit, + // balance: {} + // } + // } + // }) + + console.log( + '-------------------------------- sScrt Balance Start --------------------------------' + ) + + debug( + 'sScrt Balance: ' + + query.balance.amount / process.env.SCRT_TO_USCRT + + ' SSCRT' + ) + + return true + // if (token_info.decimals == 6) { + // console.log(`test example.js ... ok`) + // return true + // } else { + // console.log(`test example.js ... FAILED`) + // return false + // } + + // console.log(`sSCRT has ${token_info.decimals} decimals!`); +} + +export default sscrt_balance diff --git a/contracts/galactic_pools/pools/native/secretjs/Errors/error_1.js b/contracts/galactic_pools/pools/native/secretjs/Errors/error_1.js new file mode 100644 index 000000000..67a12160a --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/Errors/error_1.js @@ -0,0 +1,128 @@ +import pkg from "secretjs"; +const { Wallet, SecretNetworkClient, grpc, signAmino } = pkg; +const grpcWebUrl = "https://grpc.testnet.secretsaturn.net/"; +import fs from "fs"; +import { debug } from "console"; +import * as dotenv from "dotenv"; // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import +import fetch from "node-fetch"; +dotenv.config({ path: "../.env" }); + +// ERROR OCCUR WHE USING "PULSAR-1" VALIDATOR AKA "secretvaloper1zd2j39tgjkv3z8eqqp86q54wylellsz5dyfcc4" ON PULSAR-2 + +// A for loop == 200 +// *Deposit +// *Query the contract +// *Query the contract account + +for (let i = 0; i < 100; i++) { + const wallet = new Wallet(process.env.MNEMONIC); + const myAddress = wallet.address; + + // To create a signer secret.js client, also pass in a wallet + const secretjs = await SecretNetworkClient.create({ + grpcWebUrl, + chainId: "pulsar-2", + wallet: wallet, + walletAddress: myAddress, + }); + + let bufferData = fs.readFileSync("./../contract_details.json"); + let stData = bufferData.toString(); + let data = JSON.parse(stData); + + const contractAddress = data.address; + const codeHash = data.hash; + let amount = String(1 * process.env.SCRT_TO_USCRT); + + let gasLimit = 1000000; + try { + const tx = await secretjs.tx.compute.executeContract( + { + sender: myAddress, + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + msg: { + deposit: {}, + }, + sentFunds: [{ amount: amount, denom: "uscrt" }], // optional + }, + { + gasLimit, + } + ); + console.log( + `Deposited ${amount / process.env.SCRT_TO_USCRT} Scrt successfully` + ); + } catch (err) { + console.log(err); + } + + setTimeout(() => { + console.log("waiting!"); + }, 20000); + + const allowedTokens = [data.address]; + const permissions = ["owner", "delegated"]; + const chainId = process.env.SECRET_CHAIN_ID; + let permitName = "Permit1"; + + let permit = await secretjs.utils.accessControl.permit.sign( + myAddress, + chainId, + permitName, + allowedTokens, + permissions, + false + ); + let delegated; + try { + delegated = await secretjs.query.compute.queryContract({ + contractAddress: contractAddress, + codeHash: codeHash, // optional but way faster + query: { + with_permit: { + permit, + + query: { + delegated: {}, + }, + }, + }, + }); + } catch (err) { + console.log(err); + } + console.log("Total delegated: " + delegated.amount); + + let api_url = `https://api.pulsar.scrttestnet.com/cosmos/staking/v1beta1/delegations/${contractAddress}`; + + let balance = 0; + fetch(api_url) + .then(function (response) { + // The API call was successful! + return response.json(); + }) + .then(function (data) { + // This is the JSON from our response + for (let i = 0; i < data.delegation_responses.length; i++) { + balance += parseInt(data.delegation_responses[i].balance.amount); + } + console.log("Total delegated using Api: " + balance); + + if (balance != parseInt(delegated.amount)) { + console.warn( + "Both amounts are not equal:", + "Api:", + balance, + "contract state:", + delegated.amount + ); + } else { + console.warn("Both amounts are equal"); + } + }) + .catch(function (err) { + // There was an error + console.warn("Something went wrong.", err); + }); +} diff --git a/contracts/galactic_pools/pools/native/secretjs/deploy.js b/contracts/galactic_pools/pools/native/secretjs/deploy.js new file mode 100644 index 000000000..8156eb7f7 --- /dev/null +++ b/contracts/galactic_pools/pools/native/secretjs/deploy.js @@ -0,0 +1,227 @@ +const { + EnigmaUtils, + Secp256k1Pen, + SigningCosmWasmClient, + pubkeyToAddress, + encodeSecp256k1Pubkey, +} = require("secretjs"); + +const fs = require("fs"); + +// Load environment variables +require("dotenv").config(); + +const customFees = { + upload: { + amount: [{ amount: "2000000", denom: "uscrt" }], + gas: "3500000", + }, + init: { + amount: [{ amount: "500000", denom: "uscrt" }], + gas: "1500000", + }, + exec: { + amount: [{ amount: "500000", denom: "uscrt" }], + gas: "500000", + }, + send: { + amount: [{ amount: "80000", denom: "uscrt" }], + gas: "80000", + }, +}; + +const main = async () => { + const httpUrl = process.env.SECRET_REST_URL; + + // Use key created in tutorial #2 + const mnemonic = process.env.MNEMONIC; + + // A pen is the most basic tool you can think of for signing. + // This wraps a single keypair and allows for signing. + const signingPen = await Secp256k1Pen.fromMnemonic(mnemonic).catch((err) => { + throw new Error("Could not get signing pen: ${err}"); + }); + + // Get the public key + const pubkey = encodeSecp256k1Pubkey(signingPen.pubkey); + + // get the wallet address + const accAddress = pubkeyToAddress(pubkey, "secret"); + + // 1. Initialize client + const txEncryptionSeed = EnigmaUtils.GenerateNewSeed(); + + const client = new SigningCosmWasmClient( + httpUrl, + accAddress, + (signBytes) => signingPen.sign(signBytes), + txEncryptionSeed, + customFees + ); + console.log(`Wallet address=${accAddress}`); + // 2. Upload the contract wasm + + const wasm = fs.readFileSync("../contract.wasm"); + + console.log("Uploading contract"); + const uploadReceipt = await client.upload(wasm, {}).catch((err) => { + throw new Error(`Could not upload contract: ${err}`); + }); + + // 3. Create an instance of the Counter contract + // Get the code ID from the receipt + const { codeId } = uploadReceipt; + + // Create an instance of the Counter contract, providing a starting count //change + + let common_divisor = 10000; + + let validator_vector = [ + { + address: "secretvaloper1p0re3rp685fqsngfdvxg34wkwu9am2p4ckeq2h", + weightage: (60 * common_divisor) / 100, + }, + { + address: "secretvaloper1zd2j39tgjkv3z8eqqp86q54wylellsz5dyfcc4", + weightage: (40 * common_divisor) / 100, + }, + ]; + + let rewards_distribution = { + tier_0: { + total_number_of_winners: 1, + percentage_of_rewards: (20 * 10000) / 100, + }, + tier_1: { + total_number_of_winners: 3, + percentage_of_rewards: (10 * 10000) / 100, + }, + tier_2: { + percentage_of_rewards: (14 * 10000) / 100, + }, + tier_3: { + total_number_of_winners: 27, + percentage_of_rewards: (12 * 10000) / 100, + }, + tier_4: { + total_number_of_winners: 81, + percentage_of_rewards: (19 * 10000) / 100, + }, + tier_5: { + total_number_of_winners: 243, + percentage_of_rewards: (25 * 10000) / 100, + }, + }; + + let init_msg = { + admin: "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + triggerer: "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + reviewer: "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + triggerer_share_percentage: (1 * 10000) / 100, //dividing by 1/100 * common_divisor + denom: "uscrt", + prng_seed: "ZW5pZ21hLXJvY2tzCg==", + validator: validator_vector, + unbonding_duration: 3600 * 24 * 21, //21 days + sscrt: { + address: "secret18vd8fpwxzck93qlwghaj6arh4p7c5n8978vsyg", + hash: "9587D60B8E6B078ACE12014CEEEE089530B9FABCD76535D93666A6C127AD8813", + }, + round_duration: 3600 * 24 * 7, //7 days + rewards_distribution, + ticket_price: 10 * 1000000, + rewards_expiry_duration: 3888000, // 45 days + common_divisor, + total_admin_share: (10 * common_divisor) / 100, + shade_percentage_share: (60 * common_divisor) / 100, + galactic_pools_percentage_share: (40 * common_divisor) / 100, + shade_rewards_address: "secret1zrpgrff3p2pc3ptqu07pz9nq4ezhjr9xauyhjf", + galactic_pools_rewards_address: + "secret1pa8ng8z5lwcukvqht83r5pu2zfq5vv3zdu36uu", + reserve_percentage: (60 * common_divisor) / 100, + is_sponosorship_admin_controlled: false, + unbonding_batch_duration: 3600 * 24 * 3, + minimum_deposit_amount: 1 * 1000000, + grand_prize_address: "secret1pa8ng8z5lwcukvqht83r5pu2zfq5vv3zdu36uu", + }; + + const contract = await client + .instantiate(codeId, init_msg, "breaking_test_1", { + fee: 300_000, + }) + .catch((err) => { + throw new Error(`Could not instantiate contract: ${err}`); + }); + const { contractAddress } = contract; + console.log("contract: ", contract); + + // // // 4. Query the counter + + // // // 5. Increment the counter + + // // Query again to confirm it worked + // console.log("Querying contract for updated count"); + // response = await client + // .queryContractSmart(contractAddress, { lottery_info: {} }) + // .catch((err) => { + // throw new Error(`Could not query contract: ${err}`); + // }); + + // console.log(`New Count=${response.lottery_info.start_height}`); + // }; + + // main().catch((err) => { + // console.error(err); + // }); + + // const wasm = fs.readFileSync( + // "/Users/haseebsaeed/codes/stakepool/contract.wasm" + // ); + // // const wasm = fs.readFileSync( + // // "/Users/haseebsaeed/codes/sefi-testing/sefi-staking-testing/contract.wasm" + // // ); + + // console.log("Uploading contract"); + // const uploadReceipt = await client.upload(wasm, {}).catch((err) => { + // throw new Error(`Could not upload contract: ${err}`); + // }); + + // // 3. Create an instance of the Counter contract + // // Get the code ID from the receipt + // const { codeId } = uploadReceipt; + + // // Create an instance of the Counter contract, providing a starting count //change + // const initMsg = { + // admin: "secret14v6h248vatcsur9hwqjekvj7t6jd8anf8ykw4n", + // triggerer: "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + // denom: "uscrt", + // prng_seed: "ZW5pZ21hLXJvY2tzCg==", + // validator: "secretvaloper1xey4ymz4tmlgy6pp54e2ccj307ff6kx647p3hq", + // unbonding_period: 3600, + // }; + + // const contract = await client + // .instantiate(codeId, initMsg, "saucy_stakepool_v10") + // .catch((err) => { + // throw new Error(`Could not instantiate contract: ${err}`); + // }); + // const { contractAddress } = contract; + // console.log("contract: ", contract); + + // // // 4. Query the counter + + // // // 5. Increment the counter + + // // Query again to confirm it worked + // console.log("Querying contract for updated count"); + // response = await client + // .queryContractSmart(contractAddress, { lottery_info: {} }) + // .catch((err) => { + // throw new Error(`Could not query contract: ${err}`); + // }); + + // console.log(`New Count=${response.lottery_info.start_time}`); +}; + +main().catch((err) => { + console.error(err); +}); diff --git a/contracts/galactic_pools/pools/native/src/constants.rs b/contracts/galactic_pools/pools/native/src/constants.rs new file mode 100644 index 000000000..8d165d23c --- /dev/null +++ b/contracts/galactic_pools/pools/native/src/constants.rs @@ -0,0 +1,32 @@ +//POOL_STATE +pub const POOL_STATE_KEY: &str = "pool_state_key"; +pub const POOL_STATE_LIQUIDITY_KEY: &str = "pool_state_liquidity_key"; + +// REWARDS STATE +pub const REWARD_STATS_FOR_NTH_ROUND_KEY: &str = "reward_stats_for_nth_round_key"; + +//CONFIG +pub const CONFIG_KEY: &str = "config"; + +//ROUND +pub const ROUND_KEY: &str = "round"; + +//USER +pub const USER_INFO_KEY: &str = "user_info_key"; +pub const USER_LIQUIDITY_KEY: &str = "user_liquidity"; +pub const USER_UNBOND_KEY: &str = "user_request_withdraw_key"; +pub const USER_REWARDS_LOG_KEY: &str = "user_rewards_log "; +pub const PREFIX_REVOKED_PERMITS: &str = "prefix_revoked_permits"; +pub const PREFIX_VIEW_KEY: &str = "viewing_key"; + +//ADMIN +pub const ADMIN_WITHDRAW_KEY: &str = "admin_withdraw_key "; +pub const UNBONDING_BATCH_KEY: &str = "unbonding_batch_key"; + +//SPONSOR +pub const SPONSOR_STATS_KEY: &str = "sponsor_stats_key "; +pub const SPONSOR_ADDRESS_LIST_KEY: &str = "sponsor_address_list_key "; +pub const SPONSOR_INFO_KEY: &str = "sponsor_info_key "; +pub const SPONSOR_UNBONDING_KEY: &str = "sponsor_request_withdraw_key "; +pub const SPONSOR_NAME_AND_MESSAGE_DISPLAY_REQUEST_KEY: &str = + "sponsor_name_and_message_display_request_key"; diff --git a/contracts/galactic_pools/pools/native/src/contract.rs b/contracts/galactic_pools/pools/native/src/contract.rs new file mode 100644 index 000000000..45fd001dd --- /dev/null +++ b/contracts/galactic_pools/pools/native/src/contract.rs @@ -0,0 +1,5032 @@ +//Crate Import +use crate::{ + constants::*, + helper::*, + msg::{ + space_pad, + ContractConfigResponse, + ContractStatus, + ContractStatusResponse, + CurrentRewardsResponse, + DelegatedResponse, + ExpContract, + GalacticPoolsPermissions, + HandleAnswer, + HandleMsg, + InstantiateMsg, + LiquidityResponse, + PoolStateInfoResponse, + PoolStateLiquidityStatsResponse, + QueryMsg, + QueryWithPermit, + RecordsResponse, + RemoveSponsorCredentialsDecisions, + RequestWithdrawQueryResponse, + ResponseStatus::Success, + Review, + RewardStatsResponse, + RoundResponse, + SponsorDisplayInfo, + SponsorInfoResponse, + SponsorMessageRequestResponse, + SponsorsResponse, + UnbondingsResponse, + UserInfoResponse, + ValidatorInfo, + ViewingKeyErrorResponse, + WithdrawablelResponse, + }, + rand::sha_256, + staking::{ + // get_exp, + get_rewards, + redelegate, + stake, + undelegate, + withdraw, + }, + state::{ + AdminShareInfo, + ConfigInfo, + DigitsInfo, + GlobalSponsorDisplayRequestListState, + GlobalSponsorState, + PoolLiqState, + PoolState, + RewardsClaimed, + RewardsDistInfo, + RewardsPerTierInfo, + RewardsState, + RoundInfo, + SponsorInfo, + TierCounter, + TierLog, + TierState, + UnbondingBatch, + UnclaimedDistInfo, + UserInfo, + UserLiqState, + UserRewardsLog, + Validator, + WinningSequence, + }, + viewing_key::{ViewingKey, VIEWING_KEY_SIZE}, +}; + +use shade_protocol::{ + c_std::{ + entry_point, + to_binary, + Addr, + BankMsg, + Binary, + Coin, + CosmosMsg, + Deps, + DepsMut, + Env, + MessageInfo, + Response, + StdError, + StdResult, + Storage, + Uint128, + }, + s_toolkit::permit::{validate, Permit, RevokedPermits}, +}; + +//Cosmwasm import + +//Secret toolkit Import + +//Rust functions +use rand::{distributions::Uniform, prelude::*, SeedableRng}; +use rand_chacha::ChaChaRng; +use sha2::{Digest, Sha256}; +use std::ops::{Add, AddAssign}; + +/// pad handle responses and log attributes to blocks of 256 bytes to prevent leaking info based on +/// We make sure that responses from `handle` are padded to a multiple of this size. +pub const BLOCK_SIZE: usize = 256; + +////////////////////////////////////// Instantiate /////////////////////////////////////// +/// Returns StdResult +/// +/// Initializes the contract +/// +/// # Arguments +/// +/// * `deps` - Mutable reference to Extern containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `msg` - InitMsg passed in with the instantiation message +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + // Initialize state + // Ensuring that the validator is registered + let queried_vals = deps.querier.query_all_validators()?; + + let val_struct_vec: Vec = msg + .validator + .into_iter() + .filter_map(|validator| { + if queried_vals + .iter() + .any(|v| v.address.as_str() == validator.address.as_str()) + { + Some(Validator { + address: validator.address, + delegated: Uint128::zero(), + weightage: validator.weightage, + percentage_filled: 0, + }) + } else { + None + } + }) + .collect(); + + //Storing CanonicalAddr only + + let mut admins = Vec::new(); + + if let Some(ad) = msg.admins { + for admin in ad { + admins.push(admin) + } + } else { + let ad = info.sender.clone(); + admins.push(ad) + } + + let mut triggerers = Vec::new(); + if let Some(tri) = msg.triggerers { + for triggerer in tri { + triggerers.push(triggerer) + } + } else { + let tr = info.sender.clone(); + triggerers.push(tr) + } + + let mut reviewers = Vec::new(); + if let Some(revs) = msg.reviewers { + for reviewer in revs { + let rv = reviewer; + reviewers.push(rv) + } + } else { + let rv = info.sender.clone(); + reviewers.push(rv) + } + + let prng_seed_hashed = sha_256(&msg.prng_seed.0); + let prng_seed_hashed_twice = sha_256(&prng_seed_hashed); + let config_obj = ConfigInfo { + admins, + triggerers, + common_divisor: msg.common_divisor, + denom: msg.denom, + prng_seed: prng_seed_hashed.to_vec(), + contract_address: env.contract.address, + validators: val_struct_vec, + next_validator_for_delegation: 0, + next_validator_for_unbonding: 0, + next_unbonding_batch_time: env.block.time.seconds().add(msg.unbonding_batch_duration), + next_unbonding_batch_amount: Uint128::zero(), + next_unbonding_batch_index: 1u64, + unbonding_duration: msg.unbonding_duration, + unbonding_batch_duration: msg.unbonding_batch_duration, + minimum_deposit_amount: msg.minimum_deposit_amount, + status: ContractStatus::Normal.to_u8(), + reviewers, + sponsor_msg_edit_fee: msg.sponsor_msg_edit_fee, + exp_contract: msg.exp_contract.clone(), + }; + + let mut message: Vec = Vec::new(); + //TODO: check this + if msg.exp_contract.is_some() { + // if let Some(exp_contract) = msg.exp_contract { + // let set_vk_msg; + // set_vk_msg = experience_contract::msg::ExecuteMsg::SetViewingKey { + // key: exp_contract.vk, + // }; + // message.push(set_vk_msg.to_cosmos_msg( + // BLOCK_SIZE, + // exp_contract.contract.hash, + // exp_contract.contract.address, + // None, + // )?); + // } else { + // return Err(StdError::generic_err( + // "No viewing key provided for the experience contract", + // )); + // } + } + CONFIG_STORE.save(deps.storage, &config_obj)?; + + //Starting first round + // the entropy is hashed again to obtain the seed field value. As you can see, the entropy and seed fields have different values, enhancing the security of the random number generation process. + let round_obj = RoundInfo { + entropy: prng_seed_hashed_twice.to_vec(), + seed: prng_seed_hashed.to_vec(), + duration: msg.round_duration, + start_time: env.block.time.seconds(), + end_time: env.block.time.seconds().add(msg.round_duration), + rewards_distribution: msg.rewards_distribution, + current_round_index: 1u64, + ticket_price: msg.ticket_price, + rewards_expiry_duration: msg.rewards_expiry_duration, + admin_share: AdminShareInfo { + total_percentage_share: msg.total_admin_share, + shade_percentage_share: msg.shade_percentage_share, + galactic_pools_percentage_share: msg.galactic_pools_percentage_share, + }, + triggerer_share_percentage: msg.triggerer_share_percentage, + shade_rewards_address: msg.shade_rewards_address, + galactic_pools_rewards_address: msg.galactic_pools_rewards_address, + unclaimed_rewards_last_claimed_round: None, + unclaimed_distribution: UnclaimedDistInfo { + reserves_percentage: msg.reserve_percentage, + propagate_percentage: msg + .common_divisor + .checked_sub(msg.reserve_percentage) + .ok_or_else(|| StdError::generic_err("Under-flow sub error"))?, + }, + grand_prize_address: msg.grand_prize_address, + number_of_tickers_per_transaction: msg.number_of_tickers_per_transaction, + }; + ROUND_STORE.save(deps.storage, &round_obj)?; + + let pool_state_obj = PoolState { + total_delegated: Uint128::zero(), + rewards_returned_to_contract: Uint128::zero(), + total_reserves: Uint128::zero(), + total_sponsored: Uint128::zero(), + unbonding_batches: Vec::new(), + }; + POOL_STATE_STORE.save(deps.storage, &pool_state_obj)?; + + let sponsor_stats_obj = GlobalSponsorState { + offset: 0, + empty_slots: Vec::new(), + }; + SPONSOR_STATS_STORE.save(deps.storage, &sponsor_stats_obj)?; + + Ok(Response::new() + .add_messages(message) + .set_data(to_binary(&HandleAnswer::Initialize { status: Success })?)) +} + +fn pad_response(response: StdResult) -> StdResult { + response.map(|mut response| { + response.data = response.data.map(|mut data| { + space_pad(BLOCK_SIZE, &mut data.0); + data + }); + response + }) +} + +///////////////////////////////////// Handle ////////////////////////////////////// +/// Returns StdResult +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `msg` - HandleMsg passed in with the execute message +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: HandleMsg) -> StdResult { + let mut config: ConfigInfo = config_helper_read_only(deps.storage)?; + + let response = match msg { + // User + HandleMsg::Deposit { .. } => { + try_deposit(deps, env, info, &mut config, ContractStatus::Normal.to_u8()) + } + HandleMsg::RequestWithdraw { amount, .. } => try_request_withdraw( + deps, + env, + info, + &mut config, + amount, + ContractStatus::StopTransactions.to_u8(), + ), + HandleMsg::Withdraw { amount } => try_withdraw( + deps, + env, + info, + &mut config, + amount, + ContractStatus::StopTransactions.to_u8(), + ), + HandleMsg::ClaimRewards {} => try_claim_rewards( + deps, + env, + info, + &mut config, + ContractStatus::StopTransactions.to_u8(), + ), + HandleMsg::CreateViewingKey { entropy, .. } => try_create_viewing_key( + deps, + env, + info, + &mut config, + entropy, + ContractStatus::Normal.to_u8(), + ), + HandleMsg::SetViewingKey { key, .. } => { + try_set_viewing_key(deps, info, &mut config, key, ContractStatus::Normal.to_u8()) + } + + HandleMsg::RevokePermit { permit_name, .. } => try_revoke_permit( + deps.storage, + &info.sender.as_str(), + &permit_name, + ContractStatus::StopTransactions.to_u8(), + &config, + ), + + // Sponsor + HandleMsg::Sponsor { title, message, .. } => try_sponsor( + deps, + env, + info, + title, + message, + &mut config, + ContractStatus::Normal.to_u8(), + ), + HandleMsg::SponsorRequestWithdraw { amount, .. } => try_sponsor_request_withdraw( + deps, + info, + &mut config, + amount, + ContractStatus::StopTransactions.to_u8(), + ), + HandleMsg::SponsorWithdraw { amount } => try_sponsor_withdraw( + deps, + env, + info, + &mut config, + amount, + ContractStatus::StopTransactions.to_u8(), + ), + HandleMsg::SponsorMessageEdit { + title, + message, + delete_title, + delete_message, + .. + } => try_sponsor_message_edit( + deps, + info, + title, + message, + delete_title, + delete_message, + &mut config, + ContractStatus::StopTransactions.to_u8(), + ), + + // Triggerer + HandleMsg::EndRound {} => { + try_end_round(deps, env, info, &mut config, ContractStatus::Normal.to_u8()) + } + + // Admin + HandleMsg::UpdateConfig { + unbonding_batch_duration, + unbonding_duration, + minimum_deposit_amount, + exp_contract, + } => try_update_config( + deps, + info, + env, + &mut config, + unbonding_batch_duration, + unbonding_duration, + minimum_deposit_amount, + exp_contract, + ), + + HandleMsg::UpdateRound { + duration, + rewards_distribution, + ticket_price, + rewards_expiry_duration, + admin_share, + triggerer_share_percentage, + shade_rewards_address, + galactic_pools_rewards_address, + grand_prize_address, + unclaimed_distribution, + } => try_update_round( + deps, + info, + &mut config, + duration, + rewards_distribution, + ticket_price, + rewards_expiry_duration, + admin_share, + triggerer_share_percentage, + shade_rewards_address, + galactic_pools_rewards_address, + grand_prize_address, + unclaimed_distribution, + ), + HandleMsg::AddAdmin { admin } => try_add_admin(deps, info, &mut config, admin), + HandleMsg::RemoveAdmin { admin } => try_remove_admin(deps, info, &mut config, admin), + + HandleMsg::AddTriggerer { triggerer } => { + try_add_triggerer(deps, info, &mut config, triggerer) + } + HandleMsg::RemoveTriggerer { triggerer } => { + try_remove_triggerer(deps, info, &mut config, triggerer) + } + + HandleMsg::AddReviewer { reviewer } => try_add_reviewer(deps, info, &mut config, reviewer), + HandleMsg::RemoveReviewer { reviewer } => { + try_remove_reviewer(deps, info, &mut config, reviewer) + } + + HandleMsg::UpdateValidatorSet { + updated_validator_set, + } => try_update_validator_set(deps, env, info, &mut config, updated_validator_set), + HandleMsg::RebalanceValidatorSet {} => { + try_rebalance_validator_set(deps, env, info, &mut config) + } + HandleMsg::SetContractStatus { level, .. } => { + try_set_contract_status(deps, info, &mut config, level) + } + + HandleMsg::RequestReservesWithdraw { amount, .. } => { + try_request_reserves_withdraw(deps, info, &mut config, amount) + } + HandleMsg::ReservesWithdraw { amount, .. } => { + try_reserve_withdraw(deps, env, info, &mut config, amount) + } + HandleMsg::ReviewSponsors { decisions, .. } => { + try_review_sponsor_messages(deps, info, decisions, &config) + } + + HandleMsg::RemoveSponsorCredentials { decisions, .. } => { + try_remove_sponsor_credentials(deps, info, decisions, &mut config) + } + + HandleMsg::UnbondBatch { .. } => try_unbond_batch(deps, env, info, &mut config), + + _ => Err(StdError::generic_err("Unavailable or unknown action")), + }; + + pad_response(response) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + let response = match msg { + QueryMsg::ContractConfig {} => to_binary(&query_config(deps)?), + QueryMsg::ContractStatus {} => to_binary(&query_contract_status(deps)?), + QueryMsg::CurrentRewards {} => to_binary(&query_current_rewards(deps, _env)?), + QueryMsg::Round {} => to_binary(&query_round(deps)?), + QueryMsg::PoolState {} => to_binary(&query_pool_state_info(deps)?), + QueryMsg::PoolStateLiquidityStats {} => to_binary(&query_pool_state_liquidity_stats(deps)?), + QueryMsg::RewardsStats {} => to_binary(&query_reward_stats(deps)?), + QueryMsg::PoolStateLiquidityStatsSpecific { round_index } => to_binary( + &query_pool_state_liquidity_stats_specific(deps, round_index)?, + ), + QueryMsg::SponsorMessageRequestCheck { + start_page, + page_size, + } => to_binary(&query_sponsor_message_req_check( + deps, start_page, page_size, + )?), + QueryMsg::Sponsors { + page_size, + start_page, + } => to_binary(&query_sponsors(deps, start_page, page_size)?), + + QueryMsg::WithPermit { permit, query } => permit_queries(deps, permit, query, _env), + // QueryMsg::PastRecords {} => query_past_records(&deps), + // QueryMsg::PastAllRecords {} => query_all_past_records(&deps), + _ => authenticated_queries(deps, _env, msg), + }; + response +} + +///////////////////////////////////////// User Functions ////////////////////////////////////// + +/// Returns StdResult +/// +/// User deposit their tokens here +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a mutable reference to the Config +/// * `priority` - u8 representation of highest ContractStatus level this action is permitted +fn try_deposit( + deps: DepsMut, + env: Env, + info: MessageInfo, + mut config: &mut ConfigInfo, + priority: u8, +) -> StdResult { + // Checking if the contract is in the correct status to perform this handle function. + check_status(config.status, priority)?; + + let deposit_amount: Uint128; + + // Checking if the deposited amount is valid + deposit_amount = check_if_valid_amount(&info, &config)?; + + // Loading general use read-only stores + let round_obj: RoundInfo = round_helper_read_only(deps.storage)?; + // 1) Updating user_info and user_liquidity_snapshot objects + //Loading readonly stores + let mut user_info_obj: UserInfo = user_info_helper_read_only(deps.storage, &info.sender)?; + + user_info_obj.starting_round = Some(round_obj.current_round_index); + let mut user_liquidity_state: UserLiqState = user_liquidity_snapshot_stats_helper_read_only( + deps.storage, + round_obj.current_round_index, + &info.sender, + )?; + + // Fetching liquidity + let liquidity = user_liquidity_state + .liquidity + .unwrap_or(user_info_obj.amount_delegated); + + // Only adding to liquidity if the round has not ended yet + if env.block.time.seconds() >= round_obj.end_time { + user_liquidity_state.liquidity = Some(liquidity); + } else { + let time_remaining_in_round = round_obj + .end_time + .checked_sub(env.block.time.seconds()) + .ok_or_else(|| StdError::generic_err("Under-flow sub error"))?; + user_liquidity_state.liquidity = Some( + liquidity + .add(deposit_amount.multiply_ratio(time_remaining_in_round, round_obj.duration)), + ); + } + + //Storing user_info_obj and user_liquidity_snapshot + user_info_obj.amount_delegated.add_assign(deposit_amount); + user_info_helper_store(deps.storage, &info.sender, &user_info_obj)?; + + user_liquidity_state.amount_delegated = Some(user_info_obj.amount_delegated); + user_liquidity_snapshot_stats_helper_store( + deps.storage, + round_obj.current_round_index, + &info.sender, + user_liquidity_state, + )?; + + //2) Updating PoolState, PoolStateLiquidityStats, and Config + //Loading readonly stores + let mut pool_state_obj: PoolState = pool_state_helper_read_only(deps.storage)?; + let mut pool_liquidity_state: PoolLiqState = + pool_state_liquidity_helper_read_only(deps.storage, round_obj.current_round_index)?; + + //Updating PoolStateLiquidityStats + //Fetching Liquidity + let pool_liquidity = pool_liquidity_state + .total_liquidity + .unwrap_or(pool_state_obj.total_delegated); + + //Adding to liquidity if round has not ended yet + if env.block.time.seconds() >= round_obj.end_time { + pool_liquidity_state.total_liquidity = Some(pool_liquidity); + } else { + let time_remaining_in_round = round_obj + .end_time + .checked_sub(env.block.time.seconds()) + .ok_or_else(|| StdError::generic_err("Under-flow sub error"))?; + pool_liquidity_state.total_liquidity = Some( + pool_liquidity + .add(deposit_amount.multiply_ratio(time_remaining_in_round, round_obj.duration)), + ); + } + + let total_delegated = pool_state_obj.total_delegated.add(deposit_amount); + pool_liquidity_state.total_delegated = Some(total_delegated); + //Storing pool_state_liquidity_snapshot + pool_state_liquidity_helper_store( + deps.storage, + round_obj.current_round_index, + pool_liquidity_state, + )?; + + //Selecting Validator for deposit and updating the validator information + let selected_val_index = config.next_validator_for_delegation as usize; + config.validators[selected_val_index] + .delegated + .add_assign(deposit_amount); + + if selected_val_index == config.validators.len() - 1 { + config.next_validator_for_delegation = 0; + } else { + config.next_validator_for_delegation += 1; + } + //Updating validators stats + config_helper_store(deps.storage, &config)?; + + //Querying pending rewards sent back from the validator + let rewards = get_rewards(deps.as_ref(), &env.contract.address, &config)?; + let mut rewards_amount = Uint128::zero(); + + for reward in rewards { + if reward.validator_address.as_str() + == config.validators[selected_val_index].address.as_str() + { + rewards_amount.add_assign(reward.reward); + break; + } + } + + //Updating: PoolState + pool_state_obj.total_delegated = total_delegated; + pool_state_obj + .rewards_returned_to_contract + .add_assign(rewards_amount); + + //Storing PoolState + pool_state_helper_store(deps.storage, &pool_state_obj)?; + + //Staking to the selected validator + let mut messages = Vec::new(); + messages.push(stake( + &config.validators[selected_val_index].address, + deposit_amount, + &config.denom, + )); + + Ok(Response::new() + .add_messages(messages) + .set_data(to_binary(&HandleAnswer::Deposit { status: Success })?)) +} + +/// Returns StdResult +/// +/// User request to withdraw their funds. It take 21 days to unbond the funds. +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a mutable reference to the Config +/// * `request_withdraw_amount` - amount requested to withdraw +/// * `priority` - u8 representation of highest ContractStatus level this action is permitted +fn try_request_withdraw( + deps: DepsMut, + env: Env, + info: MessageInfo, + config: &mut ConfigInfo, + request_withdraw_amount: Uint128, + priority: u8, +) -> StdResult { + //CHECKING status + check_status(config.status, priority)?; + + //Loading data + let round_obj = round_helper_read_only(deps.storage)?; + let mut user_info = user_info_helper_read_only(deps.storage, &info.sender)?; + let mut user_liquidity_snapshot_obj = user_liquidity_snapshot_stats_helper_read_only( + deps.storage, + round_obj.current_round_index, + &info.sender, + )?; + let mut pool_state: PoolState = pool_state_helper_read_only(deps.storage)?; + let mut pool_state_liquidity_snapshot_obj: PoolLiqState = + pool_state_liquidity_helper_read_only(deps.storage, round_obj.current_round_index)?; + + // Checking: If the amount unbonded is not greater than amount delegated + if user_info.amount_delegated < request_withdraw_amount { + return Err(StdError::generic_err(format!( + "insufficient funds to redeem: balance={}, required={}", + user_info.amount_delegated, request_withdraw_amount + ))); + } + + if request_withdraw_amount == Uint128::zero() { + return Err(StdError::generic_err(format!( + "cannot request to withdraw 0 {}. Please specify a non-zero amount", + config.denom + ))); + } + //STORING USER UNBONDING + if !user_info + .unbonding_batches + .contains(&config.next_unbonding_batch_index) + { + user_info + .unbonding_batches + .push(config.next_unbonding_batch_index); + } + + let mut unbonding_amount = user_unbond_helper_read_only( + deps.storage, + config.next_unbonding_batch_index, + &info.sender, + )?; + + unbonding_amount.add_assign(request_withdraw_amount); + + user_unbond_helper_store( + deps.storage, + config.next_unbonding_batch_index, + &info.sender, + unbonding_amount, + )?; + + //If the liquidity struct for that round is not generated then we create one and store + let user_liquidity: Uint128; + if user_liquidity_snapshot_obj.liquidity.is_none() { + user_liquidity = user_info.amount_delegated; + } else { + user_liquidity = user_liquidity_snapshot_obj.liquidity.unwrap(); + } + + if env.block.time.seconds() >= round_obj.end_time { + user_liquidity_snapshot_obj.liquidity = Some(user_liquidity); + } else { + let time_remaining_in_round = round_obj + .end_time + .checked_sub(env.block.time.seconds()) + .ok_or_else(|| StdError::generic_err("Under-flow sub error 1"))?; + + user_liquidity_snapshot_obj.liquidity = Some( + if let Ok(liq) = user_liquidity.checked_sub( + request_withdraw_amount.multiply_ratio(time_remaining_in_round, round_obj.duration), + ) { + liq + } else { + return Err(StdError::generic_err("Under-flow sub error 2")); + }, + ); + } + + user_info.amount_delegated = if let Ok(a_d) = user_info + .amount_delegated + .checked_sub(request_withdraw_amount) + { + a_d + } else { + return Err(StdError::generic_err("Under-flow sub error 3")); + }; + + user_info + .amount_unbonding + .add_assign(request_withdraw_amount); + + //Snapshot of the current amount delegated + user_liquidity_snapshot_obj.amount_delegated = Some(user_info.amount_delegated); + //Storing the User and User liquidity + user_liquidity_snapshot_stats_helper_store( + deps.storage, + round_obj.current_round_index, + &info.sender, + user_liquidity_snapshot_obj, + )?; + user_info_helper_store(deps.storage, &info.sender, &user_info)?; + + //Querying pending_rewards send back from validator + //Updating the reward pool + let total_liquidity: Uint128; + if pool_state_liquidity_snapshot_obj.total_liquidity.is_none() { + total_liquidity = pool_state.total_delegated; + } else { + total_liquidity = pool_state_liquidity_snapshot_obj.total_liquidity.unwrap(); + } + + if env.block.time.seconds() >= round_obj.end_time { + pool_state_liquidity_snapshot_obj.total_liquidity = Some(total_liquidity); + } else { + let time_remaining_in_round = round_obj + .end_time + .checked_sub(env.block.time.seconds()) + .ok_or_else(|| StdError::generic_err("Under-flow sub error 4"))?; + pool_state_liquidity_snapshot_obj.total_liquidity = Some( + if let Ok(val) = total_liquidity.checked_sub( + request_withdraw_amount.multiply_ratio(time_remaining_in_round, round_obj.duration), + ) { + val + } else { + return Err(StdError::generic_err("Under-flow sub error 5")); + }, + ); + } + + pool_state.total_delegated = if let Ok(t_d) = pool_state + .total_delegated + .checked_sub(request_withdraw_amount) + { + t_d + } else { + return Err(StdError::generic_err("Under-flow sub error 6")); + }; + + pool_state_liquidity_snapshot_obj.total_delegated = Some(pool_state.total_delegated); + pool_state_liquidity_helper_store( + deps.storage, + round_obj.current_round_index, + pool_state_liquidity_snapshot_obj, + )?; + + pool_state_helper_store(deps.storage, &pool_state)?; + config + .next_unbonding_batch_amount + .add_assign(request_withdraw_amount); + + config_helper_store(deps.storage, &config)?; + + Ok( + Response::new().set_data(to_binary(&HandleAnswer::RequestWithdraw { + status: Success, + })?), + ) +} + +/// Returns StdResult +/// +/// User withdraw their requested/unbonded funds. +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a mutable reference to the Config +/// * `withdraw_amount` - amount to withdraw +/// * `priority` - u8 representation of highest ContractStatus level this action is permitted +fn try_withdraw( + deps: DepsMut, + env: Env, + info: MessageInfo, + config: &mut ConfigInfo, + withdraw_amount: Uint128, + priority: u8, +) -> StdResult { + //Loading Data from storage + check_status(config.status, priority)?; + + let mut user_info_obj = user_info_helper_read_only(deps.storage, &info.sender)?; + let pool_state_obj: PoolState = pool_state_helper_read_only(deps.storage)?; + + //Calculating amount available for withdraw + //USER UNBONDING + let mut pop_front_counter: Vec = vec![]; + + for i in 0..user_info_obj.unbonding_batches.len() { + let unbond_batch_index = user_info_obj.unbonding_batches[i]; + let unbonding_batch_obj = + unbonding_batch_helper_read_only(deps.storage, unbond_batch_index)?; + + if unbonding_batch_obj.unbonding_time.is_some() { + if env.block.time.seconds() >= unbonding_batch_obj.unbonding_time.unwrap() { + let unbonding_amount = + user_unbond_helper_read_only(deps.storage, unbond_batch_index, &info.sender)?; + + user_info_obj + .amount_withdrawable + .add_assign(unbonding_amount); + + pop_front_counter.push(unbond_batch_index); + } + } + } + + //only retaining the unclaimed batches + user_info_obj + .unbonding_batches + .retain(|val| !pop_front_counter.contains(val)); + + //Checking if amount available is greater than withdraw_amount + if withdraw_amount > user_info_obj.amount_withdrawable { + return Err(StdError::generic_err( + "Trying to withdraw more than available", + )); + } + + //Updating user's + user_info_obj.amount_withdrawable = if let Ok(a_w) = user_info_obj + .amount_withdrawable + .checked_sub(withdraw_amount) + { + a_w + } else { + return Err(StdError::generic_err("Under-flow sub error 1")); + }; + + user_info_obj.amount_unbonding = + if let Ok(a_u) = user_info_obj.amount_unbonding.checked_sub(withdraw_amount) { + a_u + } else { + return Err(StdError::generic_err("Under-flow sub error 2")); + }; + user_info_helper_store(deps.storage, &info.sender, &user_info_obj)?; + + //Updating pool state + pool_state_helper_store(deps.storage, &pool_state_obj)?; + + //Sending funcds to reciepient + let mut messages = Vec::new(); + if withdraw_amount > Uint128::zero() { + let withdraw_coins: Vec = vec![Coin { + denom: config.denom.to_string(), + amount: withdraw_amount, + }]; + messages.push(CosmosMsg::Bank(BankMsg::Send { + to_address: info.sender.into_string(), + amount: withdraw_coins, + })); + } + + let res = Response::new() + .add_messages(messages) + .set_data(to_binary(&HandleAnswer::Withdraw { status: Success })?); + + Ok(res) +} + +/// Returns StdResult +/// +/// User can win prizes here. User will generate a 'n' random sequence of numbers and this sequence will be checked against winning sequence to win prizes. +/// Number of tickets to win will be calculated based on the liquidity provided. +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a mutable reference to the Config +/// * `priority` - u8 representation of highest ContractStatus level this action is permitted +fn try_claim_rewards( + deps: DepsMut, + env: Env, + info: MessageInfo, + config: &mut ConfigInfo, + priority: u8, +) -> StdResult { + check_status(config.status, priority)?; + + // Loading dependencies + let round_obj: RoundInfo = round_helper_read_only(deps.storage)?; + let mut user_info_obj: UserInfo = user_info_helper_read_only(deps.storage, &info.sender)?; + //Some variables + let mut liquidity_current_round: Uint128; + // let mut legacy_bal: Uint128 = Uint128::zero(); + let mut total_winning_amount: Uint128 = Uint128::zero(); + let mut total_exp: Uint128 = Uint128::zero(); + let mut txn_ticket_count: u128 = 0u128; + // let mut last_round_claimed: Option = None; + + //Finding the starting and ending point to start the round + //Starting round + let starting_round: u64; + if user_info_obj.last_claim_rewards_round.is_some() { + //ERROR CHECK: If user have already claimed the prizes + if user_info_obj.last_claim_rewards_round.unwrap() + == (if let Some(val) = round_obj.current_round_index.checked_sub(1) { + val + } else { + return Err(StdError::generic_err("Under-flow sub error 1")); + }) + { + return Err(StdError::generic_err(format!( + "You claimed recently!. Wait for this round to end" + ))); + } else { + starting_round = user_info_obj.last_claim_rewards_round.unwrap().add(1); + } + } else { + //1.2)ERROR CHECK: If liquidity provided is less than current round then return error + check_if_claimable(user_info_obj.starting_round, round_obj.current_round_index)?; + starting_round = user_info_obj.starting_round.unwrap(); + } + + //Ending round + let ending_round = round_obj.current_round_index; + + //Main loop + for round_index in starting_round..ending_round { + // println!("{}", round_index); + let mut num_of_tickets: u128; + let display_tickets: u128; + let exp: Uint128; + //Loading dependencies + let user_liq_obj = user_liquidity_snapshot_stats_helper_read_only( + deps.storage, + round_index, + &info.sender, + )?; + let mut reward_stats = + reward_stats_for_nth_round_helper_read_only(deps.storage, round_index)?; + let tkt = reward_stats + .total_rewards + .checked_sub(reward_stats.total_claimed); + if tkt.is_ok() { + if tkt?.is_zero() { + (_, _, _, _, exp) = finding_user_liquidity( + config, + user_liq_obj, + round_index, + &mut user_info_obj, + &round_obj, + &reward_stats, + txn_ticket_count, + deps.storage, + &info.sender, + )?; + user_info_obj.last_claim_rewards_round = Some(round_index); + if reward_stats.total_exp_claimed.is_some() { + reward_stats.total_exp_claimed = + Some(reward_stats.total_exp_claimed.unwrap().add(exp)); + } else { + reward_stats.total_exp_claimed = Some(exp); + } + + total_exp.add_assign(exp); + + reward_stats_for_nth_round_helper_store(deps.storage, round_index, &reward_stats); + continue; + } + } else { + return Err(StdError::generic_err("Under-flow sub error 2")); + } + + //Checking if the round just expired. + if reward_stats.rewards_expiration_date.is_some() { + if env.block.time.seconds() > reward_stats.rewards_expiration_date.unwrap() { + (_, _, _, _, exp) = finding_user_liquidity( + config, + user_liq_obj, + round_index, + &mut user_info_obj, + &round_obj, + &reward_stats, + txn_ticket_count, + deps.storage, + &info.sender, + )?; + user_info_obj.last_claim_rewards_round = Some(round_index); + total_exp.add_assign(exp); + if reward_stats.total_exp_claimed.is_some() { + reward_stats.total_exp_claimed = + Some(reward_stats.total_exp_claimed.unwrap().add(exp)); + } else { + reward_stats.total_exp_claimed = Some(exp); + } + reward_stats_for_nth_round_helper_store(deps.storage, round_index, &reward_stats); + + continue; + } + } + + //Calculating Liquidity/tickets + + ( + liquidity_current_round, + num_of_tickets, + user_info_obj, + txn_ticket_count, + exp, + ) = finding_user_liquidity( + config, + user_liq_obj, + round_index, + &mut user_info_obj, + &round_obj, + &reward_stats, + txn_ticket_count, + deps.storage, + &info.sender, + )?; + display_tickets = num_of_tickets; + + if num_of_tickets == 0 { + total_exp.add_assign(exp); + if reward_stats.total_exp_claimed.is_some() { + reward_stats.total_exp_claimed = + Some(reward_stats.total_exp_claimed.unwrap().add(exp)); + } else { + reward_stats.total_exp_claimed = Some(exp); + } + reward_stats_for_nth_round_helper_store(deps.storage, round_index, &reward_stats); + continue; + } + + //Now fetch the winning combination of round + //*Generate a sequence of random number between the range defined in round_obj + let mut hasher = Sha256::new(); + hasher.update(&config.prng_seed); + hasher.update(&round_obj.entropy); + hasher.update(&info.sender.as_bytes()); + + //**generate a random number between 0 and ending_ticket + let seed: [u8; 32] = hasher.finalize().into(); + let rng = ChaChaRng::from_seed(seed); + + let mut claimed_counter = TierCounter { + tier_5: Uint128::zero(), + tier_4: Uint128::zero(), + tier_3: Uint128::zero(), + tier_2: Uint128::zero(), + tier_1: Uint128::zero(), + tier_0: Uint128::zero(), + }; + //Iters for Tier 0,1,2,3,4,5 + for tier_number in (0..6).rev() { + let winning_number: Option = match tier_number { + 0 => Some(reward_stats.winning_sequence.tier_0.winning_number), + 1 => Some(reward_stats.winning_sequence.tier_1.winning_number), + 2 => Some(reward_stats.winning_sequence.tier_2.winning_number), + 3 => Some(reward_stats.winning_sequence.tier_3.winning_number), + 4 => Some(reward_stats.winning_sequence.tier_4.winning_number), + 5 => Some(reward_stats.winning_sequence.tier_5.winning_number), + _ => None, + }; + let range_finder = match tier_number { + 0 => reward_stats.winning_sequence.tier_0.range, + 1 => reward_stats.winning_sequence.tier_1.range, + 2 => reward_stats.winning_sequence.tier_2.range, + 3 => reward_stats.winning_sequence.tier_3.range, + 4 => reward_stats.winning_sequence.tier_4.range, + 5 => reward_stats.winning_sequence.tier_5.range, + _ => Uint128::zero(), + }; + let mut digit_range = 0u128; + if range_finder.u128() > 0u128 { + if let Some(d_r) = range_finder.u128().checked_sub(1) { + digit_range = d_r; + } else { + return Err(StdError::generic_err("Under-flow sub error 7")); + } + } + let range = Uniform::new_inclusive(0, digit_range); + let mut digit_generator = rng.clone().sample_iter(&range); + // println!("no. of tickets{} Loops{}", num_of_tickets, loops); + + for _ in 0u128..num_of_tickets { + //**We need to draft 6 digits individually + let drafted_number = digit_generator.next().unwrap(); + + if drafted_number == winning_number.unwrap().u128() { + match tier_number { + 5 => { + claimed_counter.tier_5.add_assign(Uint128::one()); + } + 4 => { + claimed_counter.tier_4.add_assign(Uint128::one()); + let val = claimed_counter.tier_5.checked_sub(Uint128::one()); + if val.is_ok() { + claimed_counter.tier_5 = val? + } else { + return Err(StdError::generic_err("Under-flow sub error 8")); + } + } + 3 => { + claimed_counter.tier_3.add_assign(Uint128::one()); + let val = claimed_counter.tier_4.checked_sub(Uint128::one()); + if val.is_ok() { + claimed_counter.tier_4 = val? + } else { + return Err(StdError::generic_err("Under-flow sub error 9")); + } + } + 2 => { + claimed_counter.tier_2.add_assign(Uint128::one()); + let val = claimed_counter.tier_3.checked_sub(Uint128::one()); + if val.is_ok() { + claimed_counter.tier_3 = val? + } else { + return Err(StdError::generic_err("Under-flow sub error 10")); + } + } + 1 => { + claimed_counter.tier_1.add_assign(Uint128::one()); + let val = claimed_counter.tier_2.checked_sub(Uint128::one()); + if val.is_ok() { + claimed_counter.tier_2 = val? + } else { + return Err(StdError::generic_err("Under-flow sub error 11")); + } + } + 0 => { + claimed_counter.tier_0.add_assign(Uint128::one()); + let val = claimed_counter.tier_1.checked_sub(Uint128::one()); + if val.is_ok() { + claimed_counter.tier_1 = val? + } else { + return Err(StdError::generic_err("Under-flow sub error 12")); + } + } + _ => {} + } + } + } + //Making sure that if the previous number is matched only then go for to check for next tier. + match tier_number { + 5 => num_of_tickets = claimed_counter.tier_5.u128(), + 4 => num_of_tickets = claimed_counter.tier_4.u128(), + 3 => num_of_tickets = claimed_counter.tier_3.u128(), + 2 => num_of_tickets = claimed_counter.tier_2.u128(), + 1 => num_of_tickets = claimed_counter.tier_1.u128(), + 0 => num_of_tickets = claimed_counter.tier_0.u128(), + _ => {} + } + } + + let mut tier_state_obj = reward_stats.distribution_per_tiers; + + if tier_state_obj + .tier_5 + .claimed + .num_of_rewards_claimed + .add(claimed_counter.tier_5) + .gt(&tier_state_obj.tier_5.num_of_rewards) + { + let val = tier_state_obj + .tier_5 + .num_of_rewards + .checked_sub(tier_state_obj.tier_5.claimed.num_of_rewards_claimed); + if val.is_ok() { + claimed_counter.tier_5 = val? + } else { + return Err(StdError::generic_err("Under-flow sub error 13")); + } + }; + + if tier_state_obj + .tier_4 + .claimed + .num_of_rewards_claimed + .add(claimed_counter.tier_4) + .gt(&tier_state_obj.tier_4.num_of_rewards) + { + let val = tier_state_obj + .tier_4 + .num_of_rewards + .checked_sub(tier_state_obj.tier_4.claimed.num_of_rewards_claimed); + if val.is_ok() { + claimed_counter.tier_4 = val? + } else { + return Err(StdError::generic_err("Under-flow sub error 14")); + } + }; + + if tier_state_obj + .tier_3 + .claimed + .num_of_rewards_claimed + .add(claimed_counter.tier_3) + .gt(&tier_state_obj.tier_3.num_of_rewards) + { + let val = tier_state_obj + .tier_3 + .num_of_rewards + .checked_sub(tier_state_obj.tier_3.claimed.num_of_rewards_claimed); + if val.is_ok() { + claimed_counter.tier_3 = val? + } else { + return Err(StdError::generic_err("Under-flow sub error 15")); + } + }; + + if tier_state_obj + .tier_2 + .claimed + .num_of_rewards_claimed + .add(claimed_counter.tier_2) + .gt(&tier_state_obj.tier_2.num_of_rewards) + { + let val = tier_state_obj + .tier_2 + .num_of_rewards + .checked_sub(tier_state_obj.tier_2.claimed.num_of_rewards_claimed); + if val.is_ok() { + claimed_counter.tier_2 = val? + } else { + return Err(StdError::generic_err("Under-flow sub error 16")); + } + }; + + if tier_state_obj + .tier_1 + .claimed + .num_of_rewards_claimed + .add(claimed_counter.tier_1) + .gt(&tier_state_obj.tier_1.num_of_rewards) + { + let val = tier_state_obj + .tier_1 + .num_of_rewards + .checked_sub(tier_state_obj.tier_1.claimed.num_of_rewards_claimed); + if val.is_ok() { + claimed_counter.tier_1 = val? + } else { + return Err(StdError::generic_err("Under-flow sub error 17")); + } + }; + + if tier_state_obj + .tier_0 + .claimed + .num_of_rewards_claimed + .add(claimed_counter.tier_0) + .gt(&tier_state_obj.tier_0.num_of_rewards) + { + let val = tier_state_obj + .tier_0 + .num_of_rewards + .checked_sub(tier_state_obj.tier_0.claimed.num_of_rewards_claimed); + if val.is_ok() { + claimed_counter.tier_0 = val? + } else { + return Err(StdError::generic_err("Under-flow sub error 18")); + } + }; + + tier_state_obj + .tier_5 + .claimed + .num_of_rewards_claimed + .add_assign(claimed_counter.tier_5); + + tier_state_obj + .tier_4 + .claimed + .num_of_rewards_claimed + .add_assign(claimed_counter.tier_4); + + tier_state_obj + .tier_3 + .claimed + .num_of_rewards_claimed + .add_assign(claimed_counter.tier_3); + + tier_state_obj + .tier_2 + .claimed + .num_of_rewards_claimed + .add_assign(claimed_counter.tier_2); + + tier_state_obj + .tier_1 + .claimed + .num_of_rewards_claimed + .add_assign(claimed_counter.tier_1); + + tier_state_obj + .tier_0 + .claimed + .num_of_rewards_claimed + .add_assign(claimed_counter.tier_0); + + //Calculate Total rewards for each tier + + let user_rewards_per_tier_log = TierLog { + tier_5: RewardsPerTierInfo { + num_of_rewards_claimed: (claimed_counter + .tier_5 + .multiply_ratio(tier_state_obj.tier_5.claimed.reward_per_match.u128(), 1u128)), + reward_per_match: tier_state_obj.tier_5.claimed.reward_per_match, + }, + tier_4: RewardsPerTierInfo { + num_of_rewards_claimed: (claimed_counter + .tier_4 + .multiply_ratio(tier_state_obj.tier_4.claimed.reward_per_match.u128(), 1u128)), + reward_per_match: tier_state_obj.tier_4.claimed.reward_per_match, + }, + tier_3: RewardsPerTierInfo { + num_of_rewards_claimed: (claimed_counter + .tier_3 + .multiply_ratio(tier_state_obj.tier_3.claimed.reward_per_match.u128(), 1u128)), + reward_per_match: tier_state_obj.tier_3.claimed.reward_per_match, + }, + tier_2: RewardsPerTierInfo { + num_of_rewards_claimed: (claimed_counter + .tier_2 + .multiply_ratio(tier_state_obj.tier_2.claimed.reward_per_match.u128(), 1u128)), + reward_per_match: tier_state_obj.tier_2.claimed.reward_per_match, + }, + tier_1: RewardsPerTierInfo { + num_of_rewards_claimed: (claimed_counter + .tier_1 + .multiply_ratio(tier_state_obj.tier_1.claimed.reward_per_match.u128(), 1u128)), + reward_per_match: tier_state_obj.tier_1.claimed.reward_per_match, + }, + tier_0: RewardsPerTierInfo { + num_of_rewards_claimed: (claimed_counter + .tier_0 + .multiply_ratio(tier_state_obj.tier_0.claimed.reward_per_match.u128(), 1u128)), + reward_per_match: tier_state_obj.tier_0.claimed.reward_per_match, + }, + }; + + let amount_won = user_rewards_per_tier_log + .tier_5 + .num_of_rewards_claimed + .add(user_rewards_per_tier_log.tier_4.num_of_rewards_claimed) + .add(user_rewards_per_tier_log.tier_3.num_of_rewards_claimed) + .add(user_rewards_per_tier_log.tier_2.num_of_rewards_claimed) + .add(user_rewards_per_tier_log.tier_1.num_of_rewards_claimed) + .add(user_rewards_per_tier_log.tier_0.num_of_rewards_claimed); + + if amount_won > Uint128::zero() { + let user_rewards_log: UserRewardsLog = UserRewardsLog { + round: (round_index), + + liquidity: Some(Uint128::from(liquidity_current_round)), + rewards_per_tier: Some(user_rewards_per_tier_log), + total_amount_won: Some(amount_won), + tickets: Uint128::from(display_tickets), + ticket_price: reward_stats.ticket_price, + total_exp_gained: Some(exp), + }; + reward_stats.total_claimed.add_assign(amount_won); + if reward_stats.total_exp_claimed.is_some() { + reward_stats.total_exp_claimed = + Some(reward_stats.total_exp_claimed.unwrap().add(exp)); + } else { + reward_stats.total_exp_claimed = Some(exp); + } + reward_stats.distribution_per_tiers = tier_state_obj; + + total_winning_amount + .add_assign(user_rewards_log.total_amount_won.unwrap_or(Uint128::zero())); + total_exp.add_assign(exp); + + user_rewards_log_helper_store(deps.storage, &info.sender, &user_rewards_log)?; + reward_stats_for_nth_round_helper_store(deps.storage, round_index, &reward_stats); + } + + if txn_ticket_count.eq(&round_obj.number_of_tickers_per_transaction.u128()) { + break; + } + } + + // if last_round_claimed.is_some() { + // user_info_obj.last_claim_rewards_round = last_round_claimed; + if total_winning_amount > Uint128::zero() { + user_info_obj.total_won.add_assign(total_winning_amount); + } + user_info_helper_store(deps.storage, &info.sender, &user_info_obj)?; + // } + + let mut messages: Vec = vec![]; + if total_winning_amount.u128() > 0u128 { + let winning_coins: Vec = vec![Coin { + denom: config.denom.as_mut().into(), + amount: total_winning_amount, + }]; + + messages.push(CosmosMsg::Bank(BankMsg::Send { + to_address: info.sender.to_string(), + amount: winning_coins, + })); + } + + //TODO: check this + // //Updating last claimed and claiming xp + // let add_exp = experience_contract::msg::ExecuteMsg::AddExp { + // address: info.sender.clone().to_string(), + // exp: total_exp, + // }; + + // if config.exp_contract.is_some() { + // messages.push(add_exp.to_cosmos_msg( + // BLOCK_SIZE, + // config.exp_contract.clone().unwrap().contract.hash, + // config.exp_contract.clone().unwrap().contract.address, + // None, + // )?); + // } + + let res = + Response::new() + .add_messages(messages) + .set_data(to_binary(&HandleAnswer::ClaimRewards { + status: Success, + winning_amount: total_winning_amount, + })?); + + Ok(res) +} + +fn finding_user_liquidity( + config: &ConfigInfo, + mut user_liq_obj: UserLiqState, + round_index: u64, + user_info_obj: &mut UserInfo, + round_obj: &RoundInfo, + reward_stats: &RewardsState, + mut txn_ticket_count: u128, + storage: &mut dyn Storage, + sender: &Addr, +) -> StdResult<(Uint128, u128, UserInfo, u128, Uint128)> { + let liquidity; + let mut legacy_bal: Uint128 = Uint128::zero(); + if user_liq_obj.liquidity.is_some() { + liquidity = user_liq_obj.liquidity.unwrap(); + } else { + let mut finding_liq_round: u64 = if let Some(rn) = round_index.checked_sub(1) { + rn + } else { + return Err(StdError::generic_err("Under-flow sub error 3")); + }; + let start = if user_info_obj.last_claim_rewards_round.is_some() { + user_info_obj.last_claim_rewards_round.unwrap() + } else { + user_info_obj.starting_round.unwrap() + }; + while finding_liq_round >= start { + // println!("Finding liquidity {}", finding_liq_round); + let user_liq_obj_prev_round = + user_liquidity_snapshot_stats_helper_read_only(storage, finding_liq_round, sender)?; + if user_liq_obj_prev_round.amount_delegated.is_some() { + legacy_bal = user_liq_obj_prev_round.amount_delegated.unwrap(); + break; + } else { + finding_liq_round = if let Some(f_liq_round) = finding_liq_round.checked_sub(1) { + f_liq_round + } else { + return Err(StdError::generic_err("Under-flow sub error 4")); + }; + } + } + + user_liq_obj.liquidity = Some(legacy_bal); + user_liq_obj.amount_delegated = Some(legacy_bal); + // user_liquidity_snapshot_stats_helper_store(storage, round_index, sender, user_liq_obj)?; + + liquidity = legacy_bal; + } + let pool_state_liquidity_snapshot_obj: PoolLiqState = + pool_state_liquidity_helper_read_only(storage, round_index)?; + + //TODO config check here + let mut exp = Uint128::zero(); + if config.exp_contract.is_some() { + exp = reward_stats.total_exp.unwrap().multiply_ratio( + liquidity, + pool_state_liquidity_snapshot_obj.total_liquidity.unwrap(), + ); + } + + //Calculating total tickets + let mut num_of_tickets: u128 = liquidity + .multiply_ratio(1u128, reward_stats.ticket_price.u128()) + .u128(); + + if user_liq_obj.tickets_used.is_some() { + if let Some(tickets) = num_of_tickets.checked_sub(user_liq_obj.tickets_used.unwrap().u128()) + { + num_of_tickets = tickets; + } else { + return Err(StdError::generic_err("Under-flow sub error 5")); + } + } + + if txn_ticket_count.add(num_of_tickets) > round_obj.number_of_tickers_per_transaction.u128() { + if let Some(tickets) = round_obj + .number_of_tickers_per_transaction + .u128() + .checked_sub(txn_ticket_count) + { + num_of_tickets = tickets; + txn_ticket_count.add_assign(tickets); + } else { + return Err(StdError::generic_err("Under-flow sub error 6")); + }; + } else { + user_info_obj.last_claim_rewards_round = Some(round_index); + txn_ticket_count.add_assign(num_of_tickets); + } + + if user_liq_obj.tickets_used.is_some() { + user_liq_obj.tickets_used = + Some(Uint128::from(num_of_tickets) + user_liq_obj.tickets_used.unwrap()); + } else { + user_liq_obj.tickets_used = Some(Uint128::from(num_of_tickets)); + } + + user_liquidity_snapshot_stats_helper_store(storage, round_index, sender, user_liq_obj)?; + + Ok(( + liquidity, + num_of_tickets, + user_info_obj.clone(), + txn_ticket_count, + exp, + )) +} + +/// Returns StdResult +/// +/// creates a viewing key +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a reference to the Config +/// * `entropy` - string slice of the input String to be used as entropy in randomization +/// * `priority` - u8 representation of highest ContractStatus level this action is permitted +fn try_create_viewing_key( + deps: DepsMut, + env: Env, + info: MessageInfo, + config: &mut ConfigInfo, + entropy: String, + priority: u8, +) -> StdResult { + check_status(config.status, priority)?; + + let prng_seed = &config.prng_seed; + let key = ViewingKey::new(&env, info.clone(), &prng_seed, (&entropy).as_ref()); + write_viewing_key_helper(deps.storage, &info.sender, &key)?; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::CreateViewingKey { key })?)) +} + +/// Returns StdResult +/// +/// sets the viewing key +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a reference to the Config +/// * `key` - String to be used as the viewing key +/// * `priority` - u8 representation of highest ContractStatus level this action is permitted +fn try_set_viewing_key( + deps: DepsMut, + info: MessageInfo, + config: &mut ConfigInfo, + key: String, + priority: u8, +) -> StdResult { + check_status(config.status, priority)?; + + let vk = ViewingKey(key); + write_viewing_key_helper(deps.storage, &info.sender, &vk)?; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::SetViewingKey { status: Success })?)) +} + +/// Returns StdResult +/// +/// revoke the ability to use a specified permit +/// +/// # Arguments +/// +/// * `storage` - mutable reference to the contract's storage +/// * `sender` - a reference to the message sender +/// * `permit_name` - string slice of the name of the permit to revoke +/// * `priority` - u8 representing the highest status level this action may execute at +/// * `config` - a reference to the Config +fn try_revoke_permit( + storage: &mut dyn Storage, + sender: &str, + permit_name: &str, + priority: u8, + config: &ConfigInfo, +) -> StdResult { + check_status(config.status, priority)?; + + RevokedPermits::revoke_permit(storage, PREFIX_REVOKED_PERMITS, sender, permit_name); + + Ok(Response::new().set_data(to_binary(&HandleAnswer::RevokePermit { status: Success })?)) +} + +///////////////////////////////////////// Sponsor Functions ////////////////////////////////////// + +/// Returns StdResult +/// +/// Sponsors can delegate to the contract. +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `title` - The title of the sponsor +/// * `message` - The message by the sponsor +/// * `config` - a mutable reference to the Config +/// * `priority` - u8 representation of highest ContractStatus level this action is permitted +fn try_sponsor( + deps: DepsMut, + env: Env, + info: MessageInfo, + title: Option, + message: Option, + config: &mut ConfigInfo, + priority: u8, +) -> StdResult { + check_status(config.status, priority)?; + + //CHECKING: If sponsor amount is valid + let deposit_amount: Uint128 = check_if_valid_amount(&info, &config)?; + + //Loading sponsor info + let mut sponsor_info_obj = sponsor_info_helper_read_only(deps.storage, &info.sender)?; + //Updating sponsor info + sponsor_info_obj.amount_sponsored.add_assign(deposit_amount); + //addr_list_index is assigned when sponsor deposits. + if sponsor_info_obj.addr_list_index.is_none() { + //find index and give sponsor this index to recognize the order in the list. + let mut sponsor_stats_obj = sponsor_stats_helper_read_only(deps.storage)?; + let index: Option; + if sponsor_stats_obj.empty_slots.len() > 0 { + index = Some(sponsor_stats_obj.empty_slots.pop().unwrap()); + } else { + index = Some(sponsor_stats_obj.offset); + sponsor_stats_obj.offset.add_assign(1u32); + } + sponsor_info_obj.addr_list_index = index; + sponsor_addr_list_helper_store(deps.storage, index.unwrap(), &info.sender)?; + sponsor_stats_helper_store(deps.storage, &sponsor_stats_obj)?; + + // You can only add title and message through sponsor function when sponsoring for the first time + if sponsor_info_obj.has_requested == false { + if title.is_some() || message.is_some() { + sponsor_info_obj.has_requested = true; + sponsor_display_request_deque_push_back_helper( + deps.storage, + &GlobalSponsorDisplayRequestListState { + addr: info.sender.to_string(), + index: sponsor_info_obj.addr_list_index, + title, + message, + deque_store_index: None, + }, + )?; + } + } + }; + + sponsor_info_helper_store(deps.storage, &info.sender, &sponsor_info_obj); + + //3)Fetching Pool Store data and updating it + let mut pool_state: PoolState = pool_state_helper_read_only(deps.storage)?; + pool_state.total_sponsored.add_assign(deposit_amount); + + //4) Choosing Validator for deposit and updating the validator information + let selected_val_index = config.next_validator_for_delegation as usize; + config.validators[selected_val_index] + .delegated + .add_assign(deposit_amount); + + if selected_val_index == config.validators.len() - 1 { + config.next_validator_for_delegation = 0; + } else { + config.next_validator_for_delegation += 1; + } + + //4.3) Storing validator + config_helper_store(deps.storage, &config)?; + + //5) Querying pending_rewards send back from validator + let rewards = get_rewards(deps.as_ref(), &env.contract.address, &config).unwrap(); + let mut rewards_amount = Uint128::zero(); + for reward in rewards { + if reward.validator_address.as_str() + == config.validators[selected_val_index].address.as_str() + { + rewards_amount = rewards_amount.add(reward.reward); + } + } + + //Updating PoolState + pool_state + .rewards_returned_to_contract + .add_assign(rewards_amount); + pool_state_helper_store(deps.storage, &pool_state)?; + + //Sending staking message + let mut messages: Vec = vec![]; + messages.push(stake( + &config.validators[selected_val_index].address, + deposit_amount, + &config.denom, + )); + + Ok(Response::new() + .add_messages(messages) + .set_data(to_binary(&HandleAnswer::Sponsor { status: Success })?)) +} + +/// Returns StdResult +/// +/// Sponsors can request to withdraw their funds. It take 21 days to withdraw the funds. +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a mutable reference to the Config +/// * `request_withdraw_amount` - amount requested to withdraw +/// * `priority` - u8 representation of highest ContractStatus level this action is permitted +fn try_sponsor_request_withdraw( + deps: DepsMut, + info: MessageInfo, + config: &mut ConfigInfo, + request_withdraw_amount: Uint128, + priority: u8, +) -> StdResult { + //Checking status + check_status(config.status, priority)?; + let mut sponsor_info_obj = sponsor_info_helper_read_only(deps.storage, &info.sender)?; + + let mut pool_state: PoolState = pool_state_helper_read_only(deps.storage)?; + // Checking: If the amount unbonded is not greater than amount delegated + if sponsor_info_obj.amount_sponsored < request_withdraw_amount { + return Err(StdError::generic_err(format!( + "insufficient funds to redeem: balance={}, required={}", + sponsor_info_obj.amount_sponsored, request_withdraw_amount + ))); + } + // Checking: If the amount unbonded is not equal to zero + if request_withdraw_amount == Uint128::zero() { + return Err(StdError::generic_err(format!( + "Cannot withdraw 0 {}", + config.denom + ))); + } + //If the liquidity struct for that round is not generated then we create one and store + pool_state.total_sponsored = if let Ok(t_s) = pool_state + .total_sponsored + .checked_sub(request_withdraw_amount) + { + t_s + } else { + return Err(StdError::generic_err("Under-flow sub error 1")); + }; + + sponsor_info_obj.amount_sponsored = if let Ok(a_s) = sponsor_info_obj + .amount_sponsored + .checked_sub(request_withdraw_amount) + { + a_s + } else { + return Err(StdError::generic_err("Under-flow sub error 2")); + }; + + sponsor_info_obj + .amount_unbonding + .add_assign(request_withdraw_amount); + + // if amount delegated == 0 then remove the name from sponsors list + // Also remove to sponsors address list. + if sponsor_info_obj.amount_sponsored.u128() == 0 { + //Removing sponsor from global sponsors list + sponsor_addr_list_remove_helper_store( + deps.storage, + sponsor_info_obj.addr_list_index.unwrap(), + )?; + + //Updating sponsor stat store + let mut sponsor_stats_obj = sponsor_stats_helper_read_only(deps.storage)?; + if sponsor_info_obj.addr_list_index.unwrap() == sponsor_stats_obj.offset - 1 { + // sponsor_stats_obj.offset.sub_assign(1); + + sponsor_stats_obj.offset = if let Some(off) = sponsor_stats_obj.offset.checked_sub(1) { + off + } else { + return Err(StdError::generic_err("Under-flow sub error 3")); + } + } else { + sponsor_stats_obj + .empty_slots + .push(sponsor_info_obj.addr_list_index.unwrap()); + } + sponsor_stats_helper_store(deps.storage, &sponsor_stats_obj)?; + + //Removing any pending message requests made by the user + let len = SPONSOR_DISPLAY_REQ_STORE.get_len(deps.storage)?; + if len > 0 { + for i in 0..(len - 1) { + let req_obj = SPONSOR_DISPLAY_REQ_STORE.get_at(deps.storage, i)?; + + if req_obj.index == sponsor_info_obj.addr_list_index { + sponsor_display_request_deque_helper_remove(deps.storage, i)?; + } + } + } + + //Updating sponsor info + sponsor_info_obj.has_requested = false; + sponsor_info_obj.addr_list_index = None; + } + + //Checking if sponsor has already made unbonding request this current round. + if !sponsor_info_obj + .unbonding_batches + .contains(&config.next_unbonding_batch_index) + { + sponsor_info_obj + .unbonding_batches + .push(config.next_unbonding_batch_index); + } + + //Adding to amount unbonding for next unbonding round + let mut unbonding_amount = sponsor_unbond_helper_read_only( + deps.storage, + config.next_unbonding_batch_index, + &info.sender, + )?; + unbonding_amount.add_assign(request_withdraw_amount); + sponsor_unbond_helper_store( + deps.storage, + config.next_unbonding_batch_index, + &info.sender, + unbonding_amount, + )?; + + sponsor_info_helper_store(deps.storage, &info.sender, &sponsor_info_obj); + + //Asking the validator to undelegate the funds + pool_state_helper_store(deps.storage, &pool_state)?; + config + .next_unbonding_batch_amount + .add_assign(request_withdraw_amount); + + config_helper_store(deps.storage, &config)?; + + let res = Response::new().set_data(to_binary(&HandleAnswer::RequestWithdrawSponsor { + status: Success, + })?); + + Ok(res) +} + +/// Returns StdResult +/// +/// Sponsors withdraw their requested funds. It take 21 days to withdraw the funds after the request is made. +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a mutable reference to the Config +/// * `withdraw_amount` - amount to withdraw +/// * `priority` - u8 representation of highest ContractStatus level this action is permitted +fn try_sponsor_withdraw( + deps: DepsMut, + env: Env, + info: MessageInfo, + config: &mut ConfigInfo, + withdraw_amount: Uint128, + priority: u8, +) -> StdResult { + //loading Data from storage + check_status(config.status, priority)?; + + let mut sponsor_info_obj = sponsor_info_helper_read_only(deps.storage, &info.sender)?; + + //Checking amount available for withdraw + let mut amount_av_for_withdraw = Uint128::zero(); + //STORING USER UNBONDING + let mut pop_front_counter: Vec = vec![]; + + for i in 0..sponsor_info_obj.unbonding_batches.len() { + let unbond_batch_index = sponsor_info_obj.unbonding_batches[i]; + let unbonding_batch_obj = + unbonding_batch_helper_read_only(deps.storage, unbond_batch_index)?; + + if unbonding_batch_obj.unbonding_time.is_some() { + if env.block.time.seconds() >= unbonding_batch_obj.unbonding_time.unwrap() { + let unbonding_amount = sponsor_unbond_helper_read_only( + deps.storage, + unbond_batch_index, + &info.sender, + )?; + + amount_av_for_withdraw.add_assign(unbonding_amount); + + pop_front_counter.push(unbond_batch_index); + } + } + } + + sponsor_info_obj + .unbonding_batches + .retain(|val| !pop_front_counter.contains(val)); + + sponsor_info_obj + .amount_withdrawable + .add_assign(amount_av_for_withdraw); + + //ERROR Check + if withdraw_amount + > sponsor_info_obj + .amount_withdrawable + .add(amount_av_for_withdraw) + { + return Err(StdError::generic_err( + "Trying to withdraw more than available", + )); + } + + //Updating user and pool state + + sponsor_info_obj + .amount_withdrawable + .add_assign(amount_av_for_withdraw); + // sponsor_info_obj + // .amount_unbonding + // .sub_assign(withdraw_amount); + + sponsor_info_obj.amount_unbonding = if let Ok(a_u) = sponsor_info_obj + .amount_unbonding + .checked_sub(withdraw_amount) + { + a_u + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + + // sponsor_info_obj + // .amount_withdrawable + // .sub_assign(withdraw_amount); + sponsor_info_obj.amount_withdrawable = if let Ok(a_w) = sponsor_info_obj + .amount_withdrawable + .checked_sub(withdraw_amount) + { + a_w + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + sponsor_info_helper_store(deps.storage, &info.sender, &sponsor_info_obj); + + let mut messages: Vec = vec![]; + + if withdraw_amount > Uint128::zero() { + let withdraw_coins: Vec = vec![Coin { + denom: config.denom.to_string(), + amount: withdraw_amount, + }]; + //Sending a message to withdraw + messages.push(CosmosMsg::Bank(BankMsg::Send { + to_address: info.sender.to_string(), + amount: withdraw_coins, + })); + } + + let res = Response::new().add_messages(messages).set_data(to_binary( + &HandleAnswer::SponsorWithdraw { status: Success }, + )?); + + Ok(res) +} + +/// Returns StdResult +/// +/// Sponsors can edit the message they send before. +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `title` - The title of the sponsor +/// * `message` - The message by the sponsor +/// * `config` - a mutable reference to the Config +/// * `priority` - u8 representation of highest ContractStatus level this action is permitted +fn try_sponsor_message_edit( + deps: DepsMut, + info: MessageInfo, + title: Option, + message: Option, + delete_title: bool, + delete_message: bool, + config: &mut ConfigInfo, + priority: u8, +) -> StdResult { + check_status(config.status, priority)?; + + let mut sponsor_info_obj = sponsor_info_helper_read_only(deps.storage, &info.sender)?; + if sponsor_info_obj.amount_sponsored.u128() == 0 { + return Err(StdError::generic_err("Sponsor to avail this option")); + } + + if delete_title || delete_message { + if delete_title { + sponsor_info_obj.title = None; + } + if delete_message { + sponsor_info_obj.message = None; + } + } + + if config.sponsor_msg_edit_fee.is_some() { + let sponsor_fee_amount: Uint128 = check_if_valid_amount(&info, &config)?; + + if sponsor_fee_amount < config.sponsor_msg_edit_fee.unwrap() { + return Err(StdError::generic_err( + "Please pay fee to allow title/message edit", + )); + } + } + + // Add name/message request + if sponsor_info_obj.has_requested == false { + if title.is_some() || message.is_some() { + sponsor_info_obj.has_requested = true; + sponsor_display_request_deque_push_back_helper( + deps.storage, + &GlobalSponsorDisplayRequestListState { + addr: info.sender.to_string(), + index: sponsor_info_obj.addr_list_index, + title, + message, + deque_store_index: None, + }, + )?; + } + } else { + let len = SPONSOR_DISPLAY_REQ_STORE.get_len(deps.storage)?; + + for i in 0..(len) { + let req_obj = SPONSOR_DISPLAY_REQ_STORE.get_at(deps.storage, i)?; + + if req_obj.index == sponsor_info_obj.addr_list_index { + // sponsor_display_request_deque_helper_remove(deps.storage, i)?; + SPONSOR_DISPLAY_REQ_STORE.set_at( + deps.storage, + i, + &GlobalSponsorDisplayRequestListState { + addr: info.sender.to_string(), + index: sponsor_info_obj.addr_list_index, + title, + message, + deque_store_index: None, + }, + )?; + break; + } + } + sponsor_info_obj.has_requested = true; + } + + sponsor_info_helper_store(deps.storage, &info.sender, &sponsor_info_obj); + + Ok( + Response::new().set_data(to_binary(&HandleAnswer::SponsorMessageEdit { + status: Success, + })?), + ) +} + +///////////////////////////////////////// Admin Functions ////////////////////////////////////// + +fn try_review_sponsor_messages( + deps: DepsMut, + info: MessageInfo, + mut decisions: Vec, + config: &ConfigInfo, +) -> StdResult { + check_if_reviewer(&config, &info.sender)?; + + decisions.sort_by(|a, b| a.index.cmp(&b.index)); + + // Get reqs and + // let len = SPONSOR_DISPLAY_REQ_STORE.get_len(deps.storage)?; + let mut off = 0; + for decision in decisions { + let sponsor_disp_obj = + SPONSOR_DISPLAY_REQ_STORE.get_at(deps.storage, decision.index - off)?; + let mut sponsor_info_obj = sponsor_info_helper_read_only( + deps.storage, + &deps.api.addr_validate(sponsor_disp_obj.addr.as_str())?, + )?; + + if decision.is_accpeted { + if sponsor_disp_obj.message.is_some() { + sponsor_info_obj.message = sponsor_disp_obj.message; + } + if sponsor_disp_obj.title.is_some() { + sponsor_info_obj.title = sponsor_disp_obj.title; + } + } + sponsor_info_obj.has_requested = false; + sponsor_info_helper_store( + deps.storage, + &deps.api.addr_validate(sponsor_disp_obj.addr.as_str())?, + &sponsor_info_obj, + ); + + sponsor_display_request_deque_helper_remove(deps.storage, decision.index - off)?; + off += 1; + } + + Ok( + Response::new().set_data(to_binary(&HandleAnswer::ReviewSponsorMessages { + status: Success, + })?), + ) +} + +fn try_remove_sponsor_credentials( + deps: DepsMut, + info: MessageInfo, + decisions: Vec, + config: &ConfigInfo, +) -> StdResult { + check_if_reviewer(&config, &info.sender)?; + + for decision in decisions { + let sponsor_address = sponsor_addr_list_helper_read_only(deps.storage, decision.index)?; + + let mut sponsor_info_obj = sponsor_info_helper_read_only(deps.storage, &sponsor_address)?; + + if decision.remove_sponsor_title { + sponsor_info_obj.title = None; + } + if decision.remove_sponsor_message { + sponsor_info_obj.message = None; + } + + sponsor_info_helper_store(deps.storage, &sponsor_address, &sponsor_info_obj) + } + + Ok( + Response::new().set_data(to_binary(&HandleAnswer::RemoveSponsorCredentials { + status: Success, + })?), + ) +} + +/// Returns StdResult +/// +/// End of the round - winning sequence and difficulty is calculated. +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a mutable reference to the Config +/// * `priority` - u8 representation of highest ContractStatus level this action is permitted +fn try_end_round( + deps: DepsMut, + env: Env, + info: MessageInfo, + config: &mut ConfigInfo, + priority: u8, +) -> StdResult { + //Checking status + check_status(config.status, priority)?; + + //Checking if this function is called by Triggerer + check_if_triggerer(&config, &info.sender)?; + + //Loading dependencies and data + let mut round_obj: RoundInfo = round_helper_read_only(deps.storage)?; + + //Checking if the round can be closed or there still some time left + validate_end_time(round_obj.end_time, env.block.time.seconds())?; + + //Validate_start_time(round_obj.start_time, env.block.time)?; + //Extending the entropy using block height and time. + round_obj.entropy.extend(&env.block.height.to_be_bytes()); + round_obj + .entropy + .extend(&env.block.time.seconds().to_be_bytes()); + round_obj.entropy.extend(&config.prng_seed); + + //Querying pending_rewards and add them to winning prizes. + let rewards_obj = get_rewards(deps.as_ref(), &env.contract.address, &config)?; + let mut total_rewards: Uint128 = Uint128::zero(); + for reward_obj in rewards_obj.clone() { + total_rewards.add_assign(reward_obj.reward); + } + + //Calculating total rewards recieved + let mut pool_state: PoolState = pool_state_helper_read_only(deps.storage)?; + let mut total_rewards = pool_state.rewards_returned_to_contract.add(total_rewards); + let mut unclaimed_exp = None; + let mut messages = Vec::new(); + + //Checking when rewards are zero + if total_rewards == Uint128::zero() { + if config.exp_contract.is_some() { + // unclaimed_exp = Some(get_exp(deps.as_ref(), config)?); + // TODO: check this + // messages.push( + // experience_contract::msg::ExecuteMsg::UpdateLastClaimed {}.to_cosmos_msg( + // BLOCK_SIZE, + // config.exp_contract.clone().unwrap().contract.hash, + // config.exp_contract.clone().unwrap().contract.address, + // None, + // )?, + // ); + } + let reward_stats_for_nth_round: RewardsState = RewardsState { + //Using a helper function to create rewards distribution + distribution_per_tiers: Default::default(), + ticket_price: round_obj.ticket_price, + winning_sequence: Default::default(), + rewards_expiration_date: Some( + env.block + .time + .seconds() + .add(round_obj.rewards_expiry_duration), + ), + total_rewards: Uint128::zero(), + total_claimed: Uint128::zero(), + total_exp: unclaimed_exp, + total_exp_claimed: Some(Uint128::zero()), + }; + //*Saving the rewards_tickets of current round + reward_stats_for_nth_round_helper_store( + deps.storage, + round_obj.current_round_index, + &reward_stats_for_nth_round, + ); + round_obj.start_time = env.block.time.seconds(); + round_obj.end_time = env.block.time.seconds().add(round_obj.duration); + round_obj.current_round_index.add_assign(1u64); + round_helper_store(deps.storage, &round_obj)?; + + return Ok(Response::new() + .set_data(to_binary(&HandleAnswer::EndRound { status: Success })?) + .add_messages(messages)); + } + + //Trigger get share from rewards + let trigger_share = + total_rewards.multiply_ratio(round_obj.triggerer_share_percentage, config.common_divisor); + + total_rewards = if let Ok(t_r) = total_rewards.checked_sub(trigger_share) { + t_r + } else { + return Err(StdError::generic_err("Under-flow sub error 1")); + }; + + //Shade and PoolParty Share of the rewards + let admin_share = total_rewards.multiply_ratio( + round_obj.admin_share.total_percentage_share, + config.common_divisor, + ); + let shade_share = admin_share.multiply_ratio( + round_obj.admin_share.shade_percentage_share, + config.common_divisor, + ); + + let galactic_pools_share = if let Ok(g_p_s) = admin_share.checked_sub(shade_share) { + g_p_s + } else { + return Err(StdError::generic_err("Under-flow sub error 2")); + }; + + let mut winning_amount = if let Ok(w_a) = total_rewards.checked_sub(admin_share) { + w_a + } else { + return Err(StdError::generic_err("Under-flow sub error 3")); + }; + + //Claim the unclaimed rewards that have been expired + let when_last_redeemed_the_unclaimed_obj = round_obj + .unclaimed_rewards_last_claimed_round + .unwrap_or(0) + .add(1); + let mut reserve: Uint128 = Uint128::zero(); + let mut propagate: Uint128 = Uint128::zero(); + + for round in when_last_redeemed_the_unclaimed_obj..round_obj.current_round_index { + let rewards_expiry_check_obj = + reward_stats_for_nth_round_helper_read_only(deps.storage, round)?; + + if rewards_expiry_check_obj.rewards_expiration_date.is_some() { + if rewards_expiry_check_obj.rewards_expiration_date.unwrap() <= env.block.time.seconds() + { + round_obj.unclaimed_rewards_last_claimed_round = Some(round); + //Fetch unclaimed + let total_unclaimed = if let Ok(t_c) = rewards_expiry_check_obj + .total_rewards + .checked_sub(rewards_expiry_check_obj.total_claimed) + { + t_c + } else { + return Err(StdError::generic_err("Under-flow sub error 4")); + }; + + //Reserve vs Propagation + let reserve_share = total_unclaimed.multiply_ratio( + round_obj.unclaimed_distribution.reserves_percentage as u128, + config.common_divisor as u128, + ); + + reserve.add_assign(reserve_share); + let remaining = total_unclaimed.checked_sub(reserve_share); + if remaining.is_ok() { + propagate = propagate.add(remaining?); + } else { + return Err(StdError::generic_err("Under-flow sub error 5")); + } + } else { + break; + } + } + } + + // Fetching validator with the lowest percentage filled + // Choosing Validator for deposit and updating the validator information + let index = config.next_validator_for_delegation as usize; + + if reserve.u128() > 0 { + config.validators[index].delegated.add_assign(reserve); + pool_state.total_reserves.add_assign(reserve); + messages.push(stake( + &config.validators[index].address, + reserve, + &config.denom, + )); + + if index == config.validators.len() - 1 { + config.next_validator_for_delegation = 0; + } else { + config.next_validator_for_delegation += 1; + } + } + + if propagate.u128() > 0 { + winning_amount.add_assign(propagate); + } + + //Withdraw rewards from all validators + for validator in &config.validators { + if validator.delegated.u128() > 0 { + messages.push(withdraw(&validator.address)); + } + } + config_helper_store(deps.storage, &config)?; + + pool_state.rewards_returned_to_contract = Uint128::zero(); + pool_state_helper_store(deps.storage, &pool_state)?; + + //Calculate range for each tier + //*Getting pool liquidity + let mut pool_state_liquidity_snapshot_obj: PoolLiqState = + pool_state_liquidity_helper_read_only(deps.storage, round_obj.current_round_index)?; + if pool_state_liquidity_snapshot_obj.total_liquidity.is_none() { + pool_state_liquidity_snapshot_obj.total_liquidity = Some(pool_state.total_delegated); + pool_state_liquidity_snapshot_obj.total_delegated = Some(pool_state.total_delegated); + pool_state_liquidity_helper_store( + deps.storage, + round_obj.current_round_index, + pool_state_liquidity_snapshot_obj, + )?; + } + //Calculting total number of winners + let total_number_of_winners: Uint128 = round_obj + .rewards_distribution + .tier_5 + .total_number_of_winners + .add( + round_obj + .rewards_distribution + .tier_4 + .total_number_of_winners, + ) + .add( + round_obj + .rewards_distribution + .tier_3 + .total_number_of_winners, + ) + .add( + round_obj + .rewards_distribution + .tier_2 + .total_number_of_winners, + ) + .add( + round_obj + .rewards_distribution + .tier_1 + .total_number_of_winners, + ) + .add( + round_obj + .rewards_distribution + .tier_0 + .total_number_of_winners, + ); + + //Calculating the range of each tier using total winners for each tier + let range_tier_5 = pool_state_liquidity_snapshot_obj + .total_liquidity + .unwrap() + .multiply_ratio(1u128, round_obj.ticket_price.u128()) + .multiply_ratio(1u128, total_number_of_winners); + + let range_tier_4 = round_obj + .rewards_distribution + .tier_5 + .total_number_of_winners + .multiply_ratio( + 1u128, + round_obj + .rewards_distribution + .tier_4 + .total_number_of_winners, + ); + let range_tier_3 = round_obj + .rewards_distribution + .tier_4 + .total_number_of_winners + .multiply_ratio( + 1u128, + round_obj + .rewards_distribution + .tier_3 + .total_number_of_winners, + ); + let range_tier_2 = round_obj + .rewards_distribution + .tier_3 + .total_number_of_winners + .multiply_ratio( + 1u128, + round_obj + .rewards_distribution + .tier_2 + .total_number_of_winners, + ); + let range_tier_1 = round_obj + .rewards_distribution + .tier_2 + .total_number_of_winners + .multiply_ratio( + 1u128, + round_obj + .rewards_distribution + .tier_1 + .total_number_of_winners, + ); + let range_tier_0 = round_obj + .rewards_distribution + .tier_1 + .total_number_of_winners + .multiply_ratio( + 1u128, + round_obj + .rewards_distribution + .tier_0 + .total_number_of_winners, + ); + + //Generate a sequence of random number between the range defined in round_obj + let mut hasher = Sha256::new(); + hasher.update(&config.prng_seed); + hasher.update(&round_obj.entropy); + + let mut winning_sequence_data: WinningSequence = WinningSequence { + tier_0: DigitsInfo { + range: range_tier_0, + winning_number: Default::default(), + }, + tier_1: DigitsInfo { + range: range_tier_1, + winning_number: Default::default(), + }, + tier_2: DigitsInfo { + range: range_tier_2, + winning_number: Default::default(), + }, + tier_3: DigitsInfo { + range: range_tier_3, + winning_number: Default::default(), + }, + tier_4: DigitsInfo { + range: range_tier_4, + winning_number: Default::default(), + }, + tier_5: DigitsInfo { + range: range_tier_5, + winning_number: Default::default(), + }, + }; + + for i in 0u8..6u8 { + let range_finder = match i { + 0 => range_tier_0, + 1 => range_tier_1, + 2 => range_tier_2, + 3 => range_tier_3, + 4 => range_tier_4, + 5 => range_tier_5, + _ => Uint128::zero(), + }; + + //**generate a random number between 0 and range + let mut digit_range = range_finder.u128(); + if digit_range > 0 { + let d_n = range_finder.checked_sub(Uint128::one()); + + if d_n.is_ok() { + digit_range = d_n?.u128() + } else { + return Err(StdError::generic_err("Under-flow sub error 6")); + } + } + let range = Uniform::new_inclusive(0, digit_range); + + let mut final_hasher = hasher.clone(); + final_hasher.update(&vec![i]); + let seed: [u8; 32] = final_hasher.finalize().into(); + let rng = ChaChaRng::from_seed(seed); + let mut digit_generator = rng.clone().sample_iter(&range); + //**We need to draft 6 digits individually + + let drafted_ticket = digit_generator.next().unwrap_or(0u128); + match i { + 0 => winning_sequence_data.tier_0.winning_number = Uint128::from(drafted_ticket), + 1 => winning_sequence_data.tier_1.winning_number = Uint128::from(drafted_ticket), + 2 => winning_sequence_data.tier_2.winning_number = Uint128::from(drafted_ticket), + 3 => winning_sequence_data.tier_3.winning_number = Uint128::from(drafted_ticket), + 4 => winning_sequence_data.tier_4.winning_number = Uint128::from(drafted_ticket), + 5 => winning_sequence_data.tier_5.winning_number = Uint128::from(drafted_ticket), + _ => {} + }; + } + + //* Divide the winning amount among the distribution and save it as a RewardStatsPerRound + //** Fetching rewards distribution + let rewards_distribution_obj = &round_obj.rewards_distribution; + //** Now RewardStats for this round and Saving the reward_stats_for_current_round + + // if config.exp_contract.is_some() { + // unclaimed_exp = Some(get_exp(deps.as_ref(), config)?); + // } + + let reward_stats_for_nth_round: RewardsState = RewardsState { + //using a helper function to create rewards distribution + distribution_per_tiers: reward_distribution_per_tier_helper( + winning_amount, + &rewards_distribution_obj, + &config, + )?, + ticket_price: round_obj.ticket_price, + winning_sequence: winning_sequence_data, + rewards_expiration_date: Some( + env.block + .time + .seconds() + .add(round_obj.rewards_expiry_duration), + ), + total_rewards: winning_amount, + total_claimed: Uint128::zero(), + total_exp: unclaimed_exp, + total_exp_claimed: Some(Uint128::zero()), + }; + + //*Saving the rewards_tickets of current round_obj + reward_stats_for_nth_round_helper_store( + deps.storage, + round_obj.current_round_index, + &reward_stats_for_nth_round, + ); + + if round_obj.entropy.len() > 1024 { + round_obj.entropy = round_obj.entropy + [round_obj.entropy.len() - 1024..(round_obj.entropy.len() - 1)] + .to_vec(); + } + + round_obj.start_time = env.block.time.seconds(); + round_obj.end_time = env.block.time.seconds().add(round_obj.duration); + round_obj.current_round_index.add_assign(1u64); + round_helper_store(deps.storage, &round_obj)?; + + //Sending amount to users + if shade_share.u128() > 0 { + let shade_coins: Vec = vec![Coin { + denom: config.denom.to_string(), + amount: shade_share, + }]; + messages.push(CosmosMsg::Bank(BankMsg::Send { + to_address: round_obj.shade_rewards_address.to_string(), + amount: shade_coins, + })); + } + + if galactic_pools_share.u128() > 0 { + let galactic_pools_coins: Vec = vec![Coin { + denom: config.denom.to_string(), + amount: galactic_pools_share, + }]; + messages.push(CosmosMsg::Bank(BankMsg::Send { + to_address: round_obj.galactic_pools_rewards_address.to_string(), + amount: galactic_pools_coins, + })); + } + + if trigger_share.u128() > 0 { + let triggerer_coins: Vec = vec![Coin { + denom: config.denom.to_string(), + amount: trigger_share, + }]; + messages.push(CosmosMsg::Bank(BankMsg::Send { + to_address: config.triggerers[0].to_string(), + amount: triggerer_coins, + })); + } + + //TODO: check this + // if config.exp_contract.is_some() { + // messages.push( + // experience_contract::msg::ExecuteMsg::UpdateLastClaimed {}.to_cosmos_msg( + // BLOCK_SIZE, + // config.exp_contract.clone().unwrap().contract.hash, + // config.exp_contract.clone().unwrap().contract.address, + // None, + // )?, + // ); + // } + + let res = Response::new() + .add_messages(messages) + .set_data(to_binary(&HandleAnswer::EndRound { status: Success })?); + + Ok(res) +} + +/// Returns StdResult +/// +/// Adds admin to the vec of admins +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a reference to the Config +/// * `admin` - String for the admin +fn try_add_admin( + deps: DepsMut, + info: MessageInfo, + config: &mut ConfigInfo, + admin: Addr, +) -> StdResult { + //Only admin(s) can access this functionality + + check_if_admin(&config, &info.sender)?; + + if config.admins.contains(&admin) { + return Err(StdError::generic_err("This address already exisits")); + } else { + config.admins.push(admin); + } + + config_helper_store(deps.storage, config)?; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::AddAdmin { status: Success })?)) +} + +/// Returns StdResult +/// +/// Removes admin to the vec of admins +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a reference to the Config +/// * `admin` - String for the admin +fn try_remove_admin( + deps: DepsMut, + info: MessageInfo, + config: &mut ConfigInfo, + admin: Addr, +) -> StdResult { + let _ = check_if_admin(&config, &info.sender)?; + + if config.admins.contains(&admin) { + config.admins.retain(|ad| ad != &admin); + } else { + return Err(StdError::generic_err("This address doesn't exisits")); + } + config_helper_store(deps.storage, config)?; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::RemoveAdmin { status: Success })?)) +} + +/// Returns StdResult +/// +/// Adds triggerer to the vec of triggerers +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a reference to the Config +/// * `triggerer` - String for the triggerer +fn try_add_triggerer( + deps: DepsMut, + info: MessageInfo, + config: &mut ConfigInfo, + triggerer: Addr, +) -> StdResult { + //Only admin(s) can access this functionality + check_if_admin(&config, &info.sender)?; + + if config.triggerers.contains(&triggerer) { + return Err(StdError::generic_err("This address already exisits")); + } else { + config.triggerers.push(triggerer); + } + + config_helper_store(deps.storage, config)?; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::AddTriggerer { status: Success })?)) +} + +/// Returns StdResult +/// +/// Removes triggerer to the vec of triggerers +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a reference to the Config +/// * `triggerer` - String for the triggerer +fn try_remove_triggerer( + deps: DepsMut, + info: MessageInfo, + config: &mut ConfigInfo, + triggerer: Addr, +) -> StdResult { + check_if_admin(&config, &info.sender)?; + + if config.triggerers.contains(&triggerer) { + config.triggerers.retain(|ad| ad != &triggerer); + } else { + return Err(StdError::generic_err("This address doesn't exisits")); + } + config_helper_store(deps.storage, config)?; + + Ok( + Response::new().set_data(to_binary(&HandleAnswer::RemoveTriggerer { + status: Success, + })?), + ) +} + +/// Returns StdResult +/// +/// Adds reviewer to the vec of reviewers +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a reference to the Config +/// * `reviewer` - String for the reviewer +fn try_add_reviewer( + deps: DepsMut, + info: MessageInfo, + config: &mut ConfigInfo, + reviewer: Addr, +) -> StdResult { + //Only admin(s) can access this functionality + check_if_admin(&config, &info.sender)?; + + if config.reviewers.contains(&reviewer) { + return Err(StdError::generic_err("This address already exisits")); + } else { + config.reviewers.push(reviewer); + } + + config_helper_store(deps.storage, config)?; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::AddReviewer { status: Success })?)) +} + +/// Returns StdResult +/// +/// Removes reviewer to the vec of reviewers +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a reference to the Config +/// * `reviewer` - String for the reviewer +fn try_remove_reviewer( + deps: DepsMut, + info: MessageInfo, + config: &mut ConfigInfo, + reviewer: Addr, +) -> StdResult { + let _ = check_if_admin(&config, &info.sender)?; + + if config.reviewers.contains(&reviewer) { + config.reviewers.retain(|ad| ad != &reviewer); + } else { + return Err(StdError::generic_err("This address doesn't exisits")); + } + config_helper_store(deps.storage, config)?; + + Ok( + Response::new().set_data(to_binary(&HandleAnswer::RemoveReviewer { + status: Success, + })?), + ) +} + +/// Returns StdResult +/// +/// Changes the configuration of the contract. +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a reference to the Config +/// * `admin` - String for the admin +/// * `triggerer` - String for the triggerer +/// * `reviewer` - String for the reviewer +/// * `sscrt` - contract details of sscrt contract +/// * `unbonding_batch_duration` - time in seconds it takes before next batch is unbonded +/// * `unbonding_duration` - time in seconds taken by this chain to unbond the tokens delegated +/// * `minimum_deposit_amount` - Optional time in seconds taken by this chain to unbond the tokens delegated +fn try_update_config( + deps: DepsMut, + info: MessageInfo, + _env: Env, + config: &mut ConfigInfo, + unbonding_batch_duration: Option, + unbonding_duration: Option, + minimum_deposit_amount: Option, + exp_contract: Option, +) -> StdResult { + let _ = check_if_admin(&config, &info.sender)?; + + if let Some(duration) = unbonding_batch_duration { + config.unbonding_batch_duration = duration; + } + + if let Some(duration) = unbonding_duration { + config.unbonding_duration = duration; + } + + if let Some(amount) = minimum_deposit_amount { + config.minimum_deposit_amount = Some(amount); + } + + let mut msgs = Vec::::new(); + + // TODO: check this + // if exp_contract.is_some() { + // if let Some(exp_c) = exp_contract.clone() { + // let set_vk_msg = experience_contract::msg::ExecuteMsg::SetViewingKey { key: exp_c.vk }; + + // msgs.push(set_vk_msg.to_cosmos_msg( + // BLOCK_SIZE, + // exp_c.contract.hash, + // exp_c.contract.address, + // None, + // )?); + // } + // } + + config.exp_contract = exp_contract; + + config_helper_store(deps.storage, &config)?; + + Ok(Response::new() + .set_data(to_binary(&HandleAnswer::UpdateConfig { status: Success })?) + .add_messages(msgs)) +} + +/// Returns StdResult +/// +/// Changes the configuration of the contract. +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a reference to the Config +/// * `duration` - duration of the round +/// * `rewards_distribution` - rewards distribution of rewards between each tier +/// * `ticket_price` - price per one ticket +/// * `rewards_expiry_duration` - duration after round ends after which prizes are expired. +/// * `triggerer_share_percentage` - % of rewards for triggerer to maintain this contract +/// * `shade_rewards_address` - shade's dao address +/// * `galactic_pools_rewards_address` - galacticpool's dao address +/// * `grand_prize_address` - grand-prize contract address +/// * `unclaimed_distribution` - distribution of unclaimed rewards +fn try_update_round( + deps: DepsMut, + info: MessageInfo, + config: &mut ConfigInfo, + duration: Option, + rewards_distribution: Option, + ticket_price: Option, + rewards_expiry_duration: Option, + admin_share: Option, + triggerer_share_percentage: Option, + shade_rewards_address: Option, + galactic_pools_rewards_address: Option, + grand_prize_address: Option, + unclaimed_distribution: Option, +) -> StdResult { + let _ = check_if_admin(&config, &info.sender)?; + + let mut round_obj = round_helper_read_only(deps.storage)?; + + if duration.is_some() { + round_obj.duration = duration.unwrap(); + } + if rewards_distribution.is_some() { + round_obj.rewards_distribution = rewards_distribution.unwrap(); + } + if ticket_price.is_some() { + round_obj.ticket_price = ticket_price.unwrap(); + } + if rewards_expiry_duration.is_some() { + round_obj.rewards_expiry_duration = rewards_expiry_duration.unwrap(); + } + if admin_share.is_some() { + if admin_share + .unwrap() + .shade_percentage_share + .add(admin_share.unwrap().galactic_pools_percentage_share) + != config.common_divisor as u64 + { + return Err(StdError::generic_err( + "Total percentage shares don't add up to 100%", + )); + } + round_obj.admin_share = admin_share.unwrap(); + } + if triggerer_share_percentage.is_some() { + round_obj.triggerer_share_percentage = triggerer_share_percentage.unwrap(); + } + if shade_rewards_address.is_some() { + round_obj.shade_rewards_address = shade_rewards_address.unwrap(); + } + if galactic_pools_rewards_address.is_some() { + round_obj.galactic_pools_rewards_address = galactic_pools_rewards_address.unwrap(); + } + if grand_prize_address.is_some() { + round_obj.grand_prize_address = grand_prize_address.unwrap(); + } + if unclaimed_distribution.is_some() { + round_obj.unclaimed_distribution = unclaimed_distribution.unwrap(); + } + + round_helper_store(deps.storage, &round_obj)?; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::UpdateRound { status: Success })?)) +} + +/// Returns StdResult +/// +/// PoolParty community can request to withdraw the funds from the contract. +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a mutable reference to the Config +/// * `request_withdraw_amount` - amount requested to withdraw +fn try_request_reserves_withdraw( + deps: DepsMut, + info: MessageInfo, + config: &mut ConfigInfo, + request_withdraw_amount: Uint128, +) -> StdResult { + //Loading Structs + let _ = check_if_admin(&config, &info.sender)?; + let mut pool_state = pool_state_helper_read_only(deps.storage)?; + + // Checking if requested amount is more than amount available + if pool_state.total_reserves < request_withdraw_amount { + return Err(StdError::generic_err(format!( + "Insufficient funds to redeem: balance={}, required={}", + pool_state.total_reserves, request_withdraw_amount + ))); + } + if request_withdraw_amount == Uint128::zero() { + return Err(StdError::generic_err(format!( + "Cannot withdraw 0 {}", + config.denom + ))); + } + // Putting in request withdraw + // pool_state + // .total_reserves + // .sub_assign(request_withdraw_amount); + + pool_state.total_reserves = if let Ok(t_r) = pool_state + .total_reserves + .checked_sub(request_withdraw_amount) + { + t_r + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + + //STORING USER UNBONDING + if !pool_state + .unbonding_batches + .contains(&config.next_unbonding_batch_index) + { + pool_state + .unbonding_batches + .push(config.next_unbonding_batch_index); + } + + let mut unbonding_amount = + admin_unbond_helper_read_only(deps.storage, config.next_unbonding_batch_index)?; + + unbonding_amount.add_assign(request_withdraw_amount); + + admin_unbond_helper_store( + deps.storage, + config.next_unbonding_batch_index, + unbonding_amount, + )?; + + //Asking the validator to undelegate the funds when unbond the batch + pool_state_helper_store(deps.storage, &pool_state)?; + config + .next_unbonding_batch_amount + .add_assign(request_withdraw_amount); + config_helper_store(deps.storage, &config)?; + + let res = Response::new().set_data(to_binary(&HandleAnswer::RequestAdminWithdraw { + status: Success, + })?); + + Ok(res) +} + +/// Returns StdResult +/// +/// GalacticPools community withdraw their requested funds. It take 21 days to withdraw the funds after the request is made. +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a mutable reference to the Config +/// * `withdraw_amount` - amount to withdraw +fn try_reserve_withdraw( + deps: DepsMut, + env: Env, + info: MessageInfo, + config: &mut ConfigInfo, + withdraw_amount: Uint128, +) -> StdResult { + //loading Data from storage + let mut pool_state_obj: PoolState = pool_state_helper_read_only(deps.storage)?; + let _ = check_if_admin(&config, &info.sender)?; + + let mut admin_withdraw_obj = admin_withdraw_helper_read_only(deps.storage)?; + + //Checking amount available for withdraw + let mut amount_av_for_withdraw = Uint128::zero(); + + //STORING USER UNBONDING + + let mut pop_front_counter: Vec = vec![]; + + for i in 0..pool_state_obj.unbonding_batches.len() { + let unbond_batch_index = pool_state_obj.unbonding_batches[i]; + let unbonding_batch_obj = + unbonding_batch_helper_read_only(deps.storage, unbond_batch_index)?; + + if unbonding_batch_obj.unbonding_time.is_some() { + if env.block.time.seconds() >= unbonding_batch_obj.unbonding_time.unwrap() { + let unbonding_amount = + admin_unbond_helper_read_only(deps.storage, unbond_batch_index)?; + + amount_av_for_withdraw.add_assign(unbonding_amount); + pop_front_counter.push(unbond_batch_index); + } + } + } + + pool_state_obj + .unbonding_batches + .retain(|val| !pop_front_counter.contains(val)); + + //ERROR Check + if withdraw_amount > admin_withdraw_obj.add(amount_av_for_withdraw) { + return Err(StdError::generic_err( + "Trying to withdraw more than available", + )); + } + + //Updating user and pool state + pool_state_helper_store(deps.storage, &pool_state_obj)?; + + admin_withdraw_obj.add_assign(amount_av_for_withdraw); + // admin_withdraw_obj.sub_assign(withdraw_amount); + admin_withdraw_obj = if let Ok(a_w_o) = admin_withdraw_obj.checked_sub(withdraw_amount) { + a_w_o + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + admin_withdraw_helper_store(deps.storage, &admin_withdraw_obj)?; + + let mut messages: Vec = vec![]; + let withdraw_coins: Vec = vec![Coin { + denom: config.denom.to_string(), + amount: withdraw_amount, + }]; + //Sending a message to withdraw + let round_obj = round_helper_read_only(deps.storage)?; + + if withdraw_amount > Uint128::zero() { + messages.push(CosmosMsg::Bank(BankMsg::Send { + to_address: (&round_obj.grand_prize_address).to_string(), + amount: withdraw_coins, + })); + } + + let res = Response::new().add_messages(messages).set_data(to_binary( + &HandleAnswer::ReservesWithdraw { status: Success }, + )?); + + Ok(res) +} + +/// Returns StdResult +/// +/// User unbonding requests are done in batches since only 7 unbondings are allowed to a single address at an instance. +/// It take 21 days to withdraw the funds after the request is made. +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a mutable reference to the Config +fn try_unbond_batch( + deps: DepsMut, + env: Env, + info: MessageInfo, + config: &mut ConfigInfo, +) -> StdResult { + check_if_triggerer(&config, &info.sender)?; + //Loading data from storage + let mut pool_state = pool_state_helper_read_only(deps.storage)?; + + //Checking if triggerer can batch unbond + if env.block.time.seconds() < config.next_unbonding_batch_time { + return Err(StdError::generic_err(format!( + "Cannot unbond right now. You can unbond at {}", + config.next_unbonding_batch_time + ))); + } + + if config.next_unbonding_batch_amount == Uint128::zero() { + config.next_unbonding_batch_index.add_assign(1u64); + config.next_unbonding_batch_time = env + .block + .time + .seconds() + .add(config.unbonding_batch_duration); + config_helper_store(deps.storage, &config)?; + + let res = + Response::new().set_data(to_binary(&HandleAnswer::UnbondBatch { status: Success })?); + + return Ok(res); + } + + //Creating undelegate messages from validators list + let mut remaining_withdraw_amount = config.next_unbonding_batch_amount; + let mut validators_used: Vec = Vec::new(); + let mut messages: Vec = vec![]; + + for _ in 0..config.validators.len() { + let index = config.next_validator_for_unbonding as usize; + let withdraw_amount: Uint128; + if config.validators[index].delegated.u128() >= remaining_withdraw_amount.u128() { + withdraw_amount = remaining_withdraw_amount; + } else { + withdraw_amount = config.validators[index].delegated; + } + + //Unbonding message + if withdraw_amount > Uint128::zero() { + config.validators[index].delegated = if let Ok(del) = config.validators[index] + .delegated + .checked_sub(withdraw_amount) + { + del + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + remaining_withdraw_amount = + if let Ok(r_w_a) = remaining_withdraw_amount.checked_sub(withdraw_amount) { + r_w_a + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + validators_used.push(config.validators[index].clone()); + + messages.push(undelegate( + &config.validators[index].address, + withdraw_amount, + &config.denom, + )); + if index == config.validators.len() - 1 { + config.next_validator_for_unbonding = 0; + } else { + config.next_validator_for_unbonding += 1; + } + } + + if remaining_withdraw_amount.is_zero() { + break; + } + } + + //Querying pending_rewards send back from validator + let rewards = get_rewards(deps.as_ref(), &env.contract.address, &config)?; + let mut rewards_amount = Uint128::zero(); + for val in &validators_used { + for reward in &rewards { + if val.address.as_str().eq(reward.validator_address.as_str()) { + rewards_amount.add_assign(reward.reward); + } + } + } + + //Adding pending_rewards to total rewards returned + if rewards_amount > Uint128::zero() { + pool_state + .rewards_returned_to_contract + .add_assign(rewards_amount); + } + pool_state_helper_store(deps.storage, &pool_state)?; + + //Storing this_unbonding_batch + let unbonding_batch_obj = UnbondingBatch { + unbonding_time: Some(env.block.time.seconds().add(config.unbonding_duration)), + amount: Some(config.next_unbonding_batch_amount), + }; + unbonding_batch_helper_store( + deps.storage, + config.next_unbonding_batch_index, + &unbonding_batch_obj, + )?; + + config.next_unbonding_batch_amount = Uint128::zero(); + config.next_unbonding_batch_index.add_assign(1u64); + config.next_unbonding_batch_time = env + .block + .time + .seconds() + .add(config.unbonding_batch_duration); + config_helper_store(deps.storage, &config)?; + + let res = Response::new() + .add_messages(messages) + .set_data(to_binary(&HandleAnswer::UnbondBatch { status: Success })?); + + Ok(res) +} + +/// Returns StdResult +/// +/// Helps rebalance amount delegated to the validators +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a reference to the Config +fn try_rebalance_validator_set( + deps: DepsMut, + env: Env, + info: MessageInfo, + config: &mut ConfigInfo, +) -> StdResult { + let mut pool_state = pool_state_helper_read_only(deps.storage)?; + check_if_admin(&config, &info.sender)?; + + //Re-balance + //Create a combined vector + + let mut messages: Vec = vec![]; + let mut excess: Vec<(String, Uint128)> = Vec::new(); + let mut less: Vec<(String, Uint128)> = Vec::new(); + let mut rewards_obj = get_rewards(deps.as_ref(), &env.contract.address, &config).unwrap(); + + //Separating validators with more than ideal and less than ideal % of amount. + for validator in &mut config.validators { + let ideal_delegated_amount = pool_state + .total_delegated + .multiply_ratio(validator.weightage, config.common_divisor); + if validator.delegated > ideal_delegated_amount { + let difference = + if let Ok(diff) = validator.delegated.checked_sub(ideal_delegated_amount) { + diff + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + validator.delegated = if let Ok(del) = validator.delegated.checked_sub(difference) { + del + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + + let per_filled: u64; + if validator.weightage == 0 { + per_filled = 0; + } else { + per_filled = ((validator + .delegated + .multiply_ratio(config.common_divisor, 1u128)) + .multiply_ratio( + 1u128, + pool_state + .total_delegated + .add(pool_state.total_sponsored) + .add(pool_state.total_reserves) + .multiply_ratio(validator.weightage as u128, config.common_divisor as u128) + .u128(), + )) + .u128() as u64; + } + + validator.percentage_filled = per_filled; + excess.push((validator.address.as_mut().into(), difference)); + } else if validator.delegated == ideal_delegated_amount { + //Do nothing + } else { + let difference = + if let Ok(diff) = ideal_delegated_amount.checked_sub(validator.delegated) { + diff + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + + less.push((validator.address.as_mut().into(), difference)); + } + } + + for mut excess_val in &mut excess { + for less_val in &mut less { + //restake and update the total_delegated and percentage_filled + let restake_amount: Uint128; + //Calculating amount to restake + if less_val.1 <= excess_val.1 { + restake_amount = less_val.1; + } else { + restake_amount = excess_val.1; + } + excess_val.1 = if let Ok(e_v) = excess_val.1.checked_sub(restake_amount) { + e_v + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + less_val.1 = if let Ok(l_v) = less_val.1.checked_sub(restake_amount) { + l_v + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + if restake_amount > Uint128::zero() { + messages.push(redelegate( + &excess_val.0, + &less_val.0, + restake_amount, + &config.denom, + )); + } + + for r in &mut rewards_obj { + if r.reward.u128() > 0_u128 { + if r.validator_address.as_str() == excess_val.0.as_str() { + pool_state.rewards_returned_to_contract.add_assign(r.reward); + r.reward = Uint128::zero(); + } + if r.validator_address.as_str() == less_val.0.as_str() { + pool_state.rewards_returned_to_contract.add_assign(r.reward); + r.reward = Uint128::zero(); + } + } + } + + //Update the total_staking and percentage_filled + for validator in &mut config.validators { + if validator.address.as_str() == less_val.0.as_str() { + validator.delegated.add_assign(restake_amount); + + let per_filled; + if validator.weightage == 0 { + per_filled = 0; + } else { + if pool_state + .total_delegated + .add(pool_state.total_sponsored) + .add(pool_state.total_reserves) + .u128() + > 0 + { + per_filled = ((validator + .delegated + .multiply_ratio(config.common_divisor, 1u128)) + .multiply_ratio( + 1u128, + pool_state + .total_delegated + .add(pool_state.total_sponsored) + .add(pool_state.total_reserves) + .multiply_ratio( + validator.weightage as u128, + config.common_divisor as u128, + ) + .u128(), + )) + .u128() as u64; + } else { + per_filled = 0u64; + } + } + validator.percentage_filled = per_filled; + break; + } + } + if excess_val.1.is_zero() { + break; + } + } + } + + config.next_validator_for_delegation = 0; + config.next_validator_for_unbonding = 0; + config_helper_store(deps.storage, &config)?; + pool_state_helper_store(deps.storage, &pool_state)?; + + let res = Response::new().set_data(to_binary(&HandleAnswer::RebalanceValidatorSet { + status: Success, + })?); + + Ok(res) +} + +/// Returns StdResult +/// +/// Helps add, remove new validators. Plus options to change the % weight of the validators. +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `env` - Env of contract's environment +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a reference to the Config +/// * `updated_val_set` - updated validator set with removed validators % weightage set to zero. +fn try_update_validator_set( + deps: DepsMut, + env: Env, + info: MessageInfo, + config: &mut ConfigInfo, + updated_val_set: Vec, +) -> StdResult { + let mut pool_state = pool_state_helper_read_only(deps.storage)?; + check_if_admin(&config, &info.sender)?; + + //Re-balance + let mut final_validator_set: Vec = Vec::new(); + + //Create a combined vector + + for local_val in updated_val_set { + //Get total delegated + let index = config + .validators + .iter() + .position(|val| val.address.as_str().eq(local_val.address.as_str())); + if index.is_some() { + let per_filled: u64; + if local_val.weightage == 0 { + per_filled = 0; + } else { + per_filled = ((config.validators[index.unwrap()] + .delegated + .multiply_ratio(config.common_divisor, 1u128)) + .multiply_ratio( + 1u128, + pool_state + .total_delegated + .add(pool_state.total_sponsored) + .multiply_ratio(local_val.weightage as u128, config.common_divisor as u128) + .u128(), + )) + .u128() as u64; + } + + final_validator_set.push(Validator { + address: local_val.address, + delegated: config.validators[index.unwrap()].delegated, + weightage: local_val.weightage, + percentage_filled: per_filled, + }) + } else { + final_validator_set.push(Validator { + address: local_val.address, + delegated: Uint128::zero(), + weightage: local_val.weightage, + percentage_filled: 0, + }); + } + } + + let mut messages: Vec = vec![]; + let mut excess: Vec<(String, Uint128)> = Vec::new(); + let mut less: Vec<(String, Uint128)> = Vec::new(); + let mut rewards_obj = get_rewards(deps.as_ref(), &env.contract.address, &config).unwrap(); + + //Separating validators with more than ideal and less than ideal % of amount. + for val in &mut final_validator_set { + let ideal_delegated_amount = pool_state + .total_delegated + .multiply_ratio(val.weightage, config.common_divisor); + if val.delegated > ideal_delegated_amount { + let difference = if let Ok(diff) = val.delegated.checked_sub(ideal_delegated_amount) { + diff + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + val.delegated = if let Ok(del) = val.delegated.checked_sub(difference) { + del + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + + let per_filled: u64; + if val.weightage == 0 { + per_filled = 0; + } else { + per_filled = ((val.delegated.multiply_ratio(config.common_divisor, 1u128)) + .multiply_ratio( + 1u128, + pool_state + .total_delegated + .add(pool_state.total_sponsored) + .add(pool_state.total_reserves) + .multiply_ratio(val.weightage as u128, config.common_divisor as u128) + .u128(), + )) + .u128() as u64; + } + + val.percentage_filled = per_filled; + excess.push((val.address.as_mut().into(), difference)); + } else if val.delegated == ideal_delegated_amount { + //Do nothing + } else { + let difference = if let Ok(del) = ideal_delegated_amount.checked_sub(val.delegated) { + del + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + less.push((val.address.as_mut().into(), difference)); + } + } + + for mut excess_val in &mut excess { + for less_val in &mut less { + //restake and update the total_delegated and percentage_filled + let restake_amount: Uint128; + //Calculating amount to restake + if less_val.1 <= excess_val.1 { + restake_amount = less_val.1; + } else { + restake_amount = excess_val.1; + } + excess_val.1 = if let Ok(e_v) = excess_val.1.checked_sub(restake_amount) { + e_v + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + less_val.1 = if let Ok(l_v) = less_val.1.checked_sub(restake_amount) { + l_v + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + messages.push(redelegate( + &excess_val.0, + &less_val.0, + restake_amount, + &config.denom, + )); + + for r in &mut rewards_obj { + if r.reward.u128() > 0 { + if r.validator_address.as_str() == excess_val.0.as_str() { + pool_state.rewards_returned_to_contract.add_assign(r.reward); + r.reward = Uint128::zero(); + } + if r.validator_address.as_str() == less_val.0.as_str() { + pool_state.rewards_returned_to_contract.add_assign(r.reward); + r.reward = Uint128::zero(); + } + } + } + + //Update the total_staking and percentage_filled + for validator in &mut final_validator_set { + if validator.address.as_str() == less_val.0.as_str() { + validator.delegated.add_assign(restake_amount); + + let per_filled; + if validator.weightage == 0 { + per_filled = 0; + } else { + if pool_state + .total_delegated + .add(pool_state.total_sponsored) + .add(pool_state.total_reserves) + .u128() + > 0 + { + per_filled = ((validator + .delegated + .multiply_ratio(config.common_divisor, 1u128)) + .multiply_ratio( + 1u128, + pool_state + .total_delegated + .add(pool_state.total_sponsored) + .add(pool_state.total_reserves) + .multiply_ratio( + validator.weightage as u128, + config.common_divisor as u128, + ) + .u128(), + )) + .u128() as u64; + } else { + per_filled = 0u64; + } + } + validator.percentage_filled = per_filled; + break; + } + } + if excess_val.1.is_zero() { + break; + } + } + } + + final_validator_set.retain(|v| v.weightage != 0); + + config.validators = final_validator_set; + config.next_validator_for_delegation = 0; + config.next_validator_for_unbonding = 0; + config_helper_store(deps.storage, &config)?; + pool_state_helper_store(deps.storage, &pool_state)?; + + let res = Response::new().set_data(to_binary(&HandleAnswer::UpdateValidatorSet { + status: Success, + })?); + + Ok(res) +} + +/// Returns StdResult +/// +/// set the contract status level +/// +/// # Arguments +/// +/// * `deps` - mutable reference to Extern containing all the contract's external dependencies +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a mutable reference to the Config +/// * `level` - new ContractStatus +fn try_set_contract_status( + deps: DepsMut, + info: MessageInfo, + config: &mut ConfigInfo, + level: ContractStatus, +) -> StdResult { + let _ = check_if_admin(&config, &info.sender)?; + let new_status = level.to_u8(); + if config.status != new_status { + config.status = new_status; + config_helper_store(deps.storage, &config)?; + } + Ok( + Response::new().set_data(to_binary(&HandleAnswer::SetContractStatus { + status: Success, + })?), + ) +} + +///////////////////////////////////////// Helper for Handle Function ////////////////////////////////////// +/// Returns StdResult<()> that will error if the priority level of the action is not +/// equal to or greater than the current contract status level +/// +/// # Arguments +/// +/// * `contract_status` - u8 representation of the current contract status +/// * `priority` - u8 representing the highest status level this action may execute at +fn check_status(contract_status: u8, priority: u8) -> StdResult<()> { + if priority < contract_status { + return Err(StdError::generic_err( + "The contract admin has temporarily disabled this action", + )); + } + Ok(()) +} + +/// Returns StdResult<()> +/// +/// Checks if the 'account' send is admin address or not +/// +/// # Arguments +/// +/// +/// * `config` - a reference to the Config +/// * `account` - the account address to check +fn check_if_admin(config: &ConfigInfo, account: &Addr) -> StdResult<()> { + if config.admins.contains(account) == false { + return Err(StdError::generic_err( + "This is an admin command. Admin commands can only be run from admin address", + )); + } + + Ok(()) +} + +/// Returns StdResult<()> +/// +/// Checks if the 'account' send is triggerer address or not +/// +/// # Arguments +/// +/// +/// * `config` - a reference to the Config +/// * `account` - the account address to check +fn check_if_triggerer(config: &ConfigInfo, account: &Addr) -> StdResult<()> { + if !&config.triggerers.contains(account) { + return Err(StdError::generic_err( + "This is an triggerer command. Triggerer commands can only be run from triggerer address", + )); + } + + Ok(()) +} + +/// Returns StdResult<()> +/// +/// Checks if the 'account' send is triggerer address or not +/// +/// # Arguments +/// +/// +/// * `config` - a reference to the Config +/// * `account` - the account address to check +fn check_if_reviewer(config: &ConfigInfo, account: &Addr) -> StdResult<()> { + if !&config.reviewers.contains(account) { + return Err(StdError::generic_err( + "This is an reviewer command. Reviewer commands can only be run from reviewer address", + )); + } + + Ok(()) +} + +/// Returns StdResult<()> +/// +/// Checks if current round can be ended. validate_end_time returns an error if the round ends in the future +/// +/// # Arguments +/// +/// +/// * `end_time` - a reference to the Config +/// * `current_time` - the account address to check +fn validate_end_time(end_time: u64, current_time: u64) -> StdResult<()> { + if current_time < end_time { + Err(StdError::generic_err("Round end time is in the future")) + } else { + Ok(()) + } +} + +/// Returns StdResult +/// +/// Checks if the deposit amount deposited is greater than the minimum deposit amount, if their is any minimum deposit amount. +/// +/// # Arguments +/// +/// * `info` - It contains the essential info for authorization - identity of the call, and payment. +/// * `config` - a reference to the Config +fn check_if_valid_amount(info: &MessageInfo, config: &ConfigInfo) -> StdResult { + let mut deposit_amount = Uint128::zero(); + + for coin in &info.funds { + if coin.denom == config.denom { + deposit_amount = coin.amount + } else { + return Err(StdError::generic_err(format!( + "Wrong token given, expected {} found {}", + config.denom, coin.denom + ))); + } + } + + if config.minimum_deposit_amount.is_some() { + if deposit_amount < config.minimum_deposit_amount.unwrap() { + return Err(StdError::generic_err(format!( + "Must deposit a minimum of {} {}", + config.minimum_deposit_amount.unwrap(), + config.denom + ))); + } + } + + if deposit_amount == Uint128::zero() { + return Err(StdError::generic_err(format!( + "Must deposit atleast one {}", + config.denom + ))); + } + + return Ok(deposit_amount); +} + +/// Returns StdResult<()> that will error if the starting round and current_round_index are same. +/// +/// # Arguments +/// +/// * `starting_round` - u64 +/// * `current_round_index` - u64 +fn check_if_claimable(starting_round: Option, current_round_index: u64) -> StdResult<()> { + if starting_round.is_none() { + return Err(StdError::generic_err(format!( + "You have not deposited to the pook contract. No tickets found" + ))); + } else { + if starting_round.unwrap() == current_round_index { + return Err(StdError::generic_err(format!( + "You are not yet able to claim rewards. Wait for this round to end" + ))); + } + } + Ok(()) +} + +/// Returns RewardStatePerTier for a each of the 6 tiers. Tier 0 is the highest tier. and Tier 5 is the lowest tier. +/// +/// Helper function used by EndRound Handle - helps calculate the rewards distribution for a given winning amount per tier this round. +/// +/// # Arguments +/// +/// * `winning_amount` - The total winning amount for this round. +/// * `rewards_distribution_obj` - reference to the rewards distribution object specified in the round config +/// * `config` - a reference to the Config +fn reward_distribution_per_tier_helper( + winning_amount: Uint128, + rewards_distribution_obj: &RewardsDistInfo, + config: &ConfigInfo, +) -> StdResult { + let tier_1_total_rewards = winning_amount.multiply_ratio( + rewards_distribution_obj.tier_1.percentage_of_rewards, + config.common_divisor, + ); + let tier_1_reward_per_match = tier_1_total_rewards.multiply_ratio( + 1u64, + rewards_distribution_obj.tier_1.total_number_of_winners, + ); + + let tier_2_total_rewards = winning_amount.multiply_ratio( + rewards_distribution_obj.tier_2.percentage_of_rewards, + config.common_divisor, + ); + let tier_2_reward_per_match = tier_2_total_rewards.multiply_ratio( + 1u64, + rewards_distribution_obj.tier_2.total_number_of_winners, + ); + + let tier_3_total_rewards = winning_amount.multiply_ratio( + rewards_distribution_obj.tier_3.percentage_of_rewards, + config.common_divisor, + ); + let tier_3_reward_per_match = tier_3_total_rewards.multiply_ratio( + 1u64, + rewards_distribution_obj.tier_3.total_number_of_winners, + ); + + let tier_4_total_rewards = winning_amount.multiply_ratio( + rewards_distribution_obj.tier_4.percentage_of_rewards, + config.common_divisor, + ); + let tier_4_reward_per_match = tier_4_total_rewards.multiply_ratio( + 1u64, + rewards_distribution_obj.tier_4.total_number_of_winners, + ); + + let tier_5_total_rewards = winning_amount.multiply_ratio( + rewards_distribution_obj.tier_5.percentage_of_rewards, + config.common_divisor, + ); + let tier_5_reward_per_match = tier_5_total_rewards.multiply_ratio( + 1u64, + rewards_distribution_obj.tier_5.total_number_of_winners, + ); + + let tier_1_2_3_4_5_total_rewards = tier_1_reward_per_match + .multiply_ratio( + rewards_distribution_obj.tier_1.total_number_of_winners, + 1u128, + ) + .add(tier_2_reward_per_match.multiply_ratio( + rewards_distribution_obj.tier_2.total_number_of_winners, + 1u128, + )) + .add(tier_3_reward_per_match.multiply_ratio( + rewards_distribution_obj.tier_3.total_number_of_winners, + 1u128, + )) + .add(tier_4_reward_per_match.multiply_ratio( + rewards_distribution_obj.tier_4.total_number_of_winners, + 1u128, + )) + .add(tier_5_reward_per_match.multiply_ratio( + rewards_distribution_obj.tier_5.total_number_of_winners, + 1u128, + )); + + let tier_0_total_rewards = + if let Ok(w_am) = winning_amount.checked_sub(tier_1_2_3_4_5_total_rewards) { + w_am + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + let tier_0_reward_per_match = tier_0_total_rewards.multiply_ratio( + 1u64, + rewards_distribution_obj.tier_0.total_number_of_winners, + ); + + let tier_state = TierState { + tier_0: RewardsClaimed { + claimed: RewardsPerTierInfo { + num_of_rewards_claimed: Uint128::zero(), + reward_per_match: tier_0_reward_per_match, + }, + num_of_rewards: rewards_distribution_obj.tier_0.total_number_of_winners, + }, + tier_1: RewardsClaimed { + claimed: RewardsPerTierInfo { + num_of_rewards_claimed: Uint128::zero(), + reward_per_match: tier_1_reward_per_match, + }, + + num_of_rewards: rewards_distribution_obj.tier_1.total_number_of_winners, + }, + tier_2: RewardsClaimed { + claimed: RewardsPerTierInfo { + num_of_rewards_claimed: Uint128::zero(), + reward_per_match: tier_2_reward_per_match, + }, + + num_of_rewards: rewards_distribution_obj.tier_2.total_number_of_winners, + }, + tier_3: RewardsClaimed { + claimed: RewardsPerTierInfo { + num_of_rewards_claimed: Uint128::zero(), + reward_per_match: tier_3_reward_per_match, + }, + + num_of_rewards: rewards_distribution_obj.tier_3.total_number_of_winners, + }, + tier_4: RewardsClaimed { + claimed: RewardsPerTierInfo { + num_of_rewards_claimed: Uint128::zero(), + reward_per_match: tier_4_reward_per_match, + }, + + num_of_rewards: rewards_distribution_obj.tier_4.total_number_of_winners, + }, + tier_5: RewardsClaimed { + claimed: RewardsPerTierInfo { + num_of_rewards_claimed: Uint128::zero(), + reward_per_match: tier_5_reward_per_match, + }, + + num_of_rewards: rewards_distribution_obj.tier_5.total_number_of_winners, + }, + }; + Ok(tier_state) +} + +////////////////////////////////////// Queries /////////////////////////////////////// + +/// Returns ContractStatusResponse displaying the contract's status +/// +/// # Arguments +/// +/// * `Deps` - a reference to the contract's storage +fn query_contract_status(deps: Deps) -> StdResult { + let config = config_helper_read_only(deps.storage)?; + let i = config.status; + let item = match i { + 0 => ContractStatus::Normal, + 1 => ContractStatus::StopTransactions, + 2 => ContractStatus::StopAll, + _ => return Err(StdError::generic_err("Wrong status")), + }; + Ok(ContractStatusResponse { status: item }) +} + +/// Returns ContractConfigResponse displaying the contract's configuration +/// +/// # Arguments +/// +/// * `Deps` - a reference to the contract's storage +fn query_config(deps: Deps) -> StdResult { + let config = config_helper_read_only(deps.storage)?; + + let mut admins = Vec::new(); + for admin in config.admins { + admins.push(admin); + } + + let mut triggerers = Vec::new(); + for triggerer in config.triggerers { + triggerers.push(triggerer); + } + + let mut reviewers = Vec::new(); + for reviewer in config.reviewers { + reviewers.push(reviewer); + } + + let mut exp_contract = None; + + if let Some(exp_con) = config.exp_contract { + exp_contract = Some(exp_con.contract); + } + + Ok(ContractConfigResponse { + admins, + triggerers, + reviewers, + denom: config.denom, + contract_address: config.contract_address, + validators: config.validators, + next_unbonding_batch_time: config.next_unbonding_batch_time, + next_unbonding_batch_amount: config.next_unbonding_batch_amount, + unbonding_batch_duration: config.unbonding_batch_duration, + unbonding_duration: config.unbonding_duration, + minimum_deposit_amount: config.minimum_deposit_amount, + exp_contract, + }) +} + +/// Returns RoundResponse displaying round's configuration +/// +/// # Arguments +/// +/// * `deps` - a reference to Extern containing all the contract's external dependencies +fn query_round(deps: Deps) -> StdResult { + let round_obj = round_helper_read_only(deps.storage)?; + + Ok(RoundResponse { + duration: round_obj.duration, + start_time: round_obj.start_time, + end_time: round_obj.end_time, + rewards_distribution: round_obj.rewards_distribution, + current_round_index: round_obj.current_round_index, + ticket_price: round_obj.ticket_price, + rewards_expiry_duration: round_obj.rewards_expiry_duration, + admin_share: round_obj.admin_share, + triggerer_share_percentage: round_obj.triggerer_share_percentage, + unclaimed_distribution: round_obj.unclaimed_distribution, + }) +} + +/// Returns PoolStateInfoResponse total amount delegated to this contract +/// +/// # Arguments +/// +/// * `deps` - a reference to Extern containing all the contract's external dependencies +fn query_pool_state_info(deps: Deps) -> StdResult { + let pool_state_obj = pool_state_helper_read_only(deps.storage)?; + + Ok(PoolStateInfoResponse { + total_delegated: pool_state_obj.total_delegated, + rewards_returned_to_contract: pool_state_obj.rewards_returned_to_contract, + total_reserves: pool_state_obj.total_reserves, + total_sponsored: pool_state_obj.total_sponsored, + }) +} + +/// Returns PoolStateInfoResponse total amount delegated to this contract +/// +/// # Arguments +/// +/// * `deps` - a reference to Extern containing all the contract's external dependencies +fn query_reward_stats(deps: Deps) -> StdResult { + let lotter_config = round_helper_read_only(deps.storage)?; + let round_index = if let Some(r_i) = lotter_config.current_round_index.checked_sub(1) { + r_i + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + let reward_stats = reward_stats_for_nth_round_helper_read_only(deps.storage, round_index)?; + + Ok(RewardStatsResponse { + distribution_per_tiers: reward_stats.distribution_per_tiers, + ticket_price: reward_stats.ticket_price, + winning_sequence: reward_stats.winning_sequence, + rewards_expiration_date: reward_stats.rewards_expiration_date, + total_rewards: reward_stats.total_rewards, + total_claimed: reward_stats.total_claimed, + total_exp: reward_stats.total_exp, + total_exp_claimed: reward_stats.total_exp_claimed, + }) +} + +/// Returns PoolStateLiquidityStatsResponse total liquidity provided this current round +/// +/// # Arguments +/// +/// * `deps` - a reference to Extern containing all the contract's external dependencies +fn query_pool_state_liquidity_stats(deps: Deps) -> StdResult { + let round_obj = round_helper_read_only(deps.storage)?; + let pool_state_liquidity_snapshot_obj: PoolLiqState = + pool_state_liquidity_helper_read_only(deps.storage, round_obj.current_round_index)?; + let liquidity: Uint128; + if pool_state_liquidity_snapshot_obj.total_liquidity.is_none() { + let pool_state_obj = pool_state_helper_read_only(deps.storage)?; + liquidity = pool_state_obj.total_delegated; + } else { + liquidity = pool_state_liquidity_snapshot_obj.total_liquidity.unwrap(); + } + + Ok(PoolStateLiquidityStatsResponse { + total_liquidity: liquidity, + }) +} + +/// Returns PoolStateLiquidityStatsResponse total liquidity provided for a specific past round +/// +/// # Arguments +/// +/// * `deps` - a reference to Extern containing all the contract's external dependencies +fn query_pool_state_liquidity_stats_specific( + deps: Deps, + round_index: u64, +) -> StdResult { + let pool_state_liquidity_snapshot_obj: PoolLiqState = + pool_state_liquidity_helper_read_only(deps.storage, round_index)?; + let liquidity: Uint128; + if pool_state_liquidity_snapshot_obj.total_liquidity.is_none() { + let pool_state_obj = pool_state_helper_read_only(deps.storage)?; + liquidity = pool_state_obj.total_delegated; + } else { + liquidity = pool_state_liquidity_snapshot_obj.total_liquidity.unwrap(); + } + + Ok(PoolStateLiquidityStatsResponse { + total_liquidity: liquidity, + }) +} + +/// Returns TotalRewardsResponse displaying current rewards +/// +/// # Arguments +/// +/// * `deps` - a reference to Extern containing all the contract's external dependencies +/// * `env` - Env of contract's environment +fn query_current_rewards(deps: Deps, env: Env) -> StdResult { + let pool_state_obj = pool_state_helper_read_only(deps.storage)?; + let config_obj = config_helper_read_only(deps.storage)?; + + let rewards_obj = get_rewards(deps, &env.contract.address, &config_obj).unwrap(); + let mut total_rewards: Uint128 = pool_state_obj.rewards_returned_to_contract; + for reward in rewards_obj { + total_rewards.add_assign(reward.reward); + } + + Ok(CurrentRewardsResponse { + rewards: total_rewards, + }) +} + +/// Returns SponsorMessageRequestResponse all message request from the users +/// +/// # Arguments +/// +/// * `deps` - a reference to Extern containing all the contract's external dependencies +/// * `address` address of the user querying this +/// * `start_page` starting page +/// * `page_size` page size +fn query_sponsor_message_req_check( + deps: Deps, + start_page: Option, + page_size: Option, +) -> StdResult { + let binding = SPONSOR_DISPLAY_REQ_STORE; + let mut vec = binding.paging( + deps.storage, + start_page.unwrap_or(0), + page_size.unwrap_or(5), + )?; + + let starting_offset = start_page.unwrap_or(0) * page_size.unwrap_or(5); + for (i, item) in vec.iter_mut().enumerate() { + item.deque_store_index = Some((i as u32) + (starting_offset)); + } + + let len = binding.get_len(deps.storage)?; + + Ok(SponsorMessageRequestResponse { vec, len }) +} + +/// Returns SponsorsResponse information of all the sponsors in the global sponsors list +/// +/// # Arguments +/// +/// * `deps` - a reference to Extern containing all the contract's external dependencies +/// * `address` address of the user querying this +/// * `start_page` starting page +/// * `page_size` page size +fn query_sponsors( + deps: Deps, + start_page: Option, + page_size: Option, +) -> StdResult { + let sponsor_stats = sponsor_stats_helper_read_only(deps.storage)?; + let mut vec = vec![]; + let start_offset = start_page.unwrap_or(0) * page_size.unwrap_or(5); + let end_offset = + if sponsor_stats.offset < ((start_page.unwrap_or(0) + 1) * page_size.unwrap_or(5)) { + sponsor_stats.offset + } else { + (start_page.unwrap_or(0) + 1) * page_size.unwrap_or(5) + }; + + let length = end_offset - start_offset; + let mut index = 0; + let mut offset = 0; + for _ in 0..length { + loop { + if index > sponsor_stats.offset { + break; + } + + if offset < start_offset { + let does_exists = SPONSOR_LIST_STORE.has(deps.storage, index); + + if does_exists { + offset += 1; + } + index += 1; + } else { + let value = SPONSOR_LIST_STORE.load(deps.storage, index); + index += 1; + + if value.is_ok() { + offset += 1; + + let sponsor_info_obj = + sponsor_info_helper_read_only(deps.storage, &value.unwrap())?; + vec.push(SponsorDisplayInfo { + amount_sponsored: sponsor_info_obj.amount_sponsored, + title: sponsor_info_obj.title, + message: sponsor_info_obj.message, + addr_list_index: sponsor_info_obj.addr_list_index, + }); + break; + } + } + } + } + + let len = if let Some(len) = sponsor_stats + .offset + .checked_sub(sponsor_stats.empty_slots.len() as u32) + { + len + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + + Ok(SponsorsResponse { vec, len }) +} + +fn authenticated_queries(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + let (addresses, key) = msg.get_validation_params(); + + for address in addresses { + let addr = deps.api.addr_validate(address)?; + + let expected_key = read_viewing_key(deps.storage, &addr); + + if expected_key.is_none() { + // Checking the key will take significant time. We don't want to exit immediately if it isn't set + // in a way which will allow to time the command and determine if a viewing key doesn't exist + key.check_viewing_key(&[0u8; VIEWING_KEY_SIZE]); + } else if key.check_viewing_key(expected_key.unwrap().as_slice()) { + return match msg { + // Base + QueryMsg::Delegated { address, .. } => to_binary(&query_delegated(deps, address)?), + QueryMsg::Withdrawable { address, .. } => { + to_binary(&query_withdrawable(deps, address, env)?) + } + QueryMsg::Unbondings { address, .. } => { + to_binary(&query_unbondings(deps, address)?) + } + QueryMsg::Liquidity { + address, + round_index, + .. + } => to_binary(&query_liquidity(deps, address, round_index)?), + QueryMsg::SponsorInfo { address, .. } => { + to_binary(&query_sponsor_info(deps, address)?) + } + QueryMsg::SponsorUnbondings { address, .. } => { + to_binary(&query_sponsor_unbondings(deps, address)?) + } + QueryMsg::SponsorWithdrawable { address, .. } => { + to_binary(&query_sponsor_withdrawable(deps, address, env)?) + } + QueryMsg::Records { + address, + page_size, + start_page, + .. + } => to_binary(&query_records(deps, address, start_page, page_size)?), + + _ => { + return Err(StdError::generic_err(format!( + "This query type does not require authentication" + ))); + } + }; + } + } + + Ok(to_binary(&ViewingKeyErrorResponse { + msg: "Wrong viewing key for this address or viewing key not set".to_string(), + })?) +} + +/// Returns Binary from validating a permit and then using its creator's address when +/// performing the specified query +/// +/// # Arguments +/// +/// * `deps` - a reference to Extern containing all the contract's external dependencies +/// * `permit` - the permit used to authentic the query +/// * `query` - the query to perform +/// * `env` - Env of contract's environment +fn permit_queries( + deps: Deps, + permit: Permit, + query: QueryWithPermit, + env: Env, +) -> StdResult { + let config = config_helper_read_only(deps.storage)?; + + let token_address = config.contract_address.to_string(); + + //Checking if token is included + let account = validate(deps, PREFIX_REVOKED_PERMITS, &permit, token_address, None)?; + + // permit validated, process query + return match query { + QueryWithPermit::Delegated {} => { + //Checking permissions + if !(permit.check_permission(&GalacticPoolsPermissions::Delegated) + || permit.check_permission(&GalacticPoolsPermissions::Owner)) + { + return Err(StdError::generic_err(format!( + "Owner or Delegated permission is required for queries, got permissions {:?}", + permit.params.permissions + ))); + } + + to_binary(&query_delegated(deps, account)?) + } + QueryWithPermit::UserInfo {} => { + //Checking permissions + if !(permit.check_permission(&GalacticPoolsPermissions::UserInfo) + || permit.check_permission(&GalacticPoolsPermissions::Owner)) + { + return Err(StdError::generic_err(format!( + "Owner or Delegated permission is required for queries, got permissions {:?}", + permit.params.permissions + ))); + } + + to_binary(&query_user_info(deps, account)?) + } + QueryWithPermit::SponsorInfo {} => { + //Checking permissions + if !(permit.check_permission(&GalacticPoolsPermissions::SponsorInfo) + || permit.check_permission(&GalacticPoolsPermissions::Owner)) + { + return Err(StdError::generic_err(format!( + "Owner or Delegated permission is required for queries, got permissions {:?}", + permit.params.permissions + ))); + } + + to_binary(&query_sponsor_info(deps, account)?) + } + QueryWithPermit::SponsorUnbondings {} => { + if !(permit.check_permission(&GalacticPoolsPermissions::SponsorUnbondings) + || permit.check_permission(&GalacticPoolsPermissions::Owner)) + { + return Err(StdError::generic_err(format!( + "Owner or Unbondings permission is required for queries, got permissions {:?}", + permit.params.permissions + ))); + } + to_binary(&query_sponsor_unbondings(deps, account)?) + } + QueryWithPermit::SponsorWithdrawable {} => { + if !(permit.check_permission(&GalacticPoolsPermissions::SponsorWithdrawable) + || permit.check_permission(&GalacticPoolsPermissions::Owner)) + { + return Err(StdError::generic_err(format!( + "Owner or Unbondings permission is required for queries, got permissions {:?}", + permit.params.permissions + ))); + } + to_binary(&query_sponsor_withdrawable(deps, account, env)?) + } + QueryWithPermit::Liquidity { round_index } => { + //Checking permissions + if !(permit.check_permission(&GalacticPoolsPermissions::Liquidity) + || permit.check_permission(&GalacticPoolsPermissions::Owner)) + { + return Err(StdError::generic_err(format!( + "Owner or Liquidity permission is required for queries, got permissions {:?}", + permit.params.permissions + ))); + } + + to_binary(&query_liquidity(deps, account, round_index)?) + } + QueryWithPermit::Withdrawable {} => { + if !(permit.check_permission(&GalacticPoolsPermissions::Withdrawable) + || permit.check_permission(&GalacticPoolsPermissions::Owner)) + { + return Err(StdError::generic_err(format!( + "Owner or Withdrawable permission is required for queries, got permissions {:?}", + permit.params.permissions + ))); + } + to_binary(&query_withdrawable(deps, account, env)?) + } + QueryWithPermit::Unbondings {} => { + if !(permit.check_permission(&GalacticPoolsPermissions::Unbondings) + || permit.check_permission(&GalacticPoolsPermissions::Owner)) + { + return Err(StdError::generic_err(format!( + "Owner or Unbondings permission is required for queries, got permissions {:?}", + permit.params.permissions + ))); + } + to_binary(&query_unbondings(deps, account)?) + } + QueryWithPermit::Records { + page_size, + start_page, + } => { + if !(permit.check_permission(&GalacticPoolsPermissions::Records) + || permit.check_permission(&GalacticPoolsPermissions::Owner)) + { + return Err(StdError::generic_err(format!( + "Owner or Records permission is required for queries, got permissions {:?}", + permit.params.permissions + ))); + } + + to_binary(&query_records(deps, account, start_page, page_size)?) + } + _ => return Err(StdError::generic_err(format!("There is no such query"))), + }; +} + +/// Returns QueryResult displaying total amount delegated by th user +/// +/// # Arguments +/// +/// * `deps` - a reference to Extern containing all the contract's external dependencies +/// * `address` address of the user querying this +fn query_delegated(deps: Deps, address: String) -> StdResult { + let addr = deps.api.addr_validate(address.as_str())?; + let user_info = user_info_helper_read_only(deps.storage, &addr)?; + + Ok(DelegatedResponse { + amount: user_info.amount_delegated, + }) +} + +/// Returns QueryResult displaying total amount liquidity provided by the user +/// +/// # Arguments +/// +/// * `deps` - a reference to Extern containing all the contract's external dependencies +/// * `address` address of the user querying this +fn query_liquidity(deps: Deps, address: String, round_index: u64) -> StdResult { + let addr = deps.api.addr_validate(address.as_str())?; + + let user_liq_obj = + user_liquidity_snapshot_stats_helper_read_only(deps.storage, round_index, &addr)?; + let user_info_obj: UserInfo = user_info_helper_read_only(deps.storage, &addr)?; + let reward_stats = reward_stats_for_nth_round_helper_read_only(deps.storage, round_index)?; + let pool_state_liquidity_snapshot_obj: PoolLiqState = + pool_state_liquidity_helper_read_only(deps.storage, round_index)?; + + //Calculating User Liquidity to generate n tickets for the round + let liquidity_current_round; + let round_obj; + let mut legacy_bal = Uint128::zero(); + let total_liq; + let ticket_price; + let mut total_tickets = Uint128::zero(); + let mut user_tickets = Uint128::zero(); + let mut tickets_used = Uint128::zero(); + + //Ticket Price + if reward_stats.ticket_price.is_zero() { + round_obj = round_helper_read_only(deps.storage)?; + ticket_price = round_obj.ticket_price; + } else { + ticket_price = reward_stats.ticket_price; + } + + // Total Liquidity + if pool_state_liquidity_snapshot_obj.total_liquidity.is_some() { + total_liq = pool_state_liquidity_snapshot_obj.total_liquidity.unwrap(); + } else { + let pool_stats = pool_state_helper_read_only(deps.storage)?; + total_liq = pool_stats.total_delegated; + } + + //Total Tickets + if total_liq > Uint128::zero() { + total_tickets = total_liq.multiply_ratio(1u128, ticket_price) + } + + //User Liquidity + if user_liq_obj.liquidity.is_some() { + liquidity_current_round = user_liq_obj.liquidity.unwrap(); + } else { + let mut finding_liq_round: u64 = if let Some(r_i) = round_index.checked_sub(1) { + r_i + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + + let start = if user_info_obj.last_claim_rewards_round.is_some() { + user_info_obj.last_claim_rewards_round.unwrap() + } else { + if user_info_obj.starting_round.is_some() { + user_info_obj.starting_round.unwrap() + } else { + return Ok(LiquidityResponse { + total_liq, + total_tickets, + ticket_price, + user_liq: Uint128::default(), + user_tickets: Uint128::default(), + tickets_used: Uint128::default(), + expiry_date: reward_stats.rewards_expiration_date, + total_rewards: reward_stats.total_rewards, + unclaimed_rewards: if let Ok(u_r) = reward_stats + .total_rewards + .checked_sub(reward_stats.total_claimed) + { + u_r + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }, + }); + } + }; + while finding_liq_round >= start { + let user_liq_obj_prev_round = user_liquidity_snapshot_stats_helper_read_only( + deps.storage, + finding_liq_round, + &addr, + )?; + if user_liq_obj_prev_round.amount_delegated.is_some() { + legacy_bal = user_liq_obj_prev_round.amount_delegated.unwrap(); + break; + } else { + finding_liq_round = if let Some(f_liq) = finding_liq_round.checked_sub(1) { + f_liq + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }; + } + } + liquidity_current_round = legacy_bal; + } + + if liquidity_current_round > Uint128::zero() { + user_tickets = liquidity_current_round.multiply_ratio(1u128, ticket_price) + } + + if user_liq_obj.tickets_used.is_some() { + tickets_used = user_liq_obj.tickets_used.unwrap() + } + + Ok(LiquidityResponse { + total_liq, + total_tickets, + ticket_price, + user_liq: liquidity_current_round, + user_tickets, + tickets_used, + expiry_date: reward_stats.rewards_expiration_date, + total_rewards: reward_stats.total_rewards, + unclaimed_rewards: if let Ok(u_r) = reward_stats + .total_rewards + .checked_sub(reward_stats.total_claimed) + { + u_r + } else { + return Err(StdError::generic_err("Under-flow sub error")); + }, + }) +} + +/// Returns QueryResult displaying total amount liquidity provided by the user +/// +/// # Arguments +/// +/// * `deps` - a reference to Extern containing all the contract's external dependencies +/// * `address` address of the user querying this +fn query_user_info(deps: Deps, address: String) -> StdResult { + let addr = deps.api.addr_validate(address.as_str())?; + let user_info_obj: UserInfo = user_info_helper_read_only(deps.storage, &addr)?; + + Ok(UserInfoResponse { + amount_delegated: user_info_obj.amount_delegated, + amount_unbonding: user_info_obj.amount_unbonding, + starting_round: user_info_obj.starting_round, + total_won: user_info_obj.total_won, + last_claim_rewards_round: user_info_obj.last_claim_rewards_round, + }) +} + +/// Returns AmountWithdrawablelResponse displaying the amount that can be withdrawn this instant +/// +/// # Arguments +/// +/// * `deps` - a reference to Extern containing all the contract's external dependencies +/// * `address` address of the user querying this +/// * `env` - Env of contract's environment +fn query_withdrawable(deps: Deps, address: String, env: Env) -> StdResult { + let user_info_obj = + user_info_helper_read_only(deps.storage, &deps.api.addr_validate(address.as_str())?)?; + + let mut amount_withdrawable = user_info_obj.amount_withdrawable; + + for i in 0..user_info_obj.unbonding_batches.len() { + let unbonding_batch_index = user_info_obj.unbonding_batches[i]; + let unbonding_batch_obj = + unbonding_batch_helper_read_only(deps.storage, unbonding_batch_index)?; + let unbonding_amount = user_unbond_helper_read_only( + deps.storage, + unbonding_batch_index, + &deps.api.addr_validate(address.as_str())?, + )?; + + // already unbonding + if unbonding_batch_obj.unbonding_time.is_some() { + if env.block.time.seconds() >= unbonding_batch_obj.unbonding_time.unwrap() { + amount_withdrawable.add_assign(unbonding_amount) + } + } + } + + Ok(WithdrawablelResponse { + amount: amount_withdrawable, + }) +} + +/// Returns UnbondingsResponse displaying total unbondings happening at the moment +/// +/// # Arguments +/// * `deps` - a reference to Extern containing all the contract's external dependencies +/// * `address` address of the user querying this +/// * `start_page` page at the start of the list +/// * `page_size` number of transactions on each page +fn query_unbondings(deps: Deps, address: String) -> StdResult { + let user_info_obj = + user_info_helper_read_only(deps.storage, &deps.api.addr_validate(address.as_str())?)?; + + let config = config_helper_read_only(deps.storage)?; + + let mut vec = vec![]; + + for i in 0..user_info_obj.unbonding_batches.len() { + let unbonding_batch_index = user_info_obj.unbonding_batches[i]; + let unbonding_batch_obj = + unbonding_batch_helper_read_only(deps.storage, unbonding_batch_index)?; + let unbonding_amount = user_unbond_helper_read_only( + deps.storage, + unbonding_batch_index, + &deps.api.addr_validate(address.as_str())?, + )?; + + //1 already unbonding + if unbonding_batch_obj.unbonding_time.is_some() { + vec.push(RequestWithdrawQueryResponse { + amount: unbonding_amount, + batch_index: unbonding_batch_index, + next_batch_unbonding_time: None, + unbonding_time: unbonding_batch_obj.unbonding_time, + }) + } + // 2 not unbonded + else { + if unbonding_batch_index == config.next_unbonding_batch_index { + vec.push(RequestWithdrawQueryResponse { + amount: unbonding_amount, + batch_index: unbonding_batch_index, + next_batch_unbonding_time: Some(config.next_unbonding_batch_time), + unbonding_time: None, + }) + } + } + } + + let len = user_info_obj.unbonding_batches.len() as u32; + + Ok(UnbondingsResponse { vec, len }) +} + +/// Returns RewardsLogResponse containing information of all the rewards claimed by the user +/// +/// # Arguments +/// +/// * `deps` - a reference to Extern containing all the contract's external dependencies +/// * `address` address of the user querying this +/// * `start_page` page at the start of the list +/// * `page_size` number of transactions on each page +fn query_records( + deps: Deps, + address: String, + start_page: Option, + page_size: Option, +) -> StdResult { + let vec = user_records_helper_read_only( + deps.storage, + &deps.api.addr_validate(address.as_str())?, + start_page, + page_size, + )?; + let user_store = USER_REWARDS_LOG_STORE.add_suffix(address.as_str()); + let len = user_store.get_len(deps.storage)?; + + Ok(RecordsResponse { vec, len }) +} + +/// Returns QueryResult displaying sponsor info +/// +/// # Arguments +/// +/// * `deps` - a reference to Extern containing all the contract's external dependencies +/// * `address` address of the user querying this +fn query_sponsor_info(deps: Deps, address: String) -> StdResult { + let sponsor_info_obj: SponsorInfo = + sponsor_info_helper_read_only(deps.storage, &deps.api.addr_validate(address.as_str())?)?; + + Ok(SponsorInfoResponse { + amount_sponsored: sponsor_info_obj.amount_sponsored, + amount_withdrawable: sponsor_info_obj.amount_withdrawable, + amount_unbonding: sponsor_info_obj.amount_unbonding, + title: sponsor_info_obj.title, + message: sponsor_info_obj.message, + addr_list_index: sponsor_info_obj.addr_list_index, + unbonding_batches: sponsor_info_obj.unbonding_batches, + has_requested: sponsor_info_obj.has_requested, // req_list_index: sponsor_info_obj.req_list_index, + }) +} + +/// Returns AmountWithdrawablelResponse displaying the amount that can be withdrawn this instant +/// +/// # Arguments +/// +/// * `deps` - a reference to Extern containing all the contract's external dependencies +/// * `address` address of the user querying this +/// * `env` - Env of contract's environment +fn query_sponsor_withdrawable( + deps: Deps, + address: String, + env: Env, +) -> StdResult { + let sponsor_info_obj = + sponsor_info_helper_read_only(deps.storage, &deps.api.addr_validate(address.as_str())?)?; + + let mut amount_withdrawable = sponsor_info_obj.amount_withdrawable; + + for i in 0..sponsor_info_obj.unbonding_batches.len() { + let unbonding_batch_index = sponsor_info_obj.unbonding_batches[i]; + let unbonding_batch_obj = + unbonding_batch_helper_read_only(deps.storage, unbonding_batch_index)?; + let unbonding_amount = sponsor_unbond_helper_read_only( + deps.storage, + unbonding_batch_index, + &deps.api.addr_validate(address.as_str())?, + )?; + + // already unbonding + if unbonding_batch_obj.unbonding_time.is_some() { + if env.block.time.seconds() >= unbonding_batch_obj.unbonding_time.unwrap() { + amount_withdrawable.add_assign(unbonding_amount) + } + } + } + + Ok(WithdrawablelResponse { + amount: amount_withdrawable, + }) +} + +/// Returns UnbondingsResponse displaying total unbondings happening at the moment +/// +/// # Arguments +/// * `deps` - a reference to Extern containing all the contract's external dependencies +/// * `address` address of the user querying this +/// * `start_page` page at the start of the list +/// * `page_size` number of transactions on each page +fn query_sponsor_unbondings(deps: Deps, address: String) -> StdResult { + let sponsor_info_obj = + sponsor_info_helper_read_only(deps.storage, &deps.api.addr_validate(address.as_str())?)?; + + let config = config_helper_read_only(deps.storage)?; + + let mut vec = vec![]; + + for i in 0..sponsor_info_obj.unbonding_batches.len() { + let unbonding_batch_index = sponsor_info_obj.unbonding_batches[i]; + let unbonding_batch_obj = + unbonding_batch_helper_read_only(deps.storage, unbonding_batch_index)?; + let unbonding_amount = sponsor_unbond_helper_read_only( + deps.storage, + unbonding_batch_index, + &deps.api.addr_validate(address.as_str())?, + )?; + + //1) Already unbonding + if unbonding_batch_obj.unbonding_time.is_some() { + vec.push(RequestWithdrawQueryResponse { + amount: unbonding_amount, + batch_index: unbonding_batch_index, + next_batch_unbonding_time: None, + unbonding_time: unbonding_batch_obj.unbonding_time, + }) + } + //2) Not unbonded + else { + if unbonding_batch_index == config.next_unbonding_batch_index { + vec.push(RequestWithdrawQueryResponse { + amount: unbonding_amount, + batch_index: unbonding_batch_index, + next_batch_unbonding_time: Some(config.next_unbonding_batch_time), + unbonding_time: None, + }) + } + } + } + + let len = sponsor_info_obj.unbonding_batches.len() as u32; + + Ok(UnbondingsResponse { vec, len }) +} diff --git a/contracts/galactic_pools/pools/native/src/helper.rs b/contracts/galactic_pools/pools/native/src/helper.rs new file mode 100644 index 000000000..64ebac9cb --- /dev/null +++ b/contracts/galactic_pools/pools/native/src/helper.rs @@ -0,0 +1,391 @@ +//Crate Import +use crate::{ + constants::*, + state::{ + ConfigInfo, + GlobalSponsorDisplayRequestListState, + GlobalSponsorState, + PoolLiqState, + PoolState, + RewardsState, + RoundInfo, + SponsorInfo, + UnbondingBatch, + UserInfo, + UserLiqState, + UserRewardsLog, + }, + viewing_key::ViewingKey, +}; + +//Cosmwasm import +use shade_protocol::c_std::{Addr, StdResult, Storage, Uint128}; + +//secret toolkit import +use secret_storage_plus::{AppendStore, DequeStore, Item, Map}; +use shade_protocol::secret_storage_plus; + +pub const CONFIG_STORE: Item = Item::new(CONFIG_KEY); +pub fn config_helper_read_only(storage: &dyn Storage) -> StdResult { + return Ok(CONFIG_STORE.load(storage)?); +} + +pub fn config_helper_store(storage: &mut dyn Storage, config: &ConfigInfo) -> StdResult<()> { + CONFIG_STORE.save(storage, &config)?; + Ok(()) +} + +pub const POOL_STATE_STORE: Item = Item::new(POOL_STATE_KEY); +pub fn pool_state_helper_read_only(storage: &dyn Storage) -> StdResult { + return Ok(POOL_STATE_STORE.load(storage)?); +} + +pub fn pool_state_helper_store( + storage: &mut dyn Storage, + pool_state_obj: &PoolState, +) -> StdResult<()> { + let _ = POOL_STATE_STORE.save(storage, &pool_state_obj); + Ok(()) +} + +pub const ROUND_STORE: Item = Item::new(ROUND_KEY); +pub fn round_helper_read_only(storage: &dyn Storage) -> StdResult { + return Ok(ROUND_STORE.load(storage)?); +} +pub fn round_helper_store(storage: &mut dyn Storage, round_obj: &RoundInfo) -> StdResult<()> { + ROUND_STORE.save(storage, &round_obj)?; + Ok(()) +} + +pub const USER_INFO_STORE: Map<&Addr, UserInfo> = Map::new(USER_INFO_KEY); +pub fn user_info_helper_read_only(storage: &dyn Storage, sender: &Addr) -> StdResult { + let user_info_obj = USER_INFO_STORE.load(storage, sender).unwrap_or(UserInfo { + amount_delegated: Uint128::zero(), + amount_withdrawable: Uint128::zero(), + starting_round: None, + total_won: Uint128::zero(), + last_claim_rewards_round: None, + amount_unbonding: Uint128::zero(), + unbonding_batches: Vec::new(), + }); + return Ok(user_info_obj); +} + +pub fn user_info_helper_store( + storage: &mut dyn Storage, + sender: &Addr, + user_info_obj: &UserInfo, +) -> StdResult<()> { + let _ = USER_INFO_STORE.save(storage, sender, user_info_obj)?; + Ok(()) +} + +pub const USER_UNBOND_STORE: Map<(&Addr, u64), Uint128> = Map::new(USER_UNBOND_KEY); +pub fn user_unbond_helper_read_only( + storage: &dyn Storage, + unbond_batch_index: u64, + sender: &Addr, +) -> StdResult { + let user_unbonding_obj = USER_UNBOND_STORE + .load(storage, (sender, unbond_batch_index)) + .unwrap_or_default(); + return Ok(user_unbonding_obj); +} + +pub fn user_unbond_helper_store( + storage: &mut dyn Storage, + unbond_batch_index: u64, + + sender: &Addr, + unbonding_amount: Uint128, +) -> StdResult<()> { + let _ = USER_UNBOND_STORE.save(storage, (sender, unbond_batch_index), &unbonding_amount)?; + Ok(()) +} + +pub const ADMIN_UNBOND_STORE: Map = Map::new(USER_UNBOND_KEY); +pub fn admin_unbond_helper_read_only( + storage: &dyn Storage, + unbond_batch_index: u64, +) -> StdResult { + let admin_unbonding_obj = ADMIN_UNBOND_STORE + .load(storage, unbond_batch_index) + .unwrap_or_default(); + return Ok(admin_unbonding_obj); +} + +pub fn admin_unbond_helper_store( + storage: &mut dyn Storage, + unbond_batch_index: u64, + unbonding_amount: Uint128, +) -> StdResult<()> { + let _ = ADMIN_UNBOND_STORE.save(storage, unbond_batch_index, &unbonding_amount)?; + Ok(()) +} + +pub const SPONSOR_INFO_STORE: Map<&Addr, SponsorInfo> = Map::new(SPONSOR_INFO_KEY); +pub fn sponsor_info_helper_read_only( + storage: &dyn Storage, + sender: &Addr, +) -> StdResult { + let sponsor_obj = SPONSOR_INFO_STORE.load(storage, sender).unwrap_or_default(); + + return Ok(sponsor_obj); +} + +pub fn sponsor_info_helper_store( + storage: &mut dyn Storage, + sender: &Addr, + sponsor_info_obj: &SponsorInfo, +) { + SPONSOR_INFO_STORE + .save(storage, sender, sponsor_info_obj) + .unwrap_or_default(); +} + +pub const SPONSOR_UNBOND_STORE: Map<(&Addr, u64), Uint128> = Map::new(SPONSOR_UNBONDING_KEY); +pub fn sponsor_unbond_helper_read_only( + storage: &dyn Storage, + unbond_batch_index: u64, + sender: &Addr, +) -> StdResult { + let sponsor_unbonding_obj = SPONSOR_UNBOND_STORE + .load(storage, (sender, unbond_batch_index)) + .unwrap_or_default(); + return Ok(sponsor_unbonding_obj); +} + +pub fn sponsor_unbond_helper_store( + storage: &mut dyn Storage, + unbond_batch_index: u64, + + sender: &Addr, + unbonding_amount: Uint128, +) -> StdResult<()> { + let _ = SPONSOR_UNBOND_STORE.save(storage, (sender, unbond_batch_index), &unbonding_amount)?; + Ok(()) +} + +pub const SPONSOR_STATS_STORE: Item = Item::new(SPONSOR_STATS_KEY); +pub fn sponsor_stats_helper_read_only(storage: &dyn Storage) -> StdResult { + return Ok(SPONSOR_STATS_STORE.load(storage)?); +} + +pub fn sponsor_stats_helper_store( + storage: &mut dyn Storage, + sponsor_stats_obj: &GlobalSponsorState, +) -> StdResult<()> { + let _ = SPONSOR_STATS_STORE.save(storage, &sponsor_stats_obj); + Ok(()) +} + +pub const SPONSOR_LIST_STORE: Map = Map::new(SPONSOR_ADDRESS_LIST_KEY); +pub fn sponsor_addr_list_helper_read_only(storage: &dyn Storage, index: u32) -> StdResult { + return Ok(SPONSOR_LIST_STORE.load(storage, index)?); +} + +pub fn sponsor_addr_list_helper_store( + storage: &mut dyn Storage, + index: u32, + + addr: &Addr, +) -> StdResult<()> { + let _ = SPONSOR_LIST_STORE.save(storage, index, addr); + Ok(()) +} + +pub fn sponsor_addr_list_remove_helper_store( + storage: &mut dyn Storage, + index: u32, +) -> StdResult<()> { + let _ = SPONSOR_LIST_STORE.remove(storage, index); + Ok(()) +} + +//Sponsor Name & Message Display Request +pub const SPONSOR_DISPLAY_REQ_STORE: DequeStore = + DequeStore::new(SPONSOR_NAME_AND_MESSAGE_DISPLAY_REQUEST_KEY); +pub fn sponsor_display_request_deque_push_back_helper( + storage: &mut dyn Storage, + item: &GlobalSponsorDisplayRequestListState, +) -> StdResult<()> { + let spn_disp_req_store = SPONSOR_DISPLAY_REQ_STORE; + spn_disp_req_store.push_back(storage, item)?; + Ok(()) +} + +pub fn sponsor_display_request_deque_helper_remove( + storage: &mut dyn Storage, + index: u32, +) -> StdResult<()> { + let spn_disp_req_store = SPONSOR_DISPLAY_REQ_STORE; + spn_disp_req_store.remove(storage, index)?; + + return Ok(()); +} + +pub const USER_REWARDS_LOG_STORE: AppendStore = + AppendStore::new(USER_REWARDS_LOG_KEY); +pub fn user_records_helper_read_only( + storage: &dyn Storage, + sender: &Addr, + start_page: Option, + page_size: Option, +) -> StdResult> { + let user_store = USER_REWARDS_LOG_STORE.add_suffix(sender.as_str()); + let page_size = page_size.unwrap_or(5); + let start_page = start_page.unwrap_or(0); + let values = user_store.paging(storage, start_page, page_size)?; + + return Ok(values); +} + +pub fn user_rewards_log_helper_store( + storage: &mut dyn Storage, + sender: &Addr, + user_rewards_log_obj: &UserRewardsLog, +) -> StdResult<()> { + let user_store = USER_REWARDS_LOG_STORE.add_suffix(sender.as_str()); + user_store.push(storage, user_rewards_log_obj)?; + Ok(()) +} + +pub const USER_LIQUIDITY_STATS_STORE: Map<(&Addr, u64), UserLiqState> = + Map::new(USER_LIQUIDITY_KEY); +pub fn user_liquidity_snapshot_stats_helper_read_only( + storage: &dyn Storage, + round_index: u64, + sender: &Addr, +) -> StdResult { + let user_liquidity_snapshot_obj = USER_LIQUIDITY_STATS_STORE + .load(storage, (sender, round_index)) + .unwrap_or(UserLiqState { + amount_delegated: None, + liquidity: None, + tickets_used: None, + }); + Ok(user_liquidity_snapshot_obj) +} + +pub fn user_liquidity_snapshot_stats_helper_store( + storage: &mut dyn Storage, + round_index: u64, + sender: &Addr, + user_liq_obj: UserLiqState, +) -> StdResult<()> { + USER_LIQUIDITY_STATS_STORE.save(storage, (sender, round_index), &user_liq_obj)?; + Ok(()) +} + +pub const POOL_STATE_LIQUIDITY_STATS_STORE: Map = + Map::new(POOL_STATE_LIQUIDITY_KEY); + +pub fn pool_state_liquidity_helper_read_only( + storage: &dyn Storage, + current_round_index: u64, +) -> StdResult { + let pool_state_liquidity_snapshot_obj: PoolLiqState = POOL_STATE_LIQUIDITY_STATS_STORE + .load(storage, current_round_index) + .unwrap_or(PoolLiqState { + total_delegated: None, + total_liquidity: None, + }); + Ok(pool_state_liquidity_snapshot_obj) +} + +pub fn pool_state_liquidity_helper_store( + storage: &mut dyn Storage, + current_round_index: u64, + pool_state_liquidity_snapshot_obj: PoolLiqState, +) -> StdResult<()> { + POOL_STATE_LIQUIDITY_STATS_STORE.save( + storage, + current_round_index, + &pool_state_liquidity_snapshot_obj, + )?; + Ok(()) +} + +pub const UNBONDING_BATCH_STORE: Map = Map::new(UNBONDING_BATCH_KEY); +pub fn unbonding_batch_helper_read_only( + storage: &dyn Storage, + unbonding_round_index: u64, +) -> StdResult { + let unbonding_batch_snapshot_obj: UnbondingBatch = UNBONDING_BATCH_STORE + .load(storage, unbonding_round_index) + .unwrap_or(UnbondingBatch { + amount: None, + unbonding_time: None, + }); + + Ok(unbonding_batch_snapshot_obj) +} + +pub fn unbonding_batch_helper_store( + storage: &mut dyn Storage, + unbonding_batch_index: u64, + unbonding_batch_obj: &UnbondingBatch, +) -> StdResult<()> { + UNBONDING_BATCH_STORE.save(storage, unbonding_batch_index, unbonding_batch_obj)?; + Ok(()) +} + +pub const REWARDS_STATS_FOR_NTH_ROUND_STORE: Map = + Map::new(REWARD_STATS_FOR_NTH_ROUND_KEY); + +pub fn reward_stats_for_nth_round_helper_read_only( + storage: &dyn Storage, + round_index: u64, +) -> StdResult { + let reward_stats_for_nth_round = REWARDS_STATS_FOR_NTH_ROUND_STORE + .load(storage, round_index) + .unwrap_or(Default::default()); + + Ok(reward_stats_for_nth_round) +} + +pub fn reward_stats_for_nth_round_helper_store( + storage: &mut dyn Storage, + current_round_index: u64, + reward_stats_for_nth_round_obj: &RewardsState, +) { + REWARDS_STATS_FOR_NTH_ROUND_STORE + .save(storage, current_round_index, reward_stats_for_nth_round_obj) + .unwrap(); +} + +pub const ADMIN_AMOUNT_AVAIABLE_FOR_WITHDRAW_STORE: Item = Item::new(ADMIN_WITHDRAW_KEY); + +pub fn admin_withdraw_helper_read_only(deps_storage: &dyn Storage) -> StdResult { + let admin_withdraw_obj = ADMIN_AMOUNT_AVAIABLE_FOR_WITHDRAW_STORE + .load(deps_storage) + .unwrap_or(Default::default()); + return Ok(admin_withdraw_obj); +} + +pub fn admin_withdraw_helper_store( + deps_storage: &mut dyn Storage, + admin_withdraw_obj: &Uint128, +) -> StdResult<()> { + ADMIN_AMOUNT_AVAIABLE_FOR_WITHDRAW_STORE.save(deps_storage, &admin_withdraw_obj)?; + Ok(()) +} + +pub const VIEWING_KEY_STORE: Map<&Addr, [u8; 32]> = Map::new(PREFIX_VIEW_KEY); +pub fn write_viewing_key_helper( + storage: &mut dyn Storage, + owner: &Addr, + v_key: &ViewingKey, +) -> StdResult<()> { + VIEWING_KEY_STORE.save(storage, owner, &v_key.to_hashed())?; + Ok(()) +} + +pub fn read_viewing_key(storage: &dyn Storage, owner: &Addr) -> Option<[u8; 32]> { + let vk = VIEWING_KEY_STORE.load(storage, owner); + if vk.is_ok() { + return Some(vk.unwrap()); + } else { + return None; + } +} diff --git a/contracts/galactic_pools/pools/native/src/lib.rs b/contracts/galactic_pools/pools/native/src/lib.rs new file mode 100644 index 000000000..b9644b455 --- /dev/null +++ b/contracts/galactic_pools/pools/native/src/lib.rs @@ -0,0 +1,11 @@ +mod constants; +pub mod contract; +mod helper; +pub mod msg; +mod multi_test; +mod rand; +mod staking; +pub mod state; +mod unit_test; +mod utils; +mod viewing_key; diff --git a/contracts/galactic_pools/pools/native/src/msg.rs b/contracts/galactic_pools/pools/native/src/msg.rs new file mode 100644 index 000000000..9c29fc242 --- /dev/null +++ b/contracts/galactic_pools/pools/native/src/msg.rs @@ -0,0 +1,744 @@ +use s_toolkit::{permit::Permit, utils::types::Contract}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use shade_protocol::{c_std, s_toolkit, schemars, serde}; + +use crate::{ + state::{ + AdminShareInfo, + GlobalSponsorDisplayRequestListState, + RewardsDistInfo, + TierState, + UnclaimedDistInfo, + UserRewardsLog, + Validator, + WinningSequence, + }, + viewing_key::ViewingKey, +}; +use c_std::{Addr, Binary, Uint128}; + +//////////////////////////////////////////////////////////////// Instantiation message //////////////////////////////////////////////////////////////// +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +pub struct InstantiateMsg { + /// optional admin address, info.sender if missing + pub admins: Option>, + /// optional triggerer address, info.sender if missing + pub triggerers: Option>, + /// optional reviewer address, info.sender if missing + pub reviewers: Option>, + /// optional triggerer revenue share for ending round and unbonding batches + pub triggerer_share_percentage: u64, + /// denomination of the coin this contract delegates + pub denom: String, + /// Pseudorandom number generator seed + pub prng_seed: Binary, + /// list of all the validators, this contract will delegate to + pub validator: Vec, + /// time in seconds taken by this chain to unbond the tokens delegated + pub unbonding_duration: u64, + /// time in seconds taken before users can claim rewards + pub round_duration: u64, + /// Rewards distribution between tiers stats + pub rewards_distribution: RewardsDistInfo, + /// price to buy on ticket + pub ticket_price: Uint128, + /// time taken after the round is ended before rewards are deemed unclaimable + pub rewards_expiry_duration: u64, + /// helps determine the number of decimals in a percentage + pub common_divisor: u64, + /// total admin share out of 100% of the total staking rewards + pub total_admin_share: u64, + /// rewards send to shade protocol of the total rewards + pub shade_percentage_share: u64, + /// rewards send to galactic_pools dao of the total rewards + pub galactic_pools_percentage_share: u64, + /// shade protocol's rewards recieving address + pub shade_rewards_address: Addr, + /// galactic_pools's rewards recieving address + pub galactic_pools_rewards_address: Addr, + /// grand-prize withdraw address + pub grand_prize_address: Addr, + /// percentage of unclaimed rewards that are kept to increase the number of rewards + pub reserve_percentage: u64, + /// Admins can only deposit sponsor deposits with approved title and message + pub is_sponosorship_admin_controlled: bool, + /// How long it takes before next batch is unbonded + pub unbonding_batch_duration: u64, + /// optional minimum amount that can be deposited + pub minimum_deposit_amount: Option, + /// setting number of number_of_tickers that can be run on txn send to avoid potential errors + pub number_of_tickers_per_transaction: Uint128, + /// fee paid by sponsors to edit there message title + pub sponsor_msg_edit_fee: Option, + /// exp contract + pub exp_contract: Option, +} + +#[derive(Serialize, Deserialize, Debug, Eq, Clone, PartialEq, JsonSchema)] +pub struct ValidatorInfo { + pub address: String, + pub weightage: u64, +} + +#[derive(Serialize, Deserialize, Debug, Eq, Clone, PartialEq, JsonSchema)] +pub struct ExpContract { + pub contract: s_toolkit::utils::types::Contract, + pub vk: String, +} + +//////////////////////////////////////////////////////////////// Handle message //////////////////////////////////////////////////////////////// + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum HandleMsg { + //USER + Deposit {}, + RequestWithdraw { + amount: Uint128, + }, + Withdraw { + amount: Uint128, + }, + ClaimRewards {}, + CreateViewingKey { + entropy: String, + }, + SetViewingKey { + key: String, + }, + /// disallow the use of a permit + RevokePermit { + /// name of the permit that is no longer valid + permit_name: String, + }, + + //SPONSORS + Sponsor { + title: Option, + message: Option, + }, + SponsorRequestWithdraw { + amount: Uint128, + }, + + SponsorWithdraw { + amount: Uint128, + }, + SponsorMessageEdit { + title: Option, + message: Option, + delete_title: bool, + delete_message: bool, + }, + + // Admin + UpdateConfig { + unbonding_batch_duration: Option, + unbonding_duration: Option, + minimum_deposit_amount: Option, + exp_contract: Option, + }, + UpdateRound { + duration: Option, + rewards_distribution: Option, + ticket_price: Option, + rewards_expiry_duration: Option, + admin_share: Option, + triggerer_share_percentage: Option, + shade_rewards_address: Option, + galactic_pools_rewards_address: Option, + grand_prize_address: Option, + unclaimed_distribution: Option, + }, + + AddAdmin { + admin: Addr, + }, + RemoveAdmin { + admin: Addr, + }, + + AddTriggerer { + triggerer: Addr, + }, + RemoveTriggerer { + triggerer: Addr, + }, + + AddReviewer { + reviewer: Addr, + }, + RemoveReviewer { + reviewer: Addr, + }, + + UpdateValidatorSet { + updated_validator_set: Vec, + }, + RebalanceValidatorSet {}, + /// set contract status level to determine which functions are allowed. StopTransactions + /// status prevent mints, burns, sends, and transfers, but allows all other functions + SetContractStatus { + /// status level + level: ContractStatus, + }, + SetSponsorshipAccess { + /// status level + is_sponosorship_admin_controlled: bool, + }, + EndRound {}, + RequestReservesWithdraw { + amount: Uint128, + }, + UnbondBatch {}, + + ReservesWithdraw { + amount: Uint128, + }, + + ReviewSponsors { + decisions: Vec, + }, + RemoveSponsorCredentials { + decisions: Vec, + }, +} + +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] +pub struct Review { + pub index: u32, + pub is_accpeted: bool, +} + +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] +pub struct RemoveSponsorCredentialsDecisions { + pub index: u32, + pub remove_sponsor_title: bool, + pub remove_sponsor_message: bool, +} + +//////////////////////////////////////////////////////////////// Handle Answer //////////////////////////////////////////////////////////////// +#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum HandleAnswer { + // Native + Initialize { + status: ResponseStatus, + }, + Deposit { + status: ResponseStatus, + }, + Sponsor { + status: ResponseStatus, + }, + SponsorMessageEdit { + status: ResponseStatus, + }, + RemoveSponsorCredentials { + status: ResponseStatus, + }, + ReviewSponsorMessages { + status: ResponseStatus, + }, + Redelegate { + status: ResponseStatus, + }, + RequestWithdraw { + status: ResponseStatus, + }, + RequestWithdrawSponsor { + status: ResponseStatus, + }, + RequestAdminWithdraw { + status: ResponseStatus, + }, + Withdraw { + status: ResponseStatus, + }, + UnbondBatch { + status: ResponseStatus, + }, + SponsorWithdraw { + status: ResponseStatus, + }, + ReservesWithdraw { + status: ResponseStatus, + }, + + TriggeringCostWithdraw { + status: ResponseStatus, + }, + // Base + Transfer { + status: ResponseStatus, + }, + Send { + status: ResponseStatus, + }, + Burn { + status: ResponseStatus, + }, + RegisterReceive { + status: ResponseStatus, + }, + CreateViewingKey { + key: ViewingKey, + }, + SetViewingKey { + status: ResponseStatus, + }, + RevokePermit { + status: ResponseStatus, + }, + + // Other + EndRound { + status: ResponseStatus, + }, + AddAdmin { + status: ResponseStatus, + }, + RemoveAdmin { + status: ResponseStatus, + }, + AddTriggerer { + status: ResponseStatus, + }, + RemoveTriggerer { + status: ResponseStatus, + }, + AddReviewer { + status: ResponseStatus, + }, + RemoveReviewer { + status: ResponseStatus, + }, + UpdateConfig { + status: ResponseStatus, + }, + UpdateRound { + status: ResponseStatus, + }, + ChangeAdmin { + status: ResponseStatus, + }, + ChangeAdminShare { + status: ResponseStatus, + }, + ChangeTriggerer { + status: ResponseStatus, + }, + ChangeReviewer { + status: ResponseStatus, + }, + ChangeTriggererShare { + status: ResponseStatus, + }, + ChangeValidator { + status: ResponseStatus, + }, + + ChangeUnbondingTime { + status: ResponseStatus, + }, + SetContractStatus { + status: ResponseStatus, + }, + + ClaimRewards { + status: ResponseStatus, + winning_amount: Uint128, + }, + + UpdateValidatorSet { + status: ResponseStatus, + }, + RebalanceValidatorSet { + status: ResponseStatus, + }, + SetSponsorshipAccess { + status: ResponseStatus, + }, +} +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ResponseStatus { + Success, + Failure, +} + +//////////////////////////////////////////////////////////////// Query Message //////////////////////////////////////////////////////////////// +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + //PUBLIC + /// display the contract's configuration + ContractConfig {}, + ContractStatus {}, + + Round {}, + + CurrentRewards {}, + PoolState {}, + PoolStateLiquidityStats {}, + PoolStateLiquidityStatsSpecific { + round_index: u64, + }, + + RewardsStats {}, + + PastRecords {}, + PastAllRecords {}, + + //AUTHENTICATED + Delegated { + address: String, + key: String, + }, + + Withdrawable { + address: String, + key: String, + }, + Unbondings { + address: String, + key: String, + }, + Liquidity { + key: String, + address: String, + round_index: u64, + }, + + SponsorInfo { + key: String, + address: String, + }, + SponsorWithdrawable { + key: String, + address: String, + }, + SponsorUnbondings { + key: String, + address: String, + }, + + Records { + address: String, + page_size: Option, + start_page: Option, + key: String, + }, + SponsorMessageRequestCheck { + page_size: Option, + start_page: Option, + }, + Sponsors { + page_size: Option, + start_page: Option, + }, + /// perform queries by passing permits instead of viewing keys + WithPermit { + /// permit used to verify querier identity + permit: Permit, + /// query to perform + query: QueryWithPermit, + }, +} + +impl QueryMsg { + pub fn get_validation_params(&self) -> (Vec<&String>, ViewingKey) { + match self { + Self::Delegated { address, key } => (vec![address], ViewingKey(key.clone())), + Self::Unbondings { address, key } => (vec![address], ViewingKey(key.clone())), + Self::Withdrawable { address, key, .. } => (vec![address], ViewingKey(key.clone())), + Self::Liquidity { address, key, .. } => (vec![address], ViewingKey(key.clone())), + Self::SponsorInfo { address, key, .. } => (vec![address], ViewingKey(key.clone())), + Self::SponsorWithdrawable { address, key, .. } => { + (vec![address], ViewingKey(key.clone())) + } + Self::SponsorUnbondings { address, key, .. } => { + (vec![address], ViewingKey(key.clone())) + } + Self::Records { address, key, .. } => (vec![address], ViewingKey(key.clone())), + + _ => panic!("This query type does not require authentication"), + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum GalacticPoolsPermissions { + Delegated, + UserInfo, + SponsorInfo, + Liquidity, + Withdrawable, + SponsorWithdrawable, + Unbondings, + SponsorUnbondings, + Records, + /// Owner permission indicates that the bearer of this permit should be granted all + /// the access of the creator/signer of the permit. SNIP-721 uses this to grant + /// viewing access to all data that the permit creator owns and is whitelisted for. + /// For SNIP-721 use, a permit with Owner permission should NEVER be given to + /// anyone else. If someone wants to share private data, they should whitelist + /// the address they want to share with via a SetWhitelistedApproval tx, and that + /// address will view the data by creating their own permit with Owner permission + Owner, +} + +/// queries using permits instead of viewing keys +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryWithPermit { + Delegated {}, + UserInfo {}, + SponsorInfo {}, + Liquidity { + round_index: u64, + }, + + Withdrawable {}, + SponsorWithdrawable {}, + Unbondings {}, + SponsorUnbondings {}, + Records { + page_size: Option, + start_page: Option, + }, + Test {}, +} + +//////////////////////////////////////////////////////////////// Query Answer/Resposes //////////////////////////////////////////////////////////////// + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct ContractConfigResponse { + pub admins: Vec, + pub triggerers: Vec, + pub reviewers: Vec, + pub denom: String, + pub contract_address: Addr, + pub validators: Vec, + pub next_unbonding_batch_time: u64, + pub next_unbonding_batch_amount: Uint128, + pub unbonding_batch_duration: u64, + pub unbonding_duration: u64, + pub minimum_deposit_amount: Option, + pub exp_contract: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct ContractStatusResponse { + pub status: ContractStatus, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct RoundResponse { + pub duration: u64, + pub start_time: u64, + pub end_time: u64, + pub rewards_distribution: RewardsDistInfo, + pub current_round_index: u64, + pub ticket_price: Uint128, + pub rewards_expiry_duration: u64, + pub admin_share: AdminShareInfo, + pub triggerer_share_percentage: u64, + pub unclaimed_distribution: UnclaimedDistInfo, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct DelegatedResponse { + pub amount: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] + +pub struct LiquidityResponse { + //total liq + pub total_liq: Uint128, + //total tickets + pub total_tickets: Uint128, + //ticket price + pub ticket_price: Uint128, + //user liq + pub user_liq: Uint128, + //user tickets + pub user_tickets: Uint128, + pub tickets_used: Uint128, + pub expiry_date: Option, + pub total_rewards: Uint128, + pub unclaimed_rewards: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] + +pub struct UserInfoResponse { + pub amount_delegated: Uint128, + pub amount_unbonding: Uint128, + pub starting_round: Option, + pub total_won: Uint128, + pub last_claim_rewards_round: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct SponsorInfoResponse { + pub amount_sponsored: Uint128, + pub amount_withdrawable: Uint128, + pub amount_unbonding: Uint128, + pub title: Option, + pub message: Option, + /// index of the sponsors in storage + pub addr_list_index: Option, + pub unbonding_batches: Vec, + pub has_requested: bool, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] + +pub struct WithdrawablelResponse { + pub amount: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct UnbondingsResponse { + pub vec: Vec, + pub len: u32, +} + +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Copy)] +pub struct RequestWithdrawQueryResponse { + pub amount: Uint128, + pub batch_index: u64, + // if batch haven't unbonded yet + pub next_batch_unbonding_time: Option, + // if batch is already unbonded + pub unbonding_time: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct RecordsResponse { + pub vec: Vec, + pub len: u32, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct SponsorMessageRequestResponse { + pub vec: Vec, + pub len: u32, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct SponsorsResponse { + pub vec: Vec, + pub len: u32, +} +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Default)] +pub struct SponsorDisplayInfo { + pub amount_sponsored: Uint128, + pub title: Option, + pub message: Option, + pub addr_list_index: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct CurrentRewardsResponse { + pub rewards: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct PoolStateInfoResponse { + pub total_delegated: Uint128, + pub rewards_returned_to_contract: Uint128, + pub total_reserves: Uint128, + pub total_sponsored: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct RewardStatsResponse { + pub distribution_per_tiers: TierState, + pub ticket_price: Uint128, + pub winning_sequence: WinningSequence, + pub rewards_expiration_date: Option, + pub total_rewards: Uint128, + pub total_claimed: Uint128, + pub total_exp: Option, + pub total_exp_claimed: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct SponsorMessageReqResponse { + pub vec: Vec, + pub len: u32, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] + +pub struct PoolStateLiquidityStatsResponse { + pub total_liquidity: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct PastRecordsResponse { + pub past_rewards: Vec<(u64, u64)>, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct PastAllRecordsResponse { + pub past_rewards: Vec<(u64, u64)>, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct UserPastRecordsResponse { + pub winning_history: Vec<(u64, u64)>, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct UserAllPastRecordsResponse { + pub winning_history: Vec<(u64, u64)>, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct CreateViewingKeyResponse { + pub key: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct ViewingKeyErrorResponse { + pub msg: String, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ContractStatus { + Normal, + StopTransactions, + StopAll, +} + +impl ContractStatus { + /// Returns u8 representation of the ContractStatus + pub fn to_u8(&self) -> u8 { + match self { + ContractStatus::Normal => 0, + ContractStatus::StopTransactions => 1, + ContractStatus::StopAll => 2, + } + } +} + +// Take a Vec and pad it up to a multiple of `block_size`, using spaces at the end. +pub fn space_pad(block_size: usize, message: &mut Vec) -> &mut Vec { + let len = message.len(); + let surplus = len % block_size; + if surplus == 0 { + return message; + } + + let missing = block_size - surplus; + message.reserve(missing); + message.extend(std::iter::repeat(b' ').take(missing)); + message +} diff --git a/contracts/galactic_pools/pools/native/src/multi_test.rs b/contracts/galactic_pools/pools/native/src/multi_test.rs new file mode 100644 index 000000000..9de00c86f --- /dev/null +++ b/contracts/galactic_pools/pools/native/src/multi_test.rs @@ -0,0 +1,698 @@ +#[cfg(test)] +mod tests { + + use std::ops::Add; + + use c_std::{coins, Addr, Binary, BlockInfo, Coin, ContractInfo, Empty, StdResult, Uint128}; + + use shade_protocol::{c_std, s_toolkit}; + + use experience_contract::msg::AddContract; + use shade_protocol::multi_test::{ + App, + AppBuilder, + Contract, + ContractWrapper, + Executor, + StakingSudo, + SudoMsg, + }; + + use crate::{ + msg::{ExpContract, InstantiateMsg, ValidatorInfo}, + state::{DistInfo, RewardsDistInfo}, + }; + + const ADMIN: &str = "admin00001"; + const TRIGGERER: &str = "trigger00001"; + + const SCRT_TO_USCRT: u128 = 1000000; + + pub fn pool_info() -> Box> { + let contract = ContractWrapper::new_with_empty( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ); + Box::new(contract) + } + + fn mock_app(init_funds: &[Coin]) -> App { + AppBuilder::new().build(|router, _, storage| { + router + .bank + .init_balance(storage, &Addr::unchecked(ADMIN), init_funds.to_vec()) + .unwrap(); + }) + } + + pub fn exp_contract() -> Box> { + let contract = ContractWrapper::new_with_empty( + experience_contract::contract::execute, + experience_contract::contract::instantiate, + experience_contract::contract::query, + ); + Box::new(contract) + } + + #[track_caller] + fn instantiate_exp(app: &mut App, address: &String) -> StdResult { + let flex_id = app.store_code(exp_contract()); + + let msg = experience_contract::msg::InstantiateMsg { + entropy: "entropy lol 123".to_string(), + admin: Some(vec![Addr::unchecked(address.clone())]), + schedules: vec![experience_contract::msg::MintingScheduleUint { + mint_per_block: Uint128::from(1u128), + duration: 1000, + start_after: None, + continue_with_current_season: false, + }], + + season_ending_block: 0, + grand_prize_contract: None, + }; + Ok(app + .instantiate_contract(flex_id, Addr::unchecked(ADMIN), &msg, &[], "exp", None) + .unwrap()) + } + + fn add_validator(app: &mut App, validator: &str) -> StdResult<()> { + let _res = app + .sudo(SudoMsg::Staking(StakingSudo::AddValidator { + validator: validator.to_string(), + })) + .unwrap(); + Ok(()) + } + + #[track_caller] + fn instantiate_pool( + mut app: &mut App, + _contract_balance: Option, + exp_contract_obj: &ContractInfo, + ) -> StdResult { + let contract_id = app.store_code(pool_info()); + let common_divisor = 10000u64; + let mut validator_vector: Vec = Vec::new(); + validator_vector.push(ValidatorInfo { + address: "galacticPools".to_string(), + weightage: (60 * common_divisor) / 100, + }); + validator_vector.push(ValidatorInfo { + address: "secureSecret".to_string(), + weightage: (20 * common_divisor) / 100, + }); + validator_vector.push(ValidatorInfo { + address: "xavierCapital".to_string(), + weightage: (20 * common_divisor) / 100, + }); + + add_validator(&mut app, "galacticPools")?; + add_validator(&mut app, "secureSecret")?; + add_validator(&mut app, "xavierCapital")?; + + let rewards_distribution: RewardsDistInfo = RewardsDistInfo { + tier_0: DistInfo { + total_number_of_winners: Uint128::from(1u128), + percentage_of_rewards: (20 * 10000) / 100, + }, + tier_1: DistInfo { + total_number_of_winners: Uint128::from(3u128), + percentage_of_rewards: (10 * 10000) / 100, + }, + tier_2: DistInfo { + total_number_of_winners: Uint128::from(9u128), + percentage_of_rewards: (14 * 10000) / 100, + }, + tier_3: DistInfo { + total_number_of_winners: Uint128::from(27u128), + percentage_of_rewards: (12 * 10000) / 100, + }, + tier_4: DistInfo { + total_number_of_winners: Uint128::from(81u128), + percentage_of_rewards: (19 * 10000) / 100, + }, + tier_5: DistInfo { + total_number_of_winners: Uint128::from(243u128), + percentage_of_rewards: (25 * 10000) / 100, + }, + }; + + let init_msg = InstantiateMsg { + admins: Option::from(vec![ + Addr::unchecked("admin".to_string()), + Addr::unchecked(ADMIN.to_string()), + ]), + triggerers: Option::from(vec![Addr::unchecked(TRIGGERER.to_string())]), + reviewers: Option::from(vec![Addr::unchecked("reviewer".to_string())]), + triggerer_share_percentage: (1 * common_divisor) / 100, //dividing by 1/100 * common_divisor + denom: "uscrt".to_string(), + prng_seed: Binary::from("I'm secretbatman".as_bytes()), + validator: validator_vector, + unbonding_duration: 3600 * 24 * 21, //21 days + round_duration: 3600 * 24 * 7, //7 days + rewards_distribution, + ticket_price: Uint128::from(1 * SCRT_TO_USCRT), + rewards_expiry_duration: 3888000, // 45 days + common_divisor, + total_admin_share: (10 * common_divisor) / 100, + shade_percentage_share: (60 * common_divisor) / 100, + galactic_pools_percentage_share: (40 * common_divisor) / 100, + shade_rewards_address: Addr::unchecked(("shade".to_string())), + galactic_pools_rewards_address: Addr::unchecked(("galactic_pools".to_string())), + reserve_percentage: (60 * common_divisor) / 100, + is_sponosorship_admin_controlled: false, + unbonding_batch_duration: 3600 * 24 * 3, + minimum_deposit_amount: None, + grand_prize_address: Addr::unchecked("grand_prize".to_string()), + /// setting number of number_of_tickers that can be run on txn send to avoid potential errors + number_of_tickers_per_transaction: Uint128::from(1000000u128), + sponsor_msg_edit_fee: Some(Uint128::from(1000000u128)), + exp_contract: Some(ExpContract { + contract: s_toolkit::utils::types::Contract { + address: exp_contract_obj.address.to_string(), + hash: exp_contract_obj.code_hash.clone(), + }, + vk: "vk_1".to_string(), + }), + }; + + Ok(app + .instantiate_contract( + contract_id, + Addr::unchecked(ADMIN), + &init_msg, + &[], + "GalacticPools", + None, + ) + .unwrap()) + } + + //1)Init pool contract + + #[test] + fn test_initialize_exp_contract() -> StdResult<()> { + let mut app = mock_app(&coins(100 * SCRT_TO_USCRT, "uscrt")); + instantiate_exp(&mut app, &ADMIN.to_string())?; + Ok(()) + } + + #[test] + fn test_initialize_pool_contract() -> StdResult<()> { + let mut app = mock_app(&coins(100 * SCRT_TO_USCRT, "uscrt")); + let exp_contract_obj = instantiate_exp(&mut app, &ADMIN.to_string())?; + instantiate_pool(&mut app, Some(100 * SCRT_TO_USCRT), &exp_contract_obj)?; + Ok(()) + } + + #[test] + fn test_set_exp_contract() -> StdResult<()> { + let mut app = mock_app(&coins(100 * SCRT_TO_USCRT, "uscrt")); + + //1) Initialize exp contract + let exp_contract_obj = instantiate_exp(&mut app, &ADMIN.to_string())?; + + //1.1) Init pool contract with exp information + let pool_contract_obj = + instantiate_pool(&mut app, Some(100 * SCRT_TO_USCRT), &exp_contract_obj)?; + + //1.2) Set exp contract + let _res = app + .execute_contract( + Addr::unchecked(&ADMIN.to_string()), + &pool_contract_obj, + &crate::msg::HandleMsg::UpdateConfig { + unbonding_batch_duration: None, + unbonding_duration: None, + minimum_deposit_amount: None, + exp_contract: Some(ExpContract { + contract: s_toolkit::utils::types::Contract { + address: exp_contract_obj.address.to_string(), + hash: exp_contract_obj.code_hash.clone(), + }, + vk: "vk_1".to_string(), + }), + }, + &[], + ) + .unwrap(); + + //1.3) Query exp contract info + + let contract_config: crate::msg::ContractConfigResponse = app + .wrap() + .query_wasm_smart( + &pool_contract_obj.code_hash, + &pool_contract_obj.address, + &crate::msg::QueryMsg::ContractConfig {}, + ) + .unwrap(); + + assert_eq!( + contract_config.exp_contract, + Some(s_toolkit::utils::types::Contract { + address: exp_contract_obj.address.to_string(), + hash: exp_contract_obj.code_hash.clone(), + }) + ); + + Ok(()) + } + + #[test] + fn test_set_weights() -> StdResult<()> { + let mut app = mock_app(&coins(100 * SCRT_TO_USCRT, "uscrt")); + + //1) Initialize exp contract + let exp_contract_obj = instantiate_exp(&mut app, &ADMIN.to_string())?; + + //1.1) Init pool contract with exp information + let pool_contract_obj = + instantiate_pool(&mut app, Some(100 * SCRT_TO_USCRT), &exp_contract_obj)?; + + //1.2) Set exp contract + let _res = app + .execute_contract( + Addr::unchecked(&ADMIN.to_string()), + &pool_contract_obj, + &crate::msg::HandleMsg::UpdateConfig { + unbonding_batch_duration: None, + unbonding_duration: None, + minimum_deposit_amount: None, + exp_contract: Some(ExpContract { + contract: s_toolkit::utils::types::Contract { + address: exp_contract_obj.address.to_string(), + hash: exp_contract_obj.code_hash.clone(), + }, + vk: "vk_1".to_string(), + }), + }, + &[], + ) + .unwrap(); + + let _res = app + .execute_contract( + Addr::unchecked(&ADMIN.to_string()), + &exp_contract_obj.clone(), + &experience_contract::msg::ExecuteMsg::AddContract { + contracts: [AddContract { + address: Addr::unchecked(pool_contract_obj.address.to_string()), + code_hash: pool_contract_obj.code_hash.to_string(), + weight: 0, + }] + .to_vec(), + }, + &[], + ) + .unwrap(); + let _res = app + .execute_contract( + Addr::unchecked(&ADMIN.to_string()), + &exp_contract_obj.clone(), + &experience_contract::msg::ExecuteMsg::UpdateWeights { + weights: vec![experience_contract::msg::WeightUpdate { + address: Addr::unchecked(pool_contract_obj.address.to_string()), + weight: 100, + }], + }, + &[], + ) + .unwrap(); + + //1.3) Query exp contract info + + let verified_contracts: experience_contract::msg::QueryAnswer = app + .wrap() + .query_wasm_smart( + &exp_contract_obj.code_hash, + &exp_contract_obj.address, + &experience_contract::msg::QueryMsg::VerifiedContracts { + start_page: None, + page_size: None, + }, + ) + .unwrap(); + + let contracts = match verified_contracts { + experience_contract::msg::QueryAnswer::VerifiedContractsResponse { contracts } => { + contracts + } + _ => panic!("Unexpected QueryAnswer variant"), + }; + + match &contracts[0] { + experience_contract::msg::VerifiedContractRes { + address, + weight, + code_hash, + .. + } => { + assert_eq!(address, &pool_contract_obj.address.to_string()); + assert_eq!(code_hash, &pool_contract_obj.code_hash.to_string()); + assert_eq!(address, &pool_contract_obj.address.to_string()); + assert_eq!(weight, &100u64); + } + } + Ok(()) + } + + #[test] + fn test_multi_tests_walkthrough() -> StdResult<()> { + let mut app = mock_app(&coins(100 * SCRT_TO_USCRT, "uscrt")); + + //1) Initialize exp contract + let exp_contract_obj = instantiate_exp(&mut app, &ADMIN.to_string())?; + + //1.1) Init pool contract with exp information + let pool_contract_obj = + instantiate_pool(&mut app, Some(100 * SCRT_TO_USCRT), &exp_contract_obj)?; + + //1.2) Set exp contract + let _res = app + .execute_contract( + Addr::unchecked(&ADMIN.to_string()), + &pool_contract_obj, + &crate::msg::HandleMsg::UpdateConfig { + unbonding_batch_duration: None, + unbonding_duration: None, + minimum_deposit_amount: None, + exp_contract: Some(ExpContract { + contract: s_toolkit::utils::types::Contract { + address: exp_contract_obj.address.to_string(), + hash: exp_contract_obj.code_hash.clone(), + }, + vk: "vk_1".to_string(), + }), + }, + &[], + ) + .unwrap(); + + let _res = app + .execute_contract( + Addr::unchecked(&ADMIN.to_string()), + &exp_contract_obj.clone(), + &experience_contract::msg::ExecuteMsg::AddContract { + contracts: [AddContract { + address: Addr::unchecked(pool_contract_obj.address.to_string()), + code_hash: pool_contract_obj.code_hash.to_string(), + weight: 10, + }] + .to_vec(), + }, + &[], + ) + .unwrap(); + + let _res = app + .execute_contract( + Addr::unchecked(&ADMIN.to_string()), + &exp_contract_obj.clone(), + &experience_contract::msg::ExecuteMsg::UpdateWeights { + weights: vec![experience_contract::msg::WeightUpdate { + address: Addr::unchecked(pool_contract_obj.address.to_string()), + weight: 100, + }], + }, + &[], + ) + .unwrap(); + + //3) Set some rewards + let _res = app.sudo(SudoMsg::Staking(StakingSudo::AddRewards { + amount: Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(10 * SCRT_TO_USCRT), + }, + })); + + //2) Deposit some $$$ by 2 users + let _res = app + .send_tokens( + Addr::unchecked(ADMIN), + Addr::unchecked(&"user_1".to_string()), + &coins(10 * SCRT_TO_USCRT, "uscrt"), + ) + .unwrap(); + let _res = app + .execute_contract( + Addr::unchecked(&"user_1".to_string()), + &pool_contract_obj, + &crate::msg::HandleMsg::Deposit {}, + &coins(10 * SCRT_TO_USCRT, "uscrt"), + ) + .unwrap(); + + let _res = app.send_tokens( + Addr::unchecked(ADMIN), + Addr::unchecked(&"user_2".to_string()), + &coins(10 * SCRT_TO_USCRT, "uscrt"), + ); + let _res = app + .execute_contract( + Addr::unchecked(&"user_2".to_string()), + &pool_contract_obj, + &crate::msg::HandleMsg::Deposit {}, + &coins(10 * SCRT_TO_USCRT, "uscrt"), + ) + .unwrap(); + + //4) End round + //4.1) Update block and time + app.set_block(BlockInfo { + height: app.block_info().height.add(71), + time: app.block_info().time.plus_seconds(3600 * 24 * 7), + chain_id: app.block_info().chain_id, + random: None, + }); + + //4.3) Check total exp avaiable for the pool contract + let available_exp: experience_contract::msg::QueryAnswer = app + .wrap() + .query_wasm_smart( + &exp_contract_obj.code_hash, + &exp_contract_obj.address, + &experience_contract::msg::QueryMsg::Contract { + key: "vk_1".to_string(), + address: Addr::unchecked(pool_contract_obj.clone().address.into_string()), + }, + ) + .unwrap(); + + if let experience_contract::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } = available_exp + { + assert_eq!(available_exp, Uint128::zero()); + assert_eq!(unclaimed_exp, Uint128::from(71u128)); + } + + // 5) End round + let _res = app + .execute_contract( + Addr::unchecked(&TRIGGERER.to_string()), + &pool_contract_obj, + &crate::msg::HandleMsg::EndRound {}, + &[], + ) + .unwrap(); + + //5.1) Query Xp given to the contract(POOL CONTRACT) + let rewards_stat_obj: crate::msg::RewardStatsResponse = app + .wrap() + .query_wasm_smart( + &pool_contract_obj.code_hash, + &pool_contract_obj.address, + &crate::msg::QueryMsg::RewardsStats {}, + ) + .unwrap(); + + assert_eq!(rewards_stat_obj.total_exp, Some(Uint128::from(71u128))); + assert_eq!(rewards_stat_obj.total_exp_claimed, Some(Uint128::zero())); + + //5.2) Check total exp avaiable for the pool contract(EXP POOL) + let available_exp: experience_contract::msg::QueryAnswer = app + .wrap() + .query_wasm_smart( + &exp_contract_obj.code_hash, + &exp_contract_obj.address, + &experience_contract::msg::QueryMsg::Contract { + key: "vk_1".to_string(), + address: Addr::unchecked(pool_contract_obj.clone().address.into_string()), + }, + ) + .unwrap(); + + if let experience_contract::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } = available_exp + { + assert_eq!(available_exp, Uint128::from(71u128)); + assert_eq!(unclaimed_exp, Uint128::from(0u128)); + } + //6) Claim rewards by u1 side + let _res = app + .execute_contract( + Addr::unchecked(&"user_1".to_string()), + &pool_contract_obj, + &crate::msg::HandleMsg::ClaimRewards {}, + &[], + ) + .unwrap(); + + //6.1) Check the exp given to user(POOL CONTRACT). + let rewards_stat_obj: crate::msg::RewardStatsResponse = app + .wrap() + .query_wasm_smart( + &pool_contract_obj.code_hash, + &pool_contract_obj.address, + &crate::msg::QueryMsg::RewardsStats {}, + ) + .unwrap(); + + assert_eq!(rewards_stat_obj.total_exp, Some(Uint128::from(71u128))); + assert_eq!( + rewards_stat_obj.total_exp_claimed, + Some(Uint128::from(35u128)) + ); + + //6.2) Check total exp avaiable for the pool contract(EXP CONTRACT). + let available_exp: experience_contract::msg::QueryAnswer = app + .wrap() + .query_wasm_smart( + &exp_contract_obj.code_hash, + &exp_contract_obj.address, + &experience_contract::msg::QueryMsg::Contract { + key: "vk_1".to_string(), + address: Addr::unchecked(pool_contract_obj.clone().address.into_string()), + }, + ) + .unwrap(); + + if let experience_contract::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } = available_exp + { + assert_eq!(available_exp, Uint128::from(36u128)); + assert_eq!(unclaimed_exp, Uint128::from(0u128)); + } + //6.3) Check total exp avaiable for the user(EXP CONTRACT). + //6.3.1) Set user1 vk + let _res = app + .execute_contract( + Addr::unchecked(&"user_1".to_string()), + &exp_contract_obj, + &crate::msg::HandleMsg::SetViewingKey { + key: "vk".to_string(), + }, + &[], + ) + .unwrap(); + //6.3.2) Query exp + let available_exp: experience_contract::msg::QueryAnswer = app + .wrap() + .query_wasm_smart( + &exp_contract_obj.code_hash, + &exp_contract_obj.address, + &experience_contract::msg::QueryMsg::UserExp { + key: "vk".to_string(), + address: Addr::unchecked("user_1".to_string()), + season: None, + }, + ) + .unwrap(); + + if let experience_contract::msg::QueryAnswer::UserExp { exp } = available_exp { + assert_eq!(exp, Uint128::from(35u128)); + } + + //7) Claim rewards by u2 side + let _res = app + .execute_contract( + Addr::unchecked(&"user_2".to_string()), + &pool_contract_obj, + &crate::msg::HandleMsg::ClaimRewards {}, + &[], + ) + .unwrap(); + + //7.1) Check the exp given to user 2.(POOL CONTRACT) + let rewards_stat_obj: crate::msg::RewardStatsResponse = app + .wrap() + .query_wasm_smart( + &pool_contract_obj.code_hash, + &pool_contract_obj.address, + &crate::msg::QueryMsg::RewardsStats {}, + ) + .unwrap(); + + assert_eq!(rewards_stat_obj.total_exp, Some(Uint128::from(71u128))); + assert_eq!( + rewards_stat_obj.total_exp_claimed, + Some(Uint128::from(70u128)) + ); + + //7.2) Check total exp avaiable for the pool contract + let available_exp: experience_contract::msg::QueryAnswer = app + .wrap() + .query_wasm_smart( + &exp_contract_obj.code_hash, + &exp_contract_obj.address, + &experience_contract::msg::QueryMsg::Contract { + key: "vk_1".to_string(), + address: Addr::unchecked(pool_contract_obj.clone().address.into_string()), + }, + ) + .unwrap(); + + if let experience_contract::msg::QueryAnswer::ContractResponse { + available_exp, + unclaimed_exp, + .. + } = available_exp + { + assert_eq!(available_exp, Uint128::from(1u128)); + assert_eq!(unclaimed_exp, Uint128::from(0u128)); + } + //7.3) Check total exp avaiable for the user(EXP CONTRACT). + //7.3.1) Set user2 vk + let _res = app + .execute_contract( + Addr::unchecked(&"user_2".to_string()), + &exp_contract_obj, + &crate::msg::HandleMsg::SetViewingKey { + key: "vk".to_string(), + }, + &[], + ) + .unwrap(); + //7.3.2) Query exp + let available_exp: experience_contract::msg::QueryAnswer = app + .wrap() + .query_wasm_smart( + &exp_contract_obj.code_hash, + &exp_contract_obj.address, + &experience_contract::msg::QueryMsg::UserExp { + key: "vk".to_string(), + address: Addr::unchecked("user_2".to_string()), + season: None, + }, + ) + .unwrap(); + + if let experience_contract::msg::QueryAnswer::UserExp { exp } = available_exp { + assert_eq!(exp, Uint128::from(35u128)); + } + + Ok(()) + } +} diff --git a/contracts/galactic_pools/pools/native/src/rand.rs b/contracts/galactic_pools/pools/native/src/rand.rs new file mode 100644 index 000000000..ac47d1fb7 --- /dev/null +++ b/contracts/galactic_pools/pools/native/src/rand.rs @@ -0,0 +1,75 @@ +use rand::{RngCore, SeedableRng}; +use rand_chacha::ChaChaRng; + +use sha2::{Digest, Sha256}; + +pub fn sha_256(data: &[u8]) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(data); + let hash = hasher.finalize(); + + let mut result = [0u8; 32]; + result.copy_from_slice(hash.as_slice()); + result +} + +pub struct Prng { + rng: ChaChaRng, +} + +impl Prng { + pub fn new(seed: &[u8], entropy: &[u8]) -> Self { + let mut hasher = Sha256::new(); + + // write input message + hasher.update(&seed); + hasher.update(&entropy); + let hash = hasher.finalize(); + + let mut hash_bytes = [0u8; 32]; + hash_bytes.copy_from_slice(hash.as_slice()); + + let rng: ChaChaRng = ChaChaRng::from_seed(hash_bytes); + + Self { rng } + } + + pub fn rand_bytes(&mut self) -> [u8; 32] { + let mut bytes = [0u8; 32]; + self.rng.fill_bytes(&mut bytes); + + bytes + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// This test checks that the rng is stateful and generates + /// different random bytes every time it is called. + #[test] + fn test_rng() { + let mut rng = Prng::new(b"foo", b"bar!"); + let r1: [u8; 32] = [ + 155, 11, 21, 97, 252, 65, 160, 190, 100, 126, 85, 251, 47, 73, 160, 49, 216, 182, 93, + 30, 185, 67, 166, 22, 34, 10, 213, 112, 21, 136, 49, 214, + ]; + let r2: [u8; 32] = [ + 46, 135, 19, 242, 111, 125, 59, 215, 114, 130, 122, 155, 202, 23, 36, 118, 83, 11, 6, + 180, 97, 165, 218, 136, 134, 243, 191, 191, 149, 178, 7, 149, + ]; + let r3: [u8; 32] = [ + 9, 2, 131, 50, 199, 170, 6, 68, 168, 28, 242, 182, 35, 114, 15, 163, 65, 139, 101, 221, + 207, 147, 119, 110, 81, 195, 6, 134, 14, 253, 245, 244, + ]; + let r4: [u8; 32] = [ + 68, 196, 114, 205, 225, 64, 201, 179, 18, 77, 216, 197, 211, 13, 21, 196, 11, 102, 106, + 195, 138, 250, 29, 185, 51, 38, 183, 0, 5, 169, 65, 190, + ]; + assert_eq!(r1, rng.rand_bytes()); + assert_eq!(r2, rng.rand_bytes()); + assert_eq!(r3, rng.rand_bytes()); + assert_eq!(r4, rng.rand_bytes()); + } +} diff --git a/contracts/galactic_pools/pools/native/src/staking.rs b/contracts/galactic_pools/pools/native/src/staking.rs new file mode 100644 index 000000000..6a8da52c8 --- /dev/null +++ b/contracts/galactic_pools/pools/native/src/staking.rs @@ -0,0 +1,194 @@ +// Assuming 'shade_protocol' correctly re-exports all necessary components from 'c_std' +use shade_protocol::c_std::{ + Addr, + Coin, + CosmosMsg, + Deps, + DistributionMsg, + FullDelegation, + StakingMsg, + StdResult, + Uint128, +}; + +use serde::{Deserialize, Serialize}; + +use crate::state::ConfigInfo; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct CustomValidatorRewards { + pub validator_address: String, + pub reward: Uint128, +} + +#[non_exhaustive] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct Rewards { + delegator: String, +} + +/// Rewards response +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct RewardsResponse { + pub rewards: Vec, + pub total: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct ValidatorRewards { + pub validator_address: String, + pub reward: Vec, +} + +pub fn get_rewards( + deps: Deps, + contract: &Addr, + config: &ConfigInfo, +) -> StdResult> { + // let req: QueryRequest = QueryRequest::Staking(StakingQuery::AllDelegations { + // delegator: contract.into(), + // }) + // .into(); + + // let request = to_vec(&req).map_err(|serialize_err| { + // StdError::generic_err(format!("Serializing QueryRequest: {}", serialize_err)) + // })?; + + // let raw = deps.querier.raw_query(&request).unwrap().unwrap(); + // let dels: AllDelegationsResponse = from_binary(&raw).unwrap(); + // let delegations = dels.delegations; + + let mut result_vector: Vec = vec![]; + + // //testing + + // if delegations.is_empty() { + // return Ok(result_vector); + // } + + // let mut result_vector: Vec = vec![]; + + // let validator_vector = delegations; + // let mut collect_vals: Vec = vec![]; + + // for validator in validator_vector { + // collect_vals.push(validator.validator); + // } + + let vals = &config.validators; + + for val in vals { + // let req: QueryRequest = QueryRequest::Staking(StakingQuery::Delegation { + // delegator: contract.into(), + // validator: val.address.clone(), + // }) + // .into(); + + // let request = to_vec(&req).map_err(|serialize_err| { + // StdError::generic_err(format!("Serializing QueryRequest: {}", serialize_err)) + // })?; + + // let raw = deps.querier.raw_query(&request).unwrap().unwrap(); + // let del: DelegationResponse = from_binary(&raw).unwrap(); + + let contract_delegation: Option = + deps.querier.query_delegation(contract, &val.address)?; + + if contract_delegation.is_none() { + continue; + } + + let coins = &contract_delegation.unwrap().accumulated_rewards; + + // let denom = &validator.reward[0].denom.as_str(); + let mut amount = Uint128::zero(); + + // if config.denom.eq(coin.) + + for coin in coins { + if config.denom.as_str().eq(coin.denom.as_str()) { + amount += coin.amount; + } + } + + let validator_reward_obj = CustomValidatorRewards { + validator_address: val.address.clone(), + reward: amount, + }; + + result_vector.push(validator_reward_obj); + } + + Ok(result_vector) +} + +//TODO +// pub fn get_exp(deps: Deps, config: &ConfigInfo) -> StdResult { +// let msg = experience_contract::msg::QueryMsg::Contract { +// address: deps +// .api +// .addr_humanize(&config.contract_address)? +// .to_string(), +// key: config.exp_contract.clone().unwrap().vk, +// }; + +// let rewards = deps +// .querier +// .query_wasm_smart::( +// config.exp_contract.clone().unwrap().contract.hash, +// config.exp_contract.clone().unwrap().contract.address, +// &(&msg), +// )?; + +// let res = rewards; + +// if let experience_contract::msg::QueryAnswer::ContractResponse { unclaimed_exp, .. } = res { +// return Ok(unclaimed_exp); +// } + +// Ok(Uint128::zero()) +// } + +pub fn withdraw(validator: &String) -> CosmosMsg { + CosmosMsg::Distribution(DistributionMsg::WithdrawDelegatorReward { + validator: validator.clone(), + }) +} + +pub fn stake(validator: &String, amount: Uint128, denom: &str) -> CosmosMsg { + CosmosMsg::Staking(StakingMsg::Delegate { + validator: (validator.clone()), + amount: Coin { + denom: denom.to_string(), + amount, + }, + }) +} + +pub fn undelegate(validator: &String, amount: Uint128, denom: &str) -> CosmosMsg { + CosmosMsg::Staking(StakingMsg::Undelegate { + validator: (validator.clone()), + amount: Coin { + denom: denom.to_string(), + amount, + }, + }) +} + +pub fn redelegate( + src_validator: &String, + dst_validator: &String, + amount: Uint128, + denom: &str, +) -> CosmosMsg { + CosmosMsg::Staking(StakingMsg::Redelegate { + // delegator is automatically set to address of the calling contract + src_validator: (src_validator.clone()), + dst_validator: (dst_validator.clone()), + amount: Coin { + denom: denom.to_string(), + amount, + }, + }) +} diff --git a/contracts/galactic_pools/pools/native/src/state.rs b/contracts/galactic_pools/pools/native/src/state.rs new file mode 100644 index 000000000..c5175d783 --- /dev/null +++ b/contracts/galactic_pools/pools/native/src/state.rs @@ -0,0 +1,342 @@ +use c_std::{Addr, Uint128}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use shade_protocol::{c_std, schemars}; + +use crate::msg::ExpContract; + +//////////////////////////////////////////////////////////////// CONFIG ////////////////////////////////////////////////////////////////// + +/// Configuration Information +#[derive(Serialize, Debug, Deserialize, Clone, PartialEq)] +pub struct ConfigInfo { + /// contract admin's address + pub admins: Vec, + /// contract triggerer's canonical address + pub triggerers: Vec, + /// contract reviewer's canonical address + pub reviewers: Vec, + /// helps determine the number of decimals in a percentage + pub common_divisor: u64, + /// denomination of the coin this contract delegates + pub denom: String, + /// Pseudorandom number generator seed + pub prng_seed: Vec, + /// canonical address of this contract + pub contract_address: Addr, + /// list of all the validators, this contract will delegate to + pub validators: Vec, + /// index of the next validator, contract will delegate to + pub next_validator_for_delegation: u8, + /// index of the nect validator used for unbonding + pub next_validator_for_unbonding: u8, + /// index of the next batch going to unbond + pub next_unbonding_batch_index: u64, + /// time next batch unbond + pub next_unbonding_batch_time: u64, + /// amount to be unbonded next batch + pub next_unbonding_batch_amount: Uint128, + /// time in seconds it takes before next batch is unbonded + pub unbonding_batch_duration: u64, + /// time in seconds taken by this chain to unbond the tokens delegated + pub unbonding_duration: u64, + /// optional minimum amount that can be deposited + pub minimum_deposit_amount: Option, + /// contract status + pub status: u8, + /// fee paid by sponsors to edit there message title + pub sponsor_msg_edit_fee: Option, + /// exp contract + pub exp_contract: Option, +} +/// Validator Information +/// Config -> Validator +#[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Default)] +pub struct Validator { + /// validator address + pub address: String, + /// amount delegated to this validator + pub delegated: Uint128, + /// % of amount must be delegated to this validator + pub weightage: u64, + /// delegated/(total_delegated*weightage) + pub percentage_filled: u64, +} + +//////////////////////////////////////////////////////////////// Round ////////////////////////////////////////////////////////////////// + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct RoundInfo { + /// entropy + pub entropy: Vec, + /// duration + pub seed: Vec, + /// duration of this round in # of seconds + pub duration: u64, + /// start time of current round + pub start_time: u64, + /// ending time of current round + pub end_time: u64, + /// rewards distribution between each tier + pub rewards_distribution: RewardsDistInfo, + /// index of the current round + pub current_round_index: u64, + /// price per one ticket + pub ticket_price: Uint128, + /// duration after round ends after which prizes are expired. + pub rewards_expiry_duration: u64, + /// % of rewards that are directed to admin + pub admin_share: AdminShareInfo, + /// % of rewards for triggerer + pub triggerer_share_percentage: u64, + /// shade's dao address + pub shade_rewards_address: Addr, + /// galacticpool's dao address + pub galactic_pools_rewards_address: Addr, + /// grand-prize contract address + pub grand_prize_address: Addr, + /// round when last time expired rewards were claimed + pub unclaimed_rewards_last_claimed_round: Option, + /// distribution of unclaimed rewards + pub unclaimed_distribution: UnclaimedDistInfo, + /// setting number of number_of_tickers that can be run on txn send to avoid potential errors + pub number_of_tickers_per_transaction: Uint128, +} + +/// pre-defined rewards distribution information between each tier +/// Round -> RewardsDistInfo +#[derive(Serialize, Deserialize, Debug, Eq, Clone, PartialEq, JsonSchema)] +pub struct RewardsDistInfo { + pub tier_0: DistInfo, + pub tier_1: DistInfo, + pub tier_2: DistInfo, + pub tier_3: DistInfo, + pub tier_4: DistInfo, + pub tier_5: DistInfo, +} +/// pre-defined rewards distribution information +/// Round -> RewardsDistInfo -> DistInfo +#[derive(Serialize, Deserialize, Debug, Eq, Clone, PartialEq, JsonSchema)] +pub struct DistInfo { + pub total_number_of_winners: Uint128, + pub percentage_of_rewards: u64, +} + +/// % of rewards that are directed to admin +/// Round -> AdminShareInfo +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Default)] +pub struct AdminShareInfo { + pub total_percentage_share: u64, + pub shade_percentage_share: u64, + pub galactic_pools_percentage_share: u64, +} + +/// distribution of unclaimed rewards +/// Round -> UnclaimedDistInfo +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Default)] +pub struct UnclaimedDistInfo { + /// % of rewards that are restaked and are used to increase overall all rewards + pub reserves_percentage: u64, + /// % of rewards that are added to the winning prizes + pub propagate_percentage: u64, +} + +//////////////////////////////////////////////////////////////// POOL STATE ////////////////////////////////////////////////////////////////// + +/// Global state in this Pool contract +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Default)] +pub struct PoolState { + pub total_delegated: Uint128, + /// token(s) that are auto-claimed when contract deposits to validator + pub rewards_returned_to_contract: Uint128, + pub total_reserves: Uint128, + pub total_sponsored: Uint128, + pub unbonding_batches: Vec, +} + +/// Global liquidity state in this Pool contract for nth round. +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Copy, Default)] +pub struct PoolLiqState { + pub total_delegated: Option, + pub total_liquidity: Option, +} + +//////////////////////////////////////////////////////////////// REWARDS STATE ////////////////////////////////////////////////////////////////// + +/// State of the rewards for specific nth round. +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Default)] +pub struct RewardsState { + pub distribution_per_tiers: TierState, + pub ticket_price: Uint128, + pub winning_sequence: WinningSequence, + pub rewards_expiration_date: Option, + pub total_rewards: Uint128, + pub total_claimed: Uint128, + pub total_exp: Option, + pub total_exp_claimed: Option, +} +/// State of the rewards per tier +/// RewardsState -> TierState +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Default)] +pub struct TierState { + pub tier_0: RewardsClaimed, + pub tier_1: RewardsClaimed, + pub tier_2: RewardsClaimed, + pub tier_3: RewardsClaimed, + pub tier_4: RewardsClaimed, + pub tier_5: RewardsClaimed, +} + +/// State of the claimed rewards +/// RewardsState -> TierState -> RewardsClaimed +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Default)] +pub struct RewardsClaimed { + pub num_of_rewards: Uint128, + pub claimed: RewardsPerTierInfo, +} + +/// Winning Sequence of the prize +/// RewardsState -> WinningSequence +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Default)] +pub struct WinningSequence { + pub tier_0: DigitsInfo, + pub tier_1: DigitsInfo, + pub tier_2: DigitsInfo, + pub tier_3: DigitsInfo, + pub tier_4: DigitsInfo, + pub tier_5: DigitsInfo, +} + +/// Range consists of Information about Range of digit for specific tier and winning digit +/// Range otherwise known as Difficulty. Bigger the range more difficult it will be to win prize +/// Winning Number: between 0 and range +/// RewardsState -> WinningSequence +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Default)] +pub struct DigitsInfo { + pub range: Uint128, + pub winning_number: Uint128, +} + +//////////////////////////////////////////////////////////////// USER INFO AND STATE ////////////////////////////////////////////////////////////////// + +/// User's State +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Default)] +pub struct UserInfo { + pub amount_delegated: Uint128, + pub amount_withdrawable: Uint128, + pub amount_unbonding: Uint128, + pub unbonding_batches: Vec, + pub starting_round: Option, + pub total_won: Uint128, + pub last_claim_rewards_round: Option, +} + +/// State of user liquidity information +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Copy, Default)] +pub struct UserLiqState { + pub amount_delegated: Option, + pub liquidity: Option, + pub tickets_used: Option, +} + +/// A log user winnings +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Copy, Default)] +pub struct UserRewardsLog { + pub round: u64, + pub tickets: Uint128, + pub ticket_price: Uint128, + pub rewards_per_tier: Option, + pub liquidity: Option, + pub total_amount_won: Option, + pub total_exp_gained: Option, +} +/// A log of user winnings -> per tier +/// UserRewardsLog -> TierLog +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Copy, Default)] +pub struct TierLog { + pub tier_5: RewardsPerTierInfo, + pub tier_4: RewardsPerTierInfo, + pub tier_3: RewardsPerTierInfo, + pub tier_2: RewardsPerTierInfo, + pub tier_1: RewardsPerTierInfo, + pub tier_0: RewardsPerTierInfo, +} + +/// A log user winnings -> per tier -> Information +/// UserRewardsLog -> TierLog -> RewardsPerTierInfo +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Copy, Default)] +pub struct RewardsPerTierInfo { + pub num_of_rewards_claimed: Uint128, + pub reward_per_match: Uint128, +} + +//////////////////////////////////////////////////////////////// SPONOSR INFO AND STATE ////////////////////////////////////////////////////////////////// + +/// State of Sponsor information +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Default)] +pub struct SponsorInfo { + pub amount_sponsored: Uint128, + pub amount_withdrawable: Uint128, + pub amount_unbonding: Uint128, + pub title: Option, + pub message: Option, + /// index of the sponsors in storage + pub addr_list_index: Option, + pub unbonding_batches: Vec, + pub has_requested: bool, +} + +/// To avoid spam contract will only allow inspected titles and messages. +/// This struct contains a list of all the titles/messages that sponsors have requested to display +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] +pub struct GlobalSponsorDisplayRequestListState { + pub addr: String, + pub index: Option, + pub deque_store_index: Option, + pub title: Option, + pub message: Option, +} + +// Helps provide a unique id to a sponsor +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] +pub struct GlobalSponsorState { + //total sponsors are offset minus # of empty_slots + pub offset: u32, + pub empty_slots: Vec, +} + +//////////////////////////////////////////////////////////////// ADMIN ////////////////////////////////////////////////////////////////// + +/// state of amount available for admins to withdraw +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Default)] +pub struct AdminWithdraw { + pub amount_withdrawable: Uint128, +} + +/// Information about specific nth unbonding batch +#[derive(Serialize, Debug, Deserialize, Clone, PartialEq)] +pub struct UnbondingBatch { + pub unbonding_time: Option, + pub amount: Option, +} + +//////////////////////////////////////////////////////////////// USER + SPONSOR + ADMIN ////////////////////////////////////////////////////////////////// + +/// Request withdraw struct +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Copy)] +pub struct RequestWithdraw { + pub amount: Uint128, + pub unbonding_batch_index: u64, + pub approximate_unbonding_time: u64, +} + +//Use as value holder when claiming rewards +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Copy)] +pub struct TierCounter { + pub tier_5: Uint128, + pub tier_4: Uint128, + pub tier_3: Uint128, + pub tier_2: Uint128, + pub tier_1: Uint128, + pub tier_0: Uint128, +} diff --git a/contracts/galactic_pools/pools/native/src/unit_test.rs b/contracts/galactic_pools/pools/native/src/unit_test.rs new file mode 100644 index 000000000..9b134e155 --- /dev/null +++ b/contracts/galactic_pools/pools/native/src/unit_test.rs @@ -0,0 +1,6589 @@ +#[cfg(test)] +mod tests { + use crate::{ + constants::*, + contract::{execute, instantiate, query}, + helper::*, + msg::{ + ContractConfigResponse, + ContractStatus, + ContractStatusResponse, + CurrentRewardsResponse, + DelegatedResponse, + GalacticPoolsPermissions, + HandleAnswer, + HandleMsg, + InstantiateMsg, + LiquidityResponse, + PoolStateInfoResponse, + PoolStateLiquidityStatsResponse, + QueryMsg, + QueryWithPermit, + RecordsResponse, + RemoveSponsorCredentialsDecisions, + ResponseStatus, + ResponseStatus::Success, + Review, + RoundResponse, + SponsorMessageReqResponse, + SponsorsResponse, + UnbondingsResponse, + ValidatorInfo, + ViewingKeyErrorResponse, + WithdrawablelResponse, + }, + rand::sha_256, + state::*, + viewing_key::{ViewingKey, VIEWING_KEY_SIZE}, + }; + use c_std::{ + coin, + coins, + from_binary, + testing::{mock_env, mock_info, *}, + to_binary, + Addr, + Api, + Binary, + BlockInfo, + Coin, + ContractInfo, + Decimal, + DepsMut, + Empty, + Env, + FullDelegation, + OwnedDeps, + Response, + StdError, + StdResult, + Storage, + Timestamp, + TransactionInfo, + Uint128, + Validator, + }; + use s_toolkit::permit::{validate, Permit, PermitParams, PermitSignature, PubKey}; + use serde::{Deserialize, Serialize}; + use shade_protocol::{c_std, s_toolkit}; + use std::ops::{Add, AddAssign, Sub}; + + const REWARDS_RETURNED_FROM_VALIDATOR_PER_ACTION: u128 = 10u128; + const COMMON_DIVISOR: u64 = 10000; + const SCRT_TO_USCRT: u128 = 1000000; + + //////////////////////////////// Helper functions //////////////////////////////// + pub fn init_helper( + contract_balance: Option, + ) -> ( + Result, + OwnedDeps, + ) { + let mut deps = mock_dependencies_with_balance(&[Coin { + amount: Uint128::from(contract_balance.unwrap_or_default()), + denom: "uscrt".to_string(), + }]); + // let mut deps = mock_dependencies(); + let env = mock_env(); + + let common_divisor = 10000u64; + + let mut validator_vector: Vec = Vec::new(); + validator_vector.push(ValidatorInfo { + address: "galacticPools".to_string(), + weightage: (60 * common_divisor) / 100, + }); + validator_vector.push(ValidatorInfo { + address: "secureSecret".to_string(), + weightage: (20 * common_divisor) / 100, + }); + validator_vector.push(ValidatorInfo { + address: "xavierCapital".to_string(), + weightage: (20 * common_divisor) / 100, + }); + + let mut validators: Vec = Vec::new(); + + for validator_address in &validator_vector { + validators.push(Validator { + address: (validator_address.address.clone()), + commission: Decimal::percent(1), + max_commission: Decimal::percent(2), + max_change_rate: Decimal::percent(3), + }) + } + + let mut delegation_vec: Vec = Vec::new(); + // and another one on val2 + let delegation1 = FullDelegation { + delegator: deps.api.addr_validate("cosmos2contract").unwrap(), + validator: "galacticPools".to_string().clone(), + amount: coin(8888, "uscrt"), + can_redelegate: coin(4567, "uscrt"), + accumulated_rewards: coins(10 * SCRT_TO_USCRT, "uscrt"), + }; + delegation_vec.push(delegation1); + let delegation2 = FullDelegation { + delegator: deps.api.addr_validate("cosmos2contract").unwrap(), + validator: "secureSecret".to_string().clone(), + amount: coin(8888, "uscrt"), + can_redelegate: coin(4567, "uscrt"), + accumulated_rewards: coins(10 * SCRT_TO_USCRT, "uscrt"), + }; + delegation_vec.push(delegation2); + + let delegation3 = FullDelegation { + delegator: deps.api.addr_validate("cosmos2contract").unwrap(), + validator: "xavierCapital".to_string().clone(), + amount: coin(8888, "uscrt"), + can_redelegate: coin(4567, "uscrt"), + accumulated_rewards: coins(10 * SCRT_TO_USCRT, "uscrt"), + }; + delegation_vec.push(delegation3); + + let rewards_distribution: RewardsDistInfo = RewardsDistInfo { + tier_0: DistInfo { + total_number_of_winners: Uint128::from(1u128), + percentage_of_rewards: (20 * 10000) / 100, + }, + tier_1: DistInfo { + total_number_of_winners: Uint128::from(3u128), + percentage_of_rewards: (10 * 10000) / 100, + }, + tier_2: DistInfo { + total_number_of_winners: Uint128::from(9u128), + percentage_of_rewards: (14 * 10000) / 100, + }, + tier_3: DistInfo { + total_number_of_winners: Uint128::from(27u128), + percentage_of_rewards: (12 * 10000) / 100, + }, + tier_4: DistInfo { + total_number_of_winners: Uint128::from(81u128), + percentage_of_rewards: (19 * 10000) / 100, + }, + tier_5: DistInfo { + total_number_of_winners: Uint128::from(243u128), + percentage_of_rewards: (25 * 10000) / 100, + }, + }; + + deps.querier + .update_staking("uscrt", &validators, &delegation_vec); + + let init_msg = InstantiateMsg { + admins: Option::from(vec![Addr::unchecked("admin")]), + triggerers: Option::from(vec![Addr::unchecked("triggerer")]), + reviewers: Option::from(vec![Addr::unchecked("reviewer")]), + triggerer_share_percentage: (1 * common_divisor) / 100, //dividing by 1/100 * common_divisor + denom: "uscrt".to_string(), + prng_seed: Binary::from("I'm secretbatman".as_bytes()), + validator: validator_vector, + unbonding_duration: 3600 * 24 * 21, //21 days + round_duration: 3600 * 24 * 7, //7 days + rewards_distribution, + ticket_price: Uint128::from(1 * SCRT_TO_USCRT), + rewards_expiry_duration: 3888000, // 45 days + common_divisor, + total_admin_share: (10 * common_divisor) / 100, + shade_percentage_share: (60 * common_divisor) / 100, + galactic_pools_percentage_share: (40 * common_divisor) / 100, + shade_rewards_address: Addr::unchecked("shade"), + galactic_pools_rewards_address: Addr::unchecked("galactic_pools"), + reserve_percentage: (60 * common_divisor) / 100, + is_sponosorship_admin_controlled: false, + unbonding_batch_duration: 3600 * 24 * 3, + minimum_deposit_amount: None, + grand_prize_address: Addr::unchecked("grand_prize"), + /// setting number of number_of_tickers that can be run on txn send to avoid potential errors + number_of_tickers_per_transaction: Uint128::from(1000000u128), + sponsor_msg_edit_fee: Some(Uint128::from(1000000u128)), + exp_contract: None, + }; + + let mock_message_info = mock_info("", &[]); + + let init_results = instantiate(deps.as_mut(), env, mock_message_info, init_msg); + (init_results, deps) + } + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] + pub struct ValidatorRewards { + pub validator_address: String, + pub reward: Vec, + } + + /// Returns a default enviroment with height, time, chain_id, and contract address + /// You can submit as is to most contracts, or modify height/time if you want to + /// test for expiration. + /// + /// This is intended for use in test code only. + pub fn custom_mock_env( + height: Option, + time: Option, + chain_id: Option<&str>, + transaction_index: Option, + ) -> Env { + Env { + block: BlockInfo { + height: height.unwrap_or_default(), + time: Timestamp::from_seconds(time.unwrap_or_default()), + chain_id: chain_id.unwrap_or_default().to_string(), + random: None, + }, + transaction: Some(TransactionInfo { + index: transaction_index.unwrap_or_default(), + hash: String::new(), + }), + contract: ContractInfo { + address: Addr::unchecked(MOCK_CONTRACT_ADDR), + code_hash: "".to_string(), + }, + } + } + + pub fn deposits_filler_unit_test_helper( + contact_balance: Option, + ) -> OwnedDeps { + let (_init_result, mut deps) = init_helper(contact_balance); + + let round_obj = round_read_only_unit_test_helper(&deps.storage); + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(30000 * SCRT_TO_USCRT), + Some(0u64), + Some(round_obj.start_time), + "secretbatman", + None, + ); + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(5000 * (SCRT_TO_USCRT)), + Some(0u64), + Some(round_obj.start_time), + "Superman", + None, + ); + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(5000 * (SCRT_TO_USCRT)), + Some(0u64), + Some(round_obj.start_time), + "Spider-man", + None, + ); + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(5000 * (SCRT_TO_USCRT)), + Some(0u64), + Some(round_obj.start_time), + "Wonder-Women", + None, + ); + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(5000 * (SCRT_TO_USCRT)), + Some(0u64), + Some(round_obj.start_time), + "Thor", + None, + ); + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(5000 * (SCRT_TO_USCRT)), + Some(0u64), + Some(round_obj.start_time), + "Captain-America", + None, + ); + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(5000 * (SCRT_TO_USCRT)), + Some(0u64), + Some(round_obj.start_time), + "Ironman", + None, + ); + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(5000 * (SCRT_TO_USCRT)), + Some(0u64), + Some(round_obj.start_time), + "Loki", + None, + ); + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(5000 * (SCRT_TO_USCRT)), + Some(0u64), + Some(round_obj.start_time), + "Aqua-man", + None, + ); + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(30000 * (SCRT_TO_USCRT)), + Some(0u64), + Some((round_obj.start_time + round_obj.end_time) / 2), + "secretbatman", + None, + ); + + return deps; + } + + pub fn deposit_unit_test_helper( + mut_deps: DepsMut, + deposit_amount: Uint128, + height: Option, + time: Option, + deposit_sender: &str, + denom_default_uscrt: Option<&str>, + ) -> StdResult<()> { + let handle_msg = HandleMsg::Deposit {}; + let mut denom: &str = "uscrt"; + if denom_default_uscrt.is_some() { + denom = denom_default_uscrt.unwrap(); + } + + let handle_result = execute( + mut_deps, + custom_mock_env(height, time, None, None), + mock_info(deposit_sender, &[Coin { + amount: deposit_amount, + denom: denom.to_string(), + }]), + handle_msg, + ); + + if handle_result.is_err() { + return Err(handle_result.unwrap_err()); + } else { + Ok(()) + } + } + + pub fn request_withdraw_unit_test_helper( + mut_deps: DepsMut, + request_withdraw_amount: Uint128, + height: Option, + time: Option, + request_sender: &str, + ) -> StdResult<()> { + let handle_msg = HandleMsg::RequestWithdraw { + amount: request_withdraw_amount, + }; + + let handle_result = execute( + mut_deps, + custom_mock_env(height, time, None, None), + mock_info(request_sender, &[]), + handle_msg, + ); + + if handle_result.is_err() { + return Err(handle_result.unwrap_err()); + } else { + Ok(()) + } + } + + pub fn withdraw_unit_test_helper( + mut_deps: DepsMut, + withdraw_amount: Uint128, + height: Option, + time: Option, + request_sender: &str, + ) -> StdResult<()> { + let handle_msg = HandleMsg::Withdraw { + amount: withdraw_amount, + }; + + let handle_result = execute( + mut_deps, + custom_mock_env(height, time, None, None), + mock_info(request_sender, &[]), + handle_msg, + ); + + if handle_result.is_err() { + return Err(handle_result.unwrap_err()); + } else { + Ok(()) + } + } + + pub fn claim_rewards_unit_test_helper( + mut_deps: DepsMut, + height: Option, + time: Option, + request_sender: &str, + ) -> StdResult<()> { + let handle_msg = HandleMsg::ClaimRewards {}; + let handle_result = execute( + mut_deps, + custom_mock_env(height, time, None, None), + mock_info(request_sender, &[]), + handle_msg, + ); + + if handle_result.is_err() { + return Err(handle_result.unwrap_err()); + } else { + Ok(()) + } + } + + pub fn create_viewking_key_unit_test_helper( + mut_deps: DepsMut, + request_sender: &str, + ) -> StdResult<()> { + let handle_msg = HandleMsg::CreateViewingKey { + entropy: "".to_string(), + }; + let handle_result = execute( + mut_deps, + custom_mock_env(None, None, None, None), + mock_info(request_sender, &[]), + handle_msg, + ); + + if handle_result.is_err() { + return Err(handle_result.unwrap_err()); + } else { + Ok(()) + } + } + + pub fn set_viewing_key_unit_test_helper( + mut_deps: DepsMut, + request_sender: &str, + key: &str, + ) -> StdResult<()> { + // Set VK + let handle_msg = HandleMsg::SetViewingKey { + key: key.to_string(), + }; + let handle_result = execute( + mut_deps, + custom_mock_env(None, None, None, None), + mock_info(request_sender, &[]), + handle_msg, + ); + if handle_result.is_err() { + return Err(handle_result.unwrap_err()); + } else { + Ok(()) + } + } + + pub fn sponsor_unit_test_helper( + mut_deps: DepsMut, + deposit_amount: Uint128, + height: Option, + time: Option, + sponsor: &str, + denom_default_uscrt: Option<&str>, + ) -> StdResult<()> { + let handle_msg = HandleMsg::Sponsor { + title: None, + message: None, + }; + let mut denom: &str = "uscrt"; + if denom_default_uscrt.is_some() { + denom = denom_default_uscrt.unwrap(); + } + + let handle_result = execute( + mut_deps, + custom_mock_env(height, time, None, None), + mock_info(sponsor, &[Coin { + amount: deposit_amount, + denom: denom.to_string(), + }]), + handle_msg, + ); + + if handle_result.is_err() { + return Err(handle_result.unwrap_err()); + } else { + Ok(()) + } + } + + pub fn sponsor_request_withdraw_unit_test_helper( + mut_deps: DepsMut, + request_withdraw_amount: Uint128, + height: Option, + time: Option, + request_sender: &str, + ) -> StdResult<()> { + let handle_msg = HandleMsg::SponsorRequestWithdraw { + amount: request_withdraw_amount, + }; + + let handle_result = execute( + mut_deps, + custom_mock_env(height, time, None, None), + mock_info(request_sender, &[]), + handle_msg, + ); + + if handle_result.is_err() { + return Err(handle_result.unwrap_err()); + } else { + Ok(()) + } + } + + pub fn sponsor_withdraw_unit_test_helper( + mut_deps: DepsMut, + withdraw_amount: Uint128, + height: Option, + time: Option, + request_sender: &str, + ) -> StdResult<()> { + let handle_msg = HandleMsg::SponsorWithdraw { + amount: withdraw_amount, + }; + + let handle_result = execute( + mut_deps, + custom_mock_env(height, time, None, None), + mock_info(request_sender, &[]), + handle_msg, + ); + + if handle_result.is_err() { + return Err(handle_result.unwrap_err()); + } else { + Ok(()) + } + } + + pub fn end_round_unit_test_helper(deps: DepsMut) -> StdResult { + let round_obj = round_read_only_unit_test_helper(deps.storage); + + let handle_msg = HandleMsg::EndRound {}; + let mocked_env = custom_mock_env(None, Some(round_obj.end_time), None, None); + let mocked_info = mock_info("triggerer", &[]); + + let handle_result = execute(deps, mocked_env, mocked_info, handle_msg); + + if handle_result.is_err() { + return Err(handle_result.unwrap_err()); + } else { + Ok(handle_result.unwrap()) + } + } + + // //////////////////////////////// Readonly State Helper functions //////////////////////////////// + pub fn user_info_read_only_unit_test_helper( + user: &Addr, + deps_storage: &dyn Storage, + ) -> UserInfo { + let user_info_obj = USER_INFO_STORE + .load(deps_storage, user) + .unwrap_or(UserInfo { + amount_delegated: Uint128::zero(), + amount_withdrawable: Uint128::zero(), + starting_round: None, + total_won: Uint128::zero(), + last_claim_rewards_round: None, + amount_unbonding: Uint128::zero(), + unbonding_batches: Vec::new(), + }); + + return user_info_obj; + } + + pub fn user_liquidity_stats_read_only_unit_test_helper( + user: &Addr, + deps_storage: &dyn Storage, + round_index: u64, + ) -> UserLiqState { + let user_liquidity_snapshot_obj = USER_LIQUIDITY_STATS_STORE + .load(deps_storage, (user, round_index)) + .unwrap_or(UserLiqState { + amount_delegated: None, + liquidity: None, + tickets_used: None, + }); + user_liquidity_snapshot_obj + } + + pub fn config_read_only_unit_test_helper(deps_storage: &dyn Storage) -> ConfigInfo { + CONFIG_STORE.load(deps_storage).unwrap() + } + + pub fn pool_state_read_only_unit_test_helper(deps_storage: &dyn Storage) -> PoolState { + POOL_STATE_STORE.load(deps_storage).unwrap() + } + + pub fn pool_state_liquidity_snapshot_read_only_unit_test_helper( + deps_storage: &dyn Storage, + current_round_index: u64, + ) -> PoolLiqState { + let pool_state_liquidity_snapshot_obj: PoolLiqState = POOL_STATE_LIQUIDITY_STATS_STORE + .load(deps_storage, current_round_index) + .unwrap_or(PoolLiqState { + total_delegated: None, + total_liquidity: None, + }); + pool_state_liquidity_snapshot_obj + } + + pub fn round_read_only_unit_test_helper(deps_storage: &dyn Storage) -> RoundInfo { + ROUND_STORE.load(deps_storage).unwrap() + } + + pub fn rewards_stats_for_nth_round_read_only_unit_test_helper( + deps_storage: &dyn Storage, + round: u64, + ) -> RewardsState { + let reward_stats_for_nth_round = REWARDS_STATS_FOR_NTH_ROUND_STORE + .load(deps_storage, round) + .unwrap_or(Default::default()); + + reward_stats_for_nth_round + } + + pub fn sponsor_info_unit_test_read_only_helper( + deps_storage: &dyn Storage, + sender: &Addr, + ) -> StdResult { + let sponsor_info_obj = SPONSOR_INFO_STORE + .load(deps_storage, sender) + .unwrap_or_default(); + + return Ok(sponsor_info_obj); + } + + pub fn sponsor_state_unit_test_read_only_helper( + deps_storage: &dyn Storage, + ) -> StdResult { + return Ok(SPONSOR_STATS_STORE.load(deps_storage)?); + } + + ////////////////////////////////////// Tests ////////////////////////////////////// + ////////////////////////////////////// User Tests ////////////////////////////////////// + + #[test] + fn test_init() -> StdResult<()> { + // test default + let (init_result, deps) = init_helper(None); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config.admins[0], Addr::unchecked("admin")); + + assert_eq!(config.triggerers[0], Addr::unchecked("triggerer")); + + assert_eq!(config.common_divisor, COMMON_DIVISOR); + assert_eq!(config.denom, "uscrt".to_string()); + let prng_seed_hashed = sha_256(&Binary::from("I'm secretbatman".as_bytes()).0); + + assert_eq!(config.prng_seed, prng_seed_hashed.to_vec()); + assert_eq!(config.contract_address, Addr::unchecked(MOCK_CONTRACT_ADDR)); + assert_eq!(config.validators[0].address, "galacticPools".to_string()); + assert_eq!(config.validators[1].address, "secureSecret".to_string()); + assert_eq!(config.validators[2].address, "xavierCapital".to_string()); + assert_eq!(config.unbonding_duration, 3600 * 24 * 21); // Seconds in 21 days + assert_eq!(config.minimum_deposit_amount, None); + assert_eq!(config.status, ContractStatus::Normal.to_u8()); + + let pool_state_obj = pool_state_read_only_unit_test_helper(&deps.storage); + assert_eq!(pool_state_obj.total_delegated, Uint128::zero()); + assert_eq!(pool_state_obj.rewards_returned_to_contract, Uint128::zero()); + assert_eq!(pool_state_obj.total_reserves, Uint128::zero()); + assert_eq!(pool_state_obj.total_sponsored, Uint128::zero()); + + let round_obj = round_read_only_unit_test_helper(&deps.storage); + assert_eq!( + round_obj.entropy, + sha_256(&sha_256(&Binary::from("I'm secretbatman".as_bytes()).0).to_vec()) + ); + assert_eq!( + round_obj.seed, + sha_256(&Binary::from("I'm secretbatman".as_bytes()).0).to_vec() + ); + assert_eq!(round_obj.start_time, 1571797419u64); + assert_eq!(round_obj.end_time, 1571797419 + 3600 * 24 * 7); // Seconds in 7 days + assert_eq!(round_obj.current_round_index, 1u64); + assert_eq!(round_obj.ticket_price, Uint128::from(1u128 * SCRT_TO_USCRT)); // 1 SCRT + assert_eq!(round_obj.rewards_expiry_duration, 3600 * 24 * 45); // Seconds in 45 days + assert_eq!( + round_obj.admin_share.shade_percentage_share, + (60 * COMMON_DIVISOR) / 100 + ); + assert_eq!( + round_obj.admin_share.galactic_pools_percentage_share, + (40 * COMMON_DIVISOR) / 100 + ); + assert_eq!( + round_obj.triggerer_share_percentage, + (1 * COMMON_DIVISOR) / 100 + ); + assert_eq!(round_obj.shade_rewards_address, Addr::unchecked("shade")); + assert_eq!( + round_obj.galactic_pools_rewards_address, + Addr::unchecked("galactic_pools") + ); + assert_eq!(round_obj.unclaimed_rewards_last_claimed_round, None); + + Ok(()) + } + + #[test] + fn test_deposit() -> StdResult<()> { + //0) Checking validity of the deposit + //0.1) Error Check: Denom send must me uscrt + //0.2) Error Check: Minimum amount must be 1 uscrt + //0.3) Error Check: Minimum amount must be 1 scrt or 1000000 uscrt + //0.4) Checking: Maximum possible amount that can be deposited + let (_init_result, mut deps) = init_helper(None); + //0.1) Error Check: Denom send must me uscrt + let deposit_results = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(30000 * SCRT_TO_USCRT), + None, + None, + "secretbatman", + Some("uatom"), + ) + .unwrap_err(); + assert_eq!( + deposit_results, + StdError::generic_err("Wrong token given, expected uscrt found uatom") + ); + //0.2) Error Check: Minimum amount must be 1 uscrt + let deposit_results = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(0u128), + None, + None, + "secretbatman", + Some("uscrt"), + ) + .unwrap_err(); + assert_eq!( + deposit_results, + StdError::generic_err("Must deposit atleast one uscrt") + ); + //0.3) Error Check: Minimum amount must be 1 scrt or 1000000 uscrt + let handle_msg = HandleMsg::UpdateConfig { + unbonding_batch_duration: None, + unbonding_duration: None, + minimum_deposit_amount: Some(Uint128::from(1 * SCRT_TO_USCRT)), + exp_contract: None, + }; + let _handle_result = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("admin", &[]), + handle_msg, + ) + .unwrap(); + let deposit_results = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(30000u128), + None, + None, + "secretbatman", + Some("uscrt"), + ) + .unwrap_err(); + assert_eq!( + deposit_results, + StdError::generic_err("Must deposit a minimum of 1000000 uscrt",) + ); + //0.2) Checking: Maximum possible amount that can be deposited + //340282366920938463463374607431768211455 39 Digits u128 + //18446744073709551615 20 digits u64 + //200*MILLION*1000000(to uscrt) 100,000,000,000,000 total uscrt in secret Network - 16 digits + //340282366920938463463374607431768 is the largest possible number user can deposit. + let (_init_result, mut deps) = init_helper(None); + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(340282366920938463463374607431768u128), + Some(0), + Some(0), + "secretbatman", + Some("uscrt"), + )?; + let user = user_info_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + &deps.storage, + ); + assert_eq!( + user.amount_delegated, + Uint128::from(340282366920938463463374607431768u128) + ); + + //1) Checking user + //1.1) Amount Delegated + //1.2) Liquidity Provided + //1.3) Amount Delegated and liquidity Provided after the end lottery time + let (_init_result, deps) = init_helper(None); + let round_obj = round_read_only_unit_test_helper(&deps.storage); + //1.1) Calculating secretbatman's Delegated Amount. He delegated 30000 SCRT at t0 and 30000 SCRT at t1/2; so he delegated 60000 SCRT. + let mut deps = deposits_filler_unit_test_helper(None); + let user = user_info_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + deps.as_ref().storage, + ); + assert_eq!( + user.amount_delegated, + Uint128::from((30000 + 30000) * SCRT_TO_USCRT) + ); + //1.2) Calculating Liquidity Provided. + //30000+15000 since 30000 Scrt were delegated at the start and 30000 were delegated at the middle. + let user_liquidity_snapshot_obj = user_liquidity_stats_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + deps.as_ref().storage, + round_obj.current_round_index, + ); + assert_eq!( + user_liquidity_snapshot_obj.liquidity.unwrap(), + Uint128::from((30000 + 15000) * SCRT_TO_USCRT) + ); + //1.3) Amount Delegated and liquidity Provided after the end lottery time + let round_obj = round_read_only_unit_test_helper(deps.as_ref().storage); + deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(10000 * SCRT_TO_USCRT), + None, + Some(round_obj.end_time), + "secretbatman", + None, + )?; + let user = user_info_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + deps.as_ref().storage, + ); + assert_eq!( + user.amount_delegated, + Uint128::from((30000 + 30000 + 10000) * SCRT_TO_USCRT) + ); + let user_liquidity_snapshot_obj = user_liquidity_stats_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + deps.as_ref().storage, + round_obj.current_round_index, + ); + assert_eq!( + user_liquidity_snapshot_obj.liquidity.unwrap(), + Uint128::from((30000 + 15000 + 0) * SCRT_TO_USCRT) + ); + + //2) Checking: + //2.1) Total deposits - pool state + //2.2) Total liquidity provided by the users - pool state liquidity + //2.3) Total Rewards returned - pool state + //2.4) Total liquidity in pool state when no-deposits are made - pool state liquidity + //2.5) Total liquidity in pool state when deposits are made - pool state liquidity + let mut deps = deposits_filler_unit_test_helper(None); + let round_obj = round_read_only_unit_test_helper(deps.as_ref().storage); + //2.1) Total Amount delegated + //secretbatman 30000 @ t0 and 30000 @ t1/2. So 60000 SCRT. + //Superman 5000 @ t0 + //Spider-man 5000 @ t0 + //Wonder-Women 5000 @ t0 + //Thor 5000 @ t0 + //Captain-America 5000 @ t0 + //Iron-man 5000 @ t0 + //Loki 5000 @ t0 + //Aqua-man 5000 @ t0 + let pool_state = pool_state_read_only_unit_test_helper(deps.as_ref().storage); + assert_eq!( + pool_state.total_delegated, + Uint128::from(100000 * SCRT_TO_USCRT) + ); + //2.2) Total Liquidity provided + let pool_state_liquidity_snapshot_obj: PoolLiqState = + pool_state_liquidity_snapshot_read_only_unit_test_helper( + deps.as_ref().storage, + round_obj.current_round_index, + ); + //70000 were delegated at the start and 30000 were delegated at the middle. hence + assert_eq!( + pool_state_liquidity_snapshot_obj.total_liquidity.unwrap(), + Uint128::from((70000 + (30000 / 2)) * SCRT_TO_USCRT) + ); + //2.3) Checking: Total Rewards returned + //Rewards returned are usually variable. But for this test, we kept rewards constant. + assert_eq!( + pool_state.rewards_returned_to_contract, + Uint128::from(REWARDS_RETURNED_FROM_VALIDATOR_PER_ACTION * 10 * SCRT_TO_USCRT) + ); // 10 scrt on each deposit and a total of 10 deposits + + //Checking liquidity after + //2.4)No-deposit during round 2. So total liquidity equals total amount delegated. + end_round_unit_test_helper(deps.as_mut())?; //round 1 -> 2 liq:85000 + end_round_unit_test_helper(deps.as_mut())?; //round 2 -> 3 liq: 100000 + let round_obj = round_read_only_unit_test_helper(deps.as_ref().storage); + let pool_state_liquidity_snapshot_obj: PoolLiqState = + pool_state_liquidity_snapshot_read_only_unit_test_helper( + deps.as_ref().storage, + round_obj.current_round_index.sub(1u64), + ); + assert_eq!( + pool_state_liquidity_snapshot_obj.total_liquidity.unwrap(), + Uint128::from(100000 * SCRT_TO_USCRT) + ); + + //2.5)With a deposit during round + let round_obj = round_read_only_unit_test_helper(&deps.storage); + deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(10000 * SCRT_TO_USCRT), + None, + Some(round_obj.start_time), + "secretbatman", + None, + )?; + end_round_unit_test_helper(deps.as_mut())?; //round 3 -> 4 liq:110000 + let round_obj = round_read_only_unit_test_helper(&deps.storage); + let pool_state_liquidity_snapshot_obj: PoolLiqState = + pool_state_liquidity_snapshot_read_only_unit_test_helper( + &deps.storage, + round_obj.current_round_index.sub(1u64), + ); + assert_eq!( + pool_state_liquidity_snapshot_obj.total_liquidity.unwrap(), + Uint128::from((100000 + 10000) * SCRT_TO_USCRT) + ); + Ok(()) + } + + #[test] + fn test_request_withdraw() -> StdResult<()> { + //0) Checking validity of the deposit + //0.1) Checking what happens if user request_withdraw more than deposited + //0.2) Checking validity of the deposit + let mut deps = deposits_filler_unit_test_helper(None); + let res = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((150000 * SCRT_TO_USCRT) as u128), + None, + None, + "secretbatman", + ); + assert_eq!( + res.unwrap_err(), + StdError::generic_err(format!( + "insufficient funds to redeem: balance=60000000000, required=150000000000", + )) + ); + + //1) Checking USERINFO after requesting the amount + //1.1)Checking delegated amount after request_withdraw + let mut deps = deposits_filler_unit_test_helper(None); + let round_obj = round_read_only_unit_test_helper(&deps.storage); + let _ = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((50000 * SCRT_TO_USCRT) as u128), + None, + Some((round_obj.start_time + round_obj.end_time) / 2), + "secretbatman", + )?; + let user = user_info_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + &deps.storage, + ); + assert_eq!(user.amount_delegated.u128(), 10000 * SCRT_TO_USCRT); + //1.2) Checking unbonding information + assert_eq!(user.unbonding_batches[0], 1); + assert_eq!(user.unbonding_batches.len(), 1); + + //1.3) Calculating Liquidity Provided. + let user_liquidity_snapshot_obj = user_liquidity_stats_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + &deps.storage, + round_obj.current_round_index, + ); + assert_eq!( + user_liquidity_snapshot_obj.liquidity.unwrap(), + Uint128::from((45000 - 25000) * SCRT_TO_USCRT) + ); + + //2) Checking poolstate after request withdraw + let mut deps = deposits_filler_unit_test_helper(None); + let round_obj = round_read_only_unit_test_helper(&deps.storage); + + let pool_obj = pool_state_read_only_unit_test_helper(&deps.storage); + assert_eq!( + pool_obj.total_delegated, + Uint128::from(100000 * SCRT_TO_USCRT) + ); + assert_eq!( + pool_obj.rewards_returned_to_contract.u128(), + 100 * SCRT_TO_USCRT + ); + + //2.1) Checking poolstate & LiquidityStats after request withdraw + let _ = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((50000 * SCRT_TO_USCRT) as u128), + None, + Some((round_obj.start_time + round_obj.end_time) / 2), + "secretbatman", + )?; + let pool_obj = pool_state_read_only_unit_test_helper(&deps.storage); + assert_eq!( + pool_obj.total_delegated, + Uint128::from(50000 * SCRT_TO_USCRT) + ); + + //2.2) Total Liquidity provided + let pool_state_liquidity_snapshot_obj: PoolLiqState = + pool_state_liquidity_snapshot_read_only_unit_test_helper( + &deps.storage, + round_obj.current_round_index, + ); + assert_eq!( + pool_state_liquidity_snapshot_obj.total_liquidity.unwrap(), + Uint128::from((85000 - 25000) * SCRT_TO_USCRT) + ); + //2.3) Checking: Total Rewards returned + assert_eq!( + pool_obj.rewards_returned_to_contract.u128(), + 100 * SCRT_TO_USCRT + ); + + //3) Request Withdraw after round ends + let mut deps = deposits_filler_unit_test_helper(None); + let round_obj = round_read_only_unit_test_helper(&deps.storage); + let _ = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((50000 * SCRT_TO_USCRT) as u128), + None, + Some(round_obj.end_time), + "secretbatman", + )?; + let user = user_info_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + &deps.storage, + ); + assert_eq!(user.amount_delegated.u128(), 10000 * SCRT_TO_USCRT); + //3.2) Checking unbonding information + assert_eq!(user.unbonding_batches[0], 1); + assert_eq!(user.unbonding_batches.len(), 1); + + //3.3) Calculating Liquidity Provided. + let user_liquidity_snapshot_obj = user_liquidity_stats_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + &deps.storage, + round_obj.current_round_index, + ); + // Liquidity will not change + assert_eq!( + user_liquidity_snapshot_obj.liquidity.unwrap(), + Uint128::from((45000) * SCRT_TO_USCRT) + ); + + //Checking the validator stats + // Done in test_validator_walk_through + Ok(()) + } + + #[test] + fn test_withdraw() -> StdResult<()> { + //1)Checking: Withdraw more than contract balance + let (_init_result, mut deps) = init_helper(Some(60000000)); + //Deposit + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(70 * SCRT_TO_USCRT), + None, + None, + "secretbatman", + None, + ); + //Request withdraw + let _ = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((70 * SCRT_TO_USCRT) as u128), + None, + None, + "secretbatman", + )?; + let user = user_info_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + &deps.storage, + ); + assert_eq!(user.unbonding_batches.len(), 1); + assert_eq!(user.unbonding_batches[0], 1); + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + )?; + //1.1) Withdraw + let res = withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((70 * SCRT_TO_USCRT) as u128), + None, + None, + "secretbatman", + ); + assert_eq!( + res.unwrap_err(), + StdError::generic_err("Trying to withdraw more than available") + ); + + //2)Error Check: Amount available for withdraw is less than withdraw amount + let (_init_result, mut deps) = init_helper(Some(800 * SCRT_TO_USCRT)); + + //Deposit + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(100 * SCRT_TO_USCRT), + None, + None, + "secretbatman", + None, + )?; + //Request withdraw + let _ = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((60 * SCRT_TO_USCRT) as u128), + None, + None, + "secretbatman", + )?; + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + )?; + //2.1)Withdraw + let res = withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((70 * SCRT_TO_USCRT) as u128), + None, + Some(config.next_unbonding_batch_time + config.unbonding_batch_duration), + "secretbatman", + ); + assert_eq!( + res.unwrap_err(), + StdError::generic_err("Trying to withdraw more than available") + ); + + //3)Error Check: Amount available for withdraw is less than withdraw amount + let (_init_result, mut deps) = init_helper(Some(800 * SCRT_TO_USCRT)); + //Deposit + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(100 * SCRT_TO_USCRT), + None, + None, + "secretbatman", + None, + )?; + //Request withdraw + let _ = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((50 * SCRT_TO_USCRT) as u128), + None, + None, + "secretbatman", + )?; + let _ = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((50 * SCRT_TO_USCRT) as u128), + None, + Some(10), + "secretbatman", + )?; + let user = user_info_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + &deps.storage, + ); + assert_eq!(user.unbonding_batches.len(), 1); + assert_eq!(user.unbonding_batches[0], 1); + + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + )?; + //3.1)Withdraw + let res = withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((60 * SCRT_TO_USCRT) as u128), + None, + Some(config.next_unbonding_batch_time + config.unbonding_batch_duration), + "secretbatman", + ); + assert_eq!( + res.unwrap_err(), + StdError::generic_err("Trying to withdraw more than available") + ); + + //4) Checking user obj + let (_init_result, mut deps) = init_helper(Some(100 * SCRT_TO_USCRT)); + //Deposit + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(100 * SCRT_TO_USCRT), + None, + None, + "secretbatman", + None, + )?; + //Request withdraw + let _ = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((100 * SCRT_TO_USCRT) as u128), + None, + None, + "secretbatman", + )?; + //4.1) user obj check after request withdraw + let user = user_info_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + &deps.storage, + ); + assert_eq!(user.unbonding_batches.len(), 1); + assert_eq!(user.unbonding_batches[0], 1); + + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + )?; + + //Withdraw + let config = config_read_only_unit_test_helper(&deps.storage); + let _ = withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((100 * SCRT_TO_USCRT) as u128), + None, + Some(config.next_unbonding_batch_time + config.unbonding_duration), + "secretbatman", + )?; + //4.2) user obj check after withdraw + let user = user_info_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + &deps.storage, + ); + assert_eq!(user.unbonding_batches.len(), 0); + assert_eq!(user.amount_delegated, Uint128::from(0 * SCRT_TO_USCRT)); + + Ok(()) + } + + #[test] + fn test_claim_rewards() -> StdResult<()> { + //1) Testing the checks used + //1.1) Error Check: When user just deposited right this round + //1.2) Error Check: When user just claimed previous round and this round has not end yet + //1) Testing the checks used + let mut deps = deposits_filler_unit_test_helper(None); + //1.1) Error Check: When user just deposited right this round + let res = + claim_rewards_unit_test_helper(deps.as_mut(), None, Some(1814400), "secretbatman"); + assert_eq!( + res.unwrap_err(), + StdError::generic_err(format!( + "You are not yet able to claim rewards. Wait for this round to end" + )) + ); + //1.2) Error Check: When user just claimed previous round and this round has not end yet + let _ = end_round_unit_test_helper(deps.as_mut()); + //1.2.1)Claimed first time + let round_obj = round_read_only_unit_test_helper(&mut deps.storage); + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secretbatman", + ); + //1.2.2)Trying to claim again in the same round + let res = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secretbatman", + ); + assert_eq!( + res.unwrap_err(), + StdError::generic_err(format!("You claimed recently!. Wait for this round to end")) + ); + + //2)Checking if the round just expired. + let mut deps = deposits_filler_unit_test_helper(None); + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-1 day-0 + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-2 day-7 + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-3 day-14 + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-4 day-21 + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-5 day-28 + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-6 day-35 + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-7 day-49 Round 1 expired at day 45. + // So round 1 will be skipped when rewards are claimed + let round_obj = round_read_only_unit_test_helper(deps.as_ref().storage); + let rewards_stats_for_nth_round_obj = + rewards_stats_for_nth_round_read_only_unit_test_helper(&deps.storage, 1); + assert!( + round_obj.end_time + >= rewards_stats_for_nth_round_obj + .rewards_expiration_date + .unwrap() + ); + + //2) Checking liquidity + //2.1) User deposits once and didn't deposit for few rounds. But keeps claiming rewards + let mut deps = deposits_filler_unit_test_helper(None); //round-1 + let _ = end_round_unit_test_helper(deps.as_mut()); //round-1 + let user_liquidity_snapshot_obj = user_liquidity_stats_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + &deps.storage, + 1, + ); + assert_eq!( + user_liquidity_snapshot_obj.liquidity.unwrap().u128(), + 45000 * SCRT_TO_USCRT + ); + + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-2 + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-3 + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-4 + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-5 + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-6 + let user_liquidity_snapshot_obj = user_liquidity_stats_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + &deps.storage, + 6, + ); + assert!(user_liquidity_snapshot_obj.liquidity.is_none()); //Since no deposit are made user liquidity is zero for round 6 until user deposits/withdraw or claim rewards + + let round_obj = round_read_only_unit_test_helper(&mut deps.storage); + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(10000 * SCRT_TO_USCRT), + None, + Some((round_obj.start_time + round_obj.end_time) / 2), + "secretbatman", + None, + ); + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-7 + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-8 + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-9 + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-10 + let round_obj = round_read_only_unit_test_helper(deps.as_ref().storage); + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secretbatman", + ); + let user_liquidity_snapshot_obj = user_liquidity_stats_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + &deps.storage, + 10, + ); + assert_eq!( + user_liquidity_snapshot_obj.liquidity.unwrap().u128(), + 70000 * SCRT_TO_USCRT + ); + + //2.2) User deposits once in round-1 and request withdraw all in round 1 as well. Then try claiming in far future round + let mut deps = deposits_filler_unit_test_helper(None); //round-1 + let round_obj = round_read_only_unit_test_helper(&mut deps.storage); + let _ = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((60000 * SCRT_TO_USCRT) as u128), + None, + Some((round_obj.start_time + round_obj.end_time) / 2), + "secretbatman", + )?; + for _ in 1..6 { + let _ = end_round_unit_test_helper(deps.as_mut())?; + //round-1 to 5 + } + + let round_obj = round_read_only_unit_test_helper(&mut deps.storage); + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secretbatman", + ); + for _ in 6..10 { + let _ = end_round_unit_test_helper(deps.as_mut())?; + //round-6 to 9 + } + //User rewards log will exist for round-1 but not after that round + // for i in 2..10 { + // let user_rewards_log_obj = user_rewards_log_unit_test_helper( + // &deps.storage, + // Uint128(i), + // HumanAddr("secretbatman".to_string()), + // ); + // assert!(user_rewards_log_obj.liquidity.is_none()); + // } + + let round_obj = round_read_only_unit_test_helper(&mut deps.storage); + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(10000 * SCRT_TO_USCRT), + None, + Some(round_obj.start_time), + "secretbatman", + None, + )?; + let round_obj = round_read_only_unit_test_helper(&mut deps.storage); + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secretbatman", + )?; + let user_liquidity_snapshot_obj = user_liquidity_stats_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + &deps.storage, + 10, + ); + assert_eq!( + user_liquidity_snapshot_obj.liquidity.unwrap().u128(), + 10000 * SCRT_TO_USCRT + ); + + //starts round + let (_init_result, mut deps) = init_helper(None); + let round_obj = round_read_only_unit_test_helper(&mut deps.storage); + //deposit 2 million scrt at t0 + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(2000000 * SCRT_TO_USCRT), + Some(0u64), + Some(round_obj.start_time), + "secretbatman", + None, + ); + //end_round x 2 + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-1 + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-1 + + //claim_rewards + // must take 4 iterations + // 1- last claim round == None + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secretbatman", + )?; + let user_info = + user_info_read_only_unit_test_helper(&Addr::unchecked("secretbatman"), &deps.storage); + assert_eq!(user_info.last_claim_rewards_round, None); + + let user_liquidity_snapshot_obj = user_liquidity_stats_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + &deps.storage, + 1, + ); + assert_eq!( + user_liquidity_snapshot_obj.tickets_used.unwrap().u128(), + 1000000 + ); + // 2- last claim round == 1 + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secretbatman", + )?; + let user_info = + user_info_read_only_unit_test_helper(&Addr::unchecked("secretbatman"), &deps.storage); + assert_eq!(user_info.last_claim_rewards_round.unwrap(), 1); + + let user_liquidity_snapshot_obj = user_liquidity_stats_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + &deps.storage, + 1, + ); + assert_eq!( + user_liquidity_snapshot_obj.tickets_used.unwrap().u128(), + 2000000 + ); + + // 3- last claim round == 1 + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secretbatman", + )?; + let user_info = + user_info_read_only_unit_test_helper(&Addr::unchecked("secretbatman"), &deps.storage); + assert_eq!(user_info.last_claim_rewards_round.unwrap(), 1); + + let user_liquidity_snapshot_obj = user_liquidity_stats_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + &deps.storage, + 2, + ); + assert_eq!( + user_liquidity_snapshot_obj.tickets_used.unwrap().u128(), + 1000000 + ); + // 4- last claim round == 2` + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secretbatman", + )?; + let user_info = + user_info_read_only_unit_test_helper(&Addr::unchecked("secretbatman"), &deps.storage); + + assert_eq!(user_info.last_claim_rewards_round.unwrap(), 2); + + let user_liquidity_snapshot_obj = user_liquidity_stats_read_only_unit_test_helper( + &deps.api.addr_validate("secretbatman")?, + &deps.storage, + 2, + ); + assert_eq!( + user_liquidity_snapshot_obj.tickets_used.unwrap().u128(), + 2000000 + ); + + //Checking + let (_init_result, mut deps) = init_helper(None); + let round_obj = round_read_only_unit_test_helper(&mut deps.storage); + //deposit 2 million scrt at t0 + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(2000000 * SCRT_TO_USCRT), + Some(0u64), + Some(round_obj.start_time), + "secretbatman", + None, + ); + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-1 + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some( + round_obj + .end_time + .add(round_obj.rewards_expiry_duration + 1), + ), + "secretbatman", + )?; + + let round_obj = round_read_only_unit_test_helper(&mut deps.storage); + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-2 + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some( + round_obj + .end_time + .add(round_obj.rewards_expiry_duration + 1), + ), + "secretbatman", + )?; + let round_obj = round_read_only_unit_test_helper(&mut deps.storage); + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-3 + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secretbatman", + )?; + assert_eq!( + user_liquidity_snapshot_obj.liquidity.unwrap().u128(), + 2000000 * SCRT_TO_USCRT + ); + let round_obj = round_read_only_unit_test_helper(&mut deps.storage); + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-4 + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secretbatman", + )?; + let round_obj = round_read_only_unit_test_helper(&mut deps.storage); + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-5 + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secretbatman", + )?; + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secretbatman", + )?; + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secretbatman", + )?; + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secretbatman", + )?; + + Ok(()) + } + + #[test] + fn test_create_viewing_key() -> StdResult<()> { + let (init_result, mut deps) = init_helper(None); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + // Checking the CreateViewingKey + let handle_msg = HandleMsg::CreateViewingKey { + entropy: "".to_string(), + }; + let mock_info = mock_info("secretbatman", &[]); + + let handle_result = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info.clone(), + handle_msg, + ); + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + let answer: HandleAnswer = from_binary(&handle_result?.data.unwrap())?; + // Checking if the viewing key matches + let key = match answer { + HandleAnswer::CreateViewingKey { key } => key, + _ => panic!("NOPE"), + }; + let saved_vk = read_viewing_key(&deps.storage, &mock_info.sender).unwrap(); + assert!(key.check_viewing_key(saved_vk.as_slice())); + + Ok(()) + } + + #[test] + fn test_set_viewing_key() -> StdResult<()> { + let (init_result, mut deps) = init_helper(None); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + // Setting VK + let handle_msg = HandleMsg::SetViewingKey { + key: "hi lol".to_string(), + }; + let mock_info = mock_info("bob", &[]); + + let handle_result = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info.clone(), + handle_msg, + ); + let unwrapped_result: HandleAnswer = from_binary(&handle_result?.data.unwrap())?; + assert_eq!( + to_binary(&unwrapped_result)?, + to_binary(&HandleAnswer::SetViewingKey { + status: ResponseStatus::Success + })?, + ); + + // Set valid VK + let actual_vk = ViewingKey("x".to_string().repeat(VIEWING_KEY_SIZE)); + let handle_msg = HandleMsg::SetViewingKey { + key: actual_vk.to_string().clone(), + }; + + let handle_result = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info.clone(), + handle_msg, + ); + let unwrapped_result: HandleAnswer = from_binary(&handle_result?.data.unwrap())?; + assert_eq!( + to_binary(&unwrapped_result)?, + to_binary(&HandleAnswer::SetViewingKey { status: Success })?, + ); + let saved_vk = read_viewing_key(&deps.storage, &mock_info.sender).unwrap(); + //Checking set viewing key + assert!(actual_vk.check_viewing_key(&saved_vk)); + Ok(()) + } + + //Revoke Permit has a bug in secret-toolkit waiting for it to be fixed. + //TODO comments + #[test] + fn test_revoke_permit() -> StdResult<()> { + let (init_result, mut deps) = init_helper(None); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = HandleMsg::RevokePermit { + permit_name: "galactic_pools_batman".to_string(), + }; + let mock_info = mock_info("secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", &[]); + + let handle_result = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info.clone(), + handle_msg, + ); + let unwrapped_result: HandleAnswer = from_binary(&handle_result?.data.unwrap())?; + assert_eq!( + to_binary(&unwrapped_result)?, + to_binary(&HandleAnswer::RevokePermit { + status: ResponseStatus::Success + })?, + ); + + //2) Checking the results of the query when permission is owner + + let token = "cosmos2contract".to_string(); + let permit: Permit = Permit { + params: PermitParams { + allowed_tokens: vec![token.clone()], + permit_name: "galactic_pools_batman".to_string(), + chain_id: "pulsar-2".to_string(), + permissions: vec![GalacticPoolsPermissions::Owner], + }, + signature: PermitSignature { + pub_key: PubKey { + r#type: "tendermint/PubKeySecp256k1".to_string(), + value: Binary::from_base64("AihRsQtwF56zrW7J1VP4KqTNBF5GTMXoFusm6zfIixvD")?, + }, + signature: Binary::from_base64( + "q7MQVPXCwA89cBMl/dCQhZ87dxzrNhxlQUUEznf4JvhWluAhRaNvblSofu79lYGUJ0+mfH1KMCsmF+kkARHYpQ==", + )?, + }, + }; + + let query_msg = QueryMsg::WithPermit { + permit, + query: QueryWithPermit::Delegated {}, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg); + assert_eq!( + query_result.unwrap_err(), + StdError::generic_err(format!( + "Permit \"galactic_pools_batman\" was revoked by account \"secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7\"" + )) + ); + + Ok(()) + } + + ////////////////////////////////////// Sponsors ////////////////////////////////////// + #[test] + fn test_sponsor() -> StdResult<()> { + //Initializing + let (_, mut deps) = init_helper(None); + + //0) Error Check: Denom send must me uscrt + let deposit_results = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(30000 * SCRT_TO_USCRT), + None, + None, + "secretbatman", + Some("uatom"), + ) + .unwrap_err(); + assert_eq!( + deposit_results, + StdError::generic_err("Wrong token given, expected uscrt found uatom") + ); + + //1)Sponsoring + let handle_msg = HandleMsg::Sponsor { + title: None, + message: None, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretrichierich", &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + //1.1)Checking pool state + let pool_state = pool_state_read_only_unit_test_helper(&deps.storage); + assert_eq!( + pool_state.total_sponsored, + Uint128::from(10000 * SCRT_TO_USCRT) + ); + + //1.2)Total amount deposited and sponsored should be equal to 100,000 SCRT (delegated) and 10,000 SCRT(Sponsored). + let config_obj = config_read_only_unit_test_helper(&deps.storage); + let mut total_deposited_and_sponsored = Uint128::zero(); + for val in config_obj.validators { + total_deposited_and_sponsored.add_assign(val.delegated); + } + assert_eq!( + total_deposited_and_sponsored, + Uint128::from((10000) * SCRT_TO_USCRT) + ); + + let sponsor_info_obj = sponsor_info_unit_test_read_only_helper( + &deps.storage, + &deps.api.addr_validate("secretrichierich")?, + )?; + assert_eq!( + sponsor_info_obj.amount_sponsored, + Uint128::from(10000 * SCRT_TO_USCRT) + ); + + //2) Checking global request list and global sponsors list + //2.1)Initializing + let (_, mut deps) = init_helper(None); + //2.2) + //2.2.1)Sponsoring -> User 1 + let handle_msg = HandleMsg::Sponsor { + title: None, + message: None, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretuser1", &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + let sponsor_info_obj = sponsor_info_unit_test_read_only_helper( + &deps.storage, + &deps.api.addr_validate("secretuser1")?, + )?; + assert_eq!(sponsor_info_obj.addr_list_index, Some(0)); + let sponsor_state_obj = sponsor_state_unit_test_read_only_helper(&deps.storage)?; + assert_eq!(sponsor_state_obj.offset, 1); + //2.2.2)Sponsoring -> User 2 + let handle_msg = HandleMsg::Sponsor { + title: Some("user2 title".to_string()), + message: Some("user2 message".to_string()), + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretuser2", &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + let sponsor_info_obj = sponsor_info_unit_test_read_only_helper( + &deps.storage, + &deps.api.addr_validate("secretuser2")?, + )?; + assert_eq!(sponsor_info_obj.addr_list_index, Some(1)); + let sponsor_state_obj = sponsor_state_unit_test_read_only_helper(&deps.storage)?; + assert_eq!(sponsor_state_obj.offset, 2); + + //2.2.3)Sponsoring -> User 2 does it again + let handle_msg = HandleMsg::Sponsor { + title: Some("user2 changed title".to_string()), + message: Some("user2 changed message".to_string()), + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretuser2", &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + let sponsor_info_obj = sponsor_info_unit_test_read_only_helper( + &deps.storage, + &deps.api.addr_validate("secretuser2")?, + )?; + assert_eq!(sponsor_info_obj.addr_list_index, Some(1)); + let sponsor_state_obj = sponsor_state_unit_test_read_only_helper(&deps.storage)?; + assert_eq!(sponsor_state_obj.offset, 2); + + //Fetch Global Request list + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 1); + assert_eq!(len, 1); + // Messages can only be changed by message edit + assert_eq!(vec[0].title, Some("user2 title".to_string())); + assert_eq!(vec[0].message, Some("user2 message".to_string())); + } + } + Ok(()) + } + + #[test] + fn test_req_withdraw_sponsor() -> StdResult<()> { + //Initializing + let (_, mut deps) = init_helper(None); + //Sponsoring some amount + let handle_msg = HandleMsg::Sponsor { + title: None, + message: None, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretrichierich", &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + + //1)Checking validity of the amount + let handle_msg = HandleMsg::SponsorRequestWithdraw { + amount: Uint128::from(150000 * SCRT_TO_USCRT), + }; + let res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretrichierich", &[]), + handle_msg, + ); + + assert_eq!( + res.unwrap_err(), + StdError::generic_err(format!( + "insufficient funds to redeem: balance=10000000000, required=150000000000", + )) + ); + + //2)Making request to unbond + let handle_msg = HandleMsg::SponsorRequestWithdraw { + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretrichierich", &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(15000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + + //2.1)Unbonding batch + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + )?; + //2.2)Checking pool state + let pool_state = pool_state_read_only_unit_test_helper(&deps.storage); + assert_eq!(pool_state.total_sponsored, Uint128::from(0 * SCRT_TO_USCRT)); + assert_eq!( + pool_state.rewards_returned_to_contract, + Uint128::from((20) * SCRT_TO_USCRT) + ); + + let sponsor = sponsor_info_helper_read_only( + &deps.storage, + &deps.api.addr_validate("secretrichierich")?, + )?; + assert_eq!(sponsor.amount_sponsored.u128(), 0 * SCRT_TO_USCRT); + //3.2) Checking unbonding information + assert_eq!(sponsor.unbonding_batches[0], 1); + assert_eq!(sponsor.unbonding_batches.len(), 1); + + //3.3)Checking validators Total amount deposited and sponsored should be equal to 100,000 SCRT (delegated) + let config_obj = config_read_only_unit_test_helper(&deps.storage); + let mut total_deposited_and_sponsored = Uint128::zero(); + for val in config_obj.validators { + total_deposited_and_sponsored = total_deposited_and_sponsored.add(val.delegated); + } + assert_eq!( + total_deposited_and_sponsored, + Uint128::from((0) * SCRT_TO_USCRT) + ); + + //3.4)Checking the validator stats + //Done -> check test_validator_walk_through + Ok(()) + } + + #[test] + fn test_withdraw_sponsor() -> StdResult<()> { + let mut deps = deposits_filler_unit_test_helper(Some(10000 * SCRT_TO_USCRT)); + //Deposit + let handle_msg = HandleMsg::Sponsor { + title: None, + message: None, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretrichierich", &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + //Request withdraw + let handle_msg = HandleMsg::SponsorRequestWithdraw { + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretrichierich", &[]), + handle_msg, + )?; + //Batch unbond + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + )?; + + //1) Error Check: Amount available for withdraw is less than withdraw amount + let handle_msg = HandleMsg::SponsorWithdraw { + amount: Uint128::from(1000000 * SCRT_TO_USCRT), + }; + + let res = execute( + deps.as_mut(), + custom_mock_env( + None, + Some(config.next_unbonding_batch_time + config.unbonding_duration.add(1)), + None, + None, + ), + mock_info("secretrichierich", &[]), + handle_msg, + ); + assert_eq!( + res.unwrap_err(), + StdError::generic_err("Trying to withdraw more than available") + ); + //1.1)sponsor check after request withdraw + let sponsor = sponsor_info_helper_read_only( + &deps.storage, + &deps.api.addr_validate("secretrichierich")?, + )?; + assert_eq!(sponsor.amount_sponsored.u128(), 0 * SCRT_TO_USCRT); + //1.2) Checking unbonding information + assert_eq!(sponsor.unbonding_batches[0], 1); + assert_eq!(sponsor.unbonding_batches.len(), 1); + + //2) Withdraw + let handle_msg = HandleMsg::SponsorWithdraw { + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }; + + let _res = execute( + deps.as_mut(), + custom_mock_env( + None, + Some(config.next_unbonding_batch_time + config.unbonding_duration.add(1)), + None, + None, + ), + mock_info("secretrichierich", &[]), + handle_msg, + )?; + + //2.1)sponsor check after request withdraw + let sponsor = sponsor_info_helper_read_only( + &deps.storage, + &deps.api.addr_validate("secretrichierich")?, + )?; + assert_eq!(sponsor.amount_sponsored.u128(), 0 * SCRT_TO_USCRT); + //2.2) Checking unbonding information + assert_eq!(sponsor.unbonding_batches.len(), 0); + + //2.3) Checking pool state + let pool_state = pool_state_read_only_unit_test_helper(&deps.storage); + assert_eq!(pool_state.total_sponsored, Uint128::from(0 * SCRT_TO_USCRT)); + + Ok(()) + } + + ////////////////////////////////////// Sponsors + Admin ////////////////////////////////////// + /// try_sponsor + try_sponsor_request_withdraw + + /// try_sponsor_withdraw + try_sponsor_message_edit + + /// try_review_sponsor_messages -> Admin + #[test] + fn test_sponsors_walkthrough() -> StdResult<()> { + let (_init_result, mut deps) = init_helper(None); + + //1)Deposit with and without the message + //1.1) Deposit with the message + let handle_msg = HandleMsg::Sponsor { + title: Some("Bruce Wayne".to_string()), + message: Some("I'm rich".to_string()), + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretbrucewayne", &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + //1.2) Query the Sponsor Message Request + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 1); + assert_eq!(len, 1); + } + } + //1.3) Deposit without the message + let handle_msg = HandleMsg::Sponsor { + title: None, + message: None, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretbrucewayne", &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + //1.4) Query the Sponsor Message Request + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 1); + assert_eq!(len, 1); + } + } + + //1.5) Query the Sponsors List + let query_msg = QueryMsg::Sponsors { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorsResponse { vec, len } => { + assert_eq!(vec.len(), 1); + assert_eq!(len, 1); + for v in vec { + assert_eq!(v.amount_sponsored.u128(), 20000 * SCRT_TO_USCRT); + assert_eq!(v.message, None); + assert_eq!(v.title, None); + assert_eq!(v.addr_list_index, Some(0)); + } + } + } + + //2) Review Sponsor Request Messages + let mut decision_vec: Vec = vec![]; + decision_vec.push(Review { + index: 0, + is_accpeted: true, + }); + let handle_msg = HandleMsg::ReviewSponsors { + decisions: decision_vec, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("reviewer", &[]), + handle_msg, + )?; + + //2.1) Query the sponsor message request + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 0); + assert_eq!(len, 0); + } + } + + //2.2) Query the Sponsors List + let query_msg = QueryMsg::Sponsors { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorsResponse { vec, len } => { + assert_eq!(vec.len(), 1); + assert_eq!(len, 1); + for v in vec { + assert_eq!(v.amount_sponsored.u128(), 20000 * SCRT_TO_USCRT); + assert_eq!(v.title, Some("Bruce Wayne".to_string())); + assert_eq!(v.message, Some("I'm rich".to_string())); + assert_eq!(v.addr_list_index, Some(0)); + } + } + } + + let sponsor_stats = SPONSOR_STATS_STORE.load(deps.as_ref().storage)?; + assert_eq!(sponsor_stats.offset, 1); + + //3) Sponsor Request withdraw all amount + let handle_msg = HandleMsg::SponsorRequestWithdraw { + amount: Uint128::from(20000 * SCRT_TO_USCRT), + }; + execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretbrucewayne", &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(20000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + let sponsor_stats = SPONSOR_STATS_STORE.load(deps.as_ref().storage)?; + assert_eq!(sponsor_stats.offset, 0); + + //3.1) Query the Sponsors List + let query_msg = QueryMsg::Sponsors { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + SponsorsResponse { vec, len } => { + assert_eq!(vec.len(), 0); + assert_eq!(len, 0); + } + } + + //4) try to edit message after you sponsor with and without the message + let (_init_result, mut deps) = init_helper(None); + + let handle_msg = HandleMsg::Sponsor { + title: None, + message: None, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretbrucewayne", &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 0); + assert_eq!(len, 0); + } + } + + let handle_msg = HandleMsg::SponsorMessageEdit { + title: Some("Bruce Wayne".to_string()), + message: Some("I'm rich".to_string()), + delete_title: false, + delete_message: false, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretbrucewayne", &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(1 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 1); + assert_eq!(len, 1); + } + } + + Ok(()) + } + + ////////////////////////////////////// Validators ////////////////////////////////////// + #[test] + fn test_validator_walk_through() -> StdResult<()> { + let (_init_result, mut deps) = init_helper(None); + + //1)Checking validators after deposit 1 + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + None, + "secretbatman", + None, + ); + let config = config_read_only_unit_test_helper(&deps.storage); + + assert_eq!(config.validators[0].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.validators[1].delegated.u128(), 0); + assert_eq!(config.validators[2].delegated.u128(), 0 * SCRT_TO_USCRT); + assert_eq!(config.next_validator_for_delegation, 1); + + //deposit 10 scrt + //2)Checking validators after deposit 2 + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + None, + "secretbatman", + None, + ); + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config.validators[0].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.validators[1].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.validators[2].delegated.u128(), 00 * SCRT_TO_USCRT); + assert_eq!(config.next_validator_for_delegation, 2); + + //3)Checking validators after deposit 3 + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + None, + "secretbatman", + None, + ); + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config.validators[0].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.validators[1].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.validators[2].delegated.u128(), 10 * SCRT_TO_USCRT); + //Since there are 3 validators so 3%3 == 0 + assert_eq!(config.next_validator_for_delegation, 0); + + //4) User check + let user_info = + user_info_read_only_unit_test_helper(&Addr::unchecked("secretbatman"), &deps.storage); + assert_eq!(user_info.amount_delegated.u128(), 30 * SCRT_TO_USCRT); + + //Request withdraw + let _ = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((10 * SCRT_TO_USCRT) as u128), + None, + None, + "secretbatman", + ); + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + )?; + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config.validators[0].delegated.u128(), 0 * SCRT_TO_USCRT); + assert_eq!(config.validators[1].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.validators[2].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.next_validator_for_unbonding, 1); + + //Request withdraw + let _ = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((10 * SCRT_TO_USCRT) as u128), + None, + None, + "secretbatman", + ); + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + )?; + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config.validators[0].delegated.u128(), 0 * SCRT_TO_USCRT); + assert_eq!(config.validators[1].delegated.u128(), 0); + assert_eq!(config.validators[2].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.next_validator_for_unbonding, 2); + + //Request withdraw + let _ = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((10 * SCRT_TO_USCRT) as u128), + None, + None, + "secretbatman", + ); + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + )?; + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config.validators[0].delegated.u128(), 0 * SCRT_TO_USCRT); + assert_eq!(config.validators[1].delegated.u128(), 0); + assert_eq!(config.validators[2].delegated.u128(), 0 * SCRT_TO_USCRT); + //Since there are 3 validators so 3%3 == 0 + assert_eq!(config.next_validator_for_unbonding, 0); + + //5)Checking user,sponsor and admin reserves in combinations. + let (_init_result, mut deps) = init_helper(None); + + //1)Checking validators after sponsor + let _ = sponsor_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + None, + "Bruce Wayne", + None, + ); + let config = config_read_only_unit_test_helper(&deps.storage); + + assert_eq!(config.validators[0].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.validators[1].delegated.u128(), 0); + assert_eq!(config.validators[2].delegated.u128(), 0 * SCRT_TO_USCRT); + assert_eq!(config.next_validator_for_delegation, 1); + + //deposit 10 scrt + // 2)Checking validators after sponsor 2 + let _ = sponsor_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + None, + "Bill gates", + None, + ); + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config.validators[0].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.validators[1].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.validators[2].delegated.u128(), 0 * SCRT_TO_USCRT); + assert_eq!(config.next_validator_for_delegation, 2); + + let _ = sponsor_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + None, + "Jordan Xavier", + None, + ); + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config.validators[0].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.validators[1].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.validators[2].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.next_validator_for_delegation, 0); + + //4) Checking sponsors + let sponsor_info = sponsor_info_unit_test_read_only_helper( + &deps.storage, + &Addr::unchecked("Bruce Wayne"), + )?; + assert_eq!(sponsor_info.amount_sponsored.u128(), 10 * SCRT_TO_USCRT); + let sponsor_info = + sponsor_info_unit_test_read_only_helper(&deps.storage, &Addr::unchecked("Bill gates"))?; + assert_eq!(sponsor_info.amount_sponsored.u128(), 10 * SCRT_TO_USCRT); + let sponsor_info = sponsor_info_unit_test_read_only_helper( + &deps.storage, + &Addr::unchecked("Jordan Xavier"), + )?; + assert_eq!(sponsor_info.amount_sponsored.u128(), 10 * SCRT_TO_USCRT); + + let _ = sponsor_request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((10 * SCRT_TO_USCRT) as u128), + None, + None, + "Bruce Wayne", + )?; + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + )?; + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config.validators[0].delegated.u128(), 00 * SCRT_TO_USCRT); + assert_eq!(config.validators[1].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.validators[2].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.next_validator_for_unbonding, 1); + + let _ = sponsor_request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((10 * SCRT_TO_USCRT) as u128), + None, + None, + "Bill gates", + )?; + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + )?; + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config.validators[0].delegated.u128(), 00 * SCRT_TO_USCRT); + assert_eq!(config.validators[1].delegated.u128(), 00 * SCRT_TO_USCRT); + assert_eq!(config.validators[2].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.next_validator_for_unbonding, 2); + + let _ = sponsor_request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from((10 * SCRT_TO_USCRT) as u128), + None, + None, + "Jordan Xavier", + )?; + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + )?; + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config.validators[0].delegated.u128(), 00 * SCRT_TO_USCRT); + assert_eq!(config.validators[1].delegated.u128(), 00 * SCRT_TO_USCRT); + assert_eq!(config.validators[2].delegated.u128(), 00 * SCRT_TO_USCRT); + assert_eq!(config.next_validator_for_unbonding, 0); + + //Checking reserves and validators + let mut deps = deposits_filler_unit_test_helper(Some(800 * SCRT_TO_USCRT)); + for _ in 0..6 { + let _ = end_round_unit_test_helper(deps.as_mut())?; + } + let pool_state = pool_state_read_only_unit_test_helper(&deps.storage); + + let config_obj = config_read_only_unit_test_helper(&deps.storage); + let mut total_delegated_sponsored_reserves = Uint128::zero(); + for val in config_obj.validators { + total_delegated_sponsored_reserves = + total_delegated_sponsored_reserves.add(val.delegated); + } + assert_eq!( + total_delegated_sponsored_reserves, + Uint128::from((100000) * SCRT_TO_USCRT).add(pool_state.total_reserves) + ); + + //Testing user deposits, sponsors and reserves in unity + let (_init_result, mut deps) = init_helper(None); + + //1)deposits + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + None, + "secretbatman", + None, + ); + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config.validators[0].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.validators[1].delegated.u128(), 0); + assert_eq!(config.validators[2].delegated.u128(), 0 * SCRT_TO_USCRT); + assert_eq!(config.next_validator_for_delegation, 1); + + let pool_state = pool_state_read_only_unit_test_helper(&deps.storage); + assert_eq!( + pool_state.total_delegated, + Uint128::from(10 * SCRT_TO_USCRT) + ); + + //2)sponsors + let _ = sponsor_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + None, + "Bruce Wayne", + None, + ); + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config.validators[0].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.validators[1].delegated.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(config.validators[2].delegated.u128(), 0 * SCRT_TO_USCRT); + assert_eq!(config.next_validator_for_delegation, 2); + + let pool_state = pool_state_read_only_unit_test_helper(&deps.storage); + assert_eq!( + pool_state.total_sponsored, + Uint128::from(10 * SCRT_TO_USCRT) + ); + + //3)reserves + for _ in 0..10 { + deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(1 * SCRT_TO_USCRT), + None, + None, + "Jordan Xavier", + None, + )?; + } + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!( + config.validators[0].delegated.u128(), + (10 + 3) * SCRT_TO_USCRT + ); + assert_eq!( + config.validators[1].delegated.u128(), + (10 + 3) * SCRT_TO_USCRT + ); + assert_eq!(config.validators[2].delegated.u128(), 4 * SCRT_TO_USCRT); + assert_eq!(config.next_validator_for_delegation, 0); + + let mut total_delegated_sponsored_reserves: Uint128 = Uint128::zero(); + for val in config.validators { + total_delegated_sponsored_reserves.add_assign(val.delegated); + } + + assert_eq!( + total_delegated_sponsored_reserves, + Uint128::from((30) * SCRT_TO_USCRT) + ); + + for _ in 0..6 { + let _ = end_round_unit_test_helper(deps.as_mut())?; + } + let config = config_read_only_unit_test_helper(&deps.storage); + let mut total_delegated_sponsored_reserves: Uint128 = Uint128::zero(); + for val in &config.validators { + total_delegated_sponsored_reserves = + total_delegated_sponsored_reserves.add(val.delegated); + } + // Still 0 6%3==0 + assert_eq!(config.next_validator_for_delegation, 0); + + let pool_state = pool_state_read_only_unit_test_helper(&deps.storage); + assert_eq!( + total_delegated_sponsored_reserves, + Uint128::from((30) * SCRT_TO_USCRT).add(pool_state.total_reserves) + ); + + //Testing user user_request_withdraw, sponsors_request_withdraw and reserves_request_withdraw + //1)user_request_withdraw + let round_obj = round_read_only_unit_test_helper(&deps.storage); + let _ = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + Some(round_obj.start_time), + "secretbatman", + )?; + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + )?; + let mut total_delegated_sponsored_reserves: Uint128 = Uint128::zero(); + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config.next_validator_for_unbonding, 1); + + for val in &config.validators { + total_delegated_sponsored_reserves = + total_delegated_sponsored_reserves.add(val.delegated); + } + let pool_state = pool_state_read_only_unit_test_helper(&deps.storage); + assert_eq!( + total_delegated_sponsored_reserves, + Uint128::from((20) * SCRT_TO_USCRT).add(pool_state.total_reserves) + ); + + //2)sponsor_request_withdraw + sponsor_request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + None, + "Bruce Wayne", + )?; + + let mut total_delegated_sponsored_reserves: Uint128 = Uint128::zero(); + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config.next_validator_for_unbonding, 1); + + for val in &config.validators { + total_delegated_sponsored_reserves = + total_delegated_sponsored_reserves.add(val.delegated); + } + let pool_state = pool_state_read_only_unit_test_helper(&deps.storage); + assert_eq!( + total_delegated_sponsored_reserves, + Uint128::from((20) * SCRT_TO_USCRT).add(pool_state.total_reserves) + ); + + //3)reserves + let pool_state = pool_state_read_only_unit_test_helper(&deps.storage); + let reserves_req_withdraw = pool_state.total_reserves; + if !reserves_req_withdraw.is_zero() { + let handle_msg = HandleMsg::RequestReservesWithdraw { + amount: reserves_req_withdraw, + }; + execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("admin", &[]), + handle_msg, + )?; + } + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + )?; + let mut total_delegated_sponsored_reserves: Uint128 = Uint128::zero(); + let config = config_read_only_unit_test_helper(&deps.storage); + for val in &config.validators { + total_delegated_sponsored_reserves = + total_delegated_sponsored_reserves.add(val.delegated); + } + let pool_state = pool_state_read_only_unit_test_helper(&deps.storage); + assert_eq!( + total_delegated_sponsored_reserves, + Uint128::from((10) * SCRT_TO_USCRT) + ); + assert_eq!( + total_delegated_sponsored_reserves, + pool_state + .total_delegated + .add(pool_state.total_reserves) + .add(pool_state.total_sponsored) + ); + Ok(()) + } + + #[test] + fn test_sponsor_edit_message() -> StdResult<()> { + let (_, mut deps) = init_helper(None); + + //1)Sending edit message without any sponsoring + let handle_msg = HandleMsg::SponsorMessageEdit { + title: None, + message: None, + delete_message: false, + delete_title: false, + }; + let res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretuser1", &[]), + handle_msg, + ); + assert_eq!( + res.unwrap_err(), + StdError::generic_err(format!("Sponsor to avail this option")) + ); + + //2)Sponsoring + let handle_msg = HandleMsg::Sponsor { + title: Some("user1 title".to_string()), + message: Some("user1 message".to_string()), + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretuser1", &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + //Fetch Global Request list + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 1); + assert_eq!(len, 1); + assert_eq!(vec[0].title, Some("user1 title".to_string())); + assert_eq!(vec[0].message, Some("user1 message".to_string())); + } + } + + //2.1)Sending edit message sponsoring + let handle_msg = HandleMsg::SponsorMessageEdit { + title: Some("user1 title updated".to_string()), + message: Some("user1 message updated".to_string()), + delete_message: false, + delete_title: false, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretuser1", &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(1 * SCRT_TO_USCRT), + }]), + handle_msg, + ); + //Fetch Global Request list + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 1); + assert_eq!(len, 1); + assert_eq!(vec[0].title, Some("user1 title updated".to_string())); + assert_eq!(vec[0].message, Some("user1 message updated".to_string())); + } + } + + //2) Review Sponsor Request Messages + let mut decision_vec: Vec = vec![]; + decision_vec.push(Review { + index: 0, + is_accpeted: true, + }); + let handle_msg = HandleMsg::ReviewSponsors { + decisions: decision_vec, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("reviewer", &[]), + handle_msg, + )?; + + //Fetch Global Request list + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 0); + assert_eq!(len, 0); + } + } + + let query_msg = QueryMsg::Sponsors { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorsResponse { vec, len } => { + assert_eq!(vec.len(), 1); + assert_eq!(len, 1); + for v in vec { + assert_eq!(v.amount_sponsored.u128(), 10000 * SCRT_TO_USCRT); + assert_eq!(v.message, Some("user1 message updated".to_string())); + assert_eq!(v.title, Some("user1 title updated".to_string())); + assert_eq!(v.addr_list_index, Some(0)); + } + } + } + + //2.2)deleteing edit message sponsoring + let handle_msg = HandleMsg::SponsorMessageEdit { + title: None, + message: None, + delete_message: true, + delete_title: false, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretuser1", &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(1 * SCRT_TO_USCRT), + }]), + handle_msg, + ); + + let query_msg = QueryMsg::Sponsors { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorsResponse { vec, len } => { + assert_eq!(vec.len(), 1); + assert_eq!(len, 1); + for v in vec { + assert_eq!(v.amount_sponsored.u128(), 10000 * SCRT_TO_USCRT); + assert_eq!(v.message, None); + assert_eq!(v.title, Some("user1 title updated".to_string())); + assert_eq!(v.addr_list_index, Some(0)); + } + } + } + + Ok(()) + } + + ////////////////////////////////////// Admin ////////////////////////////////////// + #[test] + fn test_review_sponsor_messages() -> StdResult<()> { + let (_init_result, mut deps) = init_helper(None); + + //1)Deposit with and without the message + //1.1) Deposit with the message + let handle_msg = HandleMsg::Sponsor { + title: Some("Bruce Wayne".to_string()), + message: Some("I'm rich".to_string()), + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("secretbrucewayne", &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + //1.2) Query the Sponsor Message Request + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 1); + assert_eq!(vec[0].title, Some("Bruce Wayne".to_string())); + assert_eq!(vec[0].message, Some("I'm rich".to_string())); + assert_eq!(len, 1); + } + } + + //1.3) Query the Sponsors List + let query_msg = QueryMsg::Sponsors { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorsResponse { vec, len } => { + assert_eq!(vec.len(), 1); + assert_eq!(len, 1); + for v in vec { + assert_eq!(v.amount_sponsored.u128(), 10000 * SCRT_TO_USCRT); + assert_eq!(v.message, None); + assert_eq!(v.title, None); + assert_eq!(v.addr_list_index, Some(0)); + } + } + } + + //2) Review Sponsor Request Messages + let mut decision_vec: Vec = vec![]; + decision_vec.push(Review { + index: 0, + is_accpeted: true, + }); + let handle_msg = HandleMsg::ReviewSponsors { + decisions: decision_vec, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("reviewer", &[]), + handle_msg, + )?; + + //2.1) Query the sponsor message request + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 0); + assert_eq!(len, 0); + } + } + + //1.3) Query the Sponsors List + let query_msg = QueryMsg::Sponsors { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorsResponse { vec, len } => { + assert_eq!(vec.len(), 1); + assert_eq!(len, 1); + for v in vec { + assert_eq!(v.amount_sponsored.u128(), 10000 * SCRT_TO_USCRT); + assert_eq!(v.message, Some("I'm rich".to_string())); + assert_eq!(v.title, Some("Bruce Wayne".to_string())); + assert_eq!(v.addr_list_index, Some(0)); + } + } + } + + Ok(()) + } + + #[test] + fn test_remove_sponsor_credentials() -> StdResult<()> { + let (_init_result, mut deps) = init_helper(None); + + //1)Sponsoring by 10 users + for i in 0..10 { + let sponsor = format!("user{}", i); + + let handle_msg = HandleMsg::Sponsor { + title: Some(format!("user{} Title", i)), + message: Some(format!("user{} Message", i)), + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info(&sponsor, &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + } + + //2)Accepting their sponsor requests + let mut decision_vec: Vec = vec![]; + for i in 0..10 { + decision_vec.push(Review { + index: i, + is_accpeted: true, + }); + } + let handle_msg = HandleMsg::ReviewSponsors { + decisions: decision_vec, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("reviewer", &[]), + handle_msg, + )?; + + //3)Removing credentials of even numbers only + let mut decisions_vec = vec![]; + for i in 0..10 { + if i % 2 == 0 { + decisions_vec.push(RemoveSponsorCredentialsDecisions { + index: i, + remove_sponsor_title: true, + remove_sponsor_message: true, + }) + } + } + let handle_msg = HandleMsg::RemoveSponsorCredentials { + decisions: decisions_vec, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("reviewer", &[]), + handle_msg, + )?; + + //4)Checking if removing actually worked + for i in 0..10 { + let sponsor = format!("user{}", i); + + let sponsor_obj = + sponsor_info_helper_read_only(&deps.storage, &deps.api.addr_validate(&sponsor)?)?; + + if i % 2 == 0 { + assert_eq!(sponsor_obj.title, None); + assert_eq!(sponsor_obj.message, None); + } else { + assert_eq!(sponsor_obj.title, Some(format!("user{} Title", i))); + assert_eq!(sponsor_obj.message, Some(format!("user{} Message", i))); + } + } + + Ok(()) + } + + #[test] + fn test_end_round() -> StdResult<()> { + let mut deps = deposits_filler_unit_test_helper(None); + + //1)Checking if the round is closed by Triggerer/Admin + let handle_msg = HandleMsg::EndRound {}; + + let handle_result = execute( + deps.as_mut(), + custom_mock_env(None, Some(3600 * 24 * 7), None, None), + mock_info("not-triggerer", &[]), + handle_msg, + ); + assert_eq!( + handle_result.unwrap_err(), + StdError::generic_err( + "This is an triggerer command. Triggerer commands can only be run from triggerer address", + ) + ); + + //2)Checking if the round can be closed -- time-wise + let handle_msg = HandleMsg::EndRound {}; + let handle_result = execute( + deps.as_mut(), + custom_mock_env(None, Some(3600 * 24 * 7 - 1), None, None), + mock_info("triggerer", &[]), + handle_msg, + ); + assert_eq!( + handle_result.unwrap_err(), + StdError::generic_err("Round end time is in the future",) + ); + + // DEBUG pool state rewards checking -- replace in code + // for reward in rewards_obj { + // dbg!("Reward {}", reward.reward); + // total_rewards.add_assign(reward.reward); + // } + + //Calculating total rewards recieved + //*DEBUG + //dbg!("Total Rewards{}", total_rewards); + + //3) Checking when rewards are zero + let (_init_result, mut deps) = init_helper(None); + deps.querier.update_staking("uscrt", &[], &[]); + let respose = end_round_unit_test_helper(deps.as_mut())?; + let raw = respose.data.unwrap(); + let status: HandleAnswer = from_binary(&raw)?; + let msg = HandleAnswer::EndRound { status: Success }; + assert_eq!(msg, status); + let rewards_stats_for_nth_round_obj = + rewards_stats_for_nth_round_read_only_unit_test_helper(&deps.storage, 1); + assert_eq!( + rewards_stats_for_nth_round_obj.total_rewards, + Uint128::zero() + ); + + //4) Checking total rewards distribution --> winning_amount + triggerer's share + shade's share + Galactic Pools share. + let mut deps = deposits_filler_unit_test_helper(None); + //*Total rewards are 130 scrts -> calculated as 10 scrts per one deposit, total 10 deposits are made + //and 10scrt when ending round so withdrawing rewards from all three validators hence 30 scrts. + let res = end_round_unit_test_helper(deps.as_mut())?; + assert_eq!(res.messages.len(), 6); + //*Debug + // println!("Shade share {}", shade_share); + // println!("Galactic Pool share {}", galactic_pools_share); + // println!("Triggerer share {}", trigger_share); + // println!("Winning amount {}", winning_amount); + // let total = shade_share + galactic_pools_share + trigger_share + winning_amount; + // println!( + // "Original: {}, calculated: {}, Difference: {}", + // test_total_rewards, + // total, + // test_total_rewards - total + // ); + + //5) Checking config + round + pool + let (_init_result, deps) = init_helper(None); + let config_obj = config_read_only_unit_test_helper(deps.as_ref().storage); + assert_eq!(config_obj.next_validator_for_delegation, 0); + let round_obj = round_read_only_unit_test_helper(&deps.storage); + assert_eq!(round_obj.entropy.len(), 32); + //* After round ends + let mut deps = deposits_filler_unit_test_helper(None); + let _ = end_round_unit_test_helper(deps.as_mut())?; + //*Total 10 delegations made in deposit_unit_test_simple_helper_filler() and end_round_my_mocked_querier_unit_test_helper() + //* 10%3 = 1 + let config_obj = config_read_only_unit_test_helper(deps.as_ref().storage); + assert_eq!(config_obj.next_validator_for_delegation, 1); + let round_obj = round_read_only_unit_test_helper(&deps.storage); + assert_eq!(round_obj.entropy.len(), 80); + + //6)Checking increase in reserves + //Checking pool state + let pool_state_obj = pool_state_read_only_unit_test_helper(deps.as_ref().storage); + assert_eq!(pool_state_obj.total_reserves, Uint128::zero()); + //*Expiry is almost 45 days after round ended. round ends after 7 days. 7*8 = 48days + //*since on claims are made all the winning amount comes back as reserves and some is propogated. + for _ in 0..7 { + let _ = end_round_unit_test_helper(deps.as_mut())?; + } + let pool_state_obj = pool_state_read_only_unit_test_helper(deps.as_ref().storage); + assert_ne!(pool_state_obj.total_reserves, Uint128::zero()); + + //7) Checking pool state liquidity + let mut deps = deposits_filler_unit_test_helper(None); + let _ = end_round_unit_test_helper(deps.as_mut())?; + let round_obj = round_read_only_unit_test_helper(&deps.storage); + let pool_state_liquidity = pool_state_liquidity_snapshot_read_only_unit_test_helper( + deps.as_ref().storage, + round_obj.current_round_index - 1, + ); + //We already know that in filler helper function, secretbatman deposits 30,000 scrt in the middle average liquidity to 85000 + //while total amount delegated is 100,000 + assert_eq!( + pool_state_liquidity.total_liquidity.unwrap(), + Uint128::from(85000 * SCRT_TO_USCRT) + ); + assert_eq!( + pool_state_liquidity.total_delegated.unwrap(), + Uint128::from(100000 * SCRT_TO_USCRT) + ); + //7.1) if no delegations are made during the round + let _ = end_round_unit_test_helper(deps.as_mut())?; + let round_obj = round_read_only_unit_test_helper(&deps.storage); + let pool_state_liquidity = pool_state_liquidity_snapshot_read_only_unit_test_helper( + deps.as_ref().storage, + round_obj.current_round_index - 1, + ); + assert_eq!( + pool_state_liquidity.total_liquidity.unwrap(), + Uint128::from(100000 * SCRT_TO_USCRT) + ); + assert_eq!( + pool_state_liquidity.total_delegated.unwrap(), + Uint128::from(100000 * SCRT_TO_USCRT) + ); + + //8)Claim the unclaimed rewards that have been expired and checking rewards_stats_for_nth_round_unit_test_helper + //*Checking expiration date + let mut deps = deposits_filler_unit_test_helper(None); + let _ = end_round_unit_test_helper(deps.as_mut())?; + + let rewards_stats_for_nth_round_obj = + rewards_stats_for_nth_round_read_only_unit_test_helper(deps.as_ref().storage, 1); + + let round_obj = round_read_only_unit_test_helper(deps.as_ref().storage); + let time_stamp = Timestamp::from_seconds(round_obj.start_time); + + assert_eq!( + rewards_stats_for_nth_round_obj + .rewards_expiration_date + .unwrap(), + 3600 * 24 * 45 + time_stamp.seconds() + ); + + let mut deps = deposits_filler_unit_test_helper(None); + let _ = end_round_unit_test_helper(deps.as_mut())?; + let pool_state = pool_state_read_only_unit_test_helper(deps.as_ref().storage); + assert_eq!(pool_state.rewards_returned_to_contract, Uint128::zero()); + assert_eq!(pool_state.total_reserves, Uint128::zero()); + //*Checking increase in reserves + let pool_state_obj = pool_state_read_only_unit_test_helper(deps.as_ref().storage); + assert_eq!(pool_state_obj.total_reserves, Uint128::zero()); + //* Expiry is almost 45 days after round ended. round ends after 7 days. 7*8 = 48days + //* since on claims are made all the winning amount comes back as reserves and some is propogated. + for _ in 0..2 { + for _ in 0..7 { + let _ = end_round_unit_test_helper(deps.as_mut())?; + } + } + let pool_state_obj = pool_state_read_only_unit_test_helper(deps.as_ref().storage); + assert_ne!(pool_state_obj.total_reserves, Uint128::zero()); + + Ok(()) + } + + #[test] + fn test_add_admin() -> StdResult<()> { + let (_init_result, mut deps) = init_helper(None); + + // 1) Error caused by not admin command + let env = custom_mock_env(None, None, None, None); + let info = mock_info("not-admin", &[]); + let handle_msg = HandleMsg::AddAdmin { + admin: Addr::unchecked("admin2"), + }; + let res = execute(deps.as_mut(), env, info, handle_msg).unwrap_err(); + assert_eq!( + res, + StdError::generic_err( + "This is an admin command. Admin commands can only be run from admin address", + ) + ); + + // 2) Adding admin2 + let config = config_helper_read_only(&deps.storage)?; + assert_eq!(config.admins.len(), 1); + + let env = custom_mock_env(None, None, None, None); + let info = mock_info("admin", &[]); + let handle_msg = HandleMsg::AddAdmin { + admin: Addr::unchecked("admin2"), + }; + let _res = execute(deps.as_mut(), env, info, handle_msg)?; + + let config = config_helper_read_only(&deps.storage)?; + assert_eq!(config.admins.len(), 2); + assert!(config.admins.contains(&Addr::unchecked("admin"))); + assert!(config.admins.contains(&Addr::unchecked("admin2"))); + + // 3)Error adding admin2 again + let env = custom_mock_env(None, None, None, None); + let info = mock_info("admin", &[]); + let handle_msg = HandleMsg::AddAdmin { + admin: Addr::unchecked("admin2"), + }; + let _res = execute(deps.as_mut(), env, info, handle_msg).unwrap_err(); + assert_eq!(_res, StdError::generic_err("This address already exisits",)); + + //4)Adding admin3 from admin2 + let env = custom_mock_env(None, None, None, None); + let info = mock_info("admin2", &[]); + let handle_msg = HandleMsg::AddAdmin { + admin: Addr::unchecked("admin3"), + }; + let _res = execute(deps.as_mut(), env, info, handle_msg)?; + let config = config_helper_read_only(&deps.storage)?; + assert_eq!(config.admins.len(), 3); + assert!(config.admins.contains(&Addr::unchecked("admin"))); + assert!(config.admins.contains(&Addr::unchecked("admin2"))); + assert!(config.admins.contains(&Addr::unchecked("admin3"))); + Ok(()) + } + + #[test] + fn test_remove_admin() -> StdResult<()> { + let (_init_result, mut deps) = init_helper(None); + + //1) Error caused by not admin command + let env = custom_mock_env(None, None, None, None); + let info = mock_info("not-admin", &[]); + let handle_msg = HandleMsg::RemoveAdmin { + admin: Addr::unchecked("admin"), + }; + let res = execute(deps.as_mut(), env, info, handle_msg).unwrap_err(); + assert_eq!( + res, + StdError::generic_err( + "This is an admin command. Admin commands can only be run from admin address", + ) + ); + + //2)Adding admin2 + let config = config_helper_read_only(&deps.storage)?; + assert_eq!(config.admins.len(), 1); + + let env = custom_mock_env(None, None, None, None); + let info = mock_info("admin", &[]); + let handle_msg = HandleMsg::AddAdmin { + admin: Addr::unchecked("admin2"), + }; + let _res = execute(deps.as_mut(), env, info, handle_msg)?; + + let config = config_helper_read_only(&deps.storage)?; + assert_eq!(config.admins.len(), 2); + assert!(config.admins.contains(&Addr::unchecked("admin"))); + assert!(config.admins.contains(&Addr::unchecked("admin2"))); + + //3)Remove admin from admin2 + let env = custom_mock_env(None, None, None, None); + let info = mock_info("admin2", &[]); + let handle_msg = HandleMsg::RemoveAdmin { + admin: Addr::unchecked("admin"), + }; + let _res = execute(deps.as_mut(), env, info, handle_msg)?; + let config = config_helper_read_only(&deps.storage)?; + assert_eq!(config.admins.len(), 1); + assert!(config.admins.contains(&Addr::unchecked("admin2"))); + + Ok(()) + } + + #[test] + fn test_add_triggerer() -> StdResult<()> { + let (_init_result, mut deps) = init_helper(None); + + //1)Error caused by not admin command + let env = custom_mock_env(None, None, None, None); + let info = mock_info("not-admin", &[]); + let handle_msg = HandleMsg::AddTriggerer { + triggerer: Addr::unchecked("triggerer2"), + }; + let res = execute(deps.as_mut(), env, info, handle_msg).unwrap_err(); + assert_eq!( + res, + StdError::generic_err( + "This is an admin command. Admin commands can only be run from admin address", + ) + ); + + //2)Adding triggerer2 + let config = config_helper_read_only(&deps.storage)?; + assert_eq!(config.triggerers.len(), 1); + + let env = custom_mock_env(None, None, None, None); + let info = mock_info("admin", &[]); + let handle_msg = HandleMsg::AddTriggerer { + triggerer: Addr::unchecked("triggerer2"), + }; + let _res = execute(deps.as_mut(), env, info, handle_msg)?; + + let config = config_helper_read_only(&deps.storage)?; + assert_eq!(config.triggerers.len(), 2); + assert!(config.triggerers.contains(&Addr::unchecked("triggerer"))); + assert!(config.triggerers.contains(&Addr::unchecked("triggerer2"))); + + // 3)Error adding triggerer2 again + let env = custom_mock_env(None, None, None, None); + let info = mock_info("admin", &[]); + let handle_msg = HandleMsg::AddTriggerer { + triggerer: Addr::unchecked("triggerer2"), + }; + let _res = execute(deps.as_mut(), env, info, handle_msg).unwrap_err(); + assert_eq!(_res, StdError::generic_err("This address already exisits",)); + Ok(()) + } + + #[test] + fn test_remove_triggerer() -> StdResult<()> { + let (_init_result, mut deps) = init_helper(None); + + //1) Error caused by not admin command + let env = custom_mock_env(None, None, None, None); + let info = mock_info("not-admin", &[]); + let handle_msg = HandleMsg::RemoveTriggerer { + triggerer: Addr::unchecked("triggerer2"), + }; + let res = execute(deps.as_mut(), env, info, handle_msg).unwrap_err(); + assert_eq!( + res, + StdError::generic_err( + "This is an admin command. Admin commands can only be run from admin address", + ) + ); + + //2)Adding triggerer2 + let config = config_helper_read_only(&deps.storage)?; + assert_eq!(config.triggerers.len(), 1); + assert!(config.triggerers.contains(&Addr::unchecked("triggerer"))); + + let env = custom_mock_env(None, None, None, None); + let info = mock_info("admin", &[]); + let handle_msg = HandleMsg::AddTriggerer { + triggerer: Addr::unchecked("triggerer2"), + }; + let _res = execute(deps.as_mut(), env, info, handle_msg)?; + + let config = config_helper_read_only(&deps.storage)?; + assert_eq!(config.triggerers.len(), 2); + assert!(config.triggerers.contains(&Addr::unchecked("triggerer"))); + assert!(config.triggerers.contains(&Addr::unchecked("triggerer2"))); + + //3)Remove admin from triggerer2 + let env = custom_mock_env(None, None, None, None); + let info = mock_info("admin", &[]); + let handle_msg = HandleMsg::RemoveTriggerer { + triggerer: Addr::unchecked("triggerer"), + }; + let _res = execute(deps.as_mut(), env, info, handle_msg)?; + let config = config_helper_read_only(&deps.storage)?; + assert_eq!(config.triggerers.len(), 1); + assert!(config.triggerers.contains(&Addr::unchecked("triggerer2"))); + + Ok(()) + } + + #[test] + fn test_add_reviewer() -> StdResult<()> { + let (_init_result, mut deps) = init_helper(None); + + //1)Error caused by not admin command + let env = custom_mock_env(None, None, None, None); + let info = mock_info("not-admin", &[]); + let handle_msg = HandleMsg::AddReviewer { + reviewer: Addr::unchecked("reviewer2"), + }; + let res = execute(deps.as_mut(), env, info, handle_msg).unwrap_err(); + assert_eq!( + res, + StdError::generic_err( + "This is an admin command. Admin commands can only be run from admin address", + ) + ); + + //2)Adding reviewer2 + let config = config_helper_read_only(&deps.storage)?; + assert_eq!(config.reviewers.len(), 1); + + let env = custom_mock_env(None, None, None, None); + let info = mock_info("admin", &[]); + let handle_msg = HandleMsg::AddReviewer { + reviewer: Addr::unchecked("reviewer2"), + }; + let _res = execute(deps.as_mut(), env, info, handle_msg)?; + + let config = config_helper_read_only(&deps.storage)?; + assert_eq!(config.reviewers.len(), 2); + assert!(config.reviewers.contains(&Addr::unchecked("reviewer"))); + assert!(config.reviewers.contains(&Addr::unchecked("reviewer2"))); + + // 3)Error adding reviewer2 again + let env = custom_mock_env(None, None, None, None); + let info = mock_info("admin", &[]); + let handle_msg = HandleMsg::AddReviewer { + reviewer: Addr::unchecked("reviewer2"), + }; + let _res = execute(deps.as_mut(), env, info, handle_msg).unwrap_err(); + assert_eq!(_res, StdError::generic_err("This address already exisits",)); + Ok(()) + } + + #[test] + fn test_remove_reviewer() -> StdResult<()> { + let (_init_result, mut deps) = init_helper(None); + + //1) Error caused by not admin command + let env = custom_mock_env(None, None, None, None); + let info = mock_info("not-admin", &[]); + let handle_msg = HandleMsg::RemoveReviewer { + reviewer: Addr::unchecked("reviewer2"), + }; + let res = execute(deps.as_mut(), env, info, handle_msg).unwrap_err(); + assert_eq!( + res, + StdError::generic_err( + "This is an admin command. Admin commands can only be run from admin address", + ) + ); + + //2)Adding reviewer2 + let config = config_helper_read_only(&deps.storage)?; + assert_eq!(config.reviewers.len(), 1); + assert!(config.reviewers.contains(&Addr::unchecked("reviewer"))); + + let env = custom_mock_env(None, None, None, None); + let info = mock_info("admin", &[]); + let handle_msg = HandleMsg::AddReviewer { + reviewer: Addr::unchecked("reviewer2"), + }; + let _res = execute(deps.as_mut(), env, info, handle_msg)?; + + let config = config_helper_read_only(&deps.storage)?; + assert_eq!(config.reviewers.len(), 2); + assert!(config.reviewers.contains(&Addr::unchecked("reviewer"))); + assert!(config.reviewers.contains(&Addr::unchecked("reviewer2"))); + + //3)Remove admin from reviewer2 + let env = custom_mock_env(None, None, None, None); + let info = mock_info("admin", &[]); + let handle_msg = HandleMsg::RemoveReviewer { + reviewer: Addr::unchecked("reviewer"), + }; + let _res = execute(deps.as_mut(), env, info, handle_msg)?; + let config = config_helper_read_only(&deps.storage)?; + assert_eq!(config.reviewers.len(), 1); + assert!(config.reviewers.contains(&Addr::unchecked("reviewer2"))); + + Ok(()) + } + + #[test] + fn test_update_round_change_admin_share() -> StdResult<()> { + let (_init_result, mut deps) = init_helper(None); + + // change admin + let env = custom_mock_env(None, None, None, None); + let info = mock_info("not-admin", &[]); + let handle_msg = HandleMsg::UpdateRound { + admin_share: Some(AdminShareInfo { + total_percentage_share: (7 * COMMON_DIVISOR) / 100 as u64, + shade_percentage_share: (50 * COMMON_DIVISOR) / 100 as u64, + galactic_pools_percentage_share: (40 * COMMON_DIVISOR) / 100 as u64, + }), + duration: None, + rewards_distribution: None, + ticket_price: None, + rewards_expiry_duration: None, + triggerer_share_percentage: None, + shade_rewards_address: None, + galactic_pools_rewards_address: None, + grand_prize_address: None, + unclaimed_distribution: None, + }; + let res = execute(deps.as_mut(), env, info, handle_msg).unwrap_err(); + assert_eq!( + res, + StdError::generic_err( + "This is an admin command. Admin commands can only be run from admin address", + ) + ); + + let env = custom_mock_env(None, None, None, None); + let info = mock_info("admin", &[]); + let handle_msg = HandleMsg::UpdateRound { + admin_share: Some(AdminShareInfo { + total_percentage_share: (7 * COMMON_DIVISOR) / 100 as u64, + shade_percentage_share: (50 * COMMON_DIVISOR) / 100 as u64, + galactic_pools_percentage_share: (40 * COMMON_DIVISOR) / 100 as u64, + }), + duration: None, + rewards_distribution: None, + ticket_price: None, + rewards_expiry_duration: None, + triggerer_share_percentage: None, + shade_rewards_address: None, + galactic_pools_rewards_address: None, + grand_prize_address: None, + unclaimed_distribution: None, + }; + let res = execute(deps.as_mut(), env, info, handle_msg).unwrap_err(); + assert_eq!( + res, + StdError::generic_err("Total percentage shares don't add up to 100%",) + ); + + let env = custom_mock_env(None, None, None, None); + let info = mock_info("admin", &[]); + + let handle_msg = HandleMsg::UpdateRound { + admin_share: Some(AdminShareInfo { + total_percentage_share: (7 * COMMON_DIVISOR) / 100 as u64, + shade_percentage_share: (50 * COMMON_DIVISOR) / 100 as u64, + galactic_pools_percentage_share: (50 * COMMON_DIVISOR) / 100 as u64, + }), + duration: None, + rewards_distribution: None, + ticket_price: None, + rewards_expiry_duration: None, + triggerer_share_percentage: None, + shade_rewards_address: None, + galactic_pools_rewards_address: None, + grand_prize_address: None, + unclaimed_distribution: None, + }; + let _res = execute(deps.as_mut(), env, info, handle_msg)?; + + let round_obj: RoundInfo = round_read_only_unit_test_helper(&deps.storage); + assert_eq!( + round_obj.admin_share.total_percentage_share, + (7 * COMMON_DIVISOR) / 100 as u64 + ); + assert_eq!( + round_obj.admin_share.galactic_pools_percentage_share, + (50 * COMMON_DIVISOR) / 100 as u64 + ); + assert_eq!( + round_obj.admin_share.shade_percentage_share, + (50 * COMMON_DIVISOR) / 100 as u64 + ); + Ok(()) + } + + #[test] + fn test_update_round_change_round_duration() -> StdResult<()> { + //Depositing amount + let (_init_result, mut deps) = init_helper(Some(800 * SCRT_TO_USCRT)); + let handle_msg = HandleMsg::UpdateRound { + admin_share: None, + duration: Some(1), + rewards_distribution: None, + ticket_price: None, + rewards_expiry_duration: None, + triggerer_share_percentage: None, + shade_rewards_address: None, + galactic_pools_rewards_address: None, + grand_prize_address: None, + unclaimed_distribution: None, + }; + let res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("non-admin", &[]), + handle_msg, + ) + .unwrap_err(); + assert_eq!( + res, + StdError::generic_err( + "This is an admin command. Admin commands can only be run from admin address", + ) + ); + + //Depositing amount + let (_init_result, mut deps) = init_helper(Some(800 * SCRT_TO_USCRT)); + let handle_msg = HandleMsg::UpdateRound { + admin_share: None, + duration: Some(1), + rewards_distribution: None, + ticket_price: None, + rewards_expiry_duration: None, + triggerer_share_percentage: None, + shade_rewards_address: None, + galactic_pools_rewards_address: None, + grand_prize_address: None, + unclaimed_distribution: None, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("admin", &[]), + handle_msg, + ); + let round_obj = round_read_only_unit_test_helper(&deps.storage); + assert_eq!(round_obj.duration, 1); + + Ok(()) + } + + #[test] + fn test_update_round_change_unbonding_duration() -> StdResult<()> { + let (_init_result, mut deps) = init_helper(Some(800 * SCRT_TO_USCRT)); + let handle_msg = HandleMsg::UpdateConfig { + unbonding_batch_duration: None, + unbonding_duration: Some(1), + minimum_deposit_amount: None, + exp_contract: None, + }; + let res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("non-admin", &[]), + handle_msg, + ) + .unwrap_err(); + assert_eq!( + res, + StdError::generic_err( + "This is an admin command. Admin commands can only be run from admin address", + ) + ); + + let (_init_result, mut deps) = init_helper(Some(800 * SCRT_TO_USCRT)); + let handle_msg = HandleMsg::UpdateConfig { + unbonding_batch_duration: None, + unbonding_duration: Some(1), + minimum_deposit_amount: None, + exp_contract: None, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("admin", &[]), + handle_msg, + ); + let config_obj = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config_obj.unbonding_duration, 1); + + Ok(()) + } + + #[test] + fn test_unbond_batch() -> StdResult<()> { + let (_init_result, mut deps) = init_helper(None); + //1) User Deposits + for _ in 0..10 { + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + None, + "secretbatman", + None, + ); + //1.1)Request withdraw + let _ = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + None, + "secretbatman", + ); + + end_round_unit_test_helper(deps.as_mut())?; + } + + //2) Sponsors Deposits + let _ = sponsor_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + None, + "Bruce Wayne", + None, + ); + //2.1) Sponsor_request_withdraw + sponsor_request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + None, + "Bruce Wayne", + )?; + + let pool_state = pool_state_read_only_unit_test_helper(&deps.storage); + let reserves_req_withdraw = pool_state.total_reserves; + let handle_msg = HandleMsg::RequestReservesWithdraw { + amount: reserves_req_withdraw, + }; + + execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("admin", &[]), + handle_msg, + )?; + + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!( + config.next_unbonding_batch_amount.u128(), + 110 * SCRT_TO_USCRT + pool_state.total_reserves.u128() + ); + assert_eq!(config.next_unbonding_batch_index, 1); + assert_eq!(config.unbonding_batch_duration, 3600 * 24 * 3); + assert_eq!( + config.next_unbonding_batch_time, + mock_env().block.time.seconds() + config.unbonding_batch_duration + ); + + let handle_msg = HandleMsg::UnbondBatch {}; + //3) When not triggerer + let handle_result = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("non-triggerer", &[]), + handle_msg, + ); + + assert_eq!( + handle_result.unwrap_err(), + StdError::generic_err( + "This is an triggerer command. Triggerer commands can only be run from triggerer address", + ) + ); + + //3.1) When triggerer + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _handle_result = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + ); + + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config.next_unbonding_batch_amount.u128(), 0 * SCRT_TO_USCRT); + assert_eq!(config.next_unbonding_batch_index, 2); + + assert_eq!(config.unbonding_batch_duration, 3600 * 24 * 3); + assert_eq!( + config.next_unbonding_batch_time, + mock_env().block.time.seconds() + + config.unbonding_batch_duration + + config.unbonding_batch_duration + ); + + //4) When unbonding amount == 0 + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _handle_result = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + ); + let config = config_read_only_unit_test_helper(&deps.storage); + assert_eq!(config.next_unbonding_batch_amount.u128(), 0 * SCRT_TO_USCRT); + assert_eq!(config.next_unbonding_batch_index, 3); + + Ok(()) + } + + #[test] + fn test_request_reserves_withdraw() -> StdResult<()> { + //9 different dummy users deposit 100000 SCRT using the helper filler function below. + let mut deps = deposits_filler_unit_test_helper(Some(800 * SCRT_TO_USCRT)); + + for _ in 0..8 { + let _ = end_round_unit_test_helper(deps.as_mut())?; + } + let pool_state = pool_state_read_only_unit_test_helper(&deps.storage); + let req_withdraw_amount = pool_state.total_reserves; + + let config_obj = config_read_only_unit_test_helper(&deps.storage); + let mut total_delegated_sponsored_reserves = Uint128::zero(); + for val in config_obj.validators { + total_delegated_sponsored_reserves = + total_delegated_sponsored_reserves.add(val.delegated); + } + assert_eq!( + total_delegated_sponsored_reserves, + Uint128::from((100000) * SCRT_TO_USCRT).add(req_withdraw_amount) + ); + + let handle_msg = HandleMsg::RequestReservesWithdraw { + amount: req_withdraw_amount, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("admin", &[]), + handle_msg, + )?; + + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + )?; + let pool_state = pool_state_read_only_unit_test_helper(&deps.storage); + //Total amount deposited and sponsored should be equal to 100,000 SCRT (delegated) + let config_obj = config_read_only_unit_test_helper(&deps.storage); + let mut total_delegated_sponsored_reserves = Uint128::zero(); + for val in config_obj.validators { + total_delegated_sponsored_reserves = + total_delegated_sponsored_reserves.add(val.delegated); + } + assert_eq!( + total_delegated_sponsored_reserves, + Uint128::from((100000) * SCRT_TO_USCRT).add(pool_state.total_reserves) + ); + Ok(()) + } + + #[test] + fn test_reserves_withdraw() -> StdResult<()> { + let mut deps = deposits_filler_unit_test_helper(Some(800 * SCRT_TO_USCRT)); + + for _ in 0..10 { + let _ = end_round_unit_test_helper(deps.as_mut())?; + } + + let pool_state = pool_state_read_only_unit_test_helper(&deps.storage); + let req_withdraw_amount = pool_state.total_reserves; + let handle_msg = HandleMsg::RequestReservesWithdraw { + amount: req_withdraw_amount, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("admin", &[]), + handle_msg, + )?; + + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + )?; + //Total amount deposited and sponsored should be equal to 100,000 SCRT (delegated) + let config_obj = config_read_only_unit_test_helper(&deps.storage); + + let handle_msg = HandleMsg::ReservesWithdraw { + amount: req_withdraw_amount, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env( + None, + Some(config.next_unbonding_batch_time + config_obj.unbonding_duration.add(1)), + None, + None, + ), + mock_info("admin", &[]), + handle_msg, + )?; + + Ok(()) + } + + #[test] + fn test_rebalance_validator_set() -> StdResult<()> { + let mut deps = deposits_filler_unit_test_helper(None); + + let handle_msg = HandleMsg::RebalanceValidatorSet {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("admin", &[]), + handle_msg, + )?; + + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let pool_state = pool_state_read_only_unit_test_helper(deps.as_ref().storage); + + assert_eq!( + config.validators[0].delegated, + pool_state.total_delegated.multiply_ratio( + config.validators[0].weightage as u128, + config.common_divisor as u128, + ) + ); + assert_eq!( + config.validators[1].delegated, + pool_state.total_delegated.multiply_ratio( + config.validators[1].weightage as u128, + config.common_divisor as u128, + ) + ); + assert_eq!( + config.validators[2].delegated, + pool_state.total_delegated.multiply_ratio( + config.validators[2].weightage as u128, + config.common_divisor as u128, + ) + ); + + assert_eq!(config.next_validator_for_delegation, 0); + assert_eq!(config.next_validator_for_unbonding, 0); + + Ok(()) + } + + #[test] + fn test_update_validator_set() { + let mut deps = deposits_filler_unit_test_helper(Some(800 * SCRT_TO_USCRT)); + + let mut validator_vector: Vec = Vec::new(); + validator_vector.push(ValidatorInfo { + address: "galacticPools".to_string(), + weightage: (40 * COMMON_DIVISOR) / 100, + }); + validator_vector.push(ValidatorInfo { + address: "secureSecret".to_string(), + weightage: (10 * COMMON_DIVISOR) / 100, + }); + validator_vector.push(ValidatorInfo { + address: "xavierCapital".to_string(), + weightage: (0 * COMMON_DIVISOR) / 100, + }); + validator_vector.push(ValidatorInfo { + address: "IDK".to_string(), + weightage: (30 * COMMON_DIVISOR) / 100, + }); + validator_vector.push(ValidatorInfo { + address: "IDK2".to_string(), + weightage: (20 * COMMON_DIVISOR) / 100, + }); + + let handle_msg = HandleMsg::UpdateValidatorSet { + updated_validator_set: validator_vector, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("admin", &[]), + handle_msg, + ); + + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let pool_state = pool_state_read_only_unit_test_helper(deps.as_ref().storage); + + assert_eq!( + config.validators[0].delegated, + pool_state.total_delegated.multiply_ratio( + config.validators[0].weightage as u128, + config.common_divisor as u128, + ) + ); + assert_eq!(config.validators[0].address, "galacticPools".to_string()); + assert_eq!( + config.validators[1].delegated, + pool_state.total_delegated.multiply_ratio( + config.validators[1].weightage as u128, + config.common_divisor as u128, + ) + ); + assert_eq!(config.validators[1].address, "secureSecret".to_string()); + + assert_eq!( + config.validators[2].delegated, + pool_state.total_delegated.multiply_ratio( + config.validators[2].weightage as u128, + config.common_divisor as u128, + ) + ); + assert_eq!(config.validators[2].address, "IDK".to_string()); + + assert_eq!( + config.validators[3].delegated, + pool_state.total_delegated.multiply_ratio( + config.validators[3].weightage as u128, + config.common_divisor as u128, + ) + ); + assert_eq!(config.validators[3].address, "IDK2".to_string()); + assert_eq!(config.next_validator_for_delegation, 0); + assert_eq!(config.next_validator_for_unbonding, 0); + } + + #[test] + fn test_set_contract_status() -> StdResult<()> { + //Deposit : Normal + //Sponsor: Normal + //Request_withdraw: StopTransactions + //Withdraw: StopTransactions + //End_Round: StopTransactions + //ClaimRewards: StopTransactions + //CreateViewingKey: StopTransactions + //SetViewingKey: StopTransactions + //Request_withdraw_sponsor: StopTransactions + //Withdraw_sponsor: StopTransactions + + let (_init_result, mut deps) = init_helper(None); + let handle_msg = HandleMsg::SetContractStatus { + level: ContractStatus::StopTransactions, + }; + let res = execute( + deps.as_mut(), + mock_env(), + mock_info("non-admin", &[]), + handle_msg, + ); + assert_eq!( + res.unwrap_err(), + StdError::generic_err( + "This is an admin command. Admin commands can only be run from admin address", + ) + ); + + //StopTransactions + let handle_msg = HandleMsg::SetContractStatus { + level: ContractStatus::StopTransactions, + }; + execute( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + handle_msg, + )?; + + //Deposit: Normal + let res = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(1 * SCRT_TO_USCRT), + None, + None, + "secretbatman", + None, + ); + assert_eq!( + res.unwrap_err(), + StdError::generic_err("The contract admin has temporarily disabled this action") + ); + //Sponsor : Normal + let res = sponsor_unit_test_helper( + deps.as_mut(), + Uint128::from(1 * SCRT_TO_USCRT), + None, + None, + "secretbatman", + None, + ); + assert_eq!( + res.unwrap_err(), + StdError::generic_err("The contract admin has temporarily disabled this action") + ); + + //StopAll + let handle_msg = HandleMsg::SetContractStatus { + level: ContractStatus::StopAll, + }; + execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("admin", &[]), + handle_msg, + )?; + //Request_withdraw: StopTransactions + let res = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from(1 * SCRT_TO_USCRT), + None, + None, + "secretbatman", + ); + assert_eq!( + res.unwrap_err(), + StdError::generic_err("The contract admin has temporarily disabled this action") + ); + //Withdraw: StopTransaction + let res = withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from(1 * SCRT_TO_USCRT), + None, + None, + "secretbatman", + ); + assert_eq!( + res.unwrap_err(), + StdError::generic_err("The contract admin has temporarily disabled this action") + ); + //End_Round: StopTransactions + let res = end_round_unit_test_helper(deps.as_mut()); + assert_eq!( + res.unwrap_err(), + StdError::generic_err("The contract admin has temporarily disabled this action") + ); + //ClaimRewards: StopTransactions + let res = claim_rewards_unit_test_helper(deps.as_mut(), None, None, "secretbatman"); + assert_eq!( + res.unwrap_err(), + StdError::generic_err("The contract admin has temporarily disabled this action") + ); + //CreateViewingKey: StopTransactions + let res = create_viewking_key_unit_test_helper(deps.as_mut(), "secretbatman"); + assert_eq!( + res.unwrap_err(), + StdError::generic_err("The contract admin has temporarily disabled this action") + ); + //SetViewingKey: StopTransactions + let res = set_viewing_key_unit_test_helper(deps.as_mut(), "secretbatman", "hi lol"); + assert_eq!( + res.unwrap_err(), + StdError::generic_err("The contract admin has temporarily disabled this action") + ); + + //Request_withdraw_sponsor: StopTransactions + let res = sponsor_request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::zero(), + None, + None, + "secretbatman", + ); + assert_eq!( + res.unwrap_err(), + StdError::generic_err("The contract admin has temporarily disabled this action") + ); + //Withdraw_sponsor: StopTransactions + let res = sponsor_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::zero(), + None, + None, + "secretbatman", + ); + assert_eq!( + res.unwrap_err(), + StdError::generic_err("The contract admin has temporarily disabled this action") + ); + + Ok(()) + } + + ////////////////////////////////////// Queries ////////////////////////////////////// + #[test] + fn test_query_contract_config() -> StdResult<()> { + // Error check + let (init_result, deps) = init_helper(None); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + //Checking query results + let query_msg = QueryMsg::ContractConfig {}; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg); + assert!( + query_result.is_ok(), + "query failed: {}", + query_result.err().unwrap() + ); + let query_answer: ContractConfigResponse = from_binary(&query_result.unwrap())?; + match query_answer { + ContractConfigResponse { + admins, + triggerers, + reviewers, + + denom, + contract_address, + validators, + next_unbonding_batch_time, + next_unbonding_batch_amount, + unbonding_batch_duration, + unbonding_duration, + minimum_deposit_amount, + exp_contract, + } => { + assert_eq!(admins[0], ("admin".to_string())); + assert_eq!(triggerers[0], ("triggerer".to_string())); + assert_eq!(reviewers[0], ("reviewer".to_string())); + assert_eq!(denom, "uscrt".to_string()); + assert_eq!(contract_address, ("cosmos2contract".to_string())); + assert_eq!(validators[0].address, ("galacticPools".to_string())); + assert_eq!(validators[1].address, ("secureSecret".to_string())); + assert_eq!(validators[2].address, ("xavierCapital".to_string())); + + assert_eq!( + next_unbonding_batch_time, + mock_env().block.time.seconds() + 3600 * 24 * 3 + ); + assert_eq!( + next_unbonding_batch_amount, + Uint128::from(0 * SCRT_TO_USCRT) + ); + assert_eq!(unbonding_batch_duration, 3600 * 24 * 3); + assert_eq!(unbonding_duration, 3600 * 24 * 21); + assert_eq!(minimum_deposit_amount, None); + assert_eq!(exp_contract, None); + } // _ => panic!("unexpected"), + } + + Ok(()) + } + + #[test] + fn test_query_contract_status() -> StdResult<()> { + // Error check + let (init_result, mut deps) = init_helper(None); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + //Checking query results + let query_msg = QueryMsg::ContractStatus {}; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg); + assert!( + query_result.is_ok(), + "query failed: {}", + query_result.err().unwrap() + ); + let query_answer: ContractStatusResponse = from_binary(&query_result?)?; + match query_answer { + ContractStatusResponse { status } => { + assert_eq!(status, ContractStatus::Normal) + } // _ => panic!("unexpected"), + } + + //Setting contract status to StopTransactions + let handle_msg = HandleMsg::SetContractStatus { + level: ContractStatus::StopTransactions, + }; + execute( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + handle_msg, + )?; + + //Checking query results + let query_msg = QueryMsg::ContractStatus {}; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg); + assert!( + query_result.is_ok(), + "query failed: {}", + query_result.err().unwrap() + ); + let query_answer: ContractStatusResponse = from_binary(&query_result?)?; + match query_answer { + ContractStatusResponse { status } => { + assert_eq!(status, ContractStatus::StopTransactions) + } // _ => panic!("unexpected"), + } + + Ok(()) + } + + #[test] + fn test_query_round_obj() -> StdResult<()> { + // Error check + let (init_result, deps) = init_helper(None); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + //Checking query results + let query_msg = QueryMsg::Round {}; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg); + assert!( + query_result.is_ok(), + "query failed: {}", + query_result.err().unwrap() + ); + let query_answer: RoundResponse = from_binary(&query_result?)?; + match query_answer { + RoundResponse { + duration, + start_time, + end_time, + rewards_distribution, + current_round_index, + ticket_price, + rewards_expiry_duration, + admin_share, + triggerer_share_percentage, + unclaimed_distribution, + } => { + assert_eq!(duration, 3600 * 24 * 7); + assert_eq!(start_time, mock_env().block.time.seconds()); + assert_eq!(end_time, mock_env().block.time.seconds() + 3600 * 24 * 7); + let r_d: RewardsDistInfo = RewardsDistInfo { + tier_0: DistInfo { + total_number_of_winners: Uint128::from(1u128), + percentage_of_rewards: (20 * 10000) / 100, + }, + tier_1: DistInfo { + total_number_of_winners: Uint128::from(3u128), + percentage_of_rewards: (10 * 10000) / 100, + }, + tier_2: DistInfo { + total_number_of_winners: Uint128::from(9u128), + percentage_of_rewards: (14 * 10000) / 100, + }, + tier_3: DistInfo { + total_number_of_winners: Uint128::from(27u128), + percentage_of_rewards: (12 * 10000) / 100, + }, + tier_4: DistInfo { + total_number_of_winners: Uint128::from(81u128), + percentage_of_rewards: (19 * 10000) / 100, + }, + tier_5: DistInfo { + total_number_of_winners: Uint128::from(243u128), + percentage_of_rewards: (25 * 10000) / 100, + }, + }; + assert_eq!(rewards_distribution, r_d); + assert_eq!(current_round_index, 1u64); + assert_eq!(ticket_price.u128(), 1 * SCRT_TO_USCRT); + assert_eq!(rewards_expiry_duration, 3600 * 24 * 45); + assert_eq!( + admin_share.galactic_pools_percentage_share, + (40 * COMMON_DIVISOR) / 100 as u64 + ); + assert_eq!( + admin_share.shade_percentage_share, + (60 * COMMON_DIVISOR) / 100 as u64 + ); + assert_eq!(triggerer_share_percentage, (1 * COMMON_DIVISOR) / 100); + assert_eq!(unclaimed_distribution, UnclaimedDistInfo { + reserves_percentage: (60 * COMMON_DIVISOR) / 100, + propagate_percentage: COMMON_DIVISOR.sub((60 * COMMON_DIVISOR) / 100), + }); + } // _ => panic!("unexpected"), + } + + Ok(()) + } + + #[test] + fn test_query_pool_state() -> StdResult<()> { + let deps = deposits_filler_unit_test_helper(None); + + //Checking query results + let query_msg = QueryMsg::PoolState {}; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg); + assert!( + query_result.is_ok(), + "query failed: {}", + query_result.err().unwrap() + ); + let query_answer: PoolStateInfoResponse = from_binary(&query_result?)?; + match query_answer { + PoolStateInfoResponse { + total_delegated, + rewards_returned_to_contract, + total_reserves, + total_sponsored, + } => { + assert_eq!(total_delegated.u128(), 100000 * SCRT_TO_USCRT); + assert_eq!(rewards_returned_to_contract.u128(), 10 * 10 * SCRT_TO_USCRT); + assert_eq!(total_reserves.u128(), 0 * SCRT_TO_USCRT); + assert_eq!(total_sponsored.u128(), 0 * SCRT_TO_USCRT); + } + } + + Ok(()) + } + + #[test] + fn test_query_pool_state_liquidity_stats() -> StdResult<()> { + let deps = deposits_filler_unit_test_helper(None); + + //Checking query results + let query_msg = QueryMsg::PoolStateLiquidityStats {}; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg); + assert!( + query_result.is_ok(), + "query failed: {}", + query_result.err().unwrap() + ); + let query_answer: PoolStateLiquidityStatsResponse = from_binary(&query_result?)?; + match query_answer { + PoolStateLiquidityStatsResponse { total_liquidity } => { + // 70k was deposit at t0 and 30k was deposited at t(1/2) hence the avg liquidit is 85k + assert_eq!(total_liquidity.u128(), 85000 * SCRT_TO_USCRT); + } + } + Ok(()) + } + #[test] + fn test_query_pool_state_liquidity_stats_specific() -> StdResult<()> { + let deps = deposits_filler_unit_test_helper(None); + + //Checking query results + let query_msg = QueryMsg::PoolStateLiquidityStatsSpecific { round_index: 1u64 }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg); + assert!( + query_result.is_ok(), + "query failed: {}", + query_result.err().unwrap() + ); + let query_answer: PoolStateLiquidityStatsResponse = from_binary(&query_result?)?; + match query_answer { + PoolStateLiquidityStatsResponse { total_liquidity } => { + // 70k was deposit at t0 and 30k was deposited at t(1/2) hence the avg liquidit is 85k + assert_eq!(total_liquidity.u128(), 85000 * SCRT_TO_USCRT); + } + } + Ok(()) + } + + #[test] + fn test_query_current_rewards() -> StdResult<()> { + let deps = deposits_filler_unit_test_helper(None); + + //Checking query results + let query_msg = QueryMsg::CurrentRewards {}; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg); + assert!( + query_result.is_ok(), + "query failed: {}", + query_result.err().unwrap() + ); + let query_answer: CurrentRewardsResponse = from_binary(&query_result?)?; + match query_answer { + CurrentRewardsResponse { rewards } => { + // 10 rewards of 10 tokens each were returned. And 3 rewards of 10 tokens each were pending/unclaimed at each validator + assert_eq!(rewards.u128(), (10 * 10 + 10 * 3) * SCRT_TO_USCRT); + } + } + Ok(()) + } + + //Authenticated + Permit based Queries + + #[test] + fn test_query_permits() -> StdResult<()> { + let deps = deposits_filler_unit_test_helper(None); + + let token = "cosmos2contract".to_string(); + //1) Checking signature validity + let permit: Permit = Permit { + params: PermitParams { + allowed_tokens: vec![token.clone()], + permit_name: "galactic_pools_batman".to_string(), + chain_id: "pulsar-2".to_string(), + permissions: vec![GalacticPoolsPermissions::Delegated], + }, + signature: PermitSignature { + pub_key: PubKey { + r#type: "tendermint/PubKeySecp256k1".to_string(), + value: Binary::from_base64("AihRsQtwF56zrW7J1VP4KqTNBF5GTMXoFusm6zfIixvD")?, + }, + signature: Binary::from_base64( + "mU+0UZJoFbWS2sqPoN75ed0KiMHj+Mie520IhJa6Zcxux9ky5v/jWatLugEhb8JruJ0c4Bi0ZlA+tK7ydppTug==", + )?, + }, + }; + + let address = validate::( + deps.as_ref(), + PREFIX_REVOKED_PERMITS, + &permit, + token.clone(), + None, + )?; + + assert_eq!( + address, + "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7".to_string() + ); + + //2) Check error if the signature is not correct in reference to the permit + let deps = mock_dependencies(); + + let permit: Permit = Permit { + params: PermitParams { + allowed_tokens: vec![token.clone()], + permit_name: "galactic_pools_batman".to_string(), + chain_id: "pulsar-2".to_string(), + permissions: vec![GalacticPoolsPermissions::Owner], + }, + signature: PermitSignature { + pub_key: PubKey { + r#type: "tendermint/PubKeySecp256k1".to_string(), + value: Binary::from_base64("AihRsQtwF56zrW7J1VP4KqTNBF5GTMXoFusm6zfIixvD")?, + }, + signature: Binary::from_base64( + "rhHv9JwULRoxm4/CnAJSWMa76Q40U8MKZFKgDrEAkk8m2yvUHIOYmF/oc6VsJICDePhbzHyzlg35X5pjw8Yn9A==", + )?, + }, + }; + let address = validate::( + deps.as_ref(), + PREFIX_REVOKED_PERMITS, + &permit, + token.clone(), + None, + ); + + assert_eq!( + address, + Err(StdError::generic_err( + "Failed to verify signatures for the given permit", + )) + ); + + //3) Check error if the user doesn't have the permission to particular query function + let deps = deposits_filler_unit_test_helper(None); + let token = "cosmos2contract".to_string(); + + let permit: Permit = Permit { + params: PermitParams { + allowed_tokens: vec![token.clone()], + permit_name: "galactic_pools_batman".to_string(), + chain_id: "pulsar-2".to_string(), + permissions: vec![GalacticPoolsPermissions::Unbondings], + }, + signature: PermitSignature { + pub_key: PubKey { + r#type: "tendermint/PubKeySecp256k1".to_string(), + value: Binary::from_base64("AihRsQtwF56zrW7J1VP4KqTNBF5GTMXoFusm6zfIixvD")?, + }, + signature: Binary::from_base64( + "QSMrJNzYIbaFGNronY8IEjndWtPEFnaOyYdG7mbPdQE6uonPfuhK3oBXM5Sf+TAcASEj4b+8aS1lJYfToH1O+w==", + )?, + }, + }; + + let query_msg = QueryMsg::WithPermit { + permit, + query: QueryWithPermit::Delegated {}, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg); + assert_eq!( + query_result.unwrap_err(), + StdError::generic_err( + "Owner or Delegated permission is required for queries, got permissions [Unbondings]" + ) + ); + + //4) Checking the results of the query when wrong query_message + let permit: Permit = Permit { + params: PermitParams { + allowed_tokens: vec![token.clone()], + permit_name: "galactic_pools_batman".to_string(), + chain_id: "pulsar-2".to_string(), + permissions: vec![GalacticPoolsPermissions::Owner], + }, + signature: PermitSignature { + pub_key: PubKey { + r#type: "tendermint/PubKeySecp256k1".to_string(), + value: Binary::from_base64("AihRsQtwF56zrW7J1VP4KqTNBF5GTMXoFusm6zfIixvD")?, + }, + signature: Binary::from_base64( + "q7MQVPXCwA89cBMl/dCQhZ87dxzrNhxlQUUEznf4JvhWluAhRaNvblSofu79lYGUJ0+mfH1KMCsmF+kkARHYpQ==", + )?, + }, + }; + + let query_msg = QueryMsg::WithPermit { + permit, + query: QueryWithPermit::Test {}, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg); + assert!(query_result.is_err()); + + Ok(()) + } + + #[test] + fn test_authenticated_queries() -> StdResult<()> { + //1) Checking error: if the vk is not set + let _env = custom_mock_env(None, None, None, None); + let mut deps = deposits_filler_unit_test_helper(None); + + let no_vk_yet_query_msg = QueryMsg::Delegated { + address: "secretbatman".to_string(), + key: "no_vk_yet".to_string(), + }; + let query_result: ViewingKeyErrorResponse = + from_binary(&query(deps.as_ref(), _env, no_vk_yet_query_msg)?)?; + + assert_eq!(query_result, ViewingKeyErrorResponse { + msg: "Wrong viewing key for this address or viewing key not set".to_string(), + }); + + //2) Checking after the vk is set. + let create_vk_msg = HandleMsg::CreateViewingKey { + entropy: "heheeehe".to_string(), + }; + let _env = custom_mock_env(None, None, None, None); + let info = mock_info("secretbatman", &[]); + let handle_response = execute(deps.as_mut(), _env, info, create_vk_msg)?; + let vk = match from_binary(&handle_response.data.unwrap())? { + HandleAnswer::CreateViewingKey { key } => key, + _ => panic!("Unexpected result from handle"), + }; + + let query_balance_msg = QueryMsg::Delegated { + address: ("secretbatman".to_string()), + key: vk.0, + }; + + let _env = custom_mock_env(None, None, None, None); + let query_response = query(deps.as_ref(), _env, query_balance_msg)?; + let balance = match from_binary(&query_response)? { + DelegatedResponse { amount } => amount, + }; + assert_eq!(balance, Uint128::from(60000 * SCRT_TO_USCRT)); + + //3) Checking if the vk is wrong + let wrong_vk_query_msg = QueryMsg::Delegated { + address: ("secretbatman".to_string()), + key: "wrong_vk".to_string(), + }; + let _env = custom_mock_env(None, None, None, None); + let query_result: ViewingKeyErrorResponse = + from_binary(&query(deps.as_ref(), _env, wrong_vk_query_msg)?)?; + assert_eq!(query_result, ViewingKeyErrorResponse { + msg: "Wrong viewing key for this address or viewing key not set".to_string(), + }); + + Ok(()) + } + + #[test] + fn test_query_delegated() -> StdResult<()> { + let mut deps = deposits_filler_unit_test_helper(None); + let token = "cosmos2contract".to_string(); + + //Permit Queries + //1) Depositing just for the example + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + None, + "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + None, + ); + + //1.1) Checking the results of the query when permission is delegated + let permit: Permit = Permit { + params: PermitParams { + allowed_tokens: vec![token.clone()], + permit_name: "galactic_pools_batman".to_string(), + chain_id: "pulsar-2".to_string(), + permissions: vec![GalacticPoolsPermissions::Delegated], + }, + signature: PermitSignature { + pub_key: PubKey { + r#type: "tendermint/PubKeySecp256k1".to_string(), + value: Binary::from_base64("AihRsQtwF56zrW7J1VP4KqTNBF5GTMXoFusm6zfIixvD")?, + }, + signature: Binary::from_base64( + "mU+0UZJoFbWS2sqPoN75ed0KiMHj+Mie520IhJa6Zcxux9ky5v/jWatLugEhb8JruJ0c4Bi0ZlA+tK7ydppTug==", + )?, + }, + }; + + let query_msg = QueryMsg::WithPermit { + permit, + query: QueryWithPermit::Delegated {}, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg); + assert!( + query_result.is_ok(), + "query failed: {}", + query_result.err().unwrap() + ); + let query_answer: DelegatedResponse = from_binary(&query_result?)?; + match query_answer { + DelegatedResponse { amount } => { + // 10 rewards of 10 tokens each were returned. And 3 rewards of 10 tokens each were pending/unclaimed at each validator + assert_eq!(amount.u128(), 10 * SCRT_TO_USCRT); + } + } + + //2) Checking the results of the query when permission is owner + let permit: Permit = Permit { + params: PermitParams { + allowed_tokens: vec![token.clone()], + permit_name: "galactic_pools_batman".to_string(), + chain_id: "pulsar-2".to_string(), + permissions: vec![GalacticPoolsPermissions::Owner], + }, + signature: PermitSignature { + pub_key: PubKey { + r#type: "tendermint/PubKeySecp256k1".to_string(), + value: Binary::from_base64("AihRsQtwF56zrW7J1VP4KqTNBF5GTMXoFusm6zfIixvD")?, + }, + signature: Binary::from_base64( + "q7MQVPXCwA89cBMl/dCQhZ87dxzrNhxlQUUEznf4JvhWluAhRaNvblSofu79lYGUJ0+mfH1KMCsmF+kkARHYpQ==", + )?, + }, + }; + + let query_msg = QueryMsg::WithPermit { + permit, + query: QueryWithPermit::Delegated {}, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg); + assert!( + query_result.is_ok(), + "query failed: {}", + query_result.err().unwrap() + ); + let query_answer: DelegatedResponse = from_binary(&query_result?)?; + match query_answer { + DelegatedResponse { amount } => { + // 10 rewards of 10 tokens each were returned. And 3 rewards of 10 tokens each were pending/unclaimed at each validator + assert_eq!(amount.u128(), 10 * SCRT_TO_USCRT); + } + } + + //Authenticated Queries + + Ok(()) + } + + #[test] + fn test_query_liquidity() -> StdResult<()> { + //starts round + let (_init_result, mut deps) = init_helper(None); + let round_obj = round_read_only_unit_test_helper(&mut deps.storage); + let token = "cosmos2contract".to_string(); + //Permit Queries + //1) Depositing just for the example + //1.1) Checking the results of the query when permission is delegated + let permit: Permit = Permit { + params: PermitParams { + allowed_tokens: vec![token.clone()], + permit_name: "galactic_pools_batman".to_string(), + chain_id: "pulsar-2".to_string(), + permissions: vec![GalacticPoolsPermissions::Owner], + }, + signature: PermitSignature { + pub_key: PubKey { + r#type: "tendermint/PubKeySecp256k1".to_string(), + value: Binary::from_base64("AihRsQtwF56zrW7J1VP4KqTNBF5GTMXoFusm6zfIixvD")?, + }, + signature: Binary::from_base64( + "q7MQVPXCwA89cBMl/dCQhZ87dxzrNhxlQUUEznf4JvhWluAhRaNvblSofu79lYGUJ0+mfH1KMCsmF+kkARHYpQ==", + )?, + }, + }; + + let query_msg = QueryMsg::WithPermit { + permit: permit.clone(), + query: QueryWithPermit::Liquidity { round_index: 2 }, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg); + assert!( + query_result.is_ok(), + "query failed: {}", + query_result.err().unwrap() + ); + let query_answer: LiquidityResponse = from_binary(&query_result?)?; + match query_answer { + LiquidityResponse { + total_liq, + total_tickets, + ticket_price, + user_liq, + user_tickets, + tickets_used, + expiry_date, + total_rewards, + unclaimed_rewards, + } => { + // 10 rewards of 10 tokens each were returned. And 3 rewards of 10 tokens each were pending/unclaimed at each validator + assert_eq!(total_liq.u128(), 0 * SCRT_TO_USCRT); + assert_eq!(total_tickets.u128(), 0); + assert_eq!(ticket_price.u128(), 1 * SCRT_TO_USCRT); + assert_eq!(user_liq.u128(), 0 * SCRT_TO_USCRT); + assert_eq!(user_tickets.u128(), 0); + assert_eq!(tickets_used.u128(), 0); + assert_eq!(expiry_date, None); + assert_eq!(total_rewards.u128(), 0); + assert_eq!(unclaimed_rewards.u128(), 0); + } + } + //deposit 2 million scrt at t0 + let env = custom_mock_env(None, None, None, None); + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(2000000 * SCRT_TO_USCRT), + Some(0u64), + Some(round_obj.start_time), + "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + None, + ); + let query_msg = QueryMsg::WithPermit { + permit: permit.clone(), + query: QueryWithPermit::Liquidity { round_index: 1 }, + }; + let query_result = query(deps.as_ref(), env, query_msg); + assert!( + query_result.is_ok(), + "query failed: {}", + query_result.err().unwrap() + ); + let query_answer: LiquidityResponse = from_binary(&query_result?)?; + match query_answer { + LiquidityResponse { + total_liq, + total_tickets, + ticket_price, + user_liq, + user_tickets, + tickets_used, + expiry_date, + total_rewards, + unclaimed_rewards, + } => { + // 10 rewards of 10 tokens each were returned. And 3 rewards of 10 tokens each were pending/unclaimed at each validator + assert_eq!(total_liq.u128(), 2000000 * SCRT_TO_USCRT); + assert_eq!(total_tickets.u128(), 2000000); + assert_eq!(ticket_price.u128(), 1 * SCRT_TO_USCRT); + assert_eq!(user_liq.u128(), 2000000 * SCRT_TO_USCRT); + assert_eq!(user_tickets.u128(), 2000000); + assert_eq!(tickets_used.u128(), 0); + assert_eq!(expiry_date, None); + assert_eq!(total_rewards.u128(), 0); + assert_eq!(unclaimed_rewards.u128(), 0); + } + } + //End_Round x 2 + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-1 + let _ = end_round_unit_test_helper(deps.as_mut())?; //round-1 + + //claim_rewards + // must take 4 iterations + // 1- last claim round == None + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + )?; + let user_info = user_info_read_only_unit_test_helper( + &Addr::unchecked("secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7"), + &deps.storage, + ); + let user_liquidity_snapshot_obj = user_liquidity_stats_read_only_unit_test_helper( + &deps + .api + .addr_validate("secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7")?, + &deps.storage, + 1, + ); + assert_eq!( + user_liquidity_snapshot_obj.tickets_used.unwrap().u128(), + 1000000 + ); + + //Testing + let query_msg = QueryMsg::WithPermit { + permit, + query: QueryWithPermit::Liquidity { round_index: 1 }, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg); + assert!( + query_result.is_ok(), + "query failed: {}", + query_result.err().unwrap() + ); + let query_answer: LiquidityResponse = from_binary(&query_result?)?; + match query_answer { + LiquidityResponse { + total_liq, + total_tickets, + ticket_price, + user_liq, + user_tickets, + tickets_used, + .. + } => { + // 10 rewards of 10 tokens each were returned. And 3 rewards of 10 tokens each were pending/unclaimed at each validator + assert_eq!(total_liq.u128(), 2000000 * SCRT_TO_USCRT); + assert_eq!(total_tickets.u128(), 2000000); + assert_eq!(ticket_price.u128(), 1 * SCRT_TO_USCRT); + assert_eq!(user_liq.u128(), 2000000 * SCRT_TO_USCRT); + assert_eq!(user_tickets.u128(), 2000000); + assert_eq!(tickets_used.u128(), 1000000); + } + } + + assert_eq!(user_info.last_claim_rewards_round, None); + // 2- last claim round == 1 + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + )?; + let user_info = user_info_read_only_unit_test_helper( + &Addr::unchecked("secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7"), + &deps.storage, + ); + assert_eq!(user_info.last_claim_rewards_round.unwrap(), 1); + + // 3- last claim round == 1 + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + )?; + let user_info = user_info_read_only_unit_test_helper( + &Addr::unchecked("secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7"), + &deps.storage, + ); + assert_eq!(user_info.last_claim_rewards_round.unwrap(), 1); + + let user_liquidity_snapshot_obj = user_liquidity_stats_read_only_unit_test_helper( + &deps + .api + .addr_validate("secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7")?, + &deps.storage, + 2, + ); + assert_eq!( + user_liquidity_snapshot_obj.tickets_used.unwrap().u128(), + 1000000 + ); + + // 4- last claim round == 2` + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + )?; + let _user_info = user_info_read_only_unit_test_helper( + &Addr::unchecked("secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7"), + &deps.storage, + ); + Ok(()) + } + + #[test] + fn test_query_withdrawable() -> StdResult<()> { + //Permit Queries + let mut deps = deposits_filler_unit_test_helper(None); + let token = "cosmos2contract".to_string(); + //1) Depositing just for the example + let env = mock_env(); + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + None, + "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + None, + ); + for _ in 0..10 { + let _ = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from(1 * SCRT_TO_USCRT), + None, + Some(env.block.time.seconds()), + "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + ); + } + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + mock_info("triggerer", &[]), + handle_msg, + )?; + + //1.1) Checking the results of the query when permission is delegated + let permit: Permit = Permit { + params: PermitParams { + allowed_tokens: vec![token.clone()], + permit_name: "galactic_pools_batman".to_string(), + chain_id: "pulsar-2".to_string(), + permissions: vec![GalacticPoolsPermissions::Withdrawable], + }, + signature: PermitSignature { + pub_key: PubKey { + r#type: "tendermint/PubKeySecp256k1".to_string(), + value: Binary::from_base64("AihRsQtwF56zrW7J1VP4KqTNBF5GTMXoFusm6zfIixvD")?, + }, + signature: Binary::from_base64( + "T7jQblf+v5UN3lFOZwGdvxis3Ryqc2NKvf6bdPP96NNFs7bkLUou0bKg8x4XxGILII7rRNRsjX1kxwnf5vp5aw==", + )?, + }, + }; + validate::( + deps.as_ref(), + PREFIX_REVOKED_PERMITS, + &permit, + token.clone(), + None, + )?; + + let query_msg = QueryMsg::WithPermit { + permit, + query: QueryWithPermit::Withdrawable {}, + }; + let env = custom_mock_env( + None, + Some( + mock_env() + .block + .time + .seconds() + .add(config.next_unbonding_batch_time) + + 3600 * 24 * 3 + + 3600 * 2400 * 21, + ), + None, + None, + ); + let query_result = query(deps.as_ref(), env, query_msg); + assert!( + query_result.is_ok(), + "query failed: {}", + query_result.err().unwrap() + ); + let query_answer: WithdrawablelResponse = from_binary(&query_result?)?; + match query_answer { + WithdrawablelResponse { amount } => { + assert_eq!(amount.u128(), 10 * SCRT_TO_USCRT); + } + } + + Ok(()) + } + + #[test] + fn test_query_unbondings() -> StdResult<()> { + //Permit Queries + let mut deps = deposits_filler_unit_test_helper(None); + let token = "cosmos2contract".to_string(); + //1) Depositing just for the example + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + None, + "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + None, + ); + for _ in 0..10 { + let _ = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from(1 * SCRT_TO_USCRT), + None, + None, + "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + ); + } + + let config = config_read_only_unit_test_helper(deps.as_ref().storage); + let env = custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None); + let handle_msg = HandleMsg::UnbondBatch {}; + let _res = execute( + deps.as_mut(), + env.clone(), + mock_info("triggerer", &[]), + handle_msg, + )?; + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(10 * SCRT_TO_USCRT), + None, + None, + "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + None, + ); + for _ in 0..10 { + let _ = request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from(1 * SCRT_TO_USCRT), + None, + None, + "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + ); + } + + //1.1) Checking the results of the query when permission is unbondings + let permit: Permit = Permit { + params: PermitParams { + allowed_tokens: vec![token.clone()], + permit_name: "galactic_pools_batman".to_string(), + chain_id: "pulsar-2".to_string(), + permissions: vec![GalacticPoolsPermissions::Unbondings], + }, + signature: PermitSignature { + pub_key: PubKey { + r#type: "tendermint/PubKeySecp256k1".to_string(), + value: Binary::from_base64("AihRsQtwF56zrW7J1VP4KqTNBF5GTMXoFusm6zfIixvD")?, + }, + signature: Binary::from_base64( + "QSMrJNzYIbaFGNronY8IEjndWtPEFnaOyYdG7mbPdQE6uonPfuhK3oBXM5Sf+TAcASEj4b+8aS1lJYfToH1O+w==", + )?, + }, + }; + validate::( + deps.as_ref(), + PREFIX_REVOKED_PERMITS, + &permit, + token.clone(), + None, + )?; + + let query_msg = QueryMsg::WithPermit { + permit, + query: QueryWithPermit::Unbondings {}, + }; + let query_result = query(deps.as_ref(), env.clone(), query_msg.clone()); + assert!( + query_result.is_ok(), + "query failed: {}", + query_result.err().unwrap() + ); + let query_answer: UnbondingsResponse = from_binary(&query_result?)?; + match query_answer { + UnbondingsResponse { vec, len } => { + assert_eq!(vec.len(), 2); + assert_eq!(vec[0].batch_index, 1); + assert_eq!(vec[0].amount.u128(), 10 * SCRT_TO_USCRT); + assert_eq!( + vec[0].unbonding_time.unwrap(), + env.clone().block.time.seconds() + 3600 * 24 * 21 + ); + assert_eq!(vec[0].next_batch_unbonding_time, None); + + assert_eq!(vec[1].batch_index, 2); + assert_eq!(vec[1].amount.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(vec[1].unbonding_time, None); + assert_eq!( + vec[1].next_batch_unbonding_time.unwrap(), + env.clone().block.time.seconds() + 3600 * 24 * 3 + ); + + assert_eq!(len, 2); + } + } + + //After 21 days + + let query_result = query(deps.as_ref(), env.clone(), query_msg); + assert!( + query_result.is_ok(), + "query failed: {}", + query_result.err().unwrap() + ); + let query_answer: UnbondingsResponse = from_binary(&query_result?)?; + match query_answer { + UnbondingsResponse { vec, len } => { + assert_eq!(vec.len(), 2); + assert_eq!(vec[0].batch_index, 1); + assert_eq!(vec[0].amount.u128(), 10 * SCRT_TO_USCRT); + assert_eq!( + vec[0].unbonding_time.unwrap(), + env.clone().block.time.seconds() + 3600 * 24 * 21 + ); + assert_eq!(vec[0].next_batch_unbonding_time, None); + + assert_eq!(vec[1].batch_index, 2); + assert_eq!(vec[1].amount.u128(), 10 * SCRT_TO_USCRT); + assert_eq!(vec[1].unbonding_time, None); + assert_eq!( + vec[1].next_batch_unbonding_time.unwrap(), + env.clone().block.time.seconds() + 3600 * 24 * 3 + ); + + assert_eq!(len, 2); + } + } + + Ok(()) + } + + #[test] + fn test_query_records() -> StdResult<()> { + //Permit Queries + let mut deps = deposits_filler_unit_test_helper(None); + let token = "cosmos2contract".to_string(); + //1) Depositing just for the example + let _ = deposit_unit_test_helper( + deps.as_mut(), + Uint128::from(5000 * SCRT_TO_USCRT), + None, + None, + "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + None, + ); + + for _ in 0..2 { + let round_obj = round_read_only_unit_test_helper(deps.as_ref().storage); + + let _ = end_round_unit_test_helper(deps.as_mut())?; + let _ = claim_rewards_unit_test_helper( + deps.as_mut(), + None, + Some(round_obj.end_time), + "secret1uzzzzr02xk9cuxn6ejp2axsyf4cjzznklzjmq7", + )?; + } + + let permit: Permit = Permit { + params: PermitParams { + allowed_tokens: vec![token.clone()], + permit_name: "galactic_pools_batman".to_string(), + chain_id: "pulsar-2".to_string(), + permissions: vec![GalacticPoolsPermissions::Records], + }, + signature: PermitSignature { + pub_key: PubKey { + r#type: "tendermint/PubKeySecp256k1".to_string(), + value: Binary::from_base64("AihRsQtwF56zrW7J1VP4KqTNBF5GTMXoFusm6zfIixvD")?, + }, + signature: Binary::from_base64( + "9WNoBfC8G4aJHdDBh4u8ATTZSt0xZZJyf5N+TcaxxZd1RW1pufT/80FSBWAF7ENFp7oSaW5qbOH9erd4kz9byA==", + )?, + }, + }; + validate::( + deps.as_ref(), + PREFIX_REVOKED_PERMITS, + &permit, + token.clone(), + None, + )?; + + let query_msg = QueryMsg::WithPermit { + permit, + query: QueryWithPermit::Records { + page_size: Some(100), + start_page: None, + }, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg); + assert!( + query_result.is_ok(), + "query failed: {}", + query_result.err().unwrap() + ); + let query_answer: RecordsResponse = from_binary(&query_result?)?; + match query_answer { + RecordsResponse { vec, len } => { + assert!(vec.len() != 0); + assert!(len != 0); + } + } + + Ok(()) + } + + #[test] + fn test_query_sponsors() -> StdResult<()> { + let (_init_result, mut deps) = init_helper(None); + + //1.1) Deposit with the message + for i in 0..13 { + let sponsor = format!("user{}", i); + let handle_msg = HandleMsg::Sponsor { + title: Some("Bruce Wayne".to_string()), + message: Some("I'm rich".to_string()), + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info(&sponsor, &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + } + + //1.2) Query the Sponsors List with page size and page index == NONE + let query_msg = QueryMsg::Sponsors { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorsResponse { vec, len } => { + assert_eq!(vec.len(), 5); + assert_eq!(len, 13); + //returns page = 0 & page_size = 5 + + for index in 0..5 { + assert_eq!(vec[index].addr_list_index, Some(index as u32)); + } + } + } + //1.3) Query the Sponsors List page 0 page_size 5 + let query_msg = QueryMsg::Sponsors { + page_size: Some(15), + start_page: Some(0), + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorsResponse { vec, len } => { + assert_eq!(vec.len(), 13); + assert_eq!(len, 13); + let mut index = 0; + for val in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] { + assert_eq!(vec[index].addr_list_index, Some((val) as u32)); + index += 1; + } + } + } + + //1.3) Query the Sponsors List page 0 page_size 5 + let query_msg = QueryMsg::Sponsors { + page_size: Some(5), + start_page: Some(0), + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorsResponse { vec, len } => { + assert_eq!(vec.len(), 5); + assert_eq!(len, 13); + for index in 0..5 { + assert_eq!(vec[index].addr_list_index, Some(index as u32)); + } + } + } + + //1.4) Query the Sponsors List page 1 page_size 5 + let query_msg = QueryMsg::Sponsors { + page_size: Some(5), + start_page: Some(1), + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorsResponse { vec, len } => { + assert_eq!(vec.len(), 5); + assert_eq!(len, 13); + for index in 0..5 { + assert_eq!(vec[index].addr_list_index, Some((index + 5) as u32)); + } + } + } + + //1.5) Query the Sponsors List page 1 page_size 5 + let query_msg = QueryMsg::Sponsors { + page_size: Some(5), + start_page: Some(2), + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorsResponse { vec, len } => { + assert_eq!(vec.len(), 3); + assert_eq!(len, 13); + for index in 0..3 { + assert_eq!(vec[index].addr_list_index, Some((index + 10) as u32)); + } + } + } + + //2 Now Querying after deleting sponsors from storage + //user 0,2,4,6,8,10 are removed + for i in 0..12 { + if i % 2 == 0 { + let sponsor = format!("user{}", i); + sponsor_request_withdraw_unit_test_helper( + deps.as_mut(), + Uint128::from(10000 * SCRT_TO_USCRT), + None, + None, + &sponsor, + )?; + } + } + + //2.1) Query the Sponsors List page 0 page_size 5 [1,3,5,7,9] + let query_msg = QueryMsg::Sponsors { + page_size: Some(5), + start_page: Some(0), + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorsResponse { vec, len } => { + assert_eq!(vec.len(), 5); + assert_eq!(len, 7); + let mut index = 0; + for val in [1, 3, 5, 7, 9] { + assert_eq!(vec[index].addr_list_index, Some((val) as u32)); + index += 1; + } + } + } + + //2.2) Query the Sponsors List page 1 page_size 5 [11,12] + let query_msg = QueryMsg::Sponsors { + page_size: Some(5), + start_page: Some(1), + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorsResponse { vec, len } => { + assert_eq!(vec.len(), 2); + assert_eq!(len, 7); + let mut index = 0; + for val in [11, 12] { + assert_eq!(vec[index].addr_list_index, Some((val) as u32)); + index += 1; + } + } + } + + //2.3) Query the Sponsors List page 0 page_size 10 [1,3,5,7,9,11,12] + let query_msg = QueryMsg::Sponsors { + page_size: Some(10), + start_page: Some(0), + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorsResponse { vec, len } => { + assert_eq!(vec.len(), 7); + assert_eq!(len, 7); + let mut index = 0; + for val in [1, 3, 5, 7, 9, 11, 12] { + assert_eq!(vec[index].addr_list_index, Some((val) as u32)); + index += 1; + } + } + } + + //2.3) Query the Sponsors List page 01 page_size 10 [] + let query_msg = QueryMsg::Sponsors { + page_size: Some(10), + start_page: Some(01), + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorsResponse { vec, len } => { + assert_eq!(vec.len(), 0); + assert_eq!(len, 7); + } + } + + Ok(()) + } + + #[test] + fn test_query_sponsor_msg_request() -> StdResult<()> { + let (_init_result, mut deps) = init_helper(None); + + //1.1) Deposit with the message + for i in 0..13 { + let sponsor = format!("user{}", i); + let handle_msg = HandleMsg::Sponsor { + title: Some("Bruce Wayne".to_string()), + message: Some("I'm rich".to_string()), + }; + let _res = execute( + deps.as_mut(), + custom_mock_env( + None, + Some(mock_env().block.time.seconds().add(i)), + None, + None, + ), + mock_info(&sponsor, &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + } + + //1.2) Query the Sponsors List with page size and page index == NONE + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 5); + assert_eq!(len, 13); + //returns page = 0 & page_size = 5 + for index in 0..5 { + assert_eq!(vec[index].index, Some(index as u32)); + } + } + } + + //1.3) Query the Sponsors List page 0 page_size 5 + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: Some(5), + start_page: Some(0), + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 5); + assert_eq!(len, 13); + for index in 0..5 { + assert_eq!(vec[index].index, Some(index as u32)); + } + } + } + + //1.4) Query the Sponsors List page 1 page_size 5 + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: Some(5), + start_page: Some(1), + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 5); + assert_eq!(len, 13); + for index in 0..5 { + assert_eq!(vec[index].index, Some((index + 5) as u32)); + } + } + } + + //1.5) Query the Sponsors List page 1 page_size 5 + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: Some(5), + start_page: Some(2), + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 3); + assert_eq!(len, 13); + for index in 0..3 { + assert_eq!(vec[index].index, Some((index + 10) as u32)); + assert_eq!(vec[index].deque_store_index, Some((index + 10) as u32)); + } + } + } + + //2) Reviewing requests + let (_init_result, mut deps) = init_helper(None); + + for i in 0..13 { + let sponsor = format!("user{}", i); + let title = format!("user {} title", i); + let handle_msg = HandleMsg::Sponsor { + title: Some(title), + message: Some("Some message".to_string()), + }; + let _res = execute( + deps.as_mut(), + custom_mock_env( + None, + Some(mock_env().block.time.seconds().add(i)), + None, + None, + ), + mock_info(&sponsor, &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + } + + // User addr [user0,user1,user2,user3,user4,user5,user6,user7,user8,user9,user10,user11,user12] + // User index [0. , 1. , 2. , 3. , 4. , 5. , 6. , 7. , 8. , 9. , 10. , 11. , 12. ] + + //2) Review Sponsor Request Messages + let mut decision_vec: Vec = vec![]; + + for i in (0..13).rev() { + if i % 2 == 0 { + decision_vec.push(Review { + index: i, + is_accpeted: true, + }); + } + } + let handle_msg = HandleMsg::ReviewSponsors { + decisions: decision_vec, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("reviewer", &[]), + handle_msg, + )?; + + // User addr [user1,user3,user5,user7,user9,user11] + // User index [0. , 1. , 2. , 3. , 4. , 5. ] + + let sponsor_state_obj = sponsor_state_unit_test_read_only_helper(&deps.storage)?; + assert_eq!(sponsor_state_obj.offset, 13); + // assert_eq!(sponsor_state_obj.sponsor_msg_req_empty_slots.len(), 6); + // assert_eq!(sponsor_state_obj.sponsor_msg_req_offset, 12); + + //2.1) Review Sponsor Request Messages + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: None, + start_page: None, + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 5); + assert_eq!(len, 6); + let mut ind = 0; + + // [1,3,7,9,11] -> index [1,3,4,2,0] + // User addr [user1,user3,user5,user7,user9,user11] + // User index [0. , 1. , 2. , 3. , 4. , 5. ] + + for user in [1, 3, 5, 7, 9] { + let addr = format!("user{}", user); + assert_eq!(vec[ind].index, Some(user as u32)); + assert_eq!(vec[ind].addr, addr); + ind += 1; + } + } + } + + //2.1) Review Sponsor Request Messages + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: Some(5), + start_page: Some(0), + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 5); + assert_eq!(len, 6); + let mut ind = 0; + + for user in [1, 3, 5, 7, 9] { + let addr = format!("user{}", user); + assert_eq!(vec[ind].index, Some(user as u32)); + assert_eq!(vec[ind].addr, addr); + ind += 1; + } + } + } + + // 2.1) Review Sponsor Request Messages + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: Some(5), + start_page: Some(1), + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 1); + assert_eq!(len, 6); + // [5] -> index [11] + // User addr [user11] + // User index [ 11 ] + let mut ind = 0; + for user in [11] { + let addr = format!("user{}", user); + assert_eq!(vec[ind].index, Some((user) as u32)); + assert_eq!(vec[ind].addr, addr); + ind += 1; + } + } + } + + let mut decision_vec: Vec = vec![]; + for i in 0..5 { + decision_vec.push(Review { + index: i, + is_accpeted: true, + }); + } + let handle_msg = HandleMsg::ReviewSponsors { + decisions: decision_vec, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("reviewer", &[]), + handle_msg, + )?; + + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: Some(5), + start_page: Some(0), + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 1); + assert_eq!(len, 1); + let mut ind = 0; + for user in [11] { + let addr = format!("user{}", user); + assert_eq!(vec[ind].addr, addr); + assert_eq!(vec[ind].index, Some(user as u32)); + ind += 1; + } + } + } + + for i in 13..15 { + let sponsor = format!("user{}", i); + let title = format!("user {} title", i); + let handle_msg = HandleMsg::Sponsor { + title: Some(title), + message: Some("Some message".to_string()), + }; + let _res = execute( + deps.as_mut(), + custom_mock_env( + None, + Some(mock_env().block.time.seconds().add(i)), + None, + None, + ), + mock_info(&sponsor, &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::from(10000 * SCRT_TO_USCRT), + }]), + handle_msg, + )?; + } + + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: Some(5), + start_page: Some(0), + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 3); + assert_eq!(len, 3); + let mut ind = 0; + for user in [11, 13, 14] { + let addr = format!("user{}", user); + assert_eq!(vec[ind].addr, addr); + assert_eq!(vec[ind].index, Some(user)); + ind += 1; + } + } + } + + let mut decision_vec: Vec = vec![]; + for i in 0..3 { + decision_vec.push(Review { + index: i, + is_accpeted: true, + }); + } + let handle_msg = HandleMsg::ReviewSponsors { + decisions: decision_vec, + }; + let _res = execute( + deps.as_mut(), + custom_mock_env(None, None, None, None), + mock_info("reviewer", &[]), + handle_msg, + )?; + + let query_msg = QueryMsg::SponsorMessageRequestCheck { + page_size: Some(5), + start_page: Some(0), + }; + let env = custom_mock_env(None, None, None, None); + let query_result = query(deps.as_ref(), env, query_msg)?; + + let query_answer = from_binary(&query_result)?; + match query_answer { + //no increase in Request because the no request was made. + SponsorMessageReqResponse { vec, len } => { + assert_eq!(vec.len(), 0); + assert_eq!(len, 0); + } + } + + Ok(()) + } + + // ////////////////////////////////////// Simulations ////////////////////////////////////// + + // #[test] + // fn claim_rewards_liq() -> StdResult<()> { + // let (init_result, mut deps) = init_helper(None); + + // let round_obj = round_obj_read_only_unit_test_helper(deps.as_ref().storage); + // //deposit after the round_time + // let _ = deposit_unit_test_helper( + // deps.as_mut(), + // Uint128::from(10 * SCRT_TO_USCRT), + // Some(0), + // Some(round_obj.end_time.add(3600)), + // "secretbatman", + // Some("uscrt"), + // )?; + + // //ends round + // let handle_msg = HandleMsg::EndRound {}; + // let mocked_env = + // custom_mock_env(None, Some(round_obj.end_time.add(3600 * 2)), None, None); + // let mocked_info = mock_info("triggerer", &[]); + // let handle_result = execute(deps.as_mut(), mocked_env, mocked_info, handle_msg)?; + + // //claims rewards + // let res = claim_rewards_unit_test_helper( + // deps.as_mut(), + // None, + // Some(round_obj.end_time.add(3600 * 3)), + // "secretbatman", + // )?; + + // let round_obj = round_obj_read_only_unit_test_helper(deps.as_ref().storage); + + // //ends round + // let handle_msg = HandleMsg::EndRound {}; + // let mocked_env = + // custom_mock_env(None, Some(round_obj.end_time.add(3600 * 2)), None, None); + // let mocked_info = mock_info("triggerer", &[]); + + // let handle_result = execute(deps.as_mut(), mocked_env, mocked_info, handle_msg)?; + + // //claims rewards + // let res = claim_rewards_unit_test_helper( + // deps.as_mut(), + // None, + // Some(round_obj.end_time.add(3600 * 3)), + // "secretbatman", + // )?; + // Ok(()) + // } + + // #[test] + // fn claim_rewards_sim() -> StdResult<()> { + // let (init_result, mut deps) = init_helper(None); + + // //deposit after the end round_time + // for i in 0..50 { + // let round_obj = round_obj_read_only_unit_test_helper(deps.as_ref().storage); + + // let _ = deposit_unit_test_helper( + // deps.as_mut(), + // Uint128::from(50000 * SCRT_TO_USCRT), + // Some(0), + // Some(round_obj.end_time.add(3600)), + // "secretbatman", + // Some("uscrt"), + // )?; + + // //ends round + // let handle_msg = HandleMsg::EndRound {}; + // let mocked_env = + // custom_mock_env(None, Some(round_obj.end_time.add(3600 * 2)), None, None); + // let mocked_info = mock_info("triggerer", &[]); + // let handle_result = execute(deps.as_mut(), mocked_env, mocked_info, handle_msg)?; + + // // println!("{}", i); + // //claims rewards + // // let res = claim_rewards_unit_test_helper( + // // deps.as_mut(), + // // None, + // // Some(round_obj.end_time.add(3600 * 3)), + // // "secretbatman", + // // )?; + // } + + // // let round_obj = round_obj_read_only_unit_test_helper(deps.as_ref().storage); + + // // //ends round + // // let handle_msg = HandleMsg::EndRound {}; + // // let mocked_env = + // // custom_mock_env(None, Some(round_obj.end_time.add(3600 * 2)), None, None); + // // let mocked_info = mock_info("triggerer", &[]); + + // // let handle_result = execute(deps.as_mut(), mocked_env, mocked_info, handle_msg)?; + + // // //claims rewards + // // let res = claim_rewards_unit_test_helper( + // // deps.as_mut(), + // // None, + // // Some(round_obj.end_time.add(3600 * 3)), + // // "secretbatman", + // // )?; + // Ok(()) + // } + + // #[test] + // fn testing_a_error() -> StdResult<()> { + // let (init_result, mut deps) = init_helper(None); + // let round_obj = round_obj_read_only_unit_test_helper(deps.as_ref().storage); + + // let _ = deposit_unit_test_helper( + // deps.as_mut(), + // Uint128::from(44500000u128), + // Some(0), + // None, + // "secretbatman", + // Some("uscrt"), + // )?; + + // let _ = deposit_unit_test_helper( + // deps.as_mut(), + // Uint128::from(32000001u128), + // Some(0), + // None, + // "secretbatman", + // Some("uscrt"), + // )?; + + // let res = request_withdraw_unit_test_helper( + // deps.as_mut(), + // Uint128::from(76500001 as u128), + // None, + // None, + // "secretbatman", + // ); + + // let config = config_read_only_unit_test_helper(deps.as_ref().storage); + // let handle_msg = HandleMsg::UnbondBatch {}; + // let _res = execute( + // deps.as_mut(), + // custom_mock_env(None, Some(config.next_unbonding_batch_time), None, None), + // mock_info("triggerer", &[]), + // handle_msg, + // )?; + + // Ok(()) + // } + + // // #[test] + // // fn testing_claim_percentage_worst_case() -> StdResult<()> { + // // let (_init_result, mut deps) = init_helper(None); + // // let mut tier_0: u128 = 0; + // // let mut tier_1: u128 = 0; + // // let mut tier_2: u128 = 0; + // // let mut tier_3: u128 = 0; + // // let mut tier_4: u128 = 0; + // // let mut tier_5: u128 = 0; + + // // let total_number_of_rounds = 2; + // // let number_of_deposits = 100000; + + // // for i in 0..number_of_deposits { + // // let amount_to_delegate = Uint128::from(1 * SCRT_TO_USCRT); + // // deposit_unit_test_helper( + // // deps.as_mut(), + // // amount_to_delegate, + // // None, + // // None, + // // i.to_string().as_str(), + // // None, + // // )?; + // // if i % 1000 == 0 { + // // print!("{esc}[2J{esc}[1;1H", esc = 27 as char); + // // print!("Deposit: {:?}", i); + // // } + // // } + // // for round in 1..total_number_of_rounds { + // // let _ = End_Round_my_mocked_querier_unit_test_helper(deps.as_mut()).unwrap(); + // // let round_obj = round_obj_unit_test_helper(&deps.storage); + // // for i in 0..number_of_deposits { + // // if i % 1000 == 0 { + // // print!("{esc}[2J{esc}[1;1H", esc = 27 as char); + + // // println!("Claim Rewards: {:?}", i); + // // } + + // // let _ = claim_rewards_unit_test_helper( + // // deps.as_mut(), + // // None, + // // Some(round_obj.end_time), + // // i.to_string().as_str(), + // // )?; + // // } + + // // let rewards_stats_for_nth_round_obj = + // // rewards_stats_for_nth_round_unit_test_helper(&deps.storage, round); + + // // tier_0 += rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_0 + // // .total_prizes_claimed + // // .u128(); + // // tier_1 += rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_1 + // // .total_prizes_claimed + // // .u128(); + // // tier_2 += rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_2 + // // .total_prizes_claimed + // // .u128(); + // // tier_3 += rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_3 + // // .total_prizes_claimed + // // .u128(); + // // tier_4 += rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_4 + // // .total_prizes_claimed + // // .u128(); + // // tier_5 += rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_5 + // // .total_prizes_claimed + // // .u128(); + // // } + + // // println!( + // // "Average Tier 5 prizes claimed: {} of 243, 20% of total rewards", + // // (tier_5 as f64 / total_number_of_rounds as f64) + // // ); + // // println!( + // // "Average Tier 4 prizes claimed: {} of 81, 10% of total rewards", + // // (tier_4 as f64 / total_number_of_rounds as f64) + // // ); + // // println!( + // // "Average Tier 3 prizes claimed: {} of 27, 14% of total rewards", + // // (tier_3 as f64 / total_number_of_rounds as f64) + // // ); + // // println!( + // // "Average Tier 2 prizes claimed: {} of 9, 12% of total rewards", + // // (tier_2 as f64 / total_number_of_rounds as f64) + // // ); + // // println!( + // // "Average Tier 1 prizes claimed: {} of 3, 19% of total rewards", + // // (tier_1 as f64 / total_number_of_rounds as f64) + // // ); + // // println!( + // // "Average Tier 0 prizes claimed: {} of 1, 25% of total rewards", + // // (tier_0 as f64 / total_number_of_rounds as f64) + // // ); + // // println!( + // // "% of Total Prizes claimed: {:.2}%", + // // ((tier_5 as f64 / total_number_of_rounds as f64) / 243 as f64) * 20 as f64 + // // + ((tier_4 as f64 / total_number_of_rounds as f64) / 81 as f64) * 10 as f64 + // // + ((tier_3 as f64 / total_number_of_rounds as f64) / 27 as f64) * 14 as f64 + // // + ((tier_2 as f64 / total_number_of_rounds as f64) / 9 as f64) * 12 as f64 + // // + ((tier_1 as f64 / total_number_of_rounds as f64) / 3 as f64) * 19 as f64 + // // + ((tier_0 as f64 / total_number_of_rounds as f64) / 1 as f64) * 25 as f64 + // // ); + // // Ok(()) + // // } + + // // #[test] + // // fn testing_claim_percentage() -> StdResult<()> { + // // //Calculating the average claim rate for the rewards + // // //Setting dynamic ticket multiplier + // // let mut deps = deposit_unit_test_simple_helper_filler(None); + + // // let mut tier_0: u128 = 0; + // // let mut tier_1: u128 = 0; + // // let mut tier_2: u128 = 0; + // // let mut tier_3: u128 = 0; + // // let mut tier_4: u128 = 0; + // // let mut tier_5: u128 = 0; + + // // for round in 1..100 { + // // let _ = end_round_my_mocked_querier_unit_test_helper(deps.as_mut()).unwrap(); + // // let round_obj = round_unit_test_helper(&deps.storage); + // // let _ = claim_rewards_unit_test_helper( + // // deps.as_mut(), + // // None, + // // Some(round_obj.end_time), + // // "Superman", + // // )?; + // // let _ = claim_rewards_unit_test_helper( + // // deps.as_mut(), + // // None, + // // Some(round_obj.end_time), + // // "Spider-man", + // // )?; + // // let _ = claim_rewards_unit_test_helper( + // // deps.as_mut(), + // // None, + // // Some(round_obj.end_time), + // // "Wonder-Women", + // // )?; + // // let _ = claim_rewards_unit_test_helper( + // // deps.as_mut(), + // // None, + // // Some(round_obj.end_time), + // // "Aqua-man", + // // )?; + // // let _ = claim_rewards_unit_test_helper( + // // deps.as_mut(), + // // None, + // // Some(round_obj.end_time), + // // "Ironman", + // // )?; + // // let _ = claim_rewards_unit_test_helper( + // // deps.as_mut(), + // // None, + // // Some(round_obj.end_time), + // // "Loki", + // // )?; + // // let _ = claim_rewards_unit_test_helper( + // // deps.as_mut(), + // // None, + // // Some(round_obj.end_time), + // // "Captain-America", + // // )?; + // // let _ = claim_rewards_unit_test_helper( + // // deps.as_mut(), + // // None, + // // Some(round_obj.end_time), + // // "Thor", + // // )?; + // // let _ = claim_rewards_unit_test_helper( + // // deps.as_mut(), + // // None, + // // Some(round_obj.end_time), + // // "secretbatman", + // // )?; + + // // let rewards_stats_for_nth_round_obj = + // // rewards_stats_for_nth_round_unit_test_helper(&deps.storage, round); + + // // println!( + // // "Tier 0:{} 1:{} 2:{} 3:{} 4:{} 5:{}", + // // rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_0 + // // .total_prizes_claimed + // // .u128(), + // // rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_1 + // // .total_prizes_claimed + // // .u128(), + // // rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_2 + // // .total_prizes_claimed + // // .u128(), + // // rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_3 + // // .total_prizes_claimed + // // .u128(), + // // rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_4 + // // .total_prizes_claimed + // // .u128(), + // // rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_5 + // // .total_prizes_claimed + // // .u128() + // // ); + + // // tier_0 += rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_0 + // // .total_prizes_claimed + // // .u128(); + // // tier_1 += rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_1 + // // .total_prizes_claimed + // // .u128(); + // // tier_2 += rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_2 + // // .total_prizes_claimed + // // .u128(); + // // tier_3 += rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_3 + // // .total_prizes_claimed + // // .u128(); + // // tier_4 += rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_4 + // // .total_prizes_claimed + // // .u128(); + // // tier_5 += rewards_stats_for_nth_round_obj + // // .distribution_per_tiers + // // .tier_5 + // // .total_prizes_claimed + // // .u128(); + // // } + + // // println!( + // // "Average Tier 5 prizes claimed: {} of 243, 20% of total rewards", + // // (tier_5 as f64 / 100 as f64) + // // ); + // // println!( + // // "Average Tier 4 prizes claimed: {} of 81, 10% of total rewards", + // // (tier_4 as f64 / 100 as f64) + // // ); + // // println!( + // // "Average Tier 3 prizes claimed: {} of 27, 14% of total rewards", + // // (tier_3 as f64 / 100 as f64) + // // ); + // // println!( + // // "Average Tier 2 prizes claimed: {} of 9, 12% of total rewards", + // // (tier_2 as f64 / 100 as f64) + // // ); + // // println!( + // // "Average Tier 1 prizes claimed: {} of 3, 19% of total rewards", + // // (tier_1 as f64 / 100 as f64) + // // ); + // // println!( + // // "Average Tier 0 prizes claimed: {} of 1, 25% of total rewards", + // // (tier_0 as f64 / 100 as f64) + // // ); + // // println!( + // // "% of Total Prizes claimed: {:.2}%", + // // ((tier_5 as f64 / 100 as f64) / 243 as f64) * 20 as f64 + // // + ((tier_4 as f64 / 100 as f64) / 81 as f64) * 10 as f64 + // // + ((tier_3 as f64 / 100 as f64) / 27 as f64) * 14 as f64 + // // + ((tier_2 as f64 / 100 as f64) / 9 as f64) * 12 as f64 + // // + ((tier_1 as f64 / 100 as f64) / 3 as f64) * 19 as f64 + // // + ((tier_0 as f64 / 100 as f64) / 1 as f64) * 25 as f64 + // // ); + // // Ok(()) + // // } +} diff --git a/contracts/galactic_pools/pools/native/src/utils.rs b/contracts/galactic_pools/pools/native/src/utils.rs new file mode 100644 index 000000000..ec153e6d1 --- /dev/null +++ b/contracts/galactic_pools/pools/native/src/utils.rs @@ -0,0 +1,15 @@ +use crate::viewing_key::VIEWING_KEY_SIZE; +use sha2::{Digest, Sha256}; +use std::convert::TryInto; +use subtle::ConstantTimeEq; + +pub fn ct_slice_compare(s1: &[u8], s2: &[u8]) -> bool { + bool::from(s1.ct_eq(s2)) +} + +pub fn create_hashed_password(s1: &str) -> [u8; VIEWING_KEY_SIZE] { + Sha256::digest(s1.as_bytes()) + .as_slice() + .try_into() + .expect("Wrong password length") +} diff --git a/contracts/galactic_pools/pools/native/src/viewing_key.rs b/contracts/galactic_pools/pools/native/src/viewing_key.rs new file mode 100644 index 000000000..7afc7f8d3 --- /dev/null +++ b/contracts/galactic_pools/pools/native/src/viewing_key.rs @@ -0,0 +1,64 @@ +use std::fmt; + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use c_std::{Env, MessageInfo}; +use sha2::{Digest, Sha256}; +use shade_protocol::{c_std, schemars}; + +use crate::{ + rand::Prng, + utils::{create_hashed_password, ct_slice_compare}, +}; + +pub const VIEWING_KEY_SIZE: usize = 32; +pub const VIEWING_KEY_PREFIX: &str = "api_key_"; + +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, PartialEq)] +pub struct ViewingKey(pub String); + +impl ViewingKey { + pub fn check_viewing_key(&self, hashed_pw: &[u8]) -> bool { + let mine_hashed = create_hashed_password(&self.0); + + ct_slice_compare(&mine_hashed, hashed_pw) + } + + pub fn new(env: &Env, info: MessageInfo, seed: &[u8], entropy: &[u8]) -> Self { + // 16 here represents the lengths in bytes of the block height and time. + let entropy_len = 16 + info.sender.as_str().len() + entropy.len(); + let mut rng_entropy = Vec::with_capacity(entropy_len); + rng_entropy.extend_from_slice(&env.block.height.to_be_bytes()); + rng_entropy.extend_from_slice(&env.block.time.seconds().to_be_bytes()); + rng_entropy.extend_from_slice(&info.sender.as_str().as_bytes()); + rng_entropy.extend_from_slice(entropy); + + let mut rng = Prng::new(seed, &rng_entropy); + + let rand_slice = rng.rand_bytes(); + + let mut hasher = Sha256::new(); + hasher.update(&rand_slice); + let hash = hasher.finalize(); + + let mut key = [0u8; 32]; + key.copy_from_slice(hash.as_slice()); + + Self(VIEWING_KEY_PREFIX.to_string() + &base64::encode(key)) + } + + pub fn to_hashed(&self) -> [u8; VIEWING_KEY_SIZE] { + create_hashed_password(&self.0) + } + + pub fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } +} + +impl fmt::Display for ViewingKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/contracts/liquidity_book/lb_pair/Cargo.toml b/contracts/liquidity_book/lb_pair/Cargo.toml index 8bb9f7e20..6425401af 100644 --- a/contracts/liquidity_book/lb_pair/Cargo.toml +++ b/contracts/liquidity_book/lb_pair/Cargo.toml @@ -24,7 +24,7 @@ schema = [] backtraces = ["cosmwasm-std/backtraces"] [dependencies] -shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = ["liquidity_book_impl"] } +shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol" } schemars = "0.8.16" serde = { version = "1.0" } serde-json-wasm = { version = "1.0"} diff --git a/packages/shade_protocol/Cargo.toml b/packages/shade_protocol/Cargo.toml index 57d957a06..f71df9b1a 100644 --- a/packages/shade_protocol/Cargo.toml +++ b/packages/shade_protocol/Cargo.toml @@ -116,7 +116,7 @@ backtraces = ["cosmwasm-std/backtraces"] debug-print = [] # TODO: remove this from all cargo configs [dependencies] -cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.1.11" } +cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.1.11", features = ["staking"] } cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.1.11" } cosmwasm-schema = "1.5" contract-derive = { version = "0.1.0", path = "../contract_derive" }