From 206154029088812b96d3f3ba18c10814e220bc91 Mon Sep 17 00:00:00 2001 From: hard-nett Date: Fri, 22 Dec 2023 20:18:57 -0500 Subject: [PATCH 01/23] inital privacy fork push --- .cargo/config | 5 - .circleci/config.yml | 612 --- .github/ISSUE_TEMPLATE/feature_request.md | 20 + .github/pull_request_template.md | 20 + .github/workflows/rust.yml | 102 + .gitignore | 30 +- .mergify.yml | 26 - CODE_OF_CONDUCT.md | 128 + Cargo.lock | 1303 ++++- Cargo.toml | 94 +- LICENSE | 366 +- Makefile.toml | 140 + README.md | 6 +- SECURITY.md | 39 + Shade_Protocol_Responsible_Disclosure.md | 286 + archived-contracts/bonds/.cargo/config | 5 + archived-contracts/bonds/.circleci/config.yml | 52 + archived-contracts/bonds/Cargo.toml | 47 + archived-contracts/bonds/Makefile | 68 + archived-contracts/bonds/README.md | 389 ++ archived-contracts/bonds/src/contract.rs | 215 + archived-contracts/bonds/src/handle.rs | 845 +++ archived-contracts/bonds/src/lib.rs | 44 + archived-contracts/bonds/src/query.rs | 155 + archived-contracts/bonds/src/state.rs | 99 + archived-contracts/bonds/src/test.rs | 65 + archived-contracts/bonds/src/tests/handle.rs | 500 ++ archived-contracts/bonds/src/tests/mod.rs | 502 ++ archived-contracts/bonds/src/tests/query.rs | 185 + .../dao/lp_shdswap/.cargo/config | 5 + .../dao/lp_shdswap/.circleci/config.yml | 52 + archived-contracts/dao/lp_shdswap/Cargo.toml | 38 + archived-contracts/dao/lp_shdswap/Makefile | 68 + archived-contracts/dao/lp_shdswap/README.md | 64 + .../dao/lp_shdswap/src/contract.rs | 198 + .../dao/lp_shdswap/src/execute.rs | 261 + archived-contracts/dao/lp_shdswap/src/lib.rs | 44 + .../dao/lp_shdswap/src/query.rs | 155 + .../dao/lp_shdswap/src/storage.rs | 10 + archived-contracts/dao/lp_shdswap/src/test.rs | 46 + .../dao/rewards_emission/.cargo/config | 5 + .../dao/rewards_emission/.circleci/config.yml | 52 + .../dao/rewards_emission/Cargo.toml | 29 + .../dao/rewards_emission/Makefile | 68 + .../dao/rewards_emission/README.md | 65 + .../dao/rewards_emission/src/contract.rs | 96 + .../dao/rewards_emission/src/execute.rs | 184 + .../dao/rewards_emission/src/lib.rs | 7 + .../dao/rewards_emission/src/query.rs | 57 + .../dao/rewards_emission/src/storage.rs | 14 + .../dao/rewards_emission/src/test.rs | 46 + archived-contracts/governance/.cargo/config | 5 + .../governance/.circleci/config.yml | 52 + archived-contracts/governance/Cargo.toml | 37 + archived-contracts/governance/Makefile | 68 + archived-contracts/governance/src/contract.rs | 482 ++ .../governance/src/handle/assembly.rs | 256 + .../governance/src/handle/assembly_msg.rs | 105 + .../governance/src/handle/contract.rs | 133 + .../governance/src/handle/migration.rs | 217 + .../governance/src/handle/mod.rs | 135 + .../governance/src/handle/profile.rs | 77 + .../governance/src/handle/proposal.rs | 541 ++ archived-contracts/governance/src/lib.rs | 8 + archived-contracts/governance/src/query.rs | 229 + .../governance/src/tests/handle/assembly.rs | 105 + .../src/tests/handle/assembly_msg.rs | 102 + .../governance/src/tests/handle/contract.rs | 301 ++ .../governance/src/tests/handle/migration.rs | 310 ++ .../governance/src/tests/handle/mod.rs | 127 + .../governance/src/tests/handle/profile.rs | 472 ++ .../tests/handle/proposal/assembly_voting.rs | 844 +++ .../src/tests/handle/proposal/funding.rs | 781 +++ .../src/tests/handle/proposal/mod.rs | 293 + .../src/tests/handle/proposal/voting.rs | 1022 ++++ .../governance/src/tests/handle/runstate.rs | 255 + .../governance/src/tests/mod.rs | 214 + .../governance/src/tests/query/mod.rs | 2 + .../governance/src/tests/query/public.rs | 180 + .../governance/src/tests/query/user.rs | 268 + archived-contracts/mint/.cargo/config | 5 + archived-contracts/mint/.circleci/config.yml | 52 + archived-contracts/mint/Cargo.toml | 33 + archived-contracts/mint/Makefile | 68 + archived-contracts/mint/README.md | 215 + archived-contracts/mint/src/contract.rs | 110 + archived-contracts/mint/src/handle.rs | 491 ++ archived-contracts/mint/src/lib.rs | 4 + archived-contracts/mint/src/query.rs | 74 + archived-contracts/mint/src/state.rs | 107 + archived-contracts/mint/tests/integration.rs | 260 + archived-contracts/mint/tests/unit.rs | 113 + archived-contracts/mint_router/.cargo/config | 5 + .../mint_router/.circleci/config.yml | 52 + archived-contracts/mint_router/Cargo.toml | 34 + archived-contracts/mint_router/Makefile | 68 + archived-contracts/mint_router/README.md | 210 + .../mint_router/src/contract.rs | 80 + archived-contracts/mint_router/src/handle.rs | 230 + archived-contracts/mint_router/src/lib.rs | 49 + archived-contracts/mint_router/src/query.rs | 60 + archived-contracts/mint_router/src/state.rs | 76 + archived-contracts/mint_router/src/test.rs | 414 ++ archived-contracts/mock_band/.cargo/config | 5 + .../mock_band/.circleci/config.yml | 52 + archived-contracts/mock_band/Cargo.toml | 34 + archived-contracts/mock_band/Makefile | 68 + archived-contracts/mock_band/README.md | 21 + archived-contracts/mock_band/src/contract.rs | 110 + archived-contracts/mock_band/src/execute.rs | 424 ++ archived-contracts/mock_band/src/lib.rs | 1 + .../mock_secretswap_pair/.cargo/config | 5 + .../mock_secretswap_pair/.circleci/config.yml | 52 + .../mock_secretswap_pair/Cargo.toml | 31 + .../mock_secretswap_pair/Makefile | 68 + .../mock_secretswap_pair/README.md | 21 + .../mock_secretswap_pair/src/contract.rs | 176 + .../mock_secretswap_pair/src/lib.rs | 43 + .../mock_sienna_pair/Cargo.toml | 29 + .../mock_sienna_pair/src/contract.rs | 303 ++ archived-contracts/oracle/.cargo/config | 5 + .../oracle/.circleci/config.yml | 52 + archived-contracts/oracle/Cargo.toml | 42 + archived-contracts/oracle/Makefile | 68 + archived-contracts/oracle/README.md | 142 + archived-contracts/oracle/src/contract.rs | 72 + archived-contracts/oracle/src/handle.rs | 313 ++ archived-contracts/oracle/src/lib.rs | 49 + archived-contracts/oracle/src/query.rs | 177 + archived-contracts/oracle/src/state.rs | 45 + archived-contracts/oracle/src/test.rs | 48 + .../oracle/tests/integration.rs | 18 + archived-contracts/peg_stability/Cargo.toml | 37 + archived-contracts/peg_stability/Makefile | 68 + .../peg_stability/src/contract.rs | 94 + .../peg_stability/src/handle.rs | 233 + archived-contracts/peg_stability/src/lib.rs | 6 + archived-contracts/peg_stability/src/query.rs | 199 + .../peg_stability/src/tests/handle.rs | 59 + .../peg_stability/src/tests/mod.rs | 113 + .../peg_stability/src/tests/query.rs | 29 + archived-contracts/sky/.cargo/config | 5 + archived-contracts/sky/.circleci/config.yml | 52 + archived-contracts/sky/Cargo.toml | 33 + archived-contracts/sky/Makefile | 68 + archived-contracts/sky/src/contract.rs | 161 + archived-contracts/sky/src/execute.rs | 433 ++ archived-contracts/sky/src/lib.rs | 3 + archived-contracts/sky/src/query.rs | 334 ++ archived-contracts/sky/tests/integration.rs | 318 ++ .../snip20_staking/.cargo/config | 4 + .../snip20_staking/.circleci/config.yml | 52 + archived-contracts/snip20_staking/Cargo.toml | 49 + archived-contracts/snip20_staking/README.md | 59 + .../snip20_staking/src/batch.rs | 55 + .../snip20_staking/src/contract.rs | 4738 +++++++++++++++++ .../snip20_staking/src/distributors.rs | 91 + .../snip20_staking/src/expose_balance.rs | 150 + archived-contracts/snip20_staking/src/lib.rs | 56 + archived-contracts/snip20_staking/src/msg.rs | 556 ++ archived-contracts/snip20_staking/src/rand.rs | 75 + .../snip20_staking/src/receiver.rs | 68 + .../snip20_staking/src/stake.rs | 1068 ++++ .../snip20_staking/src/stake_queries.rs | 126 + .../snip20_staking/src/state.rs | 389 ++ .../snip20_staking/src/state_staking.rs | 135 + .../snip20_staking/src/transaction_history.rs | 723 +++ .../snip20_staking/src/utils.rs | 15 + .../snip20_staking/src/viewing_key.rs | 57 + .../src/testnet_airdrop.rs | 0 contractlib/contractlib.py | 99 + contractlib/initializerlib.py | 35 + contractlib/mintlib.py | 108 + contractlib/oraclelib.py | 53 + contractlib/secretlib/secretlib.py | 182 + contractlib/snip20lib.py | 124 + contractlib/treasurylib.py | 72 + contractlib/utils.py | 15 + contracts/Staking Deep Dive | 1 + contracts/Staking Overview | 129 + contracts/admin/.cargo/config | 5 + contracts/admin/.circleci/config.yml | 52 + contracts/admin/Cargo.toml | 31 + contracts/admin/Makefile | 68 + contracts/admin/README.md | 127 + contracts/admin/src/contract.rs | 107 + contracts/admin/src/execute.rs | 167 + contracts/admin/src/lib.rs | 6 + contracts/admin/src/query.rs | 40 + contracts/admin/src/shared.rs | 35 + contracts/admin/src/test.rs | 351 ++ contracts/airdrop/.cargo/config | 5 + contracts/airdrop/.circleci/config.yml | 52 + contracts/airdrop/Cargo.toml | 31 + contracts/airdrop/Makefile | 68 + contracts/airdrop/README.md | 348 ++ contracts/airdrop/src/contract.rs | 201 + contracts/airdrop/src/handle.rs | 777 +++ contracts/airdrop/src/lib.rs | 7 + contracts/airdrop/src/query.rs | 134 + contracts/airdrop/src/state.rs | 196 + contracts/airdrop/src/test.rs | 346 ++ contracts/basic_staking/.cargo/config | 5 + contracts/basic_staking/.circleci/config.yml | 52 + contracts/basic_staking/Cargo.toml | 40 + contracts/basic_staking/Makefile | 68 + contracts/basic_staking/README.md | 83 + contracts/basic_staking/src/contract.rs | 212 + contracts/basic_staking/src/execute.rs | 938 ++++ contracts/basic_staking/src/lib.rs | 4 + contracts/basic_staking/src/query.rs | 215 + contracts/basic_staking/src/storage.rs | 34 + .../basic_staking/tests/bad_stake_token.rs | 347 ++ .../end_reward_pool_after_end_claimed.rs | 290 + .../end_reward_pool_after_end_unclaimed.rs | 288 + ...d_reward_pool_after_end_unclaimed_force.rs | 386 ++ .../end_reward_pool_before_end_claimed.rs | 291 + .../end_reward_pool_before_end_unclaimed.rs | 291 + .../tests/end_reward_pool_before_start.rs | 273 + contracts/basic_staking/tests/multi_staker.rs | 523 ++ .../basic_staking/tests/non_admin_access.rs | 186 + .../basic_staking/tests/non_stake_rewards.rs | 610 +++ .../basic_staking/tests/single_staker.rs | 593 +++ .../tests/single_staker_compounding.rs | 588 ++ .../basic_staking/tests/stake_0_total_bug.rs | 445 ++ .../basic_staking/tests/transfer_stake.rs | 454 ++ .../tests/transfer_stake_compound.rs | 454 ++ .../tests/transfer_stake_sender_no_stake.rs | 232 + .../tests/unbonding_withdrawals.rs | 316 ++ .../tests/unstake_softlock_bug.rs | 340 ++ .../basic_staking/tests/update_config.rs | 127 + contracts/dao/scrt_staking/.cargo/config | 5 + .../dao/scrt_staking/.circleci/config.yml | 52 + contracts/dao/scrt_staking/Cargo.toml | 40 + contracts/dao/scrt_staking/Makefile | 68 + contracts/dao/scrt_staking/README.md | 65 + contracts/dao/scrt_staking/src/contract.rs | 116 + contracts/dao/scrt_staking/src/execute.rs | 424 ++ contracts/dao/scrt_staking/src/lib.rs | 7 + contracts/dao/scrt_staking/src/query.rs | 182 + contracts/dao/scrt_staking/src/storage.rs | 9 + contracts/dao/scrt_staking/src/test.rs | 46 + .../dao/scrt_staking/tests/integration.rs | 420 ++ contracts/dao/scrt_staking/tests/unit.rs | 36 + contracts/dao/stkd_scrt/.cargo/config | 5 + contracts/dao/stkd_scrt/.circleci/config.yml | 52 + contracts/dao/stkd_scrt/Cargo.toml | 39 + contracts/dao/stkd_scrt/Makefile | 68 + contracts/dao/stkd_scrt/README.md | 65 + contracts/dao/stkd_scrt/src/contract.rs | 108 + contracts/dao/stkd_scrt/src/execute.rs | 158 + contracts/dao/stkd_scrt/src/lib.rs | 7 + contracts/dao/stkd_scrt/src/query.rs | 124 + contracts/dao/stkd_scrt/src/storage.rs | 7 + contracts/dao/stkd_scrt/src/test.rs | 46 + contracts/dao/stkd_scrt/tests/integration.rs | 331 ++ contracts/dao/stkd_scrt/tests/unit.rs | 36 + contracts/dao/treasury/.cargo/config | 5 + contracts/dao/treasury/.circleci/config.yml | 52 + contracts/dao/treasury/Cargo.toml | 47 + contracts/dao/treasury/Makefile | 68 + contracts/dao/treasury/README.md | 172 + .../dao/treasury/Untitled Diagram.drawio | 1 + contracts/dao/treasury/src/contract.rs | 128 + contracts/dao/treasury/src/execute.rs | 795 +++ contracts/dao/treasury/src/lib.rs | 4 + contracts/dao/treasury/src/query.rs | 173 + contracts/dao/treasury/src/storage.rs | 27 + .../dao/treasury/tests/dao/equilibrium.rs | 314 ++ .../dao/treasury/tests/dao/gains_losses.rs | 1012 ++++ contracts/dao/treasury/tests/dao/mod.rs | 322 ++ .../treasury/tests/integration/allowance.rs | 389 ++ .../integration/allowance_delay_refresh.rs | 335 ++ .../dao/treasury/tests/integration/batch.rs | 95 + .../dao/treasury/tests/integration/config.rs | 80 + .../tests/integration/execute_errors.rs | 244 + .../treasury/tests/integration/migration.rs | 151 + .../dao/treasury/tests/integration/mod.rs | 12 + .../integration/non_manager_allowances.rs | 192 + .../dao/treasury/tests/integration/query.rs | 240 + .../tests/integration/scrt_staking.rs | 861 +++ .../treasury/tests/integration/tolerance.rs | 436 ++ .../treasury/tests/integration/treasury.rs | 557 ++ .../dao/treasury/tests/integration/wrap.rs | 145 + contracts/dao/treasury/tests/mod.rs | 2 + contracts/dao/treasury_manager/.cargo/config | 5 + .../dao/treasury_manager/.circleci/config.yml | 52 + contracts/dao/treasury_manager/Cargo.toml | 48 + contracts/dao/treasury_manager/Makefile | 68 + contracts/dao/treasury_manager/README.md | 142 + .../dao/treasury_manager/src/contract.rs | 160 + contracts/dao/treasury_manager/src/execute.rs | 1446 +++++ contracts/dao/treasury_manager/src/lib.rs | 4 + contracts/dao/treasury_manager/src/query.rs | 292 + contracts/dao/treasury_manager/src/storage.rs | 21 + .../tests/integration/batch.rs | 138 + .../tests/integration/config.rs | 86 + .../tests/integration/execute_error.rs | 183 + .../tests/integration/holder_integration.rs | 345 ++ .../treasury_manager/tests/integration/mod.rs | 9 + .../tests/integration/multiple_holders.rs | 416 ++ .../tests/integration/query.rs | 400 ++ .../integration/scrt_staking_integration.rs | 548 ++ .../tests/integration/tm_unbond.rs | 222 + .../tests/integration/tolerance.rs | 467 ++ contracts/dao/treasury_manager/tests/mod.rs | 1 + contracts/headstash-contract/.cargo/config | 5 - contracts/headstash-contract/.gitignore | 15 - contracts/headstash-contract/Cargo.toml | 30 - .../headstash-contract/helpers/.eslintignore | 1 - .../headstash-contract/helpers/.eslintrc | 6 - .../headstash-contract/helpers/.gitignore | 8 - .../headstash-contract/helpers/README.md | 47 - contracts/headstash-contract/helpers/bin/run | 5 - .../headstash-contract/helpers/bin/run.cmd | 3 - .../headstash-contract/helpers/package.json | 60 - .../headstash-contract/helpers/src/airdrop.ts | 44 - .../helpers/src/commands/generateProofs.ts | 48 - .../helpers/src/commands/generateRoot.ts | 37 - .../helpers/src/commands/verifyProofs.ts | 55 - .../headstash-contract/helpers/src/index.ts | 1 - .../headstash-contract/helpers/tsconfig.json | 33 - .../headstash-contract/helpers/yarn.lock | 2767 ---------- contracts/headstash-contract/src/contract.rs | 353 -- contracts/headstash-contract/src/error.rs | 62 - contracts/headstash-contract/src/lib.rs | 28 - contracts/headstash-contract/src/msg.rs | 74 - contracts/headstash-contract/src/state.rs | 36 - .../testdata/airdrop_stage_1_list.json | 9 - .../testdata/airdrop_stage_1_test_data.json | 10 - .../airdrop_stage_1_test_multi_data.json | 42 - .../testdata/airdrop_stage_2_list.json | 13 - .../testdata/airdrop_stage_2_test_data.json | 11 - contracts/mock/mock_adapter/.cargo/config | 5 + .../mock/mock_adapter/.circleci/config.yml | 52 + contracts/mock/mock_adapter/Cargo.toml | 41 + contracts/mock/mock_adapter/Makefile | 68 + contracts/mock/mock_adapter/README.md | 21 + contracts/mock/mock_adapter/src/contract.rs | 326 ++ contracts/mock/mock_adapter/src/execute.rs | 200 + contracts/mock/mock_adapter/src/lib.rs | 1 + contracts/mock/mock_adapter/src/storage.rs | 23 + contracts/mock/mock_sienna_pair/.cargo/config | 5 + .../mock_sienna_pair/.circleci/config.yml | 52 + contracts/mock/mock_sienna_pair/Cargo.toml | 29 + contracts/mock/mock_sienna_pair/Makefile | 68 + contracts/mock/mock_sienna_pair/README.md | 21 + .../mock/mock_sienna_pair/src/contract.rs | 303 ++ contracts/mock/mock_sienna_pair/src/lib.rs | 1 + .../mock/mock_stkd_derivative/Cargo.toml | 39 + contracts/mock/mock_stkd_derivative/Makefile | 68 + contracts/mock/mock_stkd_derivative/README.md | 1 + .../mock/mock_stkd_derivative/src/contract.rs | 386 ++ .../mock/mock_stkd_derivative/src/lib.rs | 5 + .../mock/mock_stkd_derivative/src/tests.rs | 510 ++ contracts/query_auth/.cargo/config | 5 + contracts/query_auth/.circleci/config.yml | 52 + contracts/query_auth/Cargo.toml | 34 + contracts/query_auth/README.md | 202 + contracts/query_auth/src/contract.rs | 130 + contracts/query_auth/src/handle.rs | 95 + contracts/query_auth/src/lib.rs | 6 + contracts/query_auth/src/query.rs | 33 + contracts/query_auth/src/tests/handle.rs | 274 + contracts/query_auth/src/tests/mod.rs | 119 + contracts/query_auth/src/tests/query.rs | 50 + contracts/snip20/.cargo/config | 5 + contracts/snip20/.circleci/config.yml | 52 + contracts/snip20/Cargo.toml | 37 + contracts/snip20/Makefile | 68 + contracts/snip20/src/batch.rs | 53 + contracts/snip20/src/contract.rs | 443 ++ contracts/snip20/src/handle/allowance.rs | 203 + contracts/snip20/src/handle/burning.rs | 127 + contracts/snip20/src/handle/minting.rs | 158 + contracts/snip20/src/handle/mod.rs | 238 + contracts/snip20/src/handle/transfers.rs | 239 + contracts/snip20/src/lib.rs | 6 + contracts/snip20/src/query.rs | 143 + .../snip20/src/tests/handle/allowance.rs | 267 + contracts/snip20/src/tests/handle/burn.rs | 219 + contracts/snip20/src/tests/handle/mint.rs | 152 + contracts/snip20/src/tests/handle/mod.rs | 229 + contracts/snip20/src/tests/handle/transfer.rs | 134 + contracts/snip20/src/tests/handle/wrap.rs | 107 + contracts/snip20/src/tests/mod.rs | 116 + contracts/snip20/src/tests/query/mod.rs | 2 + contracts/snip20/src/tests/query/public.rs | 73 + contracts/snip20/src/tests/query/user.rs | 208 + contracts/snip20_derivative/.cargo/config | 8 + .../snip20_derivative/.circleci/config.yml | 52 + contracts/snip20_derivative/.editorconfig | 11 + .../.github/workflows/cicd.yaml | 26 + .../.images/engineering-diagram.png | Bin 0 -> 63431 bytes contracts/snip20_derivative/Cargo.toml | 52 + contracts/snip20_derivative/Developing.md | 153 + contracts/snip20_derivative/Importing.md | 62 + contracts/snip20_derivative/LICENSE | 202 + contracts/snip20_derivative/Makefile | 89 + contracts/snip20_derivative/NOTICE | 13 + contracts/snip20_derivative/Publishing.md | 115 + contracts/snip20_derivative/README.md | 901 ++++ .../examples/schema.rs | 11 +- contracts/snip20_derivative/src/contract.rs | 2660 +++++++++ contracts/snip20_derivative/src/lib.rs | 4 + contracts/snip20_derivative/src/msg.rs | 277 + .../src/staking_interface.rs | 388 ++ contracts/snip20_derivative/src/state.rs | 69 + .../snip20_derivative/tests/integration.rs | 3 + .../snip20_derivative/tests/integration.sh | 1699 ++++++ contracts/snip20_migration/Cargo.toml | 37 + contracts/snip20_migration/Makefile | 68 + contracts/snip20_migration/src/contract.rs | 175 + contracts/snip20_migration/src/lib.rs | 5 + contracts/snip20_migration/src/storage.rs | 10 + contracts/snip20_migration/src/test.rs | 271 + .../snip20_migration/tests/integration.rs | 111 + docker_setup | 4 + launch/Cargo.toml | 41 + makefile | 101 + packages/contract_derive/Cargo.toml | 16 + packages/contract_derive/src/lib.rs | 86 + packages/ethereum_verify/Cargo.toml | 26 + packages/ethereum_verify/README.md | 1 + packages/ethereum_verify/src/decode.rs | 47 + packages/ethereum_verify/src/lib.rs | 5 + .../ethereum_verify/src/signature_verify.rs | 43 + packages/multi_derive/Cargo.toml | 13 + packages/multi_derive/src/lib.rs | 73 + packages/multi_test/Cargo.toml | 64 + packages/multi_test/src/interfaces/dao.rs | 490 ++ packages/multi_test/src/interfaces/mod.rs | 19 + .../multi_test/src/interfaces/scrt_staking.rs | 83 + packages/multi_test/src/interfaces/snip20.rs | 186 + .../multi_test/src/interfaces/treasury.rs | 469 ++ .../src/interfaces/treasury_manager.rs | 667 +++ packages/multi_test/src/interfaces/utils.rs | 14 + packages/multi_test/src/lib.rs | 4 + packages/multi_test/src/multi.rs | 140 + packages/secretcli/Cargo.toml | 15 + packages/secretcli/src/cli_types.rs | 94 + packages/secretcli/src/lib.rs | 2 + packages/secretcli/src/secretcli.rs | 526 ++ packages/shade_protocol/.cargo/config | 5 + packages/shade_protocol/Cargo.toml | 141 + .../src/contract_interfaces/admin/errors.rs | 74 + .../src/contract_interfaces/admin/helpers.rs | 80 + .../src/contract_interfaces/admin/mod.rs | 112 + .../contract_interfaces/airdrop/account.rs | 99 + .../contract_interfaces/airdrop/claim_info.rs | 8 + .../src/contract_interfaces/airdrop/errors.rs | 167 + .../src/contract_interfaces/airdrop/mod.rs | 230 + .../contract_interfaces/basic_staking/mod.rs | 299 ++ .../src/contract_interfaces/bonds/errors.rs | 331 ++ .../src/contract_interfaces/bonds/mod.rs | 283 + .../src/contract_interfaces/bonds/rand.rs | 74 + .../src/contract_interfaces/bonds/utils.rs | 17 + .../contract_interfaces/dao/DAO_ADAPTER.md | 153 + .../src/contract_interfaces/dao/adapter.rs | 187 + .../src/contract_interfaces/dao/lp_shdswap.rs | 151 + .../src/contract_interfaces/dao/manager.rs | 240 + .../src/contract_interfaces/dao/mod.rs | 23 + .../dao/rewards_emission.rs | 102 + .../contract_interfaces/dao/scrt_staking.rs | 100 + .../src/contract_interfaces/dao/stkd_scrt.rs | 192 + .../src/contract_interfaces/dao/treasury.rs | 250 + .../dao/treasury_manager.rs | 247 + .../src/contract_interfaces/dex/dex.rs | 173 + .../src/contract_interfaces/dex/mod.rs | 11 + .../src/contract_interfaces/dex/secretswap.rs | 136 + .../src/contract_interfaces/dex/shadeswap.rs | 229 + .../src/contract_interfaces/dex/sienna.rs | 170 + .../governance/assembly.rs | 184 + .../governance/contract.rs | 105 + .../contract_interfaces/governance/errors.rs | 44 + .../src/contract_interfaces/governance/mod.rs | 455 ++ .../contract_interfaces/governance/profile.rs | 308 ++ .../governance/proposal.rs | 399 ++ .../governance/stored_id.rs | 261 + .../contract_interfaces/governance/vote.rs | 79 + .../mint/liability_mint.rs | 127 + .../src/contract_interfaces/mint/mint.rs | 174 + .../contract_interfaces/mint/mint_router.rs | 92 + .../src/contract_interfaces/mint/mod.rs | 6 + .../src/contract_interfaces/mod.rs | 46 + .../src/contract_interfaces/oracles/band.rs | 62 + .../src/contract_interfaces/oracles/mod.rs | 4 + .../src/contract_interfaces/oracles/oracle.rs | 97 + .../contract_interfaces/peg_stability/mod.rs | 146 + .../contract_interfaces/query_auth/auth.rs | 78 + .../contract_interfaces/query_auth/helpers.rs | 56 + .../src/contract_interfaces/query_auth/mod.rs | 145 + .../src/contract_interfaces/shade_oracles.rs | 106 + .../src/contract_interfaces/sky/cycles.rs | 339 ++ .../src/contract_interfaces/sky/mod.rs | 182 + .../src/contract_interfaces/snip20/batch.rs | 52 + .../src/contract_interfaces/snip20/errors.rs | 199 + .../src/contract_interfaces/snip20/helpers.rs | 345 ++ .../src/contract_interfaces/snip20/manager.rs | 345 ++ .../src/contract_interfaces/snip20/mod.rs | 649 +++ .../snip20/transaction_history.rs | 496 ++ .../snip20_migration/mod.rs | 89 + .../src/contract_interfaces/stkd/mod.rs | 170 + packages/shade_protocol/src/lib.rs | 43 + packages/shade_protocol/src/schemas.rs | 77 + packages/shade_protocol/src/utils/asset.rs | 225 + packages/shade_protocol/src/utils/calc.rs | 25 + packages/shade_protocol/src/utils/callback.rs | 376 ++ .../shade_protocol/src/utils/crypto/hash.rs | 35 + .../shade_protocol/src/utils/crypto/mod.rs | 5 + .../shade_protocol/src/utils/crypto/rng.rs | 85 + packages/shade_protocol/src/utils/cycle.rs | 327 ++ packages/shade_protocol/src/utils/errors.rs | 287 + .../shade_protocol/src/utils/flexible_msg.rs | 34 + .../src/utils/generic_response.rs | 8 + packages/shade_protocol/src/utils/mod.rs | 35 + packages/shade_protocol/src/utils/padding.rs | 43 + packages/shade_protocol/src/utils/price.rs | 116 + .../src/utils/storage/default.rs | 113 + .../shade_protocol/src/utils/storage/mod.rs | 5 + .../src/utils/storage/plus/iter_item.rs | 329 ++ .../src/utils/storage/plus/iter_map.rs | 389 ++ .../src/utils/storage/plus/mod.rs | 208 + .../src/utils/storage/plus/period_storage.rs | 359 ++ packages/shade_protocol/src/utils/wrap.rs | 35 + rust-toolchain | 1 + rustfmt.toml | 15 + temp-contracts/liability_mint/.cargo/config | 5 + .../liability_mint/.circleci/config.yml | 52 + temp-contracts/liability_mint/Cargo.toml | 39 + temp-contracts/liability_mint/Makefile | 68 + temp-contracts/liability_mint/README.md | 215 + temp-contracts/liability_mint/src/contract.rs | 71 + temp-contracts/liability_mint/src/execute.rs | 311 ++ temp-contracts/liability_mint/src/lib.rs | 4 + temp-contracts/liability_mint/src/query.rs | 39 + temp-contracts/liability_mint/src/storage.rs | 12 + .../liability_mint/tests/integration.rs | 195 + tools/doc2book/Cargo.toml | 11 + tools/doc2book/README.md | 59 + tools/doc2book/src/doc2book.rs | 326 ++ tools/multisig/broadcast_multi.sh | 28 + tools/multisig/sign_mutli.sh | 11 + tools/multisig/sign_permit.py | 36 + tools/multisig/wasm_msg.py | 25 + 545 files changed, 89507 insertions(+), 4920 deletions(-) delete mode 100644 .cargo/config delete mode 100644 .circleci/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/rust.yml delete mode 100644 .mergify.yml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 Makefile.toml create mode 100644 SECURITY.md create mode 100644 Shade_Protocol_Responsible_Disclosure.md create mode 100644 archived-contracts/bonds/.cargo/config create mode 100644 archived-contracts/bonds/.circleci/config.yml create mode 100644 archived-contracts/bonds/Cargo.toml create mode 100644 archived-contracts/bonds/Makefile create mode 100644 archived-contracts/bonds/README.md create mode 100644 archived-contracts/bonds/src/contract.rs create mode 100644 archived-contracts/bonds/src/handle.rs create mode 100644 archived-contracts/bonds/src/lib.rs create mode 100644 archived-contracts/bonds/src/query.rs create mode 100644 archived-contracts/bonds/src/state.rs create mode 100644 archived-contracts/bonds/src/test.rs create mode 100644 archived-contracts/bonds/src/tests/handle.rs create mode 100644 archived-contracts/bonds/src/tests/mod.rs create mode 100644 archived-contracts/bonds/src/tests/query.rs create mode 100644 archived-contracts/dao/lp_shdswap/.cargo/config create mode 100644 archived-contracts/dao/lp_shdswap/.circleci/config.yml create mode 100644 archived-contracts/dao/lp_shdswap/Cargo.toml create mode 100644 archived-contracts/dao/lp_shdswap/Makefile create mode 100644 archived-contracts/dao/lp_shdswap/README.md create mode 100644 archived-contracts/dao/lp_shdswap/src/contract.rs create mode 100644 archived-contracts/dao/lp_shdswap/src/execute.rs create mode 100644 archived-contracts/dao/lp_shdswap/src/lib.rs create mode 100644 archived-contracts/dao/lp_shdswap/src/query.rs create mode 100644 archived-contracts/dao/lp_shdswap/src/storage.rs create mode 100644 archived-contracts/dao/lp_shdswap/src/test.rs create mode 100644 archived-contracts/dao/rewards_emission/.cargo/config create mode 100644 archived-contracts/dao/rewards_emission/.circleci/config.yml create mode 100644 archived-contracts/dao/rewards_emission/Cargo.toml create mode 100644 archived-contracts/dao/rewards_emission/Makefile create mode 100644 archived-contracts/dao/rewards_emission/README.md create mode 100644 archived-contracts/dao/rewards_emission/src/contract.rs create mode 100644 archived-contracts/dao/rewards_emission/src/execute.rs create mode 100644 archived-contracts/dao/rewards_emission/src/lib.rs create mode 100644 archived-contracts/dao/rewards_emission/src/query.rs create mode 100644 archived-contracts/dao/rewards_emission/src/storage.rs create mode 100644 archived-contracts/dao/rewards_emission/src/test.rs create mode 100644 archived-contracts/governance/.cargo/config create mode 100644 archived-contracts/governance/.circleci/config.yml create mode 100644 archived-contracts/governance/Cargo.toml create mode 100644 archived-contracts/governance/Makefile create mode 100644 archived-contracts/governance/src/contract.rs create mode 100644 archived-contracts/governance/src/handle/assembly.rs create mode 100644 archived-contracts/governance/src/handle/assembly_msg.rs create mode 100644 archived-contracts/governance/src/handle/contract.rs create mode 100644 archived-contracts/governance/src/handle/migration.rs create mode 100644 archived-contracts/governance/src/handle/mod.rs create mode 100644 archived-contracts/governance/src/handle/profile.rs create mode 100644 archived-contracts/governance/src/handle/proposal.rs create mode 100644 archived-contracts/governance/src/lib.rs create mode 100644 archived-contracts/governance/src/query.rs create mode 100644 archived-contracts/governance/src/tests/handle/assembly.rs create mode 100644 archived-contracts/governance/src/tests/handle/assembly_msg.rs create mode 100644 archived-contracts/governance/src/tests/handle/contract.rs create mode 100644 archived-contracts/governance/src/tests/handle/migration.rs create mode 100644 archived-contracts/governance/src/tests/handle/mod.rs create mode 100644 archived-contracts/governance/src/tests/handle/profile.rs create mode 100644 archived-contracts/governance/src/tests/handle/proposal/assembly_voting.rs create mode 100644 archived-contracts/governance/src/tests/handle/proposal/funding.rs create mode 100644 archived-contracts/governance/src/tests/handle/proposal/mod.rs create mode 100644 archived-contracts/governance/src/tests/handle/proposal/voting.rs create mode 100644 archived-contracts/governance/src/tests/handle/runstate.rs create mode 100644 archived-contracts/governance/src/tests/mod.rs create mode 100644 archived-contracts/governance/src/tests/query/mod.rs create mode 100644 archived-contracts/governance/src/tests/query/public.rs create mode 100644 archived-contracts/governance/src/tests/query/user.rs create mode 100644 archived-contracts/mint/.cargo/config create mode 100644 archived-contracts/mint/.circleci/config.yml create mode 100644 archived-contracts/mint/Cargo.toml create mode 100644 archived-contracts/mint/Makefile create mode 100644 archived-contracts/mint/README.md create mode 100644 archived-contracts/mint/src/contract.rs create mode 100644 archived-contracts/mint/src/handle.rs create mode 100644 archived-contracts/mint/src/lib.rs create mode 100644 archived-contracts/mint/src/query.rs create mode 100644 archived-contracts/mint/src/state.rs create mode 100644 archived-contracts/mint/tests/integration.rs create mode 100644 archived-contracts/mint/tests/unit.rs create mode 100644 archived-contracts/mint_router/.cargo/config create mode 100644 archived-contracts/mint_router/.circleci/config.yml create mode 100644 archived-contracts/mint_router/Cargo.toml create mode 100644 archived-contracts/mint_router/Makefile create mode 100644 archived-contracts/mint_router/README.md create mode 100644 archived-contracts/mint_router/src/contract.rs create mode 100644 archived-contracts/mint_router/src/handle.rs create mode 100644 archived-contracts/mint_router/src/lib.rs create mode 100644 archived-contracts/mint_router/src/query.rs create mode 100644 archived-contracts/mint_router/src/state.rs create mode 100644 archived-contracts/mint_router/src/test.rs create mode 100644 archived-contracts/mock_band/.cargo/config create mode 100644 archived-contracts/mock_band/.circleci/config.yml create mode 100644 archived-contracts/mock_band/Cargo.toml create mode 100644 archived-contracts/mock_band/Makefile create mode 100644 archived-contracts/mock_band/README.md create mode 100644 archived-contracts/mock_band/src/contract.rs create mode 100644 archived-contracts/mock_band/src/execute.rs create mode 100644 archived-contracts/mock_band/src/lib.rs create mode 100644 archived-contracts/mock_secretswap_pair/.cargo/config create mode 100644 archived-contracts/mock_secretswap_pair/.circleci/config.yml create mode 100644 archived-contracts/mock_secretswap_pair/Cargo.toml create mode 100644 archived-contracts/mock_secretswap_pair/Makefile create mode 100644 archived-contracts/mock_secretswap_pair/README.md create mode 100644 archived-contracts/mock_secretswap_pair/src/contract.rs create mode 100644 archived-contracts/mock_secretswap_pair/src/lib.rs create mode 100644 archived-contracts/mock_sienna_pair/Cargo.toml create mode 100644 archived-contracts/mock_sienna_pair/src/contract.rs create mode 100644 archived-contracts/oracle/.cargo/config create mode 100644 archived-contracts/oracle/.circleci/config.yml create mode 100644 archived-contracts/oracle/Cargo.toml create mode 100644 archived-contracts/oracle/Makefile create mode 100644 archived-contracts/oracle/README.md create mode 100644 archived-contracts/oracle/src/contract.rs create mode 100644 archived-contracts/oracle/src/handle.rs create mode 100644 archived-contracts/oracle/src/lib.rs create mode 100644 archived-contracts/oracle/src/query.rs create mode 100644 archived-contracts/oracle/src/state.rs create mode 100644 archived-contracts/oracle/src/test.rs create mode 100644 archived-contracts/oracle/tests/integration.rs create mode 100644 archived-contracts/peg_stability/Cargo.toml create mode 100644 archived-contracts/peg_stability/Makefile create mode 100644 archived-contracts/peg_stability/src/contract.rs create mode 100644 archived-contracts/peg_stability/src/handle.rs create mode 100644 archived-contracts/peg_stability/src/lib.rs create mode 100644 archived-contracts/peg_stability/src/query.rs create mode 100644 archived-contracts/peg_stability/src/tests/handle.rs create mode 100644 archived-contracts/peg_stability/src/tests/mod.rs create mode 100644 archived-contracts/peg_stability/src/tests/query.rs create mode 100644 archived-contracts/sky/.cargo/config create mode 100644 archived-contracts/sky/.circleci/config.yml create mode 100644 archived-contracts/sky/Cargo.toml create mode 100644 archived-contracts/sky/Makefile create mode 100644 archived-contracts/sky/src/contract.rs create mode 100644 archived-contracts/sky/src/execute.rs create mode 100644 archived-contracts/sky/src/lib.rs create mode 100644 archived-contracts/sky/src/query.rs create mode 100644 archived-contracts/sky/tests/integration.rs create mode 100644 archived-contracts/snip20_staking/.cargo/config create mode 100644 archived-contracts/snip20_staking/.circleci/config.yml create mode 100644 archived-contracts/snip20_staking/Cargo.toml create mode 100644 archived-contracts/snip20_staking/README.md create mode 100644 archived-contracts/snip20_staking/src/batch.rs create mode 100644 archived-contracts/snip20_staking/src/contract.rs create mode 100644 archived-contracts/snip20_staking/src/distributors.rs create mode 100644 archived-contracts/snip20_staking/src/expose_balance.rs create mode 100644 archived-contracts/snip20_staking/src/lib.rs create mode 100644 archived-contracts/snip20_staking/src/msg.rs create mode 100644 archived-contracts/snip20_staking/src/rand.rs create mode 100644 archived-contracts/snip20_staking/src/receiver.rs create mode 100644 archived-contracts/snip20_staking/src/stake.rs create mode 100644 archived-contracts/snip20_staking/src/stake_queries.rs create mode 100644 archived-contracts/snip20_staking/src/state.rs create mode 100644 archived-contracts/snip20_staking/src/state_staking.rs create mode 100644 archived-contracts/snip20_staking/src/transaction_history.rs create mode 100644 archived-contracts/snip20_staking/src/utils.rs create mode 100644 archived-contracts/snip20_staking/src/viewing_key.rs create mode 100644 archived_packages/network_integration/src/testnet_airdrop.rs create mode 100644 contractlib/contractlib.py create mode 100644 contractlib/initializerlib.py create mode 100644 contractlib/mintlib.py create mode 100644 contractlib/oraclelib.py create mode 100644 contractlib/secretlib/secretlib.py create mode 100644 contractlib/snip20lib.py create mode 100644 contractlib/treasurylib.py create mode 100644 contractlib/utils.py create mode 100644 contracts/Staking Deep Dive create mode 100644 contracts/Staking Overview create mode 100644 contracts/admin/.cargo/config create mode 100644 contracts/admin/.circleci/config.yml create mode 100644 contracts/admin/Cargo.toml create mode 100644 contracts/admin/Makefile create mode 100644 contracts/admin/README.md create mode 100644 contracts/admin/src/contract.rs create mode 100644 contracts/admin/src/execute.rs create mode 100644 contracts/admin/src/lib.rs create mode 100644 contracts/admin/src/query.rs create mode 100644 contracts/admin/src/shared.rs create mode 100644 contracts/admin/src/test.rs create mode 100644 contracts/airdrop/.cargo/config create mode 100644 contracts/airdrop/.circleci/config.yml create mode 100644 contracts/airdrop/Cargo.toml create mode 100644 contracts/airdrop/Makefile create mode 100644 contracts/airdrop/README.md create mode 100644 contracts/airdrop/src/contract.rs create mode 100644 contracts/airdrop/src/handle.rs create mode 100644 contracts/airdrop/src/lib.rs create mode 100644 contracts/airdrop/src/query.rs create mode 100644 contracts/airdrop/src/state.rs create mode 100644 contracts/airdrop/src/test.rs create mode 100644 contracts/basic_staking/.cargo/config create mode 100644 contracts/basic_staking/.circleci/config.yml create mode 100644 contracts/basic_staking/Cargo.toml create mode 100644 contracts/basic_staking/Makefile create mode 100644 contracts/basic_staking/README.md create mode 100644 contracts/basic_staking/src/contract.rs create mode 100644 contracts/basic_staking/src/execute.rs create mode 100644 contracts/basic_staking/src/lib.rs create mode 100644 contracts/basic_staking/src/query.rs create mode 100644 contracts/basic_staking/src/storage.rs create mode 100644 contracts/basic_staking/tests/bad_stake_token.rs create mode 100644 contracts/basic_staking/tests/end_reward_pool_after_end_claimed.rs create mode 100644 contracts/basic_staking/tests/end_reward_pool_after_end_unclaimed.rs create mode 100644 contracts/basic_staking/tests/end_reward_pool_after_end_unclaimed_force.rs create mode 100644 contracts/basic_staking/tests/end_reward_pool_before_end_claimed.rs create mode 100644 contracts/basic_staking/tests/end_reward_pool_before_end_unclaimed.rs create mode 100644 contracts/basic_staking/tests/end_reward_pool_before_start.rs create mode 100644 contracts/basic_staking/tests/multi_staker.rs create mode 100644 contracts/basic_staking/tests/non_admin_access.rs create mode 100644 contracts/basic_staking/tests/non_stake_rewards.rs create mode 100644 contracts/basic_staking/tests/single_staker.rs create mode 100644 contracts/basic_staking/tests/single_staker_compounding.rs create mode 100644 contracts/basic_staking/tests/stake_0_total_bug.rs create mode 100644 contracts/basic_staking/tests/transfer_stake.rs create mode 100644 contracts/basic_staking/tests/transfer_stake_compound.rs create mode 100644 contracts/basic_staking/tests/transfer_stake_sender_no_stake.rs create mode 100644 contracts/basic_staking/tests/unbonding_withdrawals.rs create mode 100644 contracts/basic_staking/tests/unstake_softlock_bug.rs create mode 100644 contracts/basic_staking/tests/update_config.rs create mode 100644 contracts/dao/scrt_staking/.cargo/config create mode 100644 contracts/dao/scrt_staking/.circleci/config.yml create mode 100644 contracts/dao/scrt_staking/Cargo.toml create mode 100644 contracts/dao/scrt_staking/Makefile create mode 100644 contracts/dao/scrt_staking/README.md create mode 100644 contracts/dao/scrt_staking/src/contract.rs create mode 100644 contracts/dao/scrt_staking/src/execute.rs create mode 100644 contracts/dao/scrt_staking/src/lib.rs create mode 100644 contracts/dao/scrt_staking/src/query.rs create mode 100644 contracts/dao/scrt_staking/src/storage.rs create mode 100644 contracts/dao/scrt_staking/src/test.rs create mode 100644 contracts/dao/scrt_staking/tests/integration.rs create mode 100644 contracts/dao/scrt_staking/tests/unit.rs create mode 100644 contracts/dao/stkd_scrt/.cargo/config create mode 100644 contracts/dao/stkd_scrt/.circleci/config.yml create mode 100644 contracts/dao/stkd_scrt/Cargo.toml create mode 100644 contracts/dao/stkd_scrt/Makefile create mode 100644 contracts/dao/stkd_scrt/README.md create mode 100644 contracts/dao/stkd_scrt/src/contract.rs create mode 100644 contracts/dao/stkd_scrt/src/execute.rs create mode 100644 contracts/dao/stkd_scrt/src/lib.rs create mode 100644 contracts/dao/stkd_scrt/src/query.rs create mode 100644 contracts/dao/stkd_scrt/src/storage.rs create mode 100644 contracts/dao/stkd_scrt/src/test.rs create mode 100644 contracts/dao/stkd_scrt/tests/integration.rs create mode 100644 contracts/dao/stkd_scrt/tests/unit.rs create mode 100644 contracts/dao/treasury/.cargo/config create mode 100644 contracts/dao/treasury/.circleci/config.yml create mode 100644 contracts/dao/treasury/Cargo.toml create mode 100644 contracts/dao/treasury/Makefile create mode 100644 contracts/dao/treasury/README.md create mode 100644 contracts/dao/treasury/Untitled Diagram.drawio create mode 100644 contracts/dao/treasury/src/contract.rs create mode 100644 contracts/dao/treasury/src/execute.rs create mode 100644 contracts/dao/treasury/src/lib.rs create mode 100644 contracts/dao/treasury/src/query.rs create mode 100644 contracts/dao/treasury/src/storage.rs create mode 100644 contracts/dao/treasury/tests/dao/equilibrium.rs create mode 100644 contracts/dao/treasury/tests/dao/gains_losses.rs create mode 100644 contracts/dao/treasury/tests/dao/mod.rs create mode 100644 contracts/dao/treasury/tests/integration/allowance.rs create mode 100644 contracts/dao/treasury/tests/integration/allowance_delay_refresh.rs create mode 100644 contracts/dao/treasury/tests/integration/batch.rs create mode 100644 contracts/dao/treasury/tests/integration/config.rs create mode 100644 contracts/dao/treasury/tests/integration/execute_errors.rs create mode 100644 contracts/dao/treasury/tests/integration/migration.rs create mode 100644 contracts/dao/treasury/tests/integration/mod.rs create mode 100644 contracts/dao/treasury/tests/integration/non_manager_allowances.rs create mode 100644 contracts/dao/treasury/tests/integration/query.rs create mode 100644 contracts/dao/treasury/tests/integration/scrt_staking.rs create mode 100644 contracts/dao/treasury/tests/integration/tolerance.rs create mode 100644 contracts/dao/treasury/tests/integration/treasury.rs create mode 100644 contracts/dao/treasury/tests/integration/wrap.rs create mode 100644 contracts/dao/treasury/tests/mod.rs create mode 100644 contracts/dao/treasury_manager/.cargo/config create mode 100644 contracts/dao/treasury_manager/.circleci/config.yml create mode 100644 contracts/dao/treasury_manager/Cargo.toml create mode 100644 contracts/dao/treasury_manager/Makefile create mode 100644 contracts/dao/treasury_manager/README.md create mode 100644 contracts/dao/treasury_manager/src/contract.rs create mode 100644 contracts/dao/treasury_manager/src/execute.rs create mode 100644 contracts/dao/treasury_manager/src/lib.rs create mode 100644 contracts/dao/treasury_manager/src/query.rs create mode 100644 contracts/dao/treasury_manager/src/storage.rs create mode 100644 contracts/dao/treasury_manager/tests/integration/batch.rs create mode 100644 contracts/dao/treasury_manager/tests/integration/config.rs create mode 100644 contracts/dao/treasury_manager/tests/integration/execute_error.rs create mode 100644 contracts/dao/treasury_manager/tests/integration/holder_integration.rs create mode 100644 contracts/dao/treasury_manager/tests/integration/mod.rs create mode 100644 contracts/dao/treasury_manager/tests/integration/multiple_holders.rs create mode 100644 contracts/dao/treasury_manager/tests/integration/query.rs create mode 100644 contracts/dao/treasury_manager/tests/integration/scrt_staking_integration.rs create mode 100644 contracts/dao/treasury_manager/tests/integration/tm_unbond.rs create mode 100644 contracts/dao/treasury_manager/tests/integration/tolerance.rs create mode 100644 contracts/dao/treasury_manager/tests/mod.rs delete mode 100644 contracts/headstash-contract/.cargo/config delete mode 100644 contracts/headstash-contract/.gitignore delete mode 100644 contracts/headstash-contract/Cargo.toml delete mode 100644 contracts/headstash-contract/helpers/.eslintignore delete mode 100644 contracts/headstash-contract/helpers/.eslintrc delete mode 100644 contracts/headstash-contract/helpers/.gitignore delete mode 100644 contracts/headstash-contract/helpers/README.md delete mode 100644 contracts/headstash-contract/helpers/bin/run delete mode 100644 contracts/headstash-contract/helpers/bin/run.cmd delete mode 100644 contracts/headstash-contract/helpers/package.json delete mode 100644 contracts/headstash-contract/helpers/src/airdrop.ts delete mode 100644 contracts/headstash-contract/helpers/src/commands/generateProofs.ts delete mode 100644 contracts/headstash-contract/helpers/src/commands/generateRoot.ts delete mode 100644 contracts/headstash-contract/helpers/src/commands/verifyProofs.ts delete mode 100644 contracts/headstash-contract/helpers/src/index.ts delete mode 100644 contracts/headstash-contract/helpers/tsconfig.json delete mode 100644 contracts/headstash-contract/helpers/yarn.lock delete mode 100644 contracts/headstash-contract/src/contract.rs delete mode 100644 contracts/headstash-contract/src/error.rs delete mode 100644 contracts/headstash-contract/src/lib.rs delete mode 100644 contracts/headstash-contract/src/msg.rs delete mode 100644 contracts/headstash-contract/src/state.rs delete mode 100644 contracts/headstash-contract/testdata/airdrop_stage_1_list.json delete mode 100644 contracts/headstash-contract/testdata/airdrop_stage_1_test_data.json delete mode 100644 contracts/headstash-contract/testdata/airdrop_stage_1_test_multi_data.json delete mode 100644 contracts/headstash-contract/testdata/airdrop_stage_2_list.json delete mode 100644 contracts/headstash-contract/testdata/airdrop_stage_2_test_data.json create mode 100644 contracts/mock/mock_adapter/.cargo/config create mode 100644 contracts/mock/mock_adapter/.circleci/config.yml create mode 100644 contracts/mock/mock_adapter/Cargo.toml create mode 100644 contracts/mock/mock_adapter/Makefile create mode 100644 contracts/mock/mock_adapter/README.md create mode 100644 contracts/mock/mock_adapter/src/contract.rs create mode 100644 contracts/mock/mock_adapter/src/execute.rs create mode 100644 contracts/mock/mock_adapter/src/lib.rs create mode 100644 contracts/mock/mock_adapter/src/storage.rs create mode 100644 contracts/mock/mock_sienna_pair/.cargo/config create mode 100644 contracts/mock/mock_sienna_pair/.circleci/config.yml create mode 100644 contracts/mock/mock_sienna_pair/Cargo.toml create mode 100644 contracts/mock/mock_sienna_pair/Makefile create mode 100644 contracts/mock/mock_sienna_pair/README.md create mode 100644 contracts/mock/mock_sienna_pair/src/contract.rs create mode 100644 contracts/mock/mock_sienna_pair/src/lib.rs create mode 100644 contracts/mock/mock_stkd_derivative/Cargo.toml create mode 100644 contracts/mock/mock_stkd_derivative/Makefile create mode 100644 contracts/mock/mock_stkd_derivative/README.md create mode 100644 contracts/mock/mock_stkd_derivative/src/contract.rs create mode 100644 contracts/mock/mock_stkd_derivative/src/lib.rs create mode 100644 contracts/mock/mock_stkd_derivative/src/tests.rs create mode 100644 contracts/query_auth/.cargo/config create mode 100644 contracts/query_auth/.circleci/config.yml create mode 100644 contracts/query_auth/Cargo.toml create mode 100644 contracts/query_auth/README.md create mode 100644 contracts/query_auth/src/contract.rs create mode 100644 contracts/query_auth/src/handle.rs create mode 100644 contracts/query_auth/src/lib.rs create mode 100644 contracts/query_auth/src/query.rs create mode 100644 contracts/query_auth/src/tests/handle.rs create mode 100644 contracts/query_auth/src/tests/mod.rs create mode 100644 contracts/query_auth/src/tests/query.rs create mode 100644 contracts/snip20/.cargo/config create mode 100644 contracts/snip20/.circleci/config.yml create mode 100644 contracts/snip20/Cargo.toml create mode 100644 contracts/snip20/Makefile create mode 100644 contracts/snip20/src/batch.rs create mode 100644 contracts/snip20/src/contract.rs create mode 100644 contracts/snip20/src/handle/allowance.rs create mode 100644 contracts/snip20/src/handle/burning.rs create mode 100644 contracts/snip20/src/handle/minting.rs create mode 100644 contracts/snip20/src/handle/mod.rs create mode 100644 contracts/snip20/src/handle/transfers.rs create mode 100644 contracts/snip20/src/lib.rs create mode 100644 contracts/snip20/src/query.rs create mode 100644 contracts/snip20/src/tests/handle/allowance.rs create mode 100644 contracts/snip20/src/tests/handle/burn.rs create mode 100644 contracts/snip20/src/tests/handle/mint.rs create mode 100644 contracts/snip20/src/tests/handle/mod.rs create mode 100644 contracts/snip20/src/tests/handle/transfer.rs create mode 100644 contracts/snip20/src/tests/handle/wrap.rs create mode 100644 contracts/snip20/src/tests/mod.rs create mode 100644 contracts/snip20/src/tests/query/mod.rs create mode 100644 contracts/snip20/src/tests/query/public.rs create mode 100644 contracts/snip20/src/tests/query/user.rs create mode 100644 contracts/snip20_derivative/.cargo/config create mode 100644 contracts/snip20_derivative/.circleci/config.yml create mode 100644 contracts/snip20_derivative/.editorconfig create mode 100644 contracts/snip20_derivative/.github/workflows/cicd.yaml create mode 100644 contracts/snip20_derivative/.images/engineering-diagram.png create mode 100644 contracts/snip20_derivative/Cargo.toml create mode 100644 contracts/snip20_derivative/Developing.md create mode 100644 contracts/snip20_derivative/Importing.md create mode 100644 contracts/snip20_derivative/LICENSE create mode 100644 contracts/snip20_derivative/Makefile create mode 100644 contracts/snip20_derivative/NOTICE create mode 100644 contracts/snip20_derivative/Publishing.md create mode 100644 contracts/snip20_derivative/README.md rename contracts/{headstash-contract => snip20_derivative}/examples/schema.rs (59%) create mode 100644 contracts/snip20_derivative/src/contract.rs create mode 100644 contracts/snip20_derivative/src/lib.rs create mode 100644 contracts/snip20_derivative/src/msg.rs create mode 100644 contracts/snip20_derivative/src/staking_interface.rs create mode 100644 contracts/snip20_derivative/src/state.rs create mode 100644 contracts/snip20_derivative/tests/integration.rs create mode 100755 contracts/snip20_derivative/tests/integration.sh create mode 100644 contracts/snip20_migration/Cargo.toml create mode 100644 contracts/snip20_migration/Makefile create mode 100644 contracts/snip20_migration/src/contract.rs create mode 100644 contracts/snip20_migration/src/lib.rs create mode 100644 contracts/snip20_migration/src/storage.rs create mode 100644 contracts/snip20_migration/src/test.rs create mode 100644 contracts/snip20_migration/tests/integration.rs create mode 100755 docker_setup create mode 100644 launch/Cargo.toml create mode 100755 makefile create mode 100644 packages/contract_derive/Cargo.toml create mode 100644 packages/contract_derive/src/lib.rs create mode 100644 packages/ethereum_verify/Cargo.toml create mode 100644 packages/ethereum_verify/README.md create mode 100644 packages/ethereum_verify/src/decode.rs create mode 100644 packages/ethereum_verify/src/lib.rs create mode 100644 packages/ethereum_verify/src/signature_verify.rs create mode 100644 packages/multi_derive/Cargo.toml create mode 100644 packages/multi_derive/src/lib.rs create mode 100644 packages/multi_test/Cargo.toml create mode 100644 packages/multi_test/src/interfaces/dao.rs create mode 100644 packages/multi_test/src/interfaces/mod.rs create mode 100644 packages/multi_test/src/interfaces/scrt_staking.rs create mode 100644 packages/multi_test/src/interfaces/snip20.rs create mode 100644 packages/multi_test/src/interfaces/treasury.rs create mode 100644 packages/multi_test/src/interfaces/treasury_manager.rs create mode 100644 packages/multi_test/src/interfaces/utils.rs create mode 100644 packages/multi_test/src/lib.rs create mode 100644 packages/multi_test/src/multi.rs create mode 100644 packages/secretcli/Cargo.toml create mode 100644 packages/secretcli/src/cli_types.rs create mode 100644 packages/secretcli/src/lib.rs create mode 100644 packages/secretcli/src/secretcli.rs create mode 100644 packages/shade_protocol/.cargo/config create mode 100644 packages/shade_protocol/Cargo.toml create mode 100644 packages/shade_protocol/src/contract_interfaces/admin/errors.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/admin/helpers.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/admin/mod.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/airdrop/account.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/airdrop/claim_info.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/airdrop/errors.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/basic_staking/mod.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/bonds/errors.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/bonds/mod.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/bonds/rand.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/bonds/utils.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/dao/DAO_ADAPTER.md create mode 100644 packages/shade_protocol/src/contract_interfaces/dao/adapter.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/dao/lp_shdswap.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/dao/manager.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/dao/mod.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/dao/rewards_emission.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/dao/scrt_staking.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/dao/stkd_scrt.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/dao/treasury.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/dao/treasury_manager.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/dex/dex.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/dex/mod.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/dex/secretswap.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/dex/shadeswap.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/dex/sienna.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/governance/assembly.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/governance/contract.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/governance/errors.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/governance/mod.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/governance/profile.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/governance/proposal.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/governance/stored_id.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/governance/vote.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/mint/liability_mint.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/mint/mint.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/mint/mint_router.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/mint/mod.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/mod.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/oracles/band.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/oracles/mod.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/oracles/oracle.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/peg_stability/mod.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/query_auth/auth.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/query_auth/helpers.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/query_auth/mod.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/shade_oracles.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/sky/cycles.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/sky/mod.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/snip20/batch.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/snip20/errors.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/snip20/helpers.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/snip20/manager.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/snip20/mod.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/snip20/transaction_history.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/snip20_migration/mod.rs create mode 100644 packages/shade_protocol/src/contract_interfaces/stkd/mod.rs create mode 100644 packages/shade_protocol/src/lib.rs create mode 100644 packages/shade_protocol/src/schemas.rs create mode 100644 packages/shade_protocol/src/utils/asset.rs create mode 100644 packages/shade_protocol/src/utils/calc.rs create mode 100644 packages/shade_protocol/src/utils/callback.rs create mode 100644 packages/shade_protocol/src/utils/crypto/hash.rs create mode 100644 packages/shade_protocol/src/utils/crypto/mod.rs create mode 100644 packages/shade_protocol/src/utils/crypto/rng.rs create mode 100644 packages/shade_protocol/src/utils/cycle.rs create mode 100644 packages/shade_protocol/src/utils/errors.rs create mode 100644 packages/shade_protocol/src/utils/flexible_msg.rs create mode 100644 packages/shade_protocol/src/utils/generic_response.rs create mode 100644 packages/shade_protocol/src/utils/mod.rs create mode 100644 packages/shade_protocol/src/utils/padding.rs create mode 100644 packages/shade_protocol/src/utils/price.rs create mode 100644 packages/shade_protocol/src/utils/storage/default.rs create mode 100644 packages/shade_protocol/src/utils/storage/mod.rs create mode 100644 packages/shade_protocol/src/utils/storage/plus/iter_item.rs create mode 100644 packages/shade_protocol/src/utils/storage/plus/iter_map.rs create mode 100644 packages/shade_protocol/src/utils/storage/plus/mod.rs create mode 100644 packages/shade_protocol/src/utils/storage/plus/period_storage.rs create mode 100644 packages/shade_protocol/src/utils/wrap.rs create mode 100644 rust-toolchain create mode 100644 rustfmt.toml create mode 100644 temp-contracts/liability_mint/.cargo/config create mode 100644 temp-contracts/liability_mint/.circleci/config.yml create mode 100644 temp-contracts/liability_mint/Cargo.toml create mode 100644 temp-contracts/liability_mint/Makefile create mode 100644 temp-contracts/liability_mint/README.md create mode 100644 temp-contracts/liability_mint/src/contract.rs create mode 100644 temp-contracts/liability_mint/src/execute.rs create mode 100644 temp-contracts/liability_mint/src/lib.rs create mode 100644 temp-contracts/liability_mint/src/query.rs create mode 100644 temp-contracts/liability_mint/src/storage.rs create mode 100644 temp-contracts/liability_mint/tests/integration.rs create mode 100644 tools/doc2book/Cargo.toml create mode 100644 tools/doc2book/README.md create mode 100644 tools/doc2book/src/doc2book.rs create mode 100755 tools/multisig/broadcast_multi.sh create mode 100755 tools/multisig/sign_mutli.sh create mode 100644 tools/multisig/sign_permit.py create mode 100644 tools/multisig/wasm_msg.py diff --git a/.cargo/config b/.cargo/config deleted file mode 100644 index 5bbab80..0000000 --- a/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown --lib" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib" -integration-test = "test --package e2e -- --ignored --test-threads 1" diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 013d362..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,612 +0,0 @@ -version: 2 -workflows: - version: 2 - test: - jobs: - - contract_sg721_base - - contract_sg721_nt - - contract_base_factory - - contract_base_minter - - contract_vending_factory - - contract_vending_minter - - contract_open_edition_factory - - contract_open_edition_minter - - contract_whitelist - - sg-eth-airdrop - - test-suite - - package_sg_std - - package_sg_utils - - lint - - wasm-build - - deploy: - jobs: - - build_and_upload_contracts: - filters: - tags: - only: /^v[0-9]+\.[0-9]+\.[0-9]+.*/ - branches: - ignore: /.*/ - -jobs: - contract_sg721_base: - docker: - - image: rust:1.69.0 - working_directory: ~/project/contracts/collections/sg721-base - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cargocache-sg721-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Unit Tests - environment: - RUST_BACKTRACE: 1 - command: cargo unit-test --locked - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-sg721-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - contract_sg721_nt: - docker: - - image: rust:1.69.0 - working_directory: ~/project/contracts/collections/sg721-nt - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cargocache-sg721-nt-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Unit Tests - environment: - RUST_BACKTRACE: 1 - command: cargo unit-test --locked - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-sg721-nt-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - contract_base_factory: - docker: - - image: rust:1.69.0 - working_directory: ~/project/contracts/factories/base-factory - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cargocache-base-factory-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Unit Tests - environment: - RUST_BACKTRACE: 1 - command: cargo unit-test --locked - - run: - name: Build and run schema generator - command: cargo schema --locked - # - run: - # name: Ensure checked-in schemas are up-to-date - # command: | - # CHANGES_IN_REPO=$(git status --porcelain) - # if [[ -n "$CHANGES_IN_REPO" ]]; then - # echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - # git status && git --no-pager diff - # exit 1 - # fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-base-factory-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - contract_base_minter: - docker: - - image: rust:1.69.0 - working_directory: ~/project/contracts/minters/base-minter - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cargocache-base-minter-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Unit Tests - environment: - RUST_BACKTRACE: 1 - command: cargo unit-test --locked - - run: - name: Build and run schema generator - command: cargo schema --locked - # - run: - # name: Ensure checked-in schemas are up-to-date - # command: | - # CHANGES_IN_REPO=$(git status --porcelain) - # if [[ -n "$CHANGES_IN_REPO" ]]; then - # echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - # git status && git --no-pager diff - # exit 1 - # fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-base-minter-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - contract_vending_factory: - docker: - - image: rust:1.69.0 - working_directory: ~/project/contracts/factories/vending-factory - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cargocache-vending-factory-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Unit Tests - environment: - RUST_BACKTRACE: 1 - command: cargo unit-test --locked - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-vending-factory-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - contract_vending_minter: - docker: - - image: rust:1.69.0 - working_directory: ~/project/contracts/minters/vending-minter - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cargocache-vending-minter-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Unit Tests - environment: - RUST_BACKTRACE: 1 - command: cargo unit-test --locked - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-vending-minter-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - contract_open_edition_factory: - docker: - - image: rust:1.69.0 - working_directory: ~/project/contracts/factories/open-edition-factory - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cargocache-open-edition-factory-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Unit Tests - environment: - RUST_BACKTRACE: 1 - command: cargo unit-test --locked - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-open-edition-factory-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - contract_open_edition_minter: - docker: - - image: rust:1.69.0 - working_directory: ~/project/contracts/minters/open-edition-minter - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cargocache-open-edition-minter-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Unit Tests - environment: - RUST_BACKTRACE: 1 - command: cargo unit-test --locked - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-open-edition-minter-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - contract_whitelist: - docker: - - image: rust:1.69.0 - working_directory: ~/project/contracts/whitelists/whitelist - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cargocache-whitelist-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Unit Tests - environment: - RUST_BACKTRACE: 1 - command: cargo unit-test --locked - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-whitelist-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - test-suite: - docker: - - image: rust:1.69.0 - working_directory: ~/project/test-suite - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cargocache-test-suite-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Unit Tests - environment: - RUST_BACKTRACE: 1 - command: cargo unit-test --locked - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-test-suite-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - package_sg_std: - docker: - - image: rust:1.69.0 - working_directory: ~/project/package/sg-std - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cargocache-sg-std-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Unit Tests - environment: - RUST_BACKTRACE: 1 - command: cargo unit-test --locked - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-sg-std-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - package_sg_utils: - docker: - - image: rust:1.69.0 - working_directory: ~/project/package/sg-utils - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cargocache-sg-utils-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Unit Tests - environment: - RUST_BACKTRACE: 1 - command: cargo unit-test --locked - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-sg-utils-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - sg-eth-airdrop: - docker: - - image: rust:1.69.0 - working_directory: ~/project/contracts/sg-eth-airdrop - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cargocache-sg-eth-airdrop-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Unit Tests - environment: - RUST_BACKTRACE: 1 - command: cargo unit-test --locked - - run: - name: Build and run schema generator - command: cargo schema --locked - # - run: - # name: Ensure checked-in schemas are up-to-date - # command: | - # CHANGES_IN_REPO=$(git status --porcelain) - # if [[ -n "$CHANGES_IN_REPO" ]]; then - # echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - # git status && git --no-pager diff - # exit 1 - # fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-sg-eth-airdrop-rust:1.69.0-{{ checksum "~/project/Cargo.lock" }} - - lint: - docker: - - image: rust:1.69.0 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version; rustup target list --installed - - restore_cache: - keys: - - cargocache-v2-lint-rust:1.69.0-{{ checksum "Cargo.lock" }} - - run: - name: Add rustfmt component - command: rustup component add rustfmt - - run: - name: Add clippy component - command: rustup component add clippy - - run: - name: Check formatting of workspace - command: cargo fmt -- --check - - run: - name: Clippy linting on workspace - command: cargo clippy --all-targets -- -D warnings - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - key: cargocache-v2-lint-rust:1.69.0-{{ checksum "Cargo.lock" }} - - # This runs one time on the top level to ensure all contracts compile properly into wasm. - # We don't run the wasm build per contract build, and then reuse a lot of the same dependencies, so this speeds up CI time - # for all the other tests. - # We also sanity-check the resultant wasm files. - wasm-build: - docker: - - image: rust:1.69.0 - steps: - - checkout: - path: ~/project - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cargocache-wasm-rust-no-wasm:1.67.1-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build Wasm Release - command: | - for C in ./contracts/collections/*/ - do - echo "Compiling `basename $C`..." - (cd $C && cargo build --release --lib --target wasm32-unknown-unknown --locked) - done - for C in ./contracts/factories/*/ - do - echo "Compiling `basename $C`..." - (cd $C && cargo build --release --lib --target wasm32-unknown-unknown --locked) - done - for C in ./contracts/minters/*/ - do - echo "Compiling `basename $C`..." - (cd $C && cargo build --release --lib --target wasm32-unknown-unknown --locked) - done - for C in ./contracts/sg-eth-airdrop/ - do - echo "Compiling `basename $C`..." - (cd $C && cargo build --release --lib --target wasm32-unknown-unknown --locked) - done - for C in ./contracts/splits/ - do - echo "Compiling `basename $C`..." - (cd $C && cargo build --release --lib --target wasm32-unknown-unknown --locked) - done - for C in ./contracts/whitelists/*/ - do - echo "Compiling `basename $C`..." - (cd $C && cargo build --release --lib --target wasm32-unknown-unknown --locked) - done - - run: - name: Install check_contract - # Uses --debug for compilation speed - command: cargo install --debug --version 1.0.0 --features iterator --example check_contract -- cosmwasm-vm - - save_cache: - paths: - - /usr/local/cargo/registry - - /target/debug - - /target/release - - /target/tarpaulin - key: cargocache-wasm-rust-no-wasm:1.67.1-{{ checksum "~/project/Cargo.lock" }} - - run: - name: Check wasm contracts - command: | - for W in ./target/wasm32-unknown-unknown/release/*.wasm - do - echo -n "Checking `basename $W`... " - check_contract --supported-features iterator,staking,stargate,stargaze $W - done - - # This job roughly follows the instructions from https://circleci.com/blog/publishing-to-github-releases-via-circleci/ - build_and_upload_contracts: - docker: - # Image from https://github.com/cibuilds/github, based on alpine - - image: cibuilds/github:0.13 - steps: - - run: - name: Install Docker client - command: apk add docker-cli - - setup_remote_docker - - checkout - - run: - # We cannot mount local folders, see https://circleci.com/docs/2.0/building-docker-images/#mounting-folders - name: Prepare volume with source code - command: | - # create a dummy container which will hold a volume with config - docker create -v /code --name with_code alpine /bin/true - # copy a config file into this volume - docker cp Cargo.toml with_code:/code - docker cp Cargo.lock with_code:/code - # copy code into this volume - docker cp ./contracts with_code:/code - docker cp ./packages with_code:/code - docker cp ./test-suite with_code:/code - docker cp ./e2e with_code:/code - - run: - name: Build development contracts - command: | - docker run --volumes-from with_code cosmwasm/workspace-optimizer:0.12.13 - docker cp with_code:/code/artifacts ./artifacts - - run: - name: Show data - command: | - ls -l artifacts - cat artifacts/checksums.txt - - run: - name: Publish artifacts on GitHub - command: | - TAG="$CIRCLE_TAG" - TITLE="$TAG" - BODY="Attached there are some build artifacts generated at this tag. Those are for development purposes only! Please use crates.io to find the packages of this release." - ghr -t "$GITHUB_TOKEN" \ - -u "$CIRCLE_PROJECT_USERNAME" -r "$CIRCLE_PROJECT_REPONAME" \ - -c "$CIRCLE_SHA1" \ - -n "$TITLE" -b "$BODY" \ - -delete \ - "$TAG" ./artifacts/ diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..79a7f43 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,20 @@ + + +# Description + + + + +## Notable changes + + + +- Lowered gas price on contract handle +- Added new contract query + +## Next steps + + + +- Documentation should be updated for query +- More tests to calculate gas price diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..5f1ff4e --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,102 @@ +name: Rust + +on: [push] + +env: + CARGO_TERM_COLOR: always + +jobs: + find-contracts: # Job that list subdirectories + runs-on: ubuntu-latest + outputs: + dir: ${{ steps.set-dirs.outputs.dir }} + steps: + - uses: actions/checkout@v2 + - id: set-dirs + run: echo "::set-output name=dir::$(find ./contracts -name Cargo.toml | jq -R -s -c 'split("\n")[:-1]')" + + build-contracts: + runs-on: ubuntu-latest + needs: [find-contracts] # Depends on previous job + strategy: + matrix: + dir: ${{fromJson(needs.find-contracts.outputs.dir)}} # List matrix strategy from directories dynamically + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + target: wasm32-unknown-unknown + + - uses: Swatinem/rust-cache@v2 + + - name: Install toolchain + run: rustup target add wasm32-unknown-unknown + + - uses: actions-rs/cargo@v1.0.3 + with: + command: build + args: --release --target wasm32-unknown-unknown --manifest-path=${{matrix.dir}} + + find-packages: # Job that list subdirectories + runs-on: ubuntu-latest + outputs: + dir: ${{ steps.set-dirs.outputs.dir }} + steps: + - uses: actions/checkout@v2 + - id: set-dirs + run: echo "::set-output name=dir::$(find ./packages/ -name Cargo.toml | jq -R -s -c 'split("\n")[:-1]')" + + check-packages: + runs-on: ubuntu-latest + needs: [find-packages] # Depends on previous job + strategy: + matrix: + dir: ${{fromJson(needs.find-packages.outputs.dir)}} # List matrix strategy from directories dynamically + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + target: wasm32-unknown-unknown + + - uses: Swatinem/rust-cache@v2 + + - uses: actions-rs/cargo@v1.0.3 + with: + command: check + + coverage: + name: Collect test coverage + runs-on: ubuntu-latest + # nightly rust might break from time to time + continue-on-error: true + env: + CARGO_TERM_COLOR: always + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + target: wasm32-unknown-unknown + components: llvm-tools-preview + + - uses: Swatinem/rust-cache@v2 + + - name: Install latest nextest release + uses: taiki-e/install-action@nextest + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: Collect coverage data + run: cargo llvm-cov nextest --lcov --output-path lcov.info --ignore-filename-regex network_integration\|network_tester\|secretcli\|contract_harness + - name: Upload coverage data to codecov + uses: codecov/codecov-action@v3 + with: + files: lcov.info diff --git a/.gitignore b/.gitignore index 1aedfa0..c8dbb2c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,29 +7,23 @@ # Build results target/ +# Testing configs +*.json + +# Code coverage stuff +*.profraw + # IDEs .vscode/ .idea/ *.iml -.env - -# User-gen -metadata.csv - -# ENV -.env +node_modules/ # Auto-gen .cargo-ok -# Build artifacts -*.wasm -hash.txt -contracts.txt -artifacts/ - -# code coverage -tarpaulin-report.* - -# integration tests -gas_reports/ \ No newline at end of file +*.pyc +__pycache__/ +compiled +logs +Cargo.lock diff --git a/.mergify.yml b/.mergify.yml deleted file mode 100644 index 28110cd..0000000 --- a/.mergify.yml +++ /dev/null @@ -1,26 +0,0 @@ -queue_rules: - - name: default - conditions: - - "#approved-reviews-by>2" - -pull_request_rules: - - name: automerge to main if approved and labeled - conditions: - - "#approved-reviews-by>2" - - base=main - - label=automerge - actions: - queue: - name: default - method: squash - commit_message_template: | - {{ title }} (#{{ number }}) - {{ body }} - - name: backport to v2 - conditions: - - base=main - - label=backport/2.x - actions: - backport: - branches: - - release/v2.x diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..3684d40 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +info@securesecrets.org. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/Cargo.lock b/Cargo.lock index ccfb175..2310520 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "admin" +version = "0.2.0" +dependencies = [ + "rstest", + "secret-cosmwasm-std", + "shade-multi-test", + "shade-protocol", +] + [[package]] name = "ahash" version = "0.7.7" @@ -13,11 +23,45 @@ dependencies = [ "version_check", ] +[[package]] +name = "airdrop" +version = "0.1.0" +dependencies = [ + "ethereum-verify", + "hex", + "rs_merkle", + "shade-protocol", +] + +[[package]] +name = "anyhow" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "base16ct" -version = "0.2.0" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base64" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" @@ -31,12 +75,37 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic_staking" +version = "0.1.0" +dependencies = [ + "secret-cosmwasm-std", + "shade-multi-test", + "shade-protocol", +] + +[[package]] +name = "bech32" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" + [[package]] name = "bech32" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bincode2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49f6183038e081170ebbbadee6678966c7d54728938a3e7de7f4e780770318f" +dependencies = [ + "byteorder", + "serde", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -56,16 +125,25 @@ dependencies = [ ] [[package]] -name = "bnum" -version = "0.8.1" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9008b6bb9fc80b5277f2fe481c09e828743d9151203e804583eb4c9e15b31d" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "byteorder" +name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -73,24 +151,51 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] -name = "cosmwasm-crypto" -version = "1.5.0" +name = "const_format" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bb3c77c3b7ce472056968c745eb501c440fbc07be5004eba02782c35bfbbe3" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" dependencies = [ - "digest 0.10.7", - "ecdsa", - "ed25519-zebra", - "k256", - "rand_core 0.6.4", - "thiserror", + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "contract-derive" +version = "0.1.0" +dependencies = [ + "shade-protocol", + "syn 1.0.109", ] [[package]] @@ -108,8 +213,20 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0df41ea55f2946b6b43579659eec048cc2f66e8c8e2e3652fc5e5e476f673856" dependencies = [ - "cosmwasm-schema-derive", - "schemars", + "cosmwasm-schema-derive 1.5.0", + "schemars 0.8.16", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cosmwasm-schema" +version = "2.0.0-beta.0" +source = "git+https://github.com/CosmWasm/cosmwasm#1ce73c6b91466fc46e8b574e381cdaa1fb0b298e" +dependencies = [ + "cosmwasm-schema-derive 2.0.0-beta.0", + "schemars 0.8.16", "serde", "serde_json", "thiserror", @@ -127,25 +244,13 @@ dependencies = [ ] [[package]] -name = "cosmwasm-std" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04d6864742e3a7662d024b51a94ea81c9af21db6faea2f9a6d2232bb97c6e53e" +name = "cosmwasm-schema-derive" +version = "2.0.0-beta.0" +source = "git+https://github.com/CosmWasm/cosmwasm#1ce73c6b91466fc46e8b574e381cdaa1fb0b298e" dependencies = [ - "base64", - "bech32", - "bnum", - "cosmwasm-crypto", - "cosmwasm-derive", - "derivative", - "forward_ref", - "hex", - "schemars", - "serde", - "serde-json-wasm", - "sha2 0.10.8", - "static_assertions", - "thiserror", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -157,11 +262,17 @@ dependencies = [ "libc", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-bigint" -version = "0.5.5" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -192,52 +303,11 @@ dependencies = [ "zeroize", ] -[[package]] -name = "cw-storage-plus" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5ff29294ee99373e2cd5fd21786a3c0ced99a52fec2ca347d565489c61b723c" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - -[[package]] -name = "cw-utils" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c4a657e5caacc3a0d00ee96ca8618745d050b8f757c709babafb81208d4239c" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw2", - "schemars", - "semver", - "serde", - "thiserror", -] - -[[package]] -name = "cw2" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus", - "schemars", - "semver", - "serde", - "thiserror", -] - [[package]] name = "der" -version = "0.7.8" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" dependencies = [ "const-oid", "zeroize", @@ -270,7 +340,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid", "crypto-common", "subtle", ] @@ -283,16 +352,14 @@ checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" [[package]] name = "ecdsa" -version = "0.16.9" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ "der", - "digest 0.10.7", "elliptic-curve", "rfc6979", "signature", - "spki", ] [[package]] @@ -310,14 +377,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "elliptic-curve" -version = "0.13.8" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ "base16ct", "crypto-bigint", + "der", "digest 0.10.7", "ff", "generic-array", @@ -331,22 +405,20 @@ dependencies = [ [[package]] name = "ethereum-verify" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81ade645ce0bdfea36d04ba4642f8300c1387c650119732223fd2083c4aa359d" +version = "0.1.0" dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", + "cosmwasm-schema 1.5.0", "hex", + "secret-cosmwasm-std", "sha2 0.10.8", "sha3", ] [[package]] name = "ff" -version = "0.13.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ "rand_core 0.6.4", "subtle", @@ -358,6 +430,101 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.42", +] + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -366,7 +533,6 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] @@ -377,14 +543,14 @@ checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "group" -version = "0.13.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ "ff", "rand_core 0.6.4", @@ -401,22 +567,10 @@ dependencies = [ ] [[package]] -name = "headstash-contract" -version = "0.3.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus", - "cw-utils", - "cw2", - "ethereum-verify", - "hex", - "schemars", - "serde", - "serde_json", - "sha2 0.9.9", - "thiserror", -] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hex" @@ -433,24 +587,31 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "k256" -version = "0.13.2" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "once_cell", "sha2 0.10.8", - "signature", ] [[package]] @@ -464,110 +625,388 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.150" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" - -[[package]] -name = "once_cell" -version = "1.18.0" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] -name = "opaque-debug" -version = "0.3.0" +name = "memchr" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +name = "mock_adapter" +version = "0.1.0" dependencies = [ - "der", - "spki", + "cosmwasm-schema 2.0.0-beta.0", + "schemars 0.8.16", + "serde", + "shade-multi-test", + "shade-protocol", ] [[package]] -name = "proc-macro2" -version = "1.0.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +name = "mock_sienna_pair" +version = "0.1.0" dependencies = [ - "unicode-ident", + "cosmwasm-schema 1.5.0", + "shade-protocol", ] [[package]] -name = "quote" -version = "1.0.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +name = "mock_stkd_derivative" +version = "0.1.0" dependencies = [ - "proc-macro2", + "cosmwasm-schema 1.5.0", + "mock_sienna_pair", + "shade-multi-test", + "shade-protocol", ] [[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +name = "multi-derive" +version = "0.1.0" [[package]] -name = "rand_core" -version = "0.6.4" +name = "nanoid" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" dependencies = [ - "getrandom", + "rand", ] [[package]] -name = "rfc6979" -version = "0.4.0" +name = "num-integer" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ - "hmac", - "subtle", + "autocfg", + "num-traits", ] [[package]] -name = "ryu" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" - -[[package]] -name = "schemars" -version = "0.8.16" +name = "num-traits" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", + "autocfg", ] [[package]] -name = "schemars_derive" -version = "0.8.16" +name = "once_cell" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 1.0.109", -] +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] -name = "sec1" -version = "0.7.3" +name = "opaque-debug" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "query-authentication" +version = "0.1.0" +source = "git+https://github.com/securesecrets/query-authentication?branch=cosmwasm_v1_upgrade#473dae556a10a811a4a55563813df9107b18781a" +dependencies = [ + "bech32 0.8.1", + "cosmwasm-schema 1.5.0", + "remain", + "ripemd160", + "schemars 0.8.16", + "secp256k1 0.20.3", + "secret-cosmwasm-std", + "serde", + "sha2 0.9.9", + "thiserror", +] + +[[package]] +name = "query_auth" +version = "0.1.0" +dependencies = [ + "schemars 0.7.6", + "secret-cosmwasm-std", + "shade-multi-test", + "shade-protocol", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "remain" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bce3a7139d2ee67d07538ee5dba997364fbc243e7e7143e96eb830c74bfaa082" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.42", +] + +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint", + "hmac", + "zeroize", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "ripemd160" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "rs_merkle" +version = "1.1.0" +source = "git+https://github.com/FloppyDisck/rs-merkle?branch=node_export#b35c0aa203fdd7ba6c963ba84efb46906792c660" +dependencies = [ + "sha2 0.9.9", +] + +[[package]] +name = "rstest" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c9dc66cc29792b663ffb5269be669f1613664e69ad56441fdb895c2347b930" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5015e68a0685a95ade3eee617ff7101ab6a3fc689203101ca16ebc16f2b89c66" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "schemars" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be77ed66abed6954aabf6a3e31a84706bedbf93750d267e92ef4a6d90bbd6a61" +dependencies = [ + "schemars_derive 0.7.6", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +dependencies = [ + "dyn-clone", + "schemars_derive 0.8.16", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11af7a475c9ee266cfaa9e303a47c830ebe072bf3101ab907a7b7b9d816fa01d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals 0.25.0", + "syn 1.0.109", +] + +[[package]] +name = "schemars_derive" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals 0.26.0", + "syn 1.0.109", +] + +[[package]] +name = "scrt_staking" +version = "0.1.0" +dependencies = [ + "secret-cosmwasm-std", + "shade-multi-test", + "shade-protocol", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ "base16ct", "der", @@ -577,6 +1016,255 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" +dependencies = [ + "secp256k1-sys 0.4.2", +] + +[[package]] +name = "secp256k1" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +dependencies = [ + "secp256k1-sys 0.8.1", +] + +[[package]] +name = "secp256k1-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +dependencies = [ + "cc", +] + +[[package]] +name = "secret-cosmwasm-crypto" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8535d61c88d0a6c222df2cebb69859d8e9ba419a299a1bc84c904b0d9c00c7b2" +dependencies = [ + "digest 0.10.7", + "ed25519-zebra", + "k256", + "rand_core 0.6.4", + "thiserror", +] + +[[package]] +name = "secret-cosmwasm-std" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e4393b01aa6587007161a6bb193859deaa8165ab06c8a35f253d329ff99e4d" +dependencies = [ + "base64 0.13.1", + "cosmwasm-derive", + "derivative", + "forward_ref", + "hex", + "schemars 0.8.16", + "secret-cosmwasm-crypto", + "serde", + "serde-json-wasm", + "thiserror", + "uint", +] + +[[package]] +name = "secret-cosmwasm-storage" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb43da2cb72a53b16ea1555bca794fb828b48ab24ebeb45f8e26f1881c45a783" +dependencies = [ + "secret-cosmwasm-std", + "serde", +] + +[[package]] +name = "secret-multi-test" +version = "0.13.4" +source = "git+https://github.com/securesecrets/secret-plus-utils#96438a5bf7f1fb0acc540fe3a43e934f01e6711f" +dependencies = [ + "anyhow", + "derivative", + "itertools", + "nanoid", + "prost", + "schemars 0.8.16", + "secret-cosmwasm-std", + "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils)", + "secret-utils", + "serde", + "thiserror", +] + +[[package]] +name = "secret-storage-plus" +version = "0.13.4" +source = "git+https://github.com/securesecrets/secret-plus-utils?tag=v0.1.1#96438a5bf7f1fb0acc540fe3a43e934f01e6711f" +dependencies = [ + "bincode2", + "schemars 0.8.16", + "secret-cosmwasm-std", + "serde", +] + +[[package]] +name = "secret-storage-plus" +version = "0.13.4" +source = "git+https://github.com/securesecrets/secret-plus-utils#96438a5bf7f1fb0acc540fe3a43e934f01e6711f" +dependencies = [ + "bincode2", + "schemars 0.8.16", + "secret-cosmwasm-std", + "serde", +] + +[[package]] +name = "secret-toolkit" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338c972c0a98de51ccbb859312eb7672bc64b9050b086f058748ba26a509edbb" +dependencies = [ + "secret-toolkit-crypto", + "secret-toolkit-permit", + "secret-toolkit-serialization", + "secret-toolkit-snip20", + "secret-toolkit-snip721", + "secret-toolkit-storage", + "secret-toolkit-utils", + "secret-toolkit-viewing-key", +] + +[[package]] +name = "secret-toolkit-crypto" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003d7d5772c67f2240b7f298f96eb73a8a501916fe18c1d730ebfd591bf7e519" +dependencies = [ + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "secp256k1 0.27.0", + "secret-cosmwasm-std", + "sha2 0.10.8", +] + +[[package]] +name = "secret-toolkit-permit" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4330571400b5959450fa37040609e6804a147d83f606783506bc2275f1527712" +dependencies = [ + "bech32 0.9.1", + "remain", + "ripemd", + "schemars 0.8.16", + "secret-cosmwasm-std", + "secret-toolkit-crypto", + "serde", +] + +[[package]] +name = "secret-toolkit-serialization" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "890adaeaa710f9f7068a807eb1553edc8c30ce9907290895c9097dd642fc613b" +dependencies = [ + "bincode2", + "schemars 0.8.16", + "secret-cosmwasm-std", + "serde", +] + +[[package]] +name = "secret-toolkit-snip20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8144a11df9a75adf42acd645f938eb2c1ab508d6672033eb84b1e672247d2f05" +dependencies = [ + "schemars 0.8.16", + "secret-cosmwasm-std", + "secret-toolkit-utils", + "serde", +] + +[[package]] +name = "secret-toolkit-snip721" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2ab35fd2a52306f87ab8dceab75254cc1b87c95c43acf9e30c09372f0ee971" +dependencies = [ + "schemars 0.8.16", + "secret-cosmwasm-std", + "secret-toolkit-utils", + "serde", +] + +[[package]] +name = "secret-toolkit-storage" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55e8c5418af3e7ae1d1331c383b32d56c74a340dbc3b972d53555a768698f2a3" +dependencies = [ + "secret-cosmwasm-std", + "secret-cosmwasm-storage", + "secret-toolkit-serialization", + "serde", +] + +[[package]] +name = "secret-toolkit-utils" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83f1cba2e70fd701e3dfc6072807c02eeeb9776bee49e346a9c7745d84ff40c8" +dependencies = [ + "schemars 0.8.16", + "secret-cosmwasm-std", + "secret-cosmwasm-storage", + "serde", +] + +[[package]] +name = "secret-toolkit-viewing-key" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d89a0b69fa9b12735a612fa30e6e7e48130943982f1783b7ddd5c46ed09e921" +dependencies = [ + "base64 0.21.5", + "schemars 0.8.16", + "secret-cosmwasm-std", + "secret-cosmwasm-storage", + "secret-toolkit-crypto", + "secret-toolkit-utils", + "serde", + "subtle", +] + +[[package]] +name = "secret-utils" +version = "0.13.4" +source = "git+https://github.com/securesecrets/secret-plus-utils#96438a5bf7f1fb0acc540fe3a43e934f01e6711f" +dependencies = [ + "schemars 0.8.16", + "secret-cosmwasm-std", + "serde", + "thiserror", +] + [[package]] name = "semver" version = "1.0.20" @@ -594,9 +1282,9 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.5.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" +checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" dependencies = [ "serde", ] @@ -609,7 +1297,18 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", +] + +[[package]] +name = "serde_derive_internals" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -668,21 +1367,113 @@ dependencies = [ "keccak", ] +[[package]] +name = "shade-multi-test" +version = "0.1.0" +dependencies = [ + "admin", + "airdrop", + "basic_staking", + "mock_adapter", + "mock_sienna_pair", + "mock_stkd_derivative", + "multi-derive", + "query_auth", + "scrt_staking", + "shade-protocol", + "snip20", + "snip20_migration", + "stkd_scrt", + "treasury", + "treasury_manager", +] + +[[package]] +name = "shade-protocol" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64 0.12.3", + "chrono", + "const_format", + "contract-derive", + "cosmwasm-schema 1.5.0", + "query-authentication", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "remain", + "schemars 0.8.16", + "secret-cosmwasm-std", + "secret-cosmwasm-storage", + "secret-multi-test", + "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils?tag=v0.1.1)", + "serde", + "sha2 0.9.9", + "strum", + "strum_macros", + "subtle", + "thiserror", +] + [[package]] name = "signature" -version = "2.2.0" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ "digest 0.10.7", "rand_core 0.6.4", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "snip20" +version = "0.1.0" +dependencies = [ + "secret-cosmwasm-std", + "shade-multi-test", + "shade-protocol", +] + +[[package]] +name = "snip20_derivative" +version = "1.0.0" +dependencies = [ + "base64 0.13.1", + "cosmwasm-schema 1.5.0", + "schemars 0.8.16", + "secret-cosmwasm-std", + "secret-cosmwasm-storage", + "secret-toolkit", + "secret-toolkit-crypto", + "serde", + "shade-protocol", +] + +[[package]] +name = "snip20_migration" +version = "0.1.0" +dependencies = [ + "rstest", + "schemars 0.7.6", + "serde_json", + "shade-multi-test", + "shade-protocol", +] + [[package]] name = "spki" -version = "0.7.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", "der", @@ -694,6 +1485,33 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stkd_scrt" +version = "0.1.0" +dependencies = [ + "shade-multi-test", + "shade-protocol", +] + +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + [[package]] name = "subtle" version = "2.5.0" @@ -713,9 +1531,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8" dependencies = [ "proc-macro2", "quote", @@ -724,22 +1542,57 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "treasury" +version = "0.1.0" +dependencies = [ + "mock_adapter", + "serde_json", + "shade-multi-test", + "shade-protocol", + "treasury", +] + +[[package]] +name = "treasury_manager" +version = "0.1.0" +dependencies = [ + "chrono", + "cosmwasm-schema 2.0.0-beta.0", + "itertools", + "mock_adapter", + "schemars 0.7.6", + "shade-multi-test", + "shade-protocol", ] [[package]] @@ -748,24 +1601,70 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "zeroize" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index f10eeb5..5bbfb00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,51 +1,59 @@ [workspace] -members = [ - "contracts/headstash-contract", -] resolver = "2" +members = [ + # Packages + "packages/shade_protocol", + # "packages/secretcli", + "packages/multi_test", + "packages/multi_derive", + "packages/contract_derive", + "packages/ethereum_verify", -[workspace.package] -version = "0.3.0" -edition = "2021" -homepage = "https://terp.network" -repository = "https://github.com/terpnetwork/headstash-patch" -license = "Apache-2.0" - -[workspace.dependencies] -cosmwasm-schema = "1.3.1" -cosmwasm-std = { version = "1.5.0", default-features = false, features = ["cosmwasm_1_3"] } -cw-controllers = "1.1.1" -cw2 = "1.1.1" -cw4 = "1.1.1" -cw4-group = "1.1.1" -cw721 = "0.18.0" -cw721-base = "0.18.0" -cw-multi-test = "0.16.2" -cw-storage-plus = "1.1.0" -cw-utils = "1.0.1" -schemars = "0.8.11" -serde = { version = "1.0.145", default-features = false, features = ["derive"] } -thiserror = "1.0.31" -url = "2.2.2" -sha2 = { version = "0.10.2", default-features = false } -ethereum-verify = "3.3.0" -headstash-contract = { version = "0.3.0", path = "contracts/headstash-contract" } -semver = "1" -cw-ownable = "0.5.1" - -[profile.release.package.headstash-contract] -codegen-units = 1 -incremental = false + # Network setups + "contracts/airdrop", -[profile.release.package.cw-goop] -codegen-units = 1 -incremental = false + # Protocol contracts + "contracts/snip20", + "contracts/query_auth", + "contracts/admin", + "contracts/basic_staking", + "contracts/snip20_migration", + + # Staking + "contracts/basic_staking", + "contracts/snip20_derivative", + # DAO + # - Core + "contracts/dao/treasury", + "contracts/dao/treasury_manager", + # - Adapters + "contracts/dao/scrt_staking", + "contracts/dao/stkd_scrt", + # "contracts/dao/rewards_emission", + # "contracts/dao/lp_shdswap", + + # Mock contracts + # "contracts/mock/mock_band", //TODO: migrate to v1 + # "contracts/mock/mock_secretswap_pair", //TODO: migrate to v1 + "contracts/mock/mock_sienna_pair", + # "contracts/mock/mock_adapter", //TODO: migrate to v1 + "contracts/mock/mock_stkd_derivative", + + # Tools + # "tools/doc2book", + # "launch" +] + +exclude = ["packages/network_integration"] [profile.release] -rpath = false -lto = true -overflow-checks = true -opt-level = 3 -debug = false +opt-level = 3 +debug = false +rpath = false +lto = true debug-assertions = false +codegen-units = 1 +panic = 'abort' +incremental = false +overflow-checks = true diff --git a/LICENSE b/LICENSE index edc7db4..153d416 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,165 @@ - 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 2022 Public Awesome LLC - - 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. + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 0000000..ed806ac --- /dev/null +++ b/Makefile.toml @@ -0,0 +1,140 @@ +[env] +CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true +BUILD_TARGET = "./target/wasm32-unknown-unknown/release/" +COMPILED_DIR = "compiled" +CHECKSUM_DIR = "${COMPILED_DIR}/checksum" +CURRENT_CRATE = "NONE" +# TODO: figure out how to only exclude when on a specific task +CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = "packages/*" +DOCKER_IMAGE_VERSION = "v0.2" + +[tasks.set_flags] +workspace = false +private = true +script = ''' +RUSTFLAGS='-C link-arg=-s' +''' + +# Build for testnet + +[tasks.release] +workspace = false +run_task = [{ name = ["build_with_args", "compress_with_args"] }] +dependencies = ["set_flags"] + +[tasks.build_with_args] +private = true +workspace = false +command = "cargo" +args = ["build", "--release", "--package", "${@}", "--target", "wasm32-unknown-unknown"] + +[tasks.compress_with_args] +private = true +workspace = false +script = ''' +wasm-opt -Oz ${BUILD_TARGET}${@}.wasm -o ./${@}.wasm +echo $(md5sum ${@}.wasm | cut -f 1 -d " ") >> ${CHECKSUM_DIR}/${@}.txt +cat ./${@}.wasm | gzip -n -9 > ${COMPILED_DIR}/${@}.wasm.gz +rm ./${@}.wasm +''' + +[tasks.build-all] +# TODO: cargo skip workspace not working here +env = { "CURRENT_CRATE" = "${CARGO_MAKE_CRATE_NAME}", "CARGO_MAKE_WORKSPACE_SKIP_MEMBERS" = "packages/*" } +run_task = [{ name = ["build", "compress"] }] +dependencies = ["set_flags"] + +[tasks.build] +private = true +workspace = false +command = "cargo" +args = ["build", "--release", "--package", "${CURRENT_CRATE}", "--target", "wasm32-unknown-unknown"] + +[tasks.compress] +private = true +workspace = false +script = ''' +wasm-opt -Oz ${BUILD_TARGET}${CURRENT_CRATE}.wasm -o ./${CURRENT_CRATE}.wasm +echo $(md5sum ${CURRENT_CRATE}.wasm | cut -f 1 -d " ") >> ${CHECKSUM_DIR}/${CURRENT_CRATE}.txt +cat ./${CURRENT_CRATE}.wasm | gzip -n -9 > ${COMPILED_DIR}/${CURRENT_CRATE}.wasm.gz +rm ./${CURRENT_CRATE}.wasm +''' + +[tasks.schemas] +workspace = false +script = ''' +cargo run --bin schemas --features="snip20_migration admin airdrop bonds dao dex governance-impl mint oracles peg_stability query_auth sky snip20 staking mint_router" +''' + +# Testing + +[tasks.test] +workspace = false +command = "cargo" +args = ["test", "--package", "${@}"] + +[tasks.test-all] +workspace = false +command = "cargo" +args = ["test"] + +# Cleanup + +[tasks.clean] +workspace = false +dependencies = ["remove_compile_dir", "create_compile_dir", "create_checksum_dir"] + +[tasks.remove_compile_dir] +private = true +workspace = false +command = "rm" +args = ["-r", "${COMPILED_DIR}", "-f"] + +[tasks.create_compile_dir] +private = true +workspace = false +command = "mkdir" +args = ["${COMPILED_DIR}"] + +[tasks.create_checksum_dir] +private = true +workspace = false +command = "mkdir" +args = ["${CHECKSUM_DIR}"] + +# Docker support - can be run with `cargo make server start|connect|download +[tasks.server] +private = false +workspace = false +extend = "subcommand" +env = { "SUBCOMMAND_PREFIX" = "server" } + +[tasks.subcommand] +private = true +workspace = false +script = ''' +#!@duckscript + +cm_run_task ${SUBCOMMAND_PREFIX}-${1} +''' + + +[tasks.server-download] +workspace = false +script = ''' +docker pull securesecrets/sn-testnet:${DOCKER_IMAGE_VERSION} +''' + +[tasks.server-start] +workspace = false +script = ''' +docker run -it --rm \ + -p 26657:26657 -p 26656:26656 -p 1337:1337 \ + -v $$(pwd):/root/code --name shade-testnet securesecrets/sn-testnet:${DOCKER_IMAGE_VERSION} +''' + +[tasks.server-connect] +workspace = false +script = ''' +docker exec -it shade-testnet /bin/bash +''' diff --git a/README.md b/README.md index a68faae..856f068 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -# Headstash Patch Contracts +# Private Headstash Distribution Claim - -## [headstash-contract](./contracts/headstash-contract) -A fork of the [cw20-merkle-airdrop](https://github.com/CosmWasm/cw-plus/tree/0.9.x/contracts/cw20-merkle-airdrop), but also verifies a given signed hash is derived from a given eth_pubkey. \ No newline at end of file +This branch is a fork of the [Shade Protocol core contracts](https://github.com/securesecrets/shade). Our use case focuses only on a custmized version of the [Airdrop Contract](./contracts/airdrop/README.md), suited to our needs. Massive respect to the shade contributors. \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..257e7ae --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,39 @@ +# Security Policy + +## Supported Versions + +The main tagged versions of the app should be the only ones deployed on the mainnet network. + +| Version | Supported | +| ------- | ------------------ | +| 1.x.x | :white_check_mark: | +| 0.x.x | :x: | + +## About Reporting a Vulnerability + +According to the [Github coordinated disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/about-coordinated-disclosure-of-security-vulnerabilities#about-disclosing-vulnerabilities-in-the-industry) + +> Vulnerability disclosure is an area where collaboration between vulnerability reporters, such as security researchers, and project maintainers is very important. Both parties need to work together from the moment a potentially harmful security vulnerability is found, right until a vulnerability is disclosed to the world, ideally with a patch available. Typically, when someone lets a maintainer know privately about a security vulnerability, the maintainer develops a fix, validates it, and notifies the users of the project or package. + +> The initial report of a vulnerability is made privately, and the full details are only published once the maintainer has acknowledged the issue, and ideally made remediations or a patch available, sometimes with a delay to allow more time for the patches to be installed. For more information, see the ["OWASP Cheat Sheet Series about vulnerability disclosure"](https://cheatsheetseries.owasp.org/cheatsheets/Vulnerability_Disclosure_Cheat_Sheet.html#commercial-and-open-source-software) on the OWASP Cheat Sheet Series website. + +## Best Practices According to Github + +According to the [Github coordinated disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/about-coordinated-disclosure-of-security-vulnerabilities#best-practices-for-maintainers) + +> It's good practice to report vulnerabilities privately to maintainers. When possible, as a vulnerability reporter, we recommend you avoid: + +- Disclosing the vulnerability publicly without giving maintainers a chance to remediate. +- Bypassing the maintainers. +- Disclosing the vulnerability before a fixed version of the code is available. +- Expecting to be compensated for reporting an issue, where no public bounty program exists. + +It's acceptable for vulnerability reporters to disclose a vulnerability publicly after a period of time, if they have tried to contact the maintainers and not received a response, or contacted them and been asked to wait too long to disclose it. + +## Process for Shade Protocol + +Although Github security reports are available for the main Shade Repository, we will follow custom reporting procedure so that the reports get submitted diretly to the relevant teams in the Shade organization. This will allow us to have a more streamlined process for handling reports across all the different repositories. + +Most of the reports will be handled by the [Secure Secrets Security Team](security@securesecrets.org) and the reports have to be submitted here: [Official Vulnerability Disclosure Portal](https://securesecrets.atlassian.net/servicedesk/customer/portal/3/group/11/create/37). When a security incident is reported, the user reporting accepts the terms and conditions of the Bounty Program, and is automtically enrolled into the Bounty Program detailed in the [Shade Protocol Responsible Disclosure](./Shade_Protocol_Resposible_Disclosure.md). The user can request to be not part of the Bounty Program by sending an email follow up to the initial report, but still needs to follow the process of the Github Coordinated Disclosure and Best Practices detailed by Github above. + + diff --git a/Shade_Protocol_Responsible_Disclosure.md b/Shade_Protocol_Responsible_Disclosure.md new file mode 100644 index 0000000..ab7303f --- /dev/null +++ b/Shade_Protocol_Responsible_Disclosure.md @@ -0,0 +1,286 @@ +# Shade Protocol Responsible Disclosure Policy + +Shade Protocol is an interconnected suite of privacy preserving dApps built on the Secret Network whose smart contracts leverage the privacy preserving properties of secret contracts in order to empower private DeFi. The Shade Protocol Responsible Disclosure Framework establishes and defines the following for the Shade Security Policy: +- Vulnerability Severity Classification System (VSCS) +- Rewards By Threat Level +- Assets in Scope +- Out of Scope Work +- Proof of Concept (PoC) guidelines +- Rules of Engagement +- SLA Response Times +- Security Patch Policy and Procedure +- Rewards Payment Process + +For general information about reporting vulnerabilities, visit [security policy overview](./SECURITY.md). The Shade Protocol Responsible Disclosure Framework is subject to change at the discretion of the Shade Protocol core team. + + +## Bug Bounty Program Classes +- **Smart Contracts** +- **Website and Applications** +- **Infrastructure** + +## Vulnerability Severity Classification System + +### Smart Contracts + +#### **Critical** + +- Direct theft of any user funds +- Permanent Freezing of funds +- Protocol Insolvency +- Governance Voting Result Manipulation + +#### **High** + +- Theft of unclaimed yield +- Permanent freezing of unclaimed yield +- Temporary freezing of funds + +#### **Medium** +- Smart contract unable to operate due to lack of token funds +- Theft of gas +- Unbounded gas consumption +- Griefing (damage to users or protocol with no profit motive) + +#### **Low** +Contract fails to deliver promised returns, but doesn’t lose value + + +### Website and Applications + +#### **Critical** +- Taking down the website/application +- Direct theft of any user funds +- Execute arbitrary system commands +- Retrieve sensitive data from a server +- Taking state-modifying authenticated actions on behalf of other users with any interaction by that users. +- Subdomain takeover with already connected wallet interaction +- Malicious interactions with an already-connected wallet + +#### **High** +- Improperly disclosing confidential user information +- Changing sensitive details of other users (including modifying browser local storage) without already-connected wallet interaction. +- Injecting/modifying the static content on the target application without Javascript (Persistent) such as: + - HTML injection without Javascript + - Replacing existing text with arbitrary text + - Arbitrary file uploads, etc. + +#### **Medium** +- Injecting/modifying the static content on the target application without Javascript (Reflected) such as: + - Reflected HTML injection + - Loading external site data +- Redirecting users to malicious websites (Open Redirect) + +#### **Low** +- Taking over broken or expired outgoing links +- Disabling access to site for users +- Preventing connection to wallet +- Cookie bombing + + +### Infrastructure +#### **Critical** + - Access to any central keys address controlled by the project (e.g. private keys, seed phrases, etc.) + - Access to Central Database + +#### **High** + TBD + +#### **Medium** + TBD + +#### **Low** + TBD + +## Rewards by Threat Level + +### Smart Contract +| Vulnerability | Reward | Requirements | +| ------------ | ----------------- | --------------------- | +| Critical | ***Payout: Up to 40k SHD*** | PoC Requirement | +| High | Payout: Up to 5k SHD | PoC Requirement | +| Medium | Payout: Up to 1K SHD | PoC Requirement | +| Low | Payout: Up to 100 SHD | PoC Requirement | + + +### Website and applications + +| Vulnerability | Reward | Requirements | +| ------------ | ----------------- | --------------------- | +| Critical | ***Payout: Up to 10k SHD*** | PoC Requirement | +| High | Payout: Up to 1k SHD | PoC Requirement | +| Medium | Payout: Up to 100 SHD | PoC Requirement | +| Low | Payout: Up to 10 SHD | PoC Requirement | + +### Infrastructure + +| Vulnerability | Reward | Requirements | +| ------------ | ----------------- | --------------------- | +| Critical | ***Payout: Up to 10k SHD*** | PoC Requirement | +| High | Payout: Up to 1k SHD | PoC Requirement | +| Medium | Payout: Up to 100 SHD | PoC Requirement | +| Low | Payout: Up to 10 SHD | PoC Requirement | + + +Critical smart contract vulnerabilities are capped at 10% of economic damage, primarily taking into consideration the funds at risk. In cases of repeatable attacks, only the first attack is considered unless the smart contract cannot be upgraded or paused. High smart contract vulnerabilities will be capped at up to 100% of the funds affected. + +Critical website and application bug reports will be rewarded with 10k SHD only if the impact leads to a direct loss in funds or a manipulation of the votes or the voting result, as well as the modification of its display leading to a misrepresentation of the result or vote. All other impacts that would be classified as Critical would be rewarded 1K SHD. + +All calculations of the amount of funds at risk are done based on the time the bug report is submitted. + +### Response Times: + +| Action | Severity Level | Response Time | +| ------------ | ----------------- | --------------------- | +| Acknowledgment of report | Critical | 48 hours | +| | All severity levels except critical | 3-4 days (working days) | +| Processing of Report | Critical + High | Up to 14 days | +| | Medium + Low | Up to 14 days | +| Payout for valid reports | Critical + High | Within 14 days | +| | Medium + Low | Within 14 days | + + + +## Assets considered in scope: + +[Shade Protocol Audit Log](https://docs.shadeprotocol.io/shade-protocol/research/audit-log) + +### **Smart Contracts:** + +#### Shade Oracle - https://github.com/securesecrets/shade-oracle + +- https://github.com/securesecrets/shade-oracle/tree/release/contracts/oracle_router +- https://github.com/securesecrets/shade-oracle/tree/release/contracts/index_oracle +- https://github.com/securesecrets/shade-oracle/tree/release/contracts/shade_staking_derivatives_oracle +- https://github.com/securesecrets/shade-oracle/tree/release/contracts/shadeswap_market_oracle +- https://github.com/securesecrets/shade-oracle/tree/release/contracts/shadeswap_spot_oracle +- https://github.com/securesecrets/shade-oracle/tree/release/contracts/stride_staking_derivatives_oracle +- https://github.com/securesecrets/shade-oracle/tree/release/contracts/siennaswap_market_oracle + + + +#### ShadeSwap - https://github.com/securesecrets/shadeswap + +- https://github.com/securesecrets/shadeswap/tree/main/contracts/amm_pair +- https://github.com/securesecrets/shadeswap/tree/main/contracts/factory +- https://github.com/securesecrets/shadeswap/tree/main/contracts/lp_token +- https://github.com/securesecrets/shadeswap/tree/main/contracts/router +- https://github.com/securesecrets/shadeswap/tree/main/contracts/snip20 +- https://github.com/securesecrets/shadeswap/tree/main/contracts/staking + +#### Shade Protocol - https://github.com/securesecrets/shade + +- https://github.com/securesecrets/shade/blob/main/contracts/governance +- https://github.com/securesecrets/shade/blob/main/contracts/staking +- https://github.com/securesecrets/shade/blob/main/contracts/scrt_staking +- https://github.com/securesecrets/shade/blob/main/contracts/treasury +- https://github.com/securesecrets/shade/blob/main/contracts/mint +- https://github.com/securesecrets/shade/blob/main/contracts/oracle +- https://github.com/securesecrets/shade/blob/main/contracts/airdrop + +*In order to be eligible for a reward, the vulnerability must exist in both the deployed contract and its respective Github repository.* + +#### Website and Applications +- https://shadeprotocol.io +- https://app.shadeprotocol.io + + +## Out of Scope Work + +### Smart Contracts: + +- Basic Governance attacks +- Sybil attacks +- Centralization risks +- Disruption of price feeds from third party oracles (does not include oracle manipulation/flash loan attacks) + +### Website and Applications +- CSRF with no security impact +- Theoretical vulnerabilities without any proof or demonstration +- Missing HTTP Security Headers or cookie security flags +- Server-side information disclosure such as IPs, server names, and most stack traces +- URL Redirects (unless combined with another vulnerability to produce a more severe vulnerability) +- DDoS vulnerabilities +- Feature requests +- Best practices +- DNS Sabotage +- Self-XSS +- Captcha bypass using OCR +- Vulnerabilities used to enumerate or confirm the existence of users or tenants +- Vulnerabilities requiring unlikely user actions +- Lack of SSL/TLS best practices +- Attacks requiring privileged access from within the organization +- Vulnerabilities primarily caused by browser/plugin defects +- Any vulnerability exploit requiring CSP bypass resulting from a browser bug + + +### The following vulnerabilities are excluded from the rewards for this bug bounty program: + +- Previously-discovered bugs +- Attacks that the reporter has already exploited themselves, leading to damage +- Attacks requiring access to leaked keys/credentials +- Attacks requiring access to privileged addresses (governance, strategist) +- Attacks leveraging other DeFi protocols, unless the following are true: + - Losses or negative effects of the attack impact Shade Protocol ecosystem participants including SHD and SILK token holders + - Additional DeFi protocols used exist as smart contracts on the Secret Network mainnet and can reasonably be expected to have enough liquidity in various assets to allow the attack to succeed. + +### Proof of Concept (PoC) Guidelines: +- The smart contract PoC should always be made by forking the mainnet +- The PoC should contain runnable code for the exploit demonstration +- The PoC should mention all the dependencies, configuration files, and environmental variables that are required in order to run that PoC, as any other requirements to run the test. + - Add clear and descriptive replication steps so that the Shade Protocol Team can easily reproduce and validate your findings. +- PoCs should have clear print statements and or comments that detail each step of the attack and display relevant information, such as funds stolen/frozen etc. +- The PoC should ideally determine and provide data on the amount of funds at risk, which can be determined by calculating the total amount of tokens multiplied by the average price of the token at the time of the submission. +- The PoC must comply with any additional guidelines specified by the bug bounty program the whitehat is submitting a bug report to. + +## Rules of Engagement +*Violation of these rules can result in a temporary suspension or permanent ban from the Shade Protocol Bug Bounty Program, which may result in the forfeiture of potential payout and loss of access to all bug submissions. Shade Protocol has a zero tolerance policy for spam/incomplete bug reports and misrepresentation of bug severity.* + +The Shade Protocol team will take all reasonable actions to ensure the successful execution of and the maximum effectiveness of the Shade Protocol Bug Bounty Program. + +### Standard Program Rules: +- Unless otherwise noted, users should create accounts for testing purposes. +- Submissions must be made exclusively through the [Official Vulnerability Disclosure Portal](https://securesecrets.atlassian.net/servicedesk/customer/portal/3/group/11/create/37) to be considered for a reward. +- Communication regarding submissions must remain within Shade Protocol Bug Bounty support channels for the duration of the disclosure process. +- Users must submit a Proof of Concept (PoC) in order to receive bounties for bug reports. + - Example PoC can be found in the [Shade Security Advisories](https://github.com/securesecrets/shade/security/advisories). + +### Prohibited Behaviors: +- Any testing with mainnet contracts. Testing on mainnet is grounds for an immediate and permanent ban +- Misrepresenting assets in scope: claiming that a bug report impacts/targets an asset in scope when it does not +- Misrepresenting severity: claiming that a bug report is critical when it clearly is not +- Automated testing of services that generates significant amounts of traffic +- Attempting phishing or other social engineering attacks against Shade Protocol or its team members. +- Harassment, i.e., excessive, abusive, or bad faith communication +- Disputing a bug report in the dashboard once it has been paid or marked as closed +- Impersonation +- Threatening to publish or publishing personal information without consent +- Reporting a bug that has already been publicly disclosed +- Publicly disclosing a bug report--or even the existence of a bug report for a specific project--before it has been fixed and paid +- Publicly disclosing a bug report before 30 days have elapsed since the project closed the report as being out of scope or not requiring a fix +- Publicly disclosing a bug report deemed to be a duplicate or well-known to the project +- Submitting spam/very low-quality bug reports and submitting information through our platform that is not a bug report +- Submitting AI-generated/automated scanner bug reports +- Submitting fixes to a project's repository without their express consent +- Unauthorized disclosure or access of sensitive information beyond what is necessary to submit the report. + +## Security Patch Policy and Procedure + +All valid security bugs will be handled according to the following guidelines and will trigger an internal incident response process. We will keep you updated and work with you through the process. + +Even if you are not eligible for the bounty program you are recommend to follow all security patch procedures as highlighted in the [security policy overview](./SECURITY.md). + +Security patches are very sensitive by nature, and their exposure can provide a window of opportunity for a malicious actor to attack the protocol or any of its applications before it has been patched. As a result, Shade Protocol has adopted the following policies: + +- Due to the sensitive nature of security patches, the Shade Protocol team will **not** make the content of security patches public until the entire system has been patched. +- Users submitting bug reports are expected **not** to publicly disclose a bug report--or even the existence of a bug report for a specific project--before it has been fixed. +- Users submitting bug reports are expected **not** to publicly disclose a bug report deemed to be a duplicate or well-known to the Shade Protocol team. +- The details of the patch will be revealed after a holdover period of 10 days from the time after the successful application of patches to relevant systems. + - The holdover period provides reasonable time to ensure the stability of the contract or application before patch is revealed. + - The patch activity is considered successful if the applied patch completely mitigated the vulnerability as intended and the system remained stable. + +## Rewards Payment Process + +Users must have access to a Secret Network wallet address to receive any earned Bug Bounty rewards. Once a Bug Bounty report has been submitted, received, and verified, the Shade Protocol team will reconfirm the severity, reward payout amount, and wallet address to send bug bounty with the user who submitted the bug report. + +Once the bug report has been fully processed and the patch resulting from the Bug Bounty report has been successfully applied, the bug report will be considered “Closed”, at which time users will be notified via their provided email that they may begin the KYC process for the wallet receiving funds. diff --git a/archived-contracts/bonds/.cargo/config b/archived-contracts/bonds/.cargo/config new file mode 100644 index 0000000..c1e7c50 --- /dev/null +++ b/archived-contracts/bonds/.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" \ No newline at end of file diff --git a/archived-contracts/bonds/.circleci/config.yml b/archived-contracts/bonds/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/archived-contracts/bonds/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/bonds/Cargo.toml b/archived-contracts/bonds/Cargo.toml new file mode 100644 index 0000000..65b87f4 --- /dev/null +++ b/archived-contracts/bonds/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "bonds" +version = "0.1.0" +authors = [ + "Guy Garcia ", + "Jackson Swenson ", + "Kyle Wahlberg " +] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ + "bonds", + "math", + "query_auth_lib", + "chrono", +] } +schemars = "0.7" +time = "0.1.44" +admin = { git = "https://github.com/securesecrets/shadeadmin.git", tag = "v1.0" } +shade-oracles = { git = "https://github.com/securesecrets/shade-oracle.git", tag = "0.11"} + +[dev-dependencies] +fadroma = { branch = "v100", commit = 76867e0, git = "https://github.com/hackbg/fadroma.git", features= ["ensemble"] } +fadroma-platform-scrt = { branch = "v100", commit = 76867e0, git = "https://github.com/hackbg/fadroma.git" } +contract_harness = { version = "0.1.0", path = "../../packages/contract_harness", features = [ "snip20", "bonds", "oracle", "mock_band", "query_auth", "admin", "shade-oracles-ensemble" ] } +mock_band = { version = "0.1.0", path = "../../contracts/mock_band" } +shade-oracles-ensemble = { git = "https://github.com/securesecrets/shade-oracle.git", tag = "0.11"} + diff --git a/archived-contracts/bonds/Makefile b/archived-contracts/bonds/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/archived-contracts/bonds/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/archived-contracts/bonds/README.md b/archived-contracts/bonds/README.md new file mode 100644 index 0000000..6751ef6 --- /dev/null +++ b/archived-contracts/bonds/README.md @@ -0,0 +1,389 @@ + +# Bonds Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Init](#Init) + * [Admin](#Admin) + * Messages + * [UpdateConfig](#UpdateConfig) + * [OpenBond](#OpenBond) + * [CloseBond](#CloseBond) + * [Limit_Admin](#Limit_Admin) + * Messages + * [UpdateLimitConfig](#UpdateLimitConfig) + * [User](#User) + * Messages + * [Receive](#Receive) + * [Claim](#Claim) + * Queries + * [Config](#Config) + * [BondOpportunities](#BondOpportunities) + * [Account](#Account) + * [DepositAddresses](#DepositAddresses) + * [BondInfo](#BondInfo) + * [PriceCheck](#PriceCheck) + * [CheckAllowance](#CheckAllowance) + * [CheckBalance](#CheckBalance) + +# Introduction +Generic contract responsible for protocol and treasury bond opportunities +# Sections + +## Init +##### Request +| Name | Type | Description | optional | +|-----------------------------------|-----------|----------------------------------------------------------------------------|----------| +| limit_admin | Addr | Limit Assembly/Admin; SHOULD be a valid bech32 address | no | +| global_issuance_limit | Uint128 | Total number of tokens this contract can issue before limit reset | no | +| global_minimum_bonding_period | u64 | Minimum amount of time before any pending bonds can be claimed. | no | +| global_maximum_discount | Uint128 | Maximum allowed discount for any bond opportunities | no | +| admin | Addr | Bonds Assembly/Admin; SHOULD be a valid bech32 address | no | +| oracle | Contract | Oracle contract | no | +| treasury | Addr | Treasury address for allowance and deposit assets | no | +| issued_asset | Contract | Issued asset for this bonds contract | no | +| activated | bool | Turns entering opportunities contract-wide on/off | no | +| bond_issuance_limit | Uint128 | Default issuance limit for new bond opportunities | no | +| bonding_period | u64 | Default time for new opportunity before its pending bonds can be claimed | no | +| discount | Uint128 | Default percent discount on issued asset for new bond opportunities | no | +| global_min_accepted_issued_price | Uint128 | Min price for issued asset. Opps will never issue at lower price than this | no | +| global_err_issued_price | Uint128 | Asset price that will fail transaction due to risk | no | +| allowance_key | String | Entropy for generating snip20 viewing key for issued asset. Arbitrary. | no | +| airdrop | Contract | Airdrop contract for completing bond task and unlocking % of drop | yes | + +## Admin + +### Messages + +#### UpdateConfig +Updates the given values +##### Request +| Name | Type | Description | optional | +|-----------------------------------|-----------|-----------------------------------------------------------------------------------------------|-----------| +| admin | Addr | New contract admin; SHOULD be a valid bech32 address | yes | +| oracle | Contract | Oracle address | yes | +| treasury | Addr | Treasury address | yes | +| issued_asset | Contract | The asset this bond contract will issue to users | yes | +| activated | bool | If true, bond opportunities can be entered into | yes | +| minting_bond | bool | If true, bond is minting issued asset. If false, bond is spending on allowance from treasury | yes | +| bond_issuance_limit | Uint128 | Default issuance limit for any new opportunities | yes | +| bonding_period | Uint128 | Default bonding period in UNIX time for any new opportunities | yes | +| discount | Uint128 | Default discount % for any new opportunities | yes | +| global_min_accepted_issued_price | Uint128 | SMin price for issued asset. Opps will never issue at lower price than this | yes | +| global_err_issued_price | Uint128 | Asset price that will fail transaction due to risk | yes | +| airdrop | Contract | Airdrop contract for completing bond task and unlocking % of drop | yes | + +##### Response +``` json +{ + "update_config": { + "status": "success" + } +} +``` + +#### OpenBond +Opens new bond opportunity for a unique asset + +##### Request +| Name | Type | Description | optional | +|-------------------------------|-----------|---------------------------------------------------|-----------| +| deposit_asset | Contract | Contract for deposit asset | no | +| start_time | u64 | When the opportunity opens in UNIX time | no | +| end_time | u64 | When the opportunity closes in UNIX time | no | +| bond_issuance_limit | Uint128 | Issuance limit for this opportunity | yes | +| bonding_period | u64 | Bonding period for this opportunity in UNIX time | yes | +| discount | Uint128 | Discount % for this opportunity | yes | +| max_accepted_deposit_price | Uint128 | Maximum accepted price for deposit asset | no | +| err_deposit_price | Uint128 | Price for deposit asset that causes error | no | +| minting_bond | bool | True for minting from snip20, false for allowance | no | +##### Response +```json +{ + "open_bond": { + "status": "success", + "deposit_contract": "Contract", + "start_time": "u64 start in UNIX time", + "end_time": "u64 end in UNIX time", + "bond_issuance_limit": "opportunity limit Uint128", + "bonding_period": "u64 bonding period in UNIX time", + "discount": "opportunity discount percentage Uint128", + "max_accepted_deposit_price": "maximum price accepted for deposit asset Uint128", + "err_deposit_price": "error-causing price limit for deposit asset Uint128", + "minting_bond": "bool whether bond opp is a minting bond or not" + } +} +``` + +#### CloseBond +Closes bond opportunity for a given asset + +##### Request +| Name | Type | Description | optional | +|------------------|----------|-------------------------------|-----------| +| deposit_asset | Contract | Contract for deposit asset | no | + +##### Response +```json +{ + "close_bond": { + "status": "success", + "deposit_asset": "contract for asset who's opportunity was just closed" + } +} +``` + +## Limit Admin + +### Messages + +#### UpdateLimitConfig +Update the given limit config values +##### Request +| Name | Type | Description | optional | +|-------------------------------|-----------|-------------------------------------------------------------|-----------| +| limit_admin | Addr | New contract limit admin; SHOULD be a valid bech32 address | yes | +| global_isuance_limit | Uint128 | asset issuance limit, cumulative across all opportunities | yes | +| global_minimum_bonding_period | u64 | minimum bonding time for all opportunities, in UNIX time | yes | +| global_maximum_discount | Uint128 | maximum percent discount for all new opportunities | yes | +| reset_total_issued | bool | if true, resets global_total_issued to 0 | yes | +| reset_total_claimed | bool | if true, resets global_total_claimed to 0 | yes | + +##### Response +```json +{ + "update_limit_config": { + "status": "success" + } +} +``` + +## User + +### Messages + +#### Receive +To mint the user must use a supported asset's send function and send the amount over to the contract's address. The contract will take care of the rest. +In the msg field of a snip20 send command you must send a base64 encoded json like this one +```json +{"minimum_expected_amount": "Uint128" } +``` + +##### Response +```json +{ + "deposit": { + "status": "success", + "deposit_amount": "Deposit amount Uint128", + "pending_claim_amount": "Claim amount Uint128", + "end_date": "u64 end time of bonding period in UNIX time", + } +} +``` + +#### Claim +The user doesn't need to pass any parameters to claim. Claiming redeems all of a user's Pending Bonds. + +##### Response +```json +{ + "claim": { + "status": "success", + "amount": "claim amount Uint128", + } +} +``` + +### Queries + +#### Config +Gets the contract's config + +##### Response +```json +{ + "config": { + "config": "Contract's config" + } +} +``` + +#### BondOpportunities +Get the vector of bond opportunities currently available + +##### Response +```json +{ + "bond_opportunities": { + "bond_opportunities": "List of opportunities Vec", + } +} +``` + +#### DepositAddresses +Get the list of addresses for currently recognized deposit addresses, correlated to the open Bond Opportunities + +##### Response +```json +{ + "deposit_addresses": { + "deposit_addresses": "List of deposit addresses Vec", + } +} +``` + +#### BondInfo +Gets this contracts issuance and claimed totals, as well as the issued asset + +##### Response +```json +{ + "bond_info": { + "global_total_issued": "global total issued Uint128", + "global_total_claimed": "global total claimed Uint128", + "issued_asset": "native/issued asset Snip20Asset", + "global_min_accepted_issued_price": "global minimum accepted price for issued asset Uint128", + "global_err_issued_price": "global error limit price for issued asset Uint128" + } +} +``` + +#### PriceCheck +Gets the price for the passed asset by querying the oracle registered in the config + +##### Response +```json +{ + "price_check": { + "price": "price of passed asset in dollars Uint128", + } +} +``` + +#### CheckAllowance +Views this bond contract's allowance from its current Treasury address + +##### Response +```json +{ + "check_allowance": { + "allowance": "current queried allowance Uint128" + } +} +``` + +#### CheckBalance +Views this bond contract's current balance for its issued asset + +##### Response +```json +{ + "check_balance": { + "check_balance": "current balance Uint128" + } +} +``` + +## Account +User account, stores address + +### Structure +| Name | Type | Description | optional | +|-----------------|-------------------|-----------------------------------------------------------------------|---------- | +| address | Addr | User address | no | +| pending_bonds | Vec | Bond opportunities purchased by user that are unclaimed and maturing | no | + + +## PendingBond +Stored within user's pending_bonds vector. + +NOTE: The parameters must be in order +### Structure +| Name | Type | Description | optional | +|-----------------|-------------|-----------------------------------------------------------------------------------------|---------- | +| deposit_denom | Snip20Asset | Snip20 information for issued asset | no | +| end_time | u64 | Time that bond will be matured and claimable in UNIX time | no | +| deposit_amount | Uint128 | Amount of issued asset when opportunity was purchased | no | +| deposit_price | Uint128 | Price of deposit asset when opportunity was purchased | no | +| claim_amount | Uint128 | Amount of issued asset set to be claimed | no | +| claim_price | Uint128 | Price of issued asset when opportunity was purchased | no | +| discount | Uint128 | Discount of issued asset when opportunity was purchased | no | +| discount_price | Uint128 | Price of issued asset after discount was applied when opportunity was purchased | no | + + +## BondOpportunity +Stores information for bond opportunity + +NOTE: The parameters must be in order +### Structure +| Name | Type | Description | optional | +|-------------------------------|-------------|-----------------------------------------------------------------------|---------- | +| issuance_limit | Uint128 | Issuance limit for this bond opportunity | no | +| amount_issued | Uint128 | Amount of issued asset when opportunity was purchased | no | +| deposit_denom | Snip20Asset | Snip20 information for issued asset | no | +| start_time | u64 | Time that bond opportunity will be open in UNIX time | no | +| end_time | u64 | Time that bond opportunity will be closed in UNIX time | no | +| bonding_period | u64 | Time that users that enter the opportunity must wait before claiming | no | +| discount | Uint128 | Discount of issued asset when opportunity was purchased | no | +| max_accepted_deposit_price | Uint128 | Maximum accepted price for deposit asset | no | +| err_deposit_price | Uint128 | Error-causing limit price for deposit | no | +| minting_bond | bool | True for minting from snip20, false for allowance | no | + +## SlipMsg +Stores the user's slippage limit when entering bond opportunities + +```json +{ + "slip_msg": { + "minimum_expected_amount": "minimum expected amount to be issued Uint128" + } +} +``` + +## AccountProofMsg +The information inside permits that validate account ownership + +NOTE: The parameters must be in order +### Structure +| Name | Type | Description | optional | +|-----------|--------------|---------------------------------------------------------|----------| +| contracts | Vec | Bond contracts the permit is good for | no | +| key | String | Some permit key | no | + + +## PermitSignature +The signature that proves the validity of the data + +NOTE: The parameters must be in order +### Structure +| Name | Type | Description | optional | +|-----------|--------|---------------------------|----------| +| pub_key | pubkey | Signer's public key | no | +| signature | String | Base 64 encoded signature | no | + +## Pubkey +Public key + +NOTE: The parameters must be in order +### Structure +| Name | Type | Description | optional | +|-------|--------|------------------------------------|----------| +| type | String | Must be tendermint/PubKeySecp256k1 | no | +| value | String | The base 64 key | no | \ No newline at end of file diff --git a/archived-contracts/bonds/src/contract.rs b/archived-contracts/bonds/src/contract.rs new file mode 100644 index 0000000..e0d5e36 --- /dev/null +++ b/archived-contracts/bonds/src/contract.rs @@ -0,0 +1,215 @@ +use shade_protocol::c_std::Uint128; +use shade_protocol::c_std::{ + to_binary, Api, Binary, Env, DepsMut, Response, Querier, StdResult, Storage, +}; + +use shade_protocol::snip20::helpers::{set_viewing_key_msg, token_info_query}; + +use shade_protocol::contract_interfaces::{ + bonds::{Config, ExecuteMsg, InstantiateMsg, QueryMsg, SnipViewingKey}, + snip20::helpers::Snip20Asset, +}; + +use shade_protocol::snip20::helpers::token_config; +use shade_protocol::utils::{pad_handle_result, pad_query_result}; + +use crate::{ + handle::{self, register_receive}, + query, + state::{ + allocated_allowance_w, allowance_key_w, deposit_assets_w, config_w, + global_total_claimed_w, global_total_issued_w, issued_asset_w, + }, +}; + +// Used to pad up responses for better privacy. +pub const RESPONSE_BLOCK_SIZE: usize = 256; + +pub fn init( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let state = Config { + limit_admin: msg.limit_admin, + shade_admin: msg.shade_admin, + oracle: msg.oracle, + treasury: msg.treasury, + issued_asset: msg.issued_asset, + global_issuance_limit: msg.global_issuance_limit, + global_minimum_bonding_period: msg.global_minimum_bonding_period, + global_maximum_discount: msg.global_maximum_discount, + activated: msg.activated, + discount: msg.discount, + bond_issuance_limit: msg.bond_issuance_limit, + bonding_period: msg.bonding_period, + global_min_accepted_issued_price: msg.global_min_accepted_issued_price, + global_err_issued_price: msg.global_err_issued_price, + contract: env.contract.address.clone(), + airdrop: msg.airdrop, + query_auth: msg.query_auth, + }; + + config_w(deps.storage).save(&state)?; + + let mut messages = vec![]; + + let allowance_key: SnipViewingKey = + SnipViewingKey::new(&env, Default::default(), msg.allowance_key_entropy.as_ref()); + messages.push(set_viewing_key_msg( + allowance_key.0.clone(), + None, + 256, + state.issued_asset.code_hash.clone(), + state.issued_asset.address.clone(), + )?); + allowance_key_w(deps.storage).save(&allowance_key.0)?; + + let token_info = token_info_query( + &deps.querier, + 1, + state.issued_asset.code_hash.clone(), + state.issued_asset.address.clone(), + )?; + + let token_config = token_config( + &deps.querier, + 256, + state.issued_asset.code_hash.clone(), + state.issued_asset.address.clone(), + )?; + + issued_asset_w(deps.storage).save(&Snip20Asset { + contract: state.issued_asset.clone(), + token_info, + token_config: Option::from(token_config), + })?; + + messages.push(register_receive(&env, &state.issued_asset)?); + + // Write initial values to storage + global_total_issued_w(deps.storage).save(&Uint128::zero())?; + global_total_claimed_w(deps.storage).save(&Uint128::zero())?; + allocated_allowance_w(deps.storage).save(&Uint128::zero())?; + deposit_assets_w(deps.storage).save(&vec![])?; + + Ok(Response::new()) +} + +pub fn handle( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> StdResult { + pad_handle_result( + match msg { + ExecuteMsg::UpdateLimitConfig { + limit_admin, + shade_admin, + global_issuance_limit, + global_minimum_bonding_period, + global_maximum_discount, + reset_total_issued, + reset_total_claimed, + .. + } => handle::try_update_limit_config( + deps, + env, + limit_admin, + shade_admin, + global_issuance_limit, + global_minimum_bonding_period, + global_maximum_discount, + reset_total_issued, + reset_total_claimed, + ), + ExecuteMsg::UpdateConfig { + oracle, + treasury, + issued_asset, + activated, + bond_issuance_limit, + bonding_period, + discount, + global_min_accepted_issued_price, + global_err_issued_price, + allowance_key, + airdrop, + query_auth, + .. + } => handle::try_update_config( + deps, + env, + oracle, + treasury, + activated, + issued_asset, + bond_issuance_limit, + bonding_period, + discount, + global_min_accepted_issued_price, + global_err_issued_price, + allowance_key, + airdrop, + query_auth, + ), + ExecuteMsg::OpenBond { + deposit_asset, + start_time, + end_time, + bond_issuance_limit, + bonding_period, + discount, + max_accepted_deposit_price, + err_deposit_price, + minting_bond, + .. + } => handle::try_open_bond( + deps, + env, + deposit_asset, + start_time, + end_time, + bond_issuance_limit, + bonding_period, + discount, + max_accepted_deposit_price, + err_deposit_price, + minting_bond, + ), + ExecuteMsg::CloseBond { + deposit_asset, .. + } => handle::try_close_bond(deps, env, info, deposit_asset), + ExecuteMsg::Receive { + sender, + from, + amount, + msg, + .. + } => handle::try_deposit(deps, &env, sender, from, amount, msg), + ExecuteMsg::Claim { .. } => handle::try_claim(deps, env), + }, + RESPONSE_BLOCK_SIZE, + ) +} + +pub fn query( + deps: Deps, + msg: QueryMsg, +) -> StdResult { + pad_query_result( + match msg { + QueryMsg::Config {} => to_binary(&query::config(deps)?), + QueryMsg::BondOpportunities {} => to_binary(&query::bond_opportunities(deps)?), + QueryMsg::Account { permit } => to_binary(&query::account(deps, permit)?), + QueryMsg::DepositAddresses {} => to_binary(&query::list_deposit_addresses(deps)?), + QueryMsg::PriceCheck { asset } => to_binary(&query::price_check(asset, deps)?), + QueryMsg::BondInfo {} => to_binary(&query::bond_info(deps)?), + QueryMsg::CheckAllowance {} => to_binary(&query::check_allowance(deps)?), + QueryMsg::CheckBalance {} => to_binary(&query::check_balance(deps)?), + }, + RESPONSE_BLOCK_SIZE, + ) +} diff --git a/archived-contracts/bonds/src/handle.rs b/archived-contracts/bonds/src/handle.rs new file mode 100644 index 0000000..c153944 --- /dev/null +++ b/archived-contracts/bonds/src/handle.rs @@ -0,0 +1,845 @@ +use shade_protocol::c_std::Uint128; +use shade_protocol::c_std::{ + from_binary, to_binary, Api, Binary, CosmosMsg, Env, DepsMut, Response, Addr, + Querier, StdError, StdResult, Storage, +}; + +use shade_protocol::snip20::helpers::{allowance_query, mint_msg, register_receive, send_msg, transfer_from_msg}; + +use shade_admin::admin::{QueryMsg, ValidateAdminPermissionResponse}; + +use shade_oracles::{common::OraclePrice, router::QueryMsg::GetPrice}; + +use shade_protocol::contract_interfaces::bonds::{ + errors::*, + BondOpportunity, SlipMsg, {Account, Config, HandleAnswer, PendingBond}, +}; +use shade_protocol::contract_interfaces::{ + airdrop::ExecuteMsg::CompleteTask, + snip20::helpers::{fetch_snip20, Snip20Asset}, +}; +use shade_protocol::utils::asset::Contract; +use shade_protocol::utils::generic_response::ResponseStatus; + +use std::{cmp::Ordering, convert::TryFrom}; + +use crate::state::{ + account_r, account_w, allocated_allowance_r, allocated_allowance_w, allowance_key_r, + allowance_key_w, bond_opportunity_r, bond_opportunity_w, deposit_assets_r, + deposit_assets_w, config_r, config_w, global_total_claimed_w, global_total_issued_r, + global_total_issued_w, issued_asset_r, +}; + +pub fn try_update_limit_config( + deps: DepsMut, + env: Env, + info: MessageInfo, + limit_admin: Option, + shade_admins: Option, + global_issuance_limit: Option, + global_minimum_bonding_period: Option, + global_maximum_discount: Option, + reset_total_issued: Option, + reset_total_claimed: Option, +) -> StdResult { + let cur_config = config_r(deps.storage).load()?; + + // Limit admin only + if info.sender != cur_config.limit_admin { + return Err(not_limit_admin()); + } + + let mut config = config_w(deps.storage); + config.update(|mut state| { + if let Some(limit_admin) = limit_admin { + state.limit_admin = limit_admin; + } + if let Some(shade_admins) = shade_admins { + state.shade_admin = shade_admins; + } + if let Some(global_issuance_limit) = global_issuance_limit { + state.global_issuance_limit = global_issuance_limit; + } + if let Some(global_minimum_bonding_period) = global_minimum_bonding_period { + state.global_minimum_bonding_period = global_minimum_bonding_period; + } + if let Some(global_maximum_discount) = global_maximum_discount { + state.global_maximum_discount = global_maximum_discount; + } + Ok(state) + })?; + + if let Some(reset_total_issued) = reset_total_issued { + if reset_total_issued { + global_total_issued_w(deps.storage).save(&Uint128::zero())?; + } + } + + if let Some(reset_total_claimed) = reset_total_claimed { + if reset_total_claimed { + global_total_claimed_w(deps.storage).save(&Uint128::zero())?; + } + } + + Ok(Response::new().set_data(to_binary(&HandleAnswer::UpdateLimitConfig { + status: ResponseStatus::Success, + })?)) +} + +pub fn try_update_config( + deps: DepsMut, + env: Env, + info: MessageInfo, + oracle: Option, + treasury: Option, + activated: Option, + issuance_asset: Option, + bond_issuance_limit: Option, + bonding_period: Option, + discount: Option, + global_min_accepted_issued_price: Option, + global_err_issued_price: Option, + allowance_key: Option, + airdrop: Option, + query_auth: Option, +) -> StdResult { + let cur_config = config_r(deps.storage).load()?; + + // Admin-only + let admin_response: ValidateAdminPermissionResponse = QueryMsg::ValidateAdminPermission { + contract_address: cur_config.contract.to_string(), + admin_address: info.sender.to_string(), + } + .query( + &deps.querier, + cur_config.shade_admin.code_hash, + cur_config.shade_admin.address, + )?; + + if admin_response.error_msg.is_some() { + return Err(not_admin()); + } + + if let Some(allowance_key) = allowance_key { + allowance_key_w(deps.storage).save(&allowance_key)?; + }; + + let mut config = config_w(deps.storage); + config.update(|mut state| { + if let Some(oracle) = oracle { + state.oracle = oracle; + } + if let Some(treasury) = treasury { + state.treasury = treasury; + } + if let Some(activated) = activated { + state.activated = activated; + } + if let Some(issuance_asset) = issuance_asset { + state.issued_asset = issuance_asset; + } + if let Some(bond_issuance_limit) = bond_issuance_limit { + state.bond_issuance_limit = bond_issuance_limit; + } + if let Some(bonding_period) = bonding_period { + state.bonding_period = bonding_period; + } + if let Some(discount) = discount { + state.discount = discount; + } + if let Some(global_min_accepted_issued_price) = global_min_accepted_issued_price { + state.global_min_accepted_issued_price = global_min_accepted_issued_price; + } + if let Some(global_err_issued_price) = global_err_issued_price { + state.global_err_issued_price = global_err_issued_price; + } + if let Some(airdrop) = airdrop { + state.airdrop = Some(airdrop); + } + if let Some(query_auth) = query_auth { + state.query_auth = query_auth; + } + Ok(state) + })?; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::UpdateConfig { + status: ResponseStatus::Success, + })?)) +} + +pub fn try_deposit( + deps: DepsMut, + env: &Env, + sender: Addr, + _from: Addr, + deposit_amount: Uint128, + msg: Option, +) -> StdResult { + let config = config_r(deps.storage).load()?; + + // Check that sender isn't the treasury + if config.treasury == sender { + return Err(blacklisted(config.treasury)); + } + + if config.contract == sender { + return Err(blacklisted(config.contract)); + } + + // Check that sender isn't an admin + let admin_response: ValidateAdminPermissionResponse = QueryMsg::ValidateAdminPermission { + contract_address: config.contract.to_string(), + admin_address: sender.to_string(), + } + .query( + &deps.querier, + config.shade_admin.code_hash, + config.shade_admin.address, + )?; + + if admin_response.error_msg.is_none() { + return Err(blacklisted(sender)); + } + + // Check that sender isn't the minted asset + if config.issued_asset.address == info.sender { + return Err(issued_asset_deposit()); + } + + // Check that sender asset has an active bond opportunity + let bond_opportunity = match bond_opportunity_r(deps.storage) + .may_load(info.sender.to_string().as_bytes())? + { + Some(prev_opp) => { + bond_active(&env, &prev_opp)?; + prev_opp + } + None => { + return Err(no_bond_found(info.sender.as_str())); + } + }; + + let available = bond_opportunity + .issuance_limit + .checked_sub(bond_opportunity.amount_issued) + .unwrap(); + + // Load mint asset information + let issuance_asset = issued_asset_r(deps.storage).load()?; + + // Calculate conversion of deposit to SHD + let (amount_to_issue, deposit_price, claim_price, discount_price) = amount_to_issue( + &deps, + deposit_amount, + available, + bond_opportunity.deposit_denom.clone(), + issuance_asset, + bond_opportunity.discount, + bond_opportunity.max_accepted_deposit_price, + bond_opportunity.err_deposit_price, + config.global_min_accepted_issued_price, + config.global_err_issued_price, + )?; + + if let Some(message) = msg { + let msg: SlipMsg = from_binary(&message)?; + + // Check Slippage + if amount_to_issue.clone() < msg.minimum_expected_amount.clone() { + return Err(slippage_tolerance_exceeded( + amount_to_issue, + msg.minimum_expected_amount, + )); + } + }; + + let mut opp = + bond_opportunity_r(deps.storage).load(info.sender.to_string().as_bytes())?; + opp.amount_issued += amount_to_issue; + bond_opportunity_w(deps.storage).save(info.sender.to_string().as_bytes(), &opp)?; + + let mut messages = vec![]; + + // Deposit to treasury + messages.push(send_msg( + config.treasury.clone(), + deposit_amount.into(), + None, + None, + None, + 1, + bond_opportunity.deposit_denom.contract.code_hash.clone(), + bond_opportunity.deposit_denom.contract.address.clone(), + )?); + + // Format end date as String + let end: u64 = calculate_claim_date(env.block.time.seconds(), bond_opportunity.bonding_period); + + // Begin PendingBond + let new_bond = PendingBond { + claim_amount: amount_to_issue.clone(), + end_time: end, + deposit_denom: bond_opportunity.deposit_denom, + deposit_amount, + deposit_price, + claim_price, + discount: bond_opportunity.discount, + discount_price, + }; + + // Find user account, create if it doesn't exist + let mut account = match account_r(deps.storage).may_load(sender.as_str().as_bytes())? { + None => { + // Airdrop task + if let Some(airdrop) = config.airdrop { + let msg = CompleteTask { + address: sender.clone(), + padding: None, + }; + messages.push(msg.to_cosmos_msg(airdrop.code_hash, airdrop.address, None)?); + } + + Account { + address: sender, + pending_bonds: vec![], + } + } + Some(acc) => acc, + }; + + // Add new_bond to user's pending_bonds Vec + account.pending_bonds.push(new_bond.clone()); + + // Save account + account_w(deps.storage).save(account.address.as_str().as_bytes(), &account)?; + + if !bond_opportunity.minting_bond { + // Decrease AllocatedAllowance since user is claiming + allocated_allowance_w(deps.storage) + .update(|allocated| Ok(allocated.checked_sub(amount_to_issue.clone())?))?; + + // Transfer funds using allowance to bonds + messages.push(transfer_from_msg( + config.treasury.clone(), + env.contract.address.clone(), + amount_to_issue.into(), + None, + None, + 256, + config.issued_asset.code_hash.clone(), + config.issued_asset.address, + )?); + } else { + messages.push(mint_msg( + config.contract, + amount_to_issue.into(), + None, + None, + 256, + config.issued_asset.code_hash, + config.issued_asset.address, + )?); + } + + // Return Success response + Ok(Response::new().set_data(to_binary(&HandleAnswer::Deposit { + status: ResponseStatus::Success, + deposit_amount: new_bond.deposit_amount, + pending_claim_amount: new_bond.claim_amount, + end_date: new_bond.end_time, + })?)) +} + +pub fn try_claim( + deps: DepsMut, + env: Env, + info: MessageInfo, +) -> StdResult { + // Check if bonding period has elapsed and allow user to claim + // however much of the issuance asset they paid for with their deposit + let config = config_r(deps.storage).load()?; + + // Find user account, error out if DNE + let mut account = + match account_r(deps.storage).may_load(info.sender.as_str().as_bytes())? { + None => { + return Err(StdError::NotFound { + kind: info.sender.to_string(), + backtrace: None, + }); + } + Some(acc) => acc, + }; + + // Bring up pending bonds structure for user if account is found + let mut pending_bonds = account.pending_bonds; + if pending_bonds.is_empty() { + return Err(no_pending_bonds(account.address.as_str())); + } + + // Set up loop comparison values. + let now = env.block.time.seconds(); // Current time in seconds + let mut total = Uint128::zero(); + + // Iterate through pending bonds and compare one's end to current time + for bond in pending_bonds.iter() { + if bond.end_time <= now { + // Add claim amount to total + total = total.checked_add(bond.claim_amount).unwrap(); + } + } + + // Add case for if total is 0, error out + if total.is_zero() { + return Err(no_bonds_claimable()); + } + + // Remove claimed bonds from vector and save back to the account + pending_bonds.retain( + |bond| bond.end_time > now, // Retain only the bonds that end at a time greater than now + ); + + account.pending_bonds = pending_bonds; + account_w(deps.storage).save(info.sender.as_str().as_bytes(), &account)?; + + global_total_claimed_w(deps.storage) + .update(|global_total_claimed| Ok(global_total_claimed.checked_add(total.clone())?))?; + + //Set up empty message vec + let mut messages = vec![]; + + messages.push(send_msg( + info.sender, + total.into(), + None, + None, + None, + 256, + config.issued_asset.code_hash.clone(), + config.issued_asset.address, + )?); + + // Return Success response + Ok(Response::new().set_data(to_binary(&HandleAnswer::Claim { + status: ResponseStatus::Success, + amount: total, + })?)) +} + +pub fn try_open_bond( + deps: DepsMut, + env: Env, + info: MessageInfo, + deposit_asset: Contract, + start_time: u64, + end_time: u64, + bond_issuance_limit: Option, + bonding_period: Option, + discount: Option, + max_accepted_deposit_price: Uint128, + err_deposit_price: Uint128, + minting_bond: bool, +) -> StdResult { + let config = config_r(deps.storage).load()?; + + // Admin-only + let admin_response: ValidateAdminPermissionResponse = QueryMsg::ValidateAdminPermission { + contract_address: config.contract.to_string(), + admin_address: info.sender.to_string(), + } + .query( + &deps.querier, + config.shade_admin.code_hash, + config.shade_admin.address, + )?; + + if admin_response.error_msg.is_some() { + return Err(not_admin()); + } + + let mut messages = vec![]; + + // Check whether previous bond for this asset exists + match bond_opportunity_r(deps.storage) + .may_load(deposit_asset.address.as_str().as_bytes())? + { + Some(prev_opp) => { + let unspent = prev_opp + .issuance_limit + .checked_sub(prev_opp.amount_issued)?; + global_total_issued_w(deps.storage) + .update(|issued| Ok(issued.checked_sub(unspent.clone())?))?; + + if !prev_opp.minting_bond { + // Unallocate allowance that wasn't issued + + allocated_allowance_w(deps.storage) + .update(|allocated| Ok(allocated.checked_sub(unspent)?))?; + } + } + None => { + // Save to list of current deposit addresses + match deposit_assets_r(deps.storage).may_load()? { + None => { + let assets = vec![deposit_asset.address.clone()]; + deposit_assets_w(deps.storage).save(&assets)?; + } + Some(_assets) => { + deposit_assets_w(deps.storage).update(|mut assets| { + assets.push(deposit_asset.address.clone()); + Ok(assets) + })?; + } + }; + + // Prepare register_receive message for new asset + messages.push(register_receive(&env, &deposit_asset)?); + } + }; + + // Check optional fields, setting to config defaults if None + let limit = bond_issuance_limit.unwrap_or(config.bond_issuance_limit); + let period = bonding_period.unwrap_or(config.bonding_period); + let discount = discount.unwrap_or(config.discount); + + check_against_limits(&deps, limit, period, discount)?; + + if !minting_bond { + // Check bond issuance amount against snip20 allowance and allocated_allowance + let snip20_allowance = allowance_query( + &deps.querier, + config.treasury, + env.contract.address.clone(), + allowance_key_r(deps.storage).load()?.to_string(), + 1, + config.issued_asset.code_hash, + config.issued_asset.address, + )?; + + let allocated_allowance = allocated_allowance_r(deps.storage).load()?; + // Declaring again so 1.0 Uint128 works + let snip_allowance = Uint128::from(snip20_allowance.allowance); + + // Error out if allowance doesn't allow bond opportunity + if snip_allowance.checked_sub(allocated_allowance)? < limit { + return Err(bond_issuance_exceeds_allowance( + snip_allowance, + allocated_allowance, + limit, + )); + }; + + // Increase stored allocated_allowance by the opportunity's issuance limit + allocated_allowance_w(deps.storage) + .update(|allocated| Ok(allocated.checked_add(limit)?))?; + } + + let deposit_denom = fetch_snip20(&deposit_asset.clone(), &deps.querier)?; + + // Generate bond opportunity + let bond_opportunity = BondOpportunity { + issuance_limit: limit, + deposit_denom, + start_time, + end_time, + discount, + bonding_period: period, + amount_issued: Uint128::zero(), + max_accepted_deposit_price, + err_deposit_price, + minting_bond, + }; + + // Save bond opportunity + bond_opportunity_w(deps.storage).save( + deposit_asset.address.as_str().as_bytes(), + &bond_opportunity, + )?; + + // Increase global total issued by bond opportunity's issuance limit + global_total_issued_w(deps.storage).update(|global_total_issued| { + Ok(global_total_issued.checked_add(bond_opportunity.issuance_limit)?) + })?; + + // Return Success response + Ok(Response::new().set_data(to_binary(&HandleAnswer::OpenBond { + status: ResponseStatus::Success, + deposit_contract: bond_opportunity.deposit_denom.contract, + start_time: bond_opportunity.start_time, + end_time: bond_opportunity.end_time, + bond_issuance_limit: bond_opportunity.issuance_limit, + bonding_period: bond_opportunity.bonding_period, + discount: bond_opportunity.discount, + max_accepted_deposit_price: bond_opportunity.max_accepted_deposit_price, + err_deposit_price: bond_opportunity.err_deposit_price, + minting_bond: bond_opportunity.minting_bond, + })?)) +} + +pub fn try_close_bond( + deps: DepsMut, + env: Env, + info: MessageInfo, + deposit_asset: Contract, +) -> StdResult { + let config = config_r(deps.storage).load()?; + + // Admin-only + let admin_response: ValidateAdminPermissionResponse = QueryMsg::ValidateAdminPermission { + contract_address: config.contract.to_string(), + admin_address: info.sender.to_string(), + } + .query( + &deps.querier, + config.shade_admin.code_hash, + config.shade_admin.address, + )?; + + if admin_response.error_msg.is_some() { + return Err(not_admin()); + } + + // Check whether previous bond for this asset exists + + match bond_opportunity_r(deps.storage) + .may_load(deposit_asset.address.as_str().as_bytes())? + { + Some(prev_opp) => { + bond_opportunity_w(deps.storage) + .remove(deposit_asset.address.as_str().as_bytes()); + + // Remove asset from address list + deposit_assets_w(deps.storage).update(|mut assets| { + assets.retain(|address| *address != deposit_asset.address); + Ok(assets) + })?; + + let unspent = prev_opp + .issuance_limit + .checked_sub(prev_opp.amount_issued)?; + global_total_issued_w(deps.storage) + .update(|issued| Ok(issued.checked_sub(unspent.clone())?))?; + + if !prev_opp.minting_bond { + // Unallocate allowance that wasn't issued + + allocated_allowance_w(deps.storage) + .update(|allocated| Ok(allocated.checked_sub(unspent)?))?; + } + } + None => { + // Error out, no bond found with that deposit asset + return Err(no_bond_found(deposit_asset.address.as_str())); + } + } + + let messages = vec![]; + + // Return Success response + Ok(Response::new().set_data(to_binary(&HandleAnswer::ClosedBond { + status: ResponseStatus::Success, + deposit_asset, + })?)) +} + +fn bond_active(env: &Env, bond_opp: &BondOpportunity) -> StdResult<()> { + if bond_opp.amount_issued >= bond_opp.issuance_limit { + return Err(bond_limit_reached(bond_opp.issuance_limit)); + } + if bond_opp.start_time > env.block.time.seconds() { + return Err(bond_not_started(bond_opp.start_time, env.block.time.seconds())); + } + if bond_opp.end_time < env.block.time.seconds() { + return Err(bond_ended(bond_opp.end_time, env.block.time.seconds())); + } + Ok(()) +} + +fn check_against_limits( + deps: Deps, + bond_limit: Uint128, + bond_period: u64, + bond_discount: Uint128, +) -> StdResult { + let config = config_r(deps.storage).load()?; + // Check that global issuance limit won't be exceeded by this opportunity's limit + let global_total_issued = global_total_issued_r(deps.storage).load()?; + let global_issuance_limit = config.global_issuance_limit; + + active( + &config.activated, + &config.global_issuance_limit, + &global_total_issued, + )?; + + if global_total_issued.checked_add(bond_limit)? > global_issuance_limit { + return Err(bond_limit_exceeds_global_limit( + global_issuance_limit, + global_total_issued, + bond_limit, + )); + } else if bond_period < config.global_minimum_bonding_period { + return Err(bonding_period_below_minimum_time( + bond_period, + config.global_minimum_bonding_period, + )); + } else if bond_discount > config.global_maximum_discount { + return Err(bond_discount_above_maximum_rate( + bond_discount, + config.global_maximum_discount, + )); + } + Ok(true) +} + +pub fn active( + activated: &bool, + global_issuance_limit: &Uint128, + global_total_issued: &Uint128, +) -> StdResult<()> { + // Error out if bond contract isn't active + if !activated { + return Err(contract_not_active()); + } + + // Check whether mint limit has been reached + if global_total_issued >= global_issuance_limit { + return Err(global_limit_reached(*global_issuance_limit)); + } + + Ok(()) +} + +pub fn amount_to_issue( + deps: Deps, + deposit_amount: Uint128, + available: Uint128, + deposit_asset: Snip20Asset, + issuance_asset: Snip20Asset, + discount: Uint128, + max_accepted_deposit_price: Uint128, + err_deposit_price: Uint128, + min_accepted_issued_price: Uint128, + err_issued_price: Uint128, +) -> StdResult<(Uint128, Uint128, Uint128, Uint128)> { + let mut disc = discount; + let mut deposit_price = oracle(&deps, deposit_asset.token_info.symbol.clone())?; + if deposit_price > max_accepted_deposit_price { + if deposit_price > err_deposit_price { + return Err(deposit_price_exceeds_limit( + deposit_price.clone(), + err_deposit_price.clone(), + )); + } + deposit_price = max_accepted_deposit_price; + } + let mut issued_price = oracle(deps, issuance_asset.token_info.symbol.clone())?; + if issued_price < err_issued_price { + return Err(issued_price_below_minimum( + issued_price.clone(), + err_issued_price.clone(), + )); + } + if issued_price < min_accepted_issued_price { + disc = Uint128::zero(); + issued_price = min_accepted_issued_price; + } + let (issued_amount, discount_price) = calculate_issuance( + deposit_price.clone(), + deposit_amount, + deposit_asset.token_info.decimals, + issued_price, + issuance_asset.token_info.decimals, + disc, + min_accepted_issued_price, + ); + if issued_amount > available { + return Err(mint_exceeds_limit(issued_amount, available)); + } + Ok(( + issued_amount, + deposit_price, + issued_price, + discount_price, + )) +} + +pub fn calculate_issuance( + deposit_price: Uint128, + deposit_amount: Uint128, + deposit_decimals: u8, + issued_price: Uint128, + issued_decimals: u8, + discount: Uint128, + min_accepted_issued_price: Uint128, +) -> (Uint128, Uint128) { + // Math must be done in integers + // deposit_decimals = x + // issued_decimals = y + // deposit_price = p1 * 10^18 + // issued_price = p2 * 10^18 + // deposit_amount = a1 * 10^x + // issued_amount = a2 * 10^y + // discount = d1 * 10^18 + + // (a1 * 10^x) * (p1 * 10^18) = (a2 * 10^y) * (p2 * 10^18) * ((100 - d1) * 10^16) + + // (p1 * 10^18) + // (a1 * 10^x) * ------------------------------------ = (a2 * 10^y) + // (p2 * 10^18) * ((100 - d1)) + let percent_disc = Uint128::new(100_000).checked_sub(discount).unwrap(); // - discount.multiply_ratio(1000u128, 1_000_000_000_000_000_000u128).u128(); + let mut discount_price = issued_price.multiply_ratio(percent_disc, 100000u128); + if discount_price < min_accepted_issued_price { + discount_price = min_accepted_issued_price + } + let issued_amount = deposit_amount.multiply_ratio(deposit_price, discount_price); + let difference: i32 = i32::from(issued_decimals) + .checked_sub(i32::from(deposit_decimals)) + .unwrap(); + match difference.cmp(&0) { + Ordering::Greater => ( + issued_amount + .checked_mul(Uint128::new(10u128.pow(u32::try_from(difference).unwrap()))) + .unwrap(), + discount_price, + ), + Ordering::Less => ( + issued_amount + .multiply_ratio(1u128, 10u128.pow(u32::try_from(difference.abs()).unwrap())), + discount_price, + ), + Ordering::Equal => (issued_amount, discount_price), + } +} + +pub fn calculate_claim_date(env_time: u64, bonding_period: u64) -> u64 { + // Previously, translated the passed u64 as days and converted to seconds. + // Now, however, it treats the passed value as seconds, due to that being + // how the block environment tracks it. + let end = env_time.checked_add(bonding_period).unwrap(); + + end +} + +pub fn register_receive(env: &Env, contract: &Contract) -> StdResult { + register_receive( + env.contract.code_hash.clone(), + None, + contract + ) +} + +pub fn oracle( + deps: Deps, + key: String, +) -> StdResult { + let config: Config = config_r(deps.storage).load()?; + let answer: OraclePrice = GetPrice { key }.query( + &deps.querier, + config.oracle.code_hash, + config.oracle.address, + )?; + + // From wasn't working, so here's a fix + Ok(Uint128::new(answer.data.rate.u128())) +} diff --git a/archived-contracts/bonds/src/lib.rs b/archived-contracts/bonds/src/lib.rs new file mode 100644 index 0000000..7683a12 --- /dev/null +++ b/archived-contracts/bonds/src/lib.rs @@ -0,0 +1,44 @@ +pub mod contract; +pub mod handle; +pub mod query; +pub mod state; + +#[cfg(test)] +mod tests; + +#[cfg(target_arch = "wasm32")] +mod wasm { + use super::contract; + use shade_protocol::c_std::{ + do_handle, do_init, do_query, ExternalApi, ExternalQuerier, ExternalStorage, + }; + + #[no_mangle] + extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { + do_init( + &contract::init::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { + do_handle( + &contract::handle::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn query(msg_ptr: u32) -> u32 { + do_query( + &contract::query::, + msg_ptr, + ) + } + + // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available + // automatically because we `use cosmwasm_std`. +} diff --git a/archived-contracts/bonds/src/query.rs b/archived-contracts/bonds/src/query.rs new file mode 100644 index 0000000..16102c0 --- /dev/null +++ b/archived-contracts/bonds/src/query.rs @@ -0,0 +1,155 @@ +use crate::{ + handle::oracle, + state::{ + account_r, allowance_key_r, bond_opportunity_r, deposit_assets_r, config_r, + global_total_claimed_r, global_total_issued_r, issued_asset_r, + }, +}; + +use shade_protocol::c_std::Uint128; + +use shade_protocol::{ + snip20::helpers::{allowance_query, balance_query}, +}; + +use shade_protocol::c_std::{Api, DepsMut, Addr, Querier, StdResult, Storage}; +use shade_protocol::contract_interfaces::bonds::{ + errors::{permit_revoked, query_auth_bad_response}, + BondOpportunity, QueryAnswer, +}; + +use shade_protocol::contract_interfaces::query_auth::{ + self, QueryMsg::ValidatePermit, QueryPermit, +}; + +pub fn config(deps: Deps) -> StdResult { + Ok(QueryAnswer::Config { + config: config_r(deps.storage).load()?, + }) +} + +pub fn account( + deps: Deps, + permit: QueryPermit, +) -> StdResult { + let config = config_r(deps.storage).load()?; + // Validate address + let authorized: query_auth::QueryAnswer = ValidatePermit { permit }.query( + &deps.querier, + config.query_auth.code_hash, + config.query_auth.address, + )?; + match authorized { + query_auth::QueryAnswer::ValidatePermit { user, is_revoked } => { + if !is_revoked { + account_information(deps, user) + } else { + return Err(permit_revoked(user.as_str())); + } + } + _ => return Err(query_auth_bad_response()), + } +} + +fn account_information( + deps: Deps, + account_address: Addr, +) -> StdResult { + let account = account_r(deps.storage).load(account_address.as_str().as_bytes())?; + + // Return pending bonds + + Ok(QueryAnswer::Account { + pending_bonds: account.pending_bonds, + }) +} + +pub fn bond_opportunities( + deps: Deps, +) -> StdResult { + let deposit_assets = deposit_assets_r(deps.storage).load()?; + if deposit_assets.is_empty() { + return Ok(QueryAnswer::BondOpportunities { + bond_opportunities: vec![], + }); + } else { + let iter = deposit_assets.iter(); + let mut bond_opportunities: Vec = vec![]; + for asset in iter { + bond_opportunities + .push(bond_opportunity_r(deps.storage).load(asset.as_str().as_bytes())?); + } + return Ok(QueryAnswer::BondOpportunities { bond_opportunities }); + } +} + +pub fn bond_info(deps: Deps) -> StdResult { + let global_total_issued = global_total_issued_r(deps.storage).load()?; + let global_total_claimed = global_total_claimed_r(deps.storage).load()?; + let issued_asset = issued_asset_r(deps.storage).load()?; + let config = config_r(deps.storage).load()?; + Ok(QueryAnswer::BondInfo { + global_total_issued, + global_total_claimed, + issued_asset, + global_min_accepted_issued_price: config.global_min_accepted_issued_price, + global_err_issued_price: config.global_err_issued_price, + }) +} + +pub fn list_deposit_addresses( + deps: Deps, +) -> StdResult { + let deposit_addresses = deposit_assets_r(deps.storage).load()?; + Ok(QueryAnswer::DepositAddresses { + deposit_addresses, + }) +} + +pub fn price_check( + asset: String, + deps: Deps, +) -> StdResult { + let price = oracle(deps, asset)?; + Ok(QueryAnswer::PriceCheck { price }) +} + +pub fn check_allowance( + deps: Deps, +) -> StdResult { + let config = config_r(deps.storage).load()?; + + // Check bond issuance amount against snip20 allowance and allocated_allowance + let snip20_allowance = allowance_query( + &deps.querier, + config.treasury, + config.contract, + allowance_key_r(deps.storage).load()?.to_string(), + 1, + config.issued_asset.code_hash, + config.issued_asset.address, + )?; + + Ok(QueryAnswer::CheckAllowance { + allowance: Uint128::from(snip20_allowance.allowance), + }) +} + +pub fn check_balance( + deps: Deps, +) -> StdResult { + let config = config_r(deps.storage).load()?; + + let balance = balance_query( + &deps.querier, + config.contract, + allowance_key_r(deps.storage).load()?, + 256, + config.issued_asset.code_hash, + config.issued_asset.address, + )?; + + Ok(QueryAnswer::CheckBalance { + balance: Uint128::from(balance.amount), + }) +} diff --git a/archived-contracts/bonds/src/state.rs b/archived-contracts/bonds/src/state.rs new file mode 100644 index 0000000..e0a0394 --- /dev/null +++ b/archived-contracts/bonds/src/state.rs @@ -0,0 +1,99 @@ +use shade_protocol::c_std::Uint128; +use shade_protocol::c_std::{Addr, Storage}; +use shade_protocol::storage::{ + bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton, + Singleton, +}; +use shade_protocol::contract_interfaces::{ + bonds::{Account, BondOpportunity, Config}, + snip20::helpers::Snip20Asset, +}; + +pub static CONFIG: &[u8] = b"config"; +pub static GLOBAL_TOTAL_ISSUED: &[u8] = b"global_total_issued"; +pub static GLOBAL_TOTAL_CLAIMED: &[u8] = b"global_total_claimed"; +pub static DEPOSIT_ASSETS: &[u8] = b"deposit_assets"; +pub static ISSUED_ASSET: &[u8] = b"issued_asset"; +pub static ACCOUNTS_KEY: &[u8] = b"accounts"; +pub static BOND_OPPORTUNITIES: &[u8] = b"bond_opportunities"; +pub static ALLOCATED_ALLOWANCE: &[u8] = b"allocated_allowance"; +pub static ALLOWANCE_VIEWING_KEY: &[u8] = b"allowance_viewing_key"; + +pub fn config_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, CONFIG) +} + +pub fn config_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, CONFIG) +} + +/* Global amount issued since last issuance reset */ +pub fn global_total_issued_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, GLOBAL_TOTAL_ISSUED) +} + +pub fn global_total_issued_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, GLOBAL_TOTAL_ISSUED) +} + +/* Global amount claimed since last issuance reset */ +pub fn global_total_claimed_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, GLOBAL_TOTAL_CLAIMED) +} + +pub fn global_total_claimed_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, GLOBAL_TOTAL_CLAIMED) +} + +/* List of assets that have bond opportunities stored */ +pub fn deposit_assets_w(storage: &mut dyn Storage) -> Singleton> { + singleton(storage, DEPOSIT_ASSETS) +} + +pub fn deposit_assets_r(storage: &dyn Storage) -> ReadonlySingleton> { + singleton_read(storage, DEPOSIT_ASSETS) +} + +/* Asset minted when user claims after bonding period */ +pub fn issued_asset_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, ISSUED_ASSET) +} + +pub fn issued_asset_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, ISSUED_ASSET) +} + +// Bond account +pub fn account_r(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, ACCOUNTS_KEY) +} + +pub fn account_w(storage: &mut dyn Storage) -> Bucket { + bucket(storage, ACCOUNTS_KEY) +} + +pub fn bond_opportunity_r(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, BOND_OPPORTUNITIES) +} + +pub fn bond_opportunity_w(storage: &mut dyn Storage) -> Bucket { + bucket(storage, BOND_OPPORTUNITIES) +} + +// The amount of allowance already allocated/unclaimed from opportunities +pub fn allocated_allowance_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, ALLOCATED_ALLOWANCE) +} + +pub fn allocated_allowance_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, ALLOCATED_ALLOWANCE) +} + +// Stores the bond contracts viewing key to see its own allowance +pub fn allowance_key_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, ALLOWANCE_VIEWING_KEY) +} + +pub fn allowance_key_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, ALLOWANCE_VIEWING_KEY) +} diff --git a/archived-contracts/bonds/src/test.rs b/archived-contracts/bonds/src/test.rs new file mode 100644 index 0000000..1951015 --- /dev/null +++ b/archived-contracts/bonds/src/test.rs @@ -0,0 +1,65 @@ +mod test { + use crate::handle::{active, calculate_claim_date, calculate_issuance}; + use shade_protocol::c_std::Uint128; + use shade_protocol::{ + contract_interfaces::{ + bonds::{errors::*}, + }, + }; + + #[test] + fn checking_limits() {} + + #[test] + fn check_active() { + assert_eq!(active(&true, &Uint128::new(10), &Uint128::new(9)), Ok(())); + assert_eq!( + active(&false, &Uint128::new(10), &Uint128::new(9)), + Err(contract_not_active()) + ); + assert_eq!( + active(&true, &Uint128::new(10), &Uint128::new(10)), + Err(global_limit_reached(Uint128::new(10))) + ); + } + + #[test] + fn claim_date() { + assert_eq!(calculate_claim_date(0, 1), 1); + assert_eq!(calculate_claim_date(100_000_000, 7), 100_000_007); + } + + #[test] + fn calc_mint() { + let result = calculate_issuance( + Uint128::new(7_000_000_000_000_000_000), + Uint128::new(10_000_000), + 6, + Uint128::new(5_000_000_000_000_000_000), + 6, + Uint128::new(7_000), + Uint128::new(0), + ); + assert_eq!(result.0, Uint128::new(15_053_763)); + let result2 = calculate_issuance( + Uint128::new(10_000_000_000_000_000_000), + Uint128::new(50_000_000), + 6, + Uint128::new(50_000_000_000_000_000_000), + 8, + Uint128::new(9_000), + Uint128::new(0), + ); + assert_eq!(result2.0, Uint128::new(1_098_901_000)); + let result3 = calculate_issuance( + Uint128::new(10_000_000_000_000_000_000), + Uint128::new(5_000_000_000), + 8, + Uint128::new(50_000_000_000_000_000_000), + 6, + Uint128::new(9_000), + Uint128::new(0), + ); + assert_eq!(result3.0, Uint128::new(10989010)); + } +} diff --git a/archived-contracts/bonds/src/tests/handle.rs b/archived-contracts/bonds/src/tests/handle.rs new file mode 100644 index 0000000..8f2e342 --- /dev/null +++ b/archived-contracts/bonds/src/tests/handle.rs @@ -0,0 +1,500 @@ +use crate::tests::{ + check_balances, init_contracts, + query::{query_no_opps, query_opp_parameters}, + set_prices, +}; +use shade_protocol::c_std::Uint128; +use shade_protocol::c_std::Addr; +use shade_protocol::fadroma::core::ContractLink; +use shade_protocol::fadroma::ensemble::{ContractEnsemble, MockEnv}; +use shade_protocol::contract_interfaces::{bonds, query_auth, snip20}; +use shade_protocol::utils::asset::Contract; + +use super::{increase_allowance, query::query_acccount_parameters, setup_admin}; + +#[test] +pub fn test_bonds() { + let (mut chain, bonds, issu, depo, atom, band, _oracle, query_auth, shade_admins) = + init_contracts().unwrap(); + + set_prices( + &mut chain, + &band, + Uint128::new(10_000_000_000_000_000_000), + Uint128::new(5_000_000_000_000_000_000), + Uint128::new(20_000_000_000_000_000_000), + ) + .unwrap(); + + setup_admin(&mut chain, &shade_admins, &bonds); + + increase_allowance(&mut chain, &bonds, &issu); + + // No bond, so fail + buy_opp_fail(&mut chain, &bonds, &depo); + + open_opp( + &mut chain, + &bonds, + &depo, + "admin", + Some(100), + Some(Uint128::new(10_000_000_000)), + Some(0), + Some(Uint128::new(1000)), + Uint128::new(10_000_000_000_000_000_000_000_000), + Uint128::new(10_000_000_000_000_000_000_000_000), + false, + ); + + buy_opp(&mut chain, &bonds, &depo, Uint128::new(2_000_000_000)); + + query_acccount_parameters( + &mut chain, + &bonds.clone(), + &query_auth.clone(), + "secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq", + None, + None, + Some(Uint128::new(2_000_000_000)), + None, + None, + None, + None, + None, + ); + + query_opp_parameters( + &mut chain, + &bonds, + None, + Some(Uint128::new(1000000000)), + None, + None, + None, + None, + None, + None, + None, + None, + ); + + update_config( + &mut chain, + &bonds, + "admin", + None, + None, + None, + None, + None, + None, + None, + Some(Uint128::new(9_000_000_000_000_000_000)), + None, + None, + None, + None, + ); + + buy_opp(&mut chain, &bonds, &depo, Uint128::new(2_000_000_000)); + + query_opp_parameters( + &mut chain, + &bonds, + None, + Some(Uint128::new(2010101010)), + None, + None, + None, + None, + None, + None, + None, + None, + ); + + let msg = query_auth::ExecuteMsg::CreateViewingKey { + entropy: "random".to_string(), + padding: None, + }; + + chain + .execute( + &msg, + MockEnv::new( + "secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq", + query_auth.clone(), + ), + ) + .unwrap(); + + claim(&mut chain, &bonds); + + check_balances( + &mut chain, + &issu, + &depo, + Uint128::new(2010101010), + Uint128::new(4_000_000_000), + ) + .unwrap(); + + close_opp(&mut chain, &bonds, &depo, "admin"); + + query_no_opps(&mut chain, &bonds); + + open_opp( + &mut chain, + &bonds, + &depo, + "admin", + None, + None, + None, + None, + Uint128::new(1), + Uint128::new(1), + false, + ); + open_opp_fail( + &mut chain, + &bonds, + &depo, + "secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq", + None, + None, + None, + None, + Uint128::new(1), + Uint128::new(1), + false, + ); + open_opp_fail( + &mut chain, + &bonds, + &depo, + "admin", + None, + None, + None, + Some(Uint128::new(10000000000000000000)), + Uint128::new(1), + Uint128::new(1), + false, + ); + open_opp( + &mut chain, + &bonds, + &depo, + "admin", + None, + None, + None, + Some(Uint128::new(4_347)), + Uint128::new(1_000_000_000_000_000_000), + Uint128::new(950_000_000_000_000_000), + false, + ); + + set_prices( + &mut chain, + &band, + Uint128::new(7_500_000_000_000_000_000), + Uint128::new(980_000_000_000_000_000), + Uint128::new(20_000_000_000_000_000_000), + ) + .unwrap(); + + buy_opp(&mut chain, &bonds, &depo, Uint128::new(5)); + open_opp( + &mut chain, + &bonds, + &depo, + "admin", + None, + None, + None, + Some(Uint128::new(4_347)), + Uint128::new(1_000_000_000_000_000_000), + Uint128::new(950_000_000_000_000_000), + false, + ); + buy_opp(&mut chain, &bonds, &depo, Uint128::new(500_000_000)); // 5 units + // 4.9/9 for amount purchased, due to config issu_limit of $9 and current depo price of $.98 + query_opp_parameters( + &mut chain, + &bonds, + None, + Some(Uint128::new(54444444)), + None, + None, + None, + None, + None, + None, + None, + None, + ); + + open_opp_fail( + &mut chain, + &bonds, + &atom, + "admin", + None, + Some(Uint128::new(1000000000000000000)), + None, + None, + Uint128::new(1), + Uint128::new(1), + false, + ); + open_opp( + &mut chain, + &bonds, + &atom, + "admin", + None, + Some(Uint128::new(1000000000050)), + None, + None, + Uint128::new(1), + Uint128::new(1), + false, + ); + open_opp( + &mut chain, + &bonds, + &depo, + "admin", + None, + None, + None, + Some(Uint128::new(4_347)), + Uint128::new(1_000_000_000_000_000_000), + Uint128::new(950_000_000_000_000_000), + false, + ); + close_opp(&mut chain, &bonds, &depo, "admin"); + query_opp_parameters( + &mut chain, + &bonds, + Some(Uint128::new(1000000000050)), + None, + None, + None, + None, + None, + None, + None, + None, + None, + ); +} + +fn claim(chain: &mut ContractEnsemble, bonds: &ContractLink) -> () { + let msg = bonds::ExecuteMsg::Claim { padding: None }; + + chain + .execute( + &msg, + MockEnv::new( + "secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq", + bonds.clone(), + ), + ) + .unwrap(); +} + +fn buy_opp( + chain: &mut ContractEnsemble, + bonds: &ContractLink, + depo: &ContractLink, + amount: Uint128, +) -> () { + let msg = snip20::ExecuteMsg::Send { + recipient: bonds.address.clone(), + recipient_code_hash: Some(bonds.code_hash.clone()), + amount, + msg: None, + memo: None, + padding: None, + }; + + chain + .execute( + &msg, + MockEnv::new( + "secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq", + depo.clone(), + ), + ) + .unwrap(); +} + +fn buy_opp_fail( + chain: &mut ContractEnsemble, + bonds: &ContractLink, + depo: &ContractLink, +) -> () { + let msg = snip20::ExecuteMsg::Send { + recipient: bonds.address.clone(), + recipient_code_hash: Some(bonds.code_hash.clone()), + amount: Uint128::new(2_000_000_000), //20 + msg: None, + memo: None, + padding: None, + }; + + match chain.execute( + &msg, + MockEnv::new( + "secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq", + depo.clone(), + ), + ) { + Ok(_) => assert!(false), + Err(_) => assert!(true), + } +} + +fn open_opp( + chain: &mut ContractEnsemble, + bonds: &ContractLink, + depo: &ContractLink, + sender: &str, + time_till_opp_end: Option, + bond_issuance_limit: Option, + bonding_period: Option, + discount: Option, + max_accepted_deposit_price: Uint128, + err_deposit_price: Uint128, + minting_bond: bool, +) -> () { + let mut add: u64 = 50; + if time_till_opp_end.is_some() { + add = time_till_opp_end.unwrap(); + } + + let msg = bonds::ExecuteMsg::OpenBond { + deposit_asset: Contract { + address: depo.address.clone(), + code_hash: depo.code_hash.clone(), + }, + start_time: chain.block().time, + end_time: (chain.block().time + add), + bond_issuance_limit, + bonding_period, + discount, + max_accepted_deposit_price, + err_deposit_price, + minting_bond, + padding: None, + }; + + chain + .execute(&msg, MockEnv::new(sender, bonds.clone())) + .unwrap(); +} + +fn open_opp_fail( + chain: &mut ContractEnsemble, + bonds: &ContractLink, + depo: &ContractLink, + sender: &str, + time_till_opp_end: Option, + bond_issuance_limit: Option, + bonding_period: Option, + discount: Option, + max_accepted_deposit_price: Uint128, + err_deposit_price: Uint128, + minting_bond: bool, +) -> () { + let mut add: u64 = 0; + if time_till_opp_end.is_some() { + add = time_till_opp_end.unwrap(); + } + + let msg = bonds::ExecuteMsg::OpenBond { + deposit_asset: Contract { + address: depo.address.clone(), + code_hash: depo.code_hash.clone(), + }, + start_time: chain.block().time, + end_time: (chain.block().time + add), + bond_issuance_limit, + bonding_period, + discount, + max_accepted_deposit_price, + err_deposit_price, + minting_bond, + padding: None, + }; + + match chain.execute(&msg, MockEnv::new(sender, bonds.clone())) { + Ok(_) => { + assert!(false) + } + Err(_) => { + assert!(true) + } + } +} + +fn close_opp( + chain: &mut ContractEnsemble, + bonds: &ContractLink, + depo: &ContractLink, + sender: &str, +) -> () { + let msg = bonds::ExecuteMsg::CloseBond { + deposit_asset: Contract { + address: depo.address.clone(), + code_hash: depo.code_hash.clone(), + }, + padding: None, + }; + + chain + .execute(&msg, MockEnv::new(sender, bonds.clone())) + .unwrap(); +} + +fn update_config( + chain: &mut ContractEnsemble, + bonds: &ContractLink, + sender: &str, + oracle: Option, + treasury: Option, + issued_asset: Option, + activated: Option, + bond_issuance_limit: Option, + bonding_period: Option, + discount: Option, + global_min_accepted_issued_price: Option, + global_err_issued_price: Option, + allowance_key: Option, + airdrop: Option, + query_auth: Option, +) -> () { + let msg = bonds::ExecuteMsg::UpdateConfig { + oracle, + treasury, + issued_asset, + activated, + bond_issuance_limit, + bonding_period, + discount, + global_min_accepted_issued_price, + global_err_issued_price, + allowance_key, + airdrop, + query_auth, + padding: None, + }; + + chain + .execute(&msg, MockEnv::new(sender, bonds.clone())) + .unwrap(); +} diff --git a/archived-contracts/bonds/src/tests/mod.rs b/archived-contracts/bonds/src/tests/mod.rs new file mode 100644 index 0000000..6152bd1 --- /dev/null +++ b/archived-contracts/bonds/src/tests/mod.rs @@ -0,0 +1,502 @@ +pub mod handle; +pub mod query; + +use contract_harness::harness::{ + admin::Admin, bonds::Bonds, query_auth::QueryAuth, snip20::Snip20, +}; +use shade_protocol::c_std::{Addr, StdResult}; +use shade_protocol::fadroma::core::ContractLink; +use shade_protocol::fadroma::ensemble::{ContractEnsemble, MockEnv}; +use shade_oracles_ensemble::harness::{MockBand, OracleRouter, ProxyBandOracle}; +use shade_protocol::contract_interfaces::{ + bonds, query_auth, + snip20::{self, InitialBalance}, +}; +use shade_protocol::utils::asset::Contract; + +use shade_protocol::c_std::Uint128; +use shade_admin::admin; +use shade_oracles::{ + band::{self, proxy::InstantiateMsg, ExecuteMsg::UpdateSymbolPrice}, + router, +}; + +pub fn init_contracts() -> StdResult<( + ContractEnsemble, + ContractLink, + ContractLink, + ContractLink, + ContractLink, + ContractLink, + ContractLink, + ContractLink, + ContractLink, +)> { + let mut chain = ContractEnsemble::new(50); + + // Register shade_admin + let shade_admin = chain.register(Box::new(Admin)); + let shade_admin = chain + .instantiate( + shade_admin.id, + &admin::InstantiateMsg {}, + MockEnv::new( + "admin", + ContractLink { + address: "shade_admin".into(), + code_hash: shade_admin.code_hash, + }, + ), + )? + .instance; + + // Register snip20s + let issu = chain.register(Box::new(Snip20)); + let issu = chain + .instantiate( + issu.id, + &snip20::InstantiateMsg { + name: "Issued".into(), + admin: Some(Addr::unchecked("admin")), + symbol: "ISSU".into(), + decimals: 8, + initial_balances: Some(vec![InitialBalance { + address: Addr::unchecked("admin"), + amount: Uint128::new(1_000_000_000_000_000), + }]), + prng_seed: Default::default(), + config: None, + }, + MockEnv::new( + "admin", + ContractLink { + address: "issu".into(), + code_hash: issu.code_hash, + }, + ), + )? + .instance; + + let msg = snip20::ExecuteMsg::SetViewingKey { + key: "key".to_string(), + padding: None, + }; + chain + .execute( + &msg, + MockEnv::new( + "secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq", + issu.clone(), + ), + ) + .unwrap(); + + let depo = chain.register(Box::new(Snip20)); + let depo = chain + .instantiate( + depo.id, + &snip20::InstantiateMsg { + name: "Deposit".into(), + admin: Some(Addr::unchecked("admin")), + symbol: "DEPO".into(), + decimals: 8, + initial_balances: Some(vec![InitialBalance { + address: Addr::unchecked("secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq"), + amount: Uint128::new(1_000_000_000_000_000), + }]), + prng_seed: Default::default(), + config: None, + }, + MockEnv::new( + "admin", + ContractLink { + address: "depo".into(), + code_hash: depo.code_hash, + }, + ), + )? + .instance; + + let msg = snip20::ExecuteMsg::SetViewingKey { + key: "key".to_string(), + padding: None, + }; + chain + .execute(&msg, MockEnv::new("admin", depo.clone())) + .unwrap(); + + let atom = chain.register(Box::new(Snip20)); + let atom = chain + .instantiate( + atom.id, + &snip20::InstantiateMsg { + name: "Atom".into(), + admin: Some(Addr::unchecked("admin")), + symbol: "ATOM".into(), + decimals: 6, + initial_balances: Some(vec![InitialBalance { + address: Addr::unchecked("other_user"), + amount: Uint128::new(1_000_000_000_000_000), + }]), + prng_seed: Default::default(), + config: None, + }, + MockEnv::new( + "admin", + ContractLink { + address: "atom".into(), + code_hash: atom.code_hash, + }, + ), + )? + .instance; + + let msg = snip20::ExecuteMsg::SetViewingKey { + key: "key".to_string(), + padding: None, + }; + chain + .execute(&msg, MockEnv::new("admin", atom.clone())) + .unwrap(); + + // Register mockband + let band = chain.register(Box::new(MockBand)); + let band = chain + .instantiate( + band.id, + &band::InstantiateMsg {}, + MockEnv::new( + "admin", + ContractLink { + address: "band".into(), + code_hash: band.code_hash, + }, + ), + )? + .instance; + + // Register oracles + let issu_oracle = chain.register(Box::new(ProxyBandOracle)); + let issu_oracle = chain + .instantiate( + issu_oracle.id, + &InstantiateMsg { + admin_auth: shade_oracles::common::Contract { + address: shade_admin.address.clone(), + code_hash: shade_admin.code_hash.clone(), + }, + band: shade_oracles::common::Contract { + address: band.address.clone(), + code_hash: band.code_hash.clone(), + }, + quote_symbol: "ISSU".to_string(), + }, + MockEnv::new( + "admin", + ContractLink { + address: "issu_oracle".into(), + code_hash: issu_oracle.code_hash, + }, + ), + )? + .instance; + + // Depo oracles + let depo_oracle = chain.register(Box::new(ProxyBandOracle)); + let depo_oracle = chain + .instantiate( + depo_oracle.id, + &InstantiateMsg { + admin_auth: shade_oracles::common::Contract { + address: shade_admin.address.clone(), + code_hash: shade_admin.code_hash.clone(), + }, + band: shade_oracles::common::Contract { + address: band.address.clone(), + code_hash: band.code_hash.clone(), + }, + quote_symbol: "DEPO".to_string(), + }, + MockEnv::new( + "admin", + ContractLink { + address: "depo_oracle".into(), + code_hash: depo_oracle.code_hash, + }, + ), + )? + .instance; + + // Atom oracle + let atom_oracle = chain.register(Box::new(ProxyBandOracle)); + let atom_oracle = chain + .instantiate( + atom_oracle.id, + &InstantiateMsg { + admin_auth: shade_oracles::common::Contract { + address: shade_admin.address.clone(), + code_hash: shade_admin.code_hash.clone(), + }, + band: shade_oracles::common::Contract { + address: band.address.clone(), + code_hash: band.code_hash.clone(), + }, + quote_symbol: "ATOM".to_string(), + }, + MockEnv::new( + "admin", + ContractLink { + address: "atom_oracle".into(), + code_hash: atom_oracle.code_hash, + }, + ), + )? + .instance; + + // Oracle Router + let router = chain.register(Box::new(OracleRouter)); + let router = chain + .instantiate( + router.id, + &router::InstantiateMsg { + admin_auth: shade_oracles::common::Contract { + address: shade_admin.address.clone(), + code_hash: shade_admin.code_hash.clone(), + }, + default_oracle: shade_oracles::common::Contract { + address: depo_oracle.address.clone(), + code_hash: depo_oracle.code_hash.clone(), + }, + band: shade_oracles::common::Contract { + address: band.address.clone(), + code_hash: band.code_hash.clone(), + }, + quote_symbol: "DEPO".to_string(), + }, + MockEnv::new( + "admin", + ContractLink { + address: "router".into(), + code_hash: router.code_hash, + }, + ), + )? + .instance; + + let msg = router::ExecuteMsg::UpdateRegistry { + operation: router::RegistryOperation::Add { + oracle: shade_oracles::common::Contract { + address: issu_oracle.address.clone(), + code_hash: issu_oracle.code_hash.clone(), + }, + key: "ISSU".to_string(), + }, + }; + + assert!(chain + .execute(&msg, MockEnv::new("admin", router.clone())) + .is_ok()); + + let msg = router::ExecuteMsg::UpdateRegistry { + operation: router::RegistryOperation::Add { + oracle: shade_oracles::common::Contract { + address: atom_oracle.address.clone(), + code_hash: atom_oracle.code_hash.clone(), + }, + key: "ATOM".to_string(), + }, + }; + + assert!(chain + .execute(&msg, MockEnv::new("admin", router.clone())) + .is_ok()); + + // Register query_auth + let query_auth = chain.register(Box::new(QueryAuth)); + let query_auth = chain + .instantiate( + query_auth.id, + &query_auth::InstantiateMsg { + admin_auth: Contract { + address: shade_admin.address.clone(), + code_hash: shade_admin.code_hash.clone(), + }, + prng_seed: Default::default(), + }, + MockEnv::new( + "admin", + ContractLink { + address: "query_auth".into(), + code_hash: query_auth.code_hash, + }, + ), + )? + .instance; + + // Register bonds + let bonds = chain.register(Box::new(Bonds)); + let bonds = chain + .instantiate( + bonds.id, + &bonds::InstantiateMsg { + limit_admin: Addr::unchecked("limit_admin"), + global_issuance_limit: Uint128::new(100_000_000_000_000_000), + global_minimum_bonding_period: 0, + global_maximum_discount: Uint128::new(10_000), + oracle: Contract { + address: router.address.clone(), + code_hash: router.code_hash.clone(), + }, + treasury: Addr::unchecked("admin"), + issued_asset: Contract { + address: issu.address.clone(), + code_hash: issu.code_hash.clone(), + }, + activated: true, + bond_issuance_limit: Uint128::new(100_000_000_000_000), + bonding_period: 0, + discount: Uint128::new(10_000), + global_min_accepted_issued_price: Uint128::new(10_000_000_000_000_000_000), + global_err_issued_price: Uint128::new(5_000_000_000_000_000_000), + allowance_key_entropy: "".into(), + airdrop: None, + shade_admin: Contract { + address: shade_admin.address.clone(), + code_hash: shade_admin.code_hash.clone(), + }, + query_auth: Contract { + address: query_auth.address.clone(), + code_hash: query_auth.code_hash.clone(), + }, + }, + MockEnv::new( + "admin", + ContractLink { + address: "bonds".into(), + code_hash: bonds.code_hash, + }, + ), + )? + .instance; + + Ok(( + chain, + bonds, + issu, + depo, + atom, + band, + router, + query_auth, + shade_admin, + )) +} + +pub fn set_prices( + chain: &mut ContractEnsemble, + band: &ContractLink, + issu_price: Uint128, + depo_price: Uint128, + atom_price: Uint128, +) -> StdResult<()> { + let msg = UpdateSymbolPrice { + base_symbol: "ISSU".to_string(), + quote_symbol: "ISSU".to_string(), + rate: issu_price.u128().into(), + last_updated: None, + }; + chain + .execute(&msg, MockEnv::new("admin", band.clone())) + .unwrap(); + + let msg = UpdateSymbolPrice { + base_symbol: "DEPO".to_string(), + rate: depo_price.u128().into(), + quote_symbol: "DEPO".to_string(), + last_updated: None, + }; + chain + .execute(&msg, MockEnv::new("admin", band.clone())) + .unwrap(); + + let msg = UpdateSymbolPrice { + base_symbol: "ATOM".to_string(), + rate: atom_price.u128().into(), + quote_symbol: "ATOM".to_string(), + last_updated: None, + }; + chain + .execute(&msg, MockEnv::new("admin", band.clone())) + .unwrap(); + + Ok(()) +} + +pub fn check_balances( + chain: &mut ContractEnsemble, + issu: &ContractLink, + depo: &ContractLink, + user_expected_issu: Uint128, + admin_expected_depo: Uint128, +) -> StdResult<()> { + let msg = snip20::QueryMsg::Balance { + address: Addr::unchecked("admin".to_string()), + key: "key".to_string(), + }; + + let query: snip20::QueryAnswer = chain.query(depo.address.clone(), &msg).unwrap(); + + match query { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, admin_expected_depo); + } + _ => assert!(false), + } + + let msg = snip20::QueryMsg::Balance { + address: Addr::unchecked("secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq".to_string()), + key: "key".to_string(), + }; + + let query: snip20::QueryAnswer = chain.query(issu.address.clone(), &msg).unwrap(); + + match query { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, user_expected_issu); + } + _ => assert!(false), + }; + + Ok(()) +} + +pub fn setup_admin( + chain: &mut ContractEnsemble, + shade_admins: &ContractLink, + bonds: &ContractLink, +) -> () { + let msg = admin::ExecuteMsg::AddContract { + contract_address: bonds.address.clone().to_string(), + }; + + assert!(chain + .execute(&msg, MockEnv::new("admin", shade_admins.clone())) + .is_ok()); +} + +pub fn increase_allowance( + chain: &mut ContractEnsemble, + bonds: &ContractLink, + issu: &ContractLink, +) -> () { + let msg = snip20::ExecuteMsg::IncreaseAllowance { + spender: bonds.address.clone(), + amount: Uint128::new(9_999_999_999_999_999), + expiration: None, + padding: None, + }; + + assert!(chain + .execute(&msg, MockEnv::new("admin", issu.clone())) + .is_ok()); +} diff --git a/archived-contracts/bonds/src/tests/query.rs b/archived-contracts/bonds/src/tests/query.rs new file mode 100644 index 0000000..a210bbf --- /dev/null +++ b/archived-contracts/bonds/src/tests/query.rs @@ -0,0 +1,185 @@ +use shade_protocol::c_std::{testing::*, Binary, Addr}; +use shade_protocol::fadroma::core::ContractLink; +use shade_protocol::fadroma::ensemble::ContractEnsemble; +use shade_protocol::contract_interfaces::{ + bonds, + query_auth::{self, PermitData, QueryPermit}, + snip20::helpers::Snip20Asset, +}; + +use shade_protocol::query_authentication::transaction::{PermitSignature, PubKey}; + +use shade_protocol::c_std::Uint128; + +pub fn query_no_opps(chain: &mut ContractEnsemble, bonds: &ContractLink) -> () { + let msg = bonds::QueryMsg::BondOpportunities {}; + + let query: bonds::QueryAnswer = chain.query(bonds.address.clone(), &msg).unwrap(); + + match query { + bonds::QueryAnswer::BondOpportunities { bond_opportunities } => { + assert_eq!(bond_opportunities, vec![]); + } + _ => assert!(false), + } +} + +pub fn query_opp_parameters( + chain: &mut ContractEnsemble, + bonds: &ContractLink, + issuance_limit: Option, + amount_issued: Option, + deposit_denom: Option, + start_time: Option, + end_time: Option, + bonding_period: Option, + discount: Option, + max_accepted_deposit_price: Option, + err_deposit_price: Option, + minting_bond: Option, +) -> () { + let query: bonds::QueryAnswer = chain + .query( + bonds.address.clone(), + &bonds::QueryMsg::BondOpportunities {}, + ) + .unwrap(); + + match query { + bonds::QueryAnswer::BondOpportunities { + bond_opportunities, .. + } => { + if issuance_limit.is_some() { + assert_eq!( + bond_opportunities[0].issuance_limit, + issuance_limit.unwrap() + ) + } + if amount_issued.is_some() { + assert_eq!(bond_opportunities[0].amount_issued, amount_issued.unwrap()) + } + if deposit_denom.is_some() { + assert_eq!(bond_opportunities[0].deposit_denom, deposit_denom.unwrap()) + } + if start_time.is_some() { + assert_eq!(bond_opportunities[0].start_time, start_time.unwrap()) + } + if end_time.is_some() { + assert_eq!(bond_opportunities[0].end_time, end_time.unwrap()) + } + if bonding_period.is_some() { + assert_eq!( + bond_opportunities[0].bonding_period, + bonding_period.unwrap() + ) + } + if discount.is_some() { + assert_eq!(bond_opportunities[0].discount, discount.unwrap()) + } + if max_accepted_deposit_price.is_some() { + assert_eq!( + bond_opportunities[0].max_accepted_deposit_price, + max_accepted_deposit_price.unwrap() + ) + } + if err_deposit_price.is_some() { + assert_eq!( + bond_opportunities[0].err_deposit_price, + err_deposit_price.unwrap() + ) + } + if minting_bond.is_some() { + assert_eq!(bond_opportunities[0].minting_bond, minting_bond.unwrap()) + } + } + _ => assert!(false), + }; +} + +pub fn query_acccount_parameters( + chain: &mut ContractEnsemble, + bonds: &ContractLink, + query_auth: &ContractLink, + _sender: &str, + deposit_denom: Option, + end_time: Option, + deposit_amount: Option, + deposit_price: Option, + claim_amount: Option, + claim_price: Option, + discount: Option, + discount_price: Option, +) -> () { + let permit = get_permit(); + + let deps = mock_dependencies(20, &[]); + + // Confirm that the permit is valid + assert!(permit.clone().validate(&deps.api, None).is_ok()); + + let _query: query_auth::QueryAnswer = chain + .query( + query_auth.address.clone(), + &query_auth::QueryMsg::ValidatePermit { + permit: permit.clone(), + }, + ) + .unwrap(); + + let query: bonds::QueryAnswer = chain + .query(bonds.address.clone(), &bonds::QueryMsg::Account { permit }) + .unwrap(); + + match query { + bonds::QueryAnswer::Account { pending_bonds, .. } => { + if deposit_denom.is_some() { + assert_eq!(pending_bonds[0].deposit_denom, deposit_denom.unwrap()) + } + if end_time.is_some() { + assert_eq!(pending_bonds[0].end_time, end_time.unwrap()) + } + if deposit_price.is_some() { + assert_eq!(pending_bonds[0].deposit_price, deposit_price.unwrap()) + } + if deposit_amount.is_some() { + assert_eq!(pending_bonds[0].deposit_amount, deposit_amount.unwrap()) + } + if claim_amount.is_some() { + assert_eq!(pending_bonds[0].claim_amount, claim_amount.unwrap()) + } + if claim_price.is_some() { + assert_eq!(pending_bonds[0].claim_price, claim_price.unwrap()) + } + if discount.is_some() { + assert_eq!(pending_bonds[0].discount, discount.unwrap()) + } + if discount_price.is_some() { + assert_eq!(pending_bonds[0].discount_price, discount_price.unwrap()) + } + } + _ => assert!(false), + }; +} + +fn get_permit() -> QueryPermit { + QueryPermit { + params: PermitData { + key: "key".to_string(), + data: Binary::from_base64("c29tZSBzdHJpbmc=").unwrap() + }, + signature: PermitSignature { + pub_key: PubKey::new( + Binary::from_base64( + "A9NjbriiP7OXCpoTov9ox/35+h5k0y1K0qCY/B09YzAP" + ).unwrap() + ), + signature: Binary::from_base64( + "XRzykrPmMs0ZhksNXX+eU0TM21fYBZXZogr5wYZGGy11t2ntfySuQNQJEw6D4QKvPsiU9gYMsQ259dOzMZNAEg==" + ).unwrap() + }, + account_number: None, + chain_id: Some(String::from("chain")), + sequence: None, + memo: None + } +} diff --git a/archived-contracts/dao/lp_shdswap/.cargo/config b/archived-contracts/dao/lp_shdswap/.cargo/config new file mode 100644 index 0000000..882fe08 --- /dev/null +++ b/archived-contracts/dao/lp_shdswap/.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/archived-contracts/dao/lp_shdswap/.circleci/config.yml b/archived-contracts/dao/lp_shdswap/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/archived-contracts/dao/lp_shdswap/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/dao/lp_shdswap/Cargo.toml b/archived-contracts/dao/lp_shdswap/Cargo.toml new file mode 100644 index 0000000..7c1cd10 --- /dev/null +++ b/archived-contracts/dao/lp_shdswap/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "lp_shdswap" +version = "0.1.0" +authors = ["Jack Swenson ", "Jack Sisson "] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ + "adapter", + "dao", + "dex", + "treasury", + "lp_shdswap", + "math", + "storage_plus", +] } + +[dev-dependencies] +shade-multi-test = { path = "../../../packages/multi_test", features = [ + "admin" +] } diff --git a/archived-contracts/dao/lp_shdswap/Makefile b/archived-contracts/dao/lp_shdswap/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/archived-contracts/dao/lp_shdswap/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/archived-contracts/dao/lp_shdswap/README.md b/archived-contracts/dao/lp_shdswap/README.md new file mode 100644 index 0000000..4f9ceb1 --- /dev/null +++ b/archived-contracts/dao/lp_shdswap/README.md @@ -0,0 +1,64 @@ +# Shade Swap LP Providing and Bonding +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Init](#Init) + * [DAO Adapter](/packages/shade_protocol/src/DAO_ADAPTER.md) + * [Interface](#Interface) + * Messages + * [Receive](#Receive) + * [UpdateConfig](#UpdateConfig) + * Queries + * [Config](#Config) + * [Delegations](#Delegations) + +# Introduction +The sSCRT Staking contract receives sSCRT, redeems it for SCRT, then stakes it with a validator that falls within the criteria it has been configured with. The configured `treasury` will receive all funds from claiming rewards/unbonding. + +# Sections + +## Init +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|admin | Addr | contract owner/admin; a valid bech32 address; +|treasury | Addr | contract designated to receive all outgoing funds +|viewing_key | String | Viewing Key to be set for any relevant SNIP-20 +|token_a | Contract | One token to be provided to the pool +|token_b | Contract | Other token to be provided to the pool +|pool | Contract | Pool contract to provide LP to +|bonding | Contract | Contract to bond LP for rewards + +## Interface + +### Messages +#### UpdateConfig +Updates the given values +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|config | Config | contract designated to receive all outgoing funds + +##### Response +```json +{ + "update_config": { + "status": "success" + } +} +``` + + +### Queries + +#### Config +Gets the contract's configuration variables +##### Response +```json +{ + "config": { + "config": { + "owner": "Owner address", + } + } +} +``` diff --git a/archived-contracts/dao/lp_shdswap/src/contract.rs b/archived-contracts/dao/lp_shdswap/src/contract.rs new file mode 100644 index 0000000..118dd7c --- /dev/null +++ b/archived-contracts/dao/lp_shdswap/src/contract.rs @@ -0,0 +1,198 @@ +use shade_protocol::{ + c_std::{ + entry_point, + to_binary, + Binary, + Deps, + DepsMut, + Env, + MessageInfo, + Response, + StdError, + StdResult, + Uint128, + }, + contract_interfaces::{ + dao::{ + adapter, + lp_shdswap::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}, + }, + dex::shadeswap, + }, + snip20::helpers::{register_receive, set_viewing_key_msg}, + utils::{asset::Contract, Query}, +}; + +use crate::{execute, query, storage::*}; + +#[entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + SELF_ADDRESS.save(deps.storage, &env.contract.address)?; + VIEWING_KEY.save(deps.storage, &msg.viewing_key)?; + + let pair_info: shadeswap::PairInfoResponse = + match (shadeswap::PairQuery::GetPairInfo {}.query(&deps.querier, &msg.pair)) { + Ok(info) => info, + Err(_) => { + return Err(StdError::generic_err("Failed to query pair")); + } + }; + + let token_a = match pair_info.pair.token_0 { + shadeswap::TokenType::CustomToken { + contract_addr, + token_code_hash, + } => Contract { + address: contract_addr, + code_hash: token_code_hash, + }, + _ => { + return Err(StdError::generic_err("Unsupported token type")); + } + }; + + let token_b = match pair_info.pair.token_1 { + shadeswap::TokenType::CustomToken { + contract_addr, + token_code_hash, + } => Contract { + address: contract_addr, + code_hash: token_code_hash, + }, + _ => { + return Err(StdError::generic_err("Unsupported token type")); + } + }; + + /*let staking_info: amm_pair::QueryMsgResponse::StakingContractInfo = + amm_pair::QueryMsg::GetStakingContractInfo.query( + &deps.querier, + msg.pair.code_hash.clone(), + msg.pair.address.clone(), + )?; + + //TODO need this query + let reward_token: Contract = Contract { + address: Addr::unchecked(""), + code_hash: "".into(), + };*/ + + let config = Config { + admin: match msg.admin { + None => info.sender.clone(), + Some(admin) => admin, + }, + treasury: msg.treasury, + pair: msg.pair.clone(), + token_a: token_a.clone(), + token_b: token_b.clone(), + liquidity_token: pair_info.liquidity_token.clone(), + staking_contract: Some(Contract::default()), + //staking_info.staking_contract.clone(), + // TODO: query reward token from staking contract + reward_token: None, + //TODO: add this + split: None, + }; + // TODO verify split contract + let mut assets = vec![ + token_a.clone(), + token_b.clone(), + pair_info.liquidity_token.clone(), + ]; + + if let Some(token) = config.reward_token.clone() { + assets.push(token); + } + + let mut messages = vec![]; + + // Init unbondings & msgs + for token in assets { + UNBONDING.save(deps.storage, token.address.clone(), &Uint128::zero())?; + + messages.append(&mut vec![ + set_viewing_key_msg(msg.viewing_key.clone(), None, &token)?, + register_receive(env.contract.code_hash.clone(), None, &token)?, + ]); + } + + // Init approvals to max + /* + for token in vec![token_a, token_b] { + set_allowance(&deps, &env, + config.pair.clone(), + Uint128(9_000_000_000_000_000_000_000_000), + msg.viewing_key.clone(), + token.clone(), + ); + } + */ + + CONFIG.save(deps.storage, &config)?; + + Ok(Response::new()) +} + +#[entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg { + ExecuteMsg::Receive { + sender, + from, + amount, + msg, + .. + } => execute::receive(deps, env, info, sender, from, amount, msg), + ExecuteMsg::UpdateConfig { config } => execute::try_update_config(deps, env, info, config), + ExecuteMsg::RefreshApprovals => execute::refesh_allowances(deps, env, info), + ExecuteMsg::Adapter(adapter) => match adapter { + adapter::SubExecuteMsg::Unbond { asset, amount } => { + let asset = deps.api.addr_validate(&asset)?; + execute::unbond(deps, env, info, asset, amount) + } + adapter::SubExecuteMsg::Claim { asset } => { + let asset = deps.api.addr_validate(&asset)?; + execute::claim(deps, env, info, asset) + } + adapter::SubExecuteMsg::Update { asset } => { + let asset = deps.api.addr_validate(&asset)?; + execute::update(deps, env, info, asset) + } + }, + } +} + +#[entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => to_binary(&query::config(deps)?), + QueryMsg::Adapter(adapter) => match adapter { + adapter::SubQueryMsg::Balance { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::balance(deps, asset)?) + } + adapter::SubQueryMsg::Claimable { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::claimable(deps, asset)?) + } + adapter::SubQueryMsg::Unbonding { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::unbonding(deps, asset)?) + } + adapter::SubQueryMsg::Unbondable { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::unbondable(deps, asset)?) + } + adapter::SubQueryMsg::Reserves { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::reserves(deps, asset)?) + } + }, + } +} diff --git a/archived-contracts/dao/lp_shdswap/src/execute.rs b/archived-contracts/dao/lp_shdswap/src/execute.rs new file mode 100644 index 0000000..1a77b3a --- /dev/null +++ b/archived-contracts/dao/lp_shdswap/src/execute.rs @@ -0,0 +1,261 @@ +use crate::storage::*; +use shade_protocol::{ + c_std::{ + to_binary, + Addr, + Binary, + DepsMut, + Env, + MessageInfo, + Response, + StdError, + StdResult, + Uint128, + }, + contract_interfaces::dao::{ + adapter, + lp_shdswap::{get_supported_asset, is_supported_asset, Config, ExecuteAnswer, SplitMethod}, + }, + snip20::helpers::balance_query, + utils::{asset::Contract, generic_response::ResponseStatus}, +}; + +pub fn receive( + deps: DepsMut, + _env: Env, + info: MessageInfo, + _sender: Addr, + _from: Addr, + _amount: Uint128, + _msg: Option, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if !is_supported_asset(&config, &info.sender) { + return Err(StdError::generic_err("Unrecognized Asset")); + } + + /* Base tokens in pair + * + * max out how much LP you can provide + * bond LP token into rewards + */ + + let desired_token: Contract; + + if info.sender == config.token_a.address { + desired_token = config.token_a; + println!("{}", desired_token.address); + } else if info.sender == config.token_b.address { + desired_token = config.token_b; + println!("{}", desired_token.address); + } else if info.sender == config.liquidity_token.address { + // TODO: stake lp tokens & exit + } else { + // TODO: send to treasury, non-pair rewards token + } + + // get exchange rate &dyn Storageplit tokens + match config.split { + Some(split) => { + match split { + SplitMethod::Conversion { contract: _ } => {} /* + SplitMethod::Conversion { mint } => { + // TODO: get exchange rate + mint::QueryMsg::Mint { + offer_asset: desired_token.address.clone(), + amount: Uint128(1u128.pow(desired_token.decimals)), + }.query( + ); + }, + */ + //SplitMethod::Market { contract } => { } + //SplitMethod::Lend { contract } => { } + } + } + None => {} + } + + /*let pair_info: shadeswap::PairInfoResponse = + match (shadeswap::PairQuery::GetPairInfo {}.query(&deps.querier, &msg.pair)) { + Ok(info) => info, + Err(_) => { + return Err(StdError::generic_err("Failed to query pair")); + } + }; + + if desired_token.address == pair_info.token_0.address { + denominator = pair_info.amount_0; + } else if desired_token.address == pair_info.token_1.address { + denominator = pair_info.amount_1; + } else { + return Err(StdError::generic_err(format!( + "Asset configuration conflict, pair info missing: {}", + desired_token.address.to_string() + ))); + }*/ + + let _provide_amounts: (Uint128, Uint128); + // TODO math with exchange_rate & pool ratio & received amount + + // Can be added with a trigger if too slow + //let mut messages = vec![]; + /*messages.append(set_allowance( + &deps, + &env, + config.pair.clone(), + provide_amounts.0, + msg.viewing_key.clone(), + config.token_a.clone(), + )?); + messages.append(set_allowance( + &deps, + &env, + config.pair.clone(), + provide_amounts.0, + msg.viewing_key.clone(), + config.token_b.clone(), + )?);*/ + + /* TODO + * - add LP to pair + * - stake LP tokens in staking_contract (auto complete from pair?) + */ + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Receive { + status: ResponseStatus::Success, + })?)) +} + +pub fn try_update_config( + deps: DepsMut, + _env: Env, + info: MessageInfo, + config: Config, +) -> StdResult { + let cur_config = CONFIG.load(deps.storage)?; + + if info.sender != cur_config.admin { + return Err(StdError::generic_err("unauthorized")); + } + + // Save new info + CONFIG.save(deps.storage, &config)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { + status: ResponseStatus::Success, + config, + })?), + ) +} + +pub fn refesh_allowances(_deps: DepsMut, _env: Env, _info: MessageInfo) -> StdResult { + Ok(Response::new()) +} + +/* Claim rewards and restake, hold enough for pending unbondings + * Send available unbonded funds to treasury + */ +pub fn update(deps: DepsMut, _env: Env, _info: MessageInfo, asset: Addr) -> StdResult { + //let mut messages = vec![]; + + let config = CONFIG.load(deps.storage)?; + + if !is_supported_asset(&config, &asset) { + return Err(StdError::generic_err("Unrecognized Asset")); + } + + /* Claim Rewards + * + * If rewards is an LP denom, try to re-add LP based on balances + * e.g. SILK/SHD w/ SHD rewards + * pair/split the new SHD with SILK and provide + * + * Else send direct to treasury e.g. sSCRT/sETH w/ SHD rewards + */ + Ok( + Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Update { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn unbond( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + asset: Addr, + amount: Uint128, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + //TODO: needs treasury & manager as admin, maybe just manager? + /* + if info.sender != config.admin && info.sender != config.treasury { + return Err(StdError::generic_err("unauthorized")); + } + */ + + //let mut messages = vec![]; + + if asset == config.liquidity_token.address { + /* Pull LP token out of rewards contract + * Hold for claiming + */ + } else if vec![config.token_a.address, config.token_b.address].contains(&asset) { + /* Pull LP from rewards + * Split LP into tokens A & B + * Mark requested token for claim + */ + } else { + return Err(StdError::generic_err("Unrecognized Asset")); + } + + UNBONDING.update(deps.storage, asset.clone(), |u| -> StdResult { + Ok(u.unwrap_or_else(|| Uint128::zero()) + amount) + })?; + + Ok( + Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Unbond { + status: ResponseStatus::Success, + amount, + })?), + ) +} + +pub fn claim(deps: DepsMut, env: Env, _info: MessageInfo, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if !is_supported_asset(&config, &asset) { + return Err(StdError::generic_err("Unrecognized Asset")); + } + + let asset_contract = get_supported_asset(&config, &asset); + + //let mut messages = vec![]; + + let balance = balance_query( + &deps.querier, + env.contract.address, + VIEWING_KEY.load(deps.storage)?, + &asset_contract, + )?; + + let mut claim_amount = UNBONDING.load(deps.storage, asset.clone())?; + + if balance < claim_amount { + claim_amount = balance; + } + + UNBONDING.update(deps.storage, asset.clone(), |u| -> StdResult { + Ok(u.unwrap_or_else(|| Uint128::zero()) - claim_amount) + })?; + + Ok( + Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Claim { + status: ResponseStatus::Success, + amount: claim_amount, + })?), + ) +} diff --git a/archived-contracts/dao/lp_shdswap/src/lib.rs b/archived-contracts/dao/lp_shdswap/src/lib.rs new file mode 100644 index 0000000..d4a766c --- /dev/null +++ b/archived-contracts/dao/lp_shdswap/src/lib.rs @@ -0,0 +1,44 @@ +pub mod contract; +pub mod execute; +pub mod query; +pub mod storage; + +#[cfg(test)] +mod test; + +/*#[cfg(target_arch = "wasm32")] +mod wasm { + use super::contract; + use shade_protocol::c_std::{ + do_handle, do_init, do_query, ExternalApi, ExternalQuerier, ExternalStorage, + }; + + #[no_mangle] + extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { + do_init( + &contract::init::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { + do_handle( + &contract::handle::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn query(msg_ptr: u32) -> u32 { + do_query( + &contract::query::, + msg_ptr, + ) + } + + // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available + // automatically because we `use cosmwasm_std`. +}*/ diff --git a/archived-contracts/dao/lp_shdswap/src/query.rs b/archived-contracts/dao/lp_shdswap/src/query.rs new file mode 100644 index 0000000..6dc5b04 --- /dev/null +++ b/archived-contracts/dao/lp_shdswap/src/query.rs @@ -0,0 +1,155 @@ +use shade_protocol::c_std::{ + Addr, + Deps, + StdError, + StdResult, + Uint128, +}; + +use shade_protocol::{ + contract_interfaces::dao::{ + adapter, + lp_shdswap::{get_supported_asset, is_supported_asset, QueryAnswer}, + }, +}; + +use shade_protocol::snip20::helpers::balance_query; + +use crate::storage::*; + +pub fn config(deps: Deps) -> StdResult { + Ok(QueryAnswer::Config { + config: CONFIG.load(deps.storage)?, + }) +} + +pub fn rewards(_deps: Deps) -> StdResult { + //TODO: query pending rewards from rewards contract + Ok(Uint128::zero()) +} + +pub fn balance(deps: Deps, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if !is_supported_asset(&config, &asset) { + return Err(StdError::generic_err(format!( + "Unrecognized Asset {}", + asset + ))); + } + + let balance = Uint128::zero(); + + if vec![config.token_a.address, config.token_b.address].contains(&asset) { + // Determine balance of LP, determine redemption value + } else if config.liquidity_token.address == asset { + // Check LP tokens in rewards contract + balance + } + + Ok(adapter::QueryAnswer::Balance { amount: balance }) +} + +pub fn claimable(deps: Deps, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if !is_supported_asset(&config, &asset) { + return Err(StdError::generic_err(format!( + "Unrecognized Asset {}", + asset + ))); + } + + let asset_contract = get_supported_asset(&config, &asset); + + let balance = balance_query( + &deps.querier, + SELF_ADDRESS.load(deps.storage)?, + VIEWING_KEY.load(deps.storage)?, + &asset_contract, + )?; + + let mut claimable = UNBONDING.load(deps.storage, asset.clone())?; + + if balance < claimable { + claimable = balance; + } + + Ok(adapter::QueryAnswer::Claimable { amount: claimable }) +} + +pub fn unbonding(deps: Deps, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if !is_supported_asset(&config, &asset) { + return Err(StdError::generic_err(format!( + "Unrecognized Asset {}", + asset + ))); + } + + Ok(adapter::QueryAnswer::Unbonding { + amount: UNBONDING.load(deps.storage, asset.clone())?, + }) +} + +pub fn unbondable(deps: Deps, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if !is_supported_asset(&config, &asset) { + return Err(StdError::generic_err(format!( + "Unrecognized Asset {}", + asset + ))); + } + + let unbonding = UNBONDING.load(deps.storage, asset.clone())?; + + /* Need to check LP token redemption value + */ + let unbondable = match balance(deps, asset)? { + adapter::QueryAnswer::Balance { amount } => { + if amount < unbonding { + Uint128::zero() + } else { + amount - unbonding + } + } + _ => { + return Err(StdError::generic_err("Failed to query balance")); + } + }; + + Ok(adapter::QueryAnswer::Unbondable { amount: unbondable }) +} + +pub fn reserves(deps: Deps, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if !is_supported_asset(&config, &asset) { + return Err(StdError::generic_err(format!( + "Unrecognized Asset {}", + asset + ))); + } + + let asset_contract = get_supported_asset(&config, &asset); + + let unbonding = UNBONDING.load(deps.storage, asset.clone())?; + + let balance = balance_query( + &deps.querier, + SELF_ADDRESS.load(deps.storage)?, + VIEWING_KEY.load(deps.storage)?, + &asset_contract, + )?; + + if unbonding >= balance { + return Ok(adapter::QueryAnswer::Reserves { + amount: Uint128::zero(), + }); + } else { + return Ok(adapter::QueryAnswer::Reserves { + amount: balance - unbonding, + }); + } +} diff --git a/archived-contracts/dao/lp_shdswap/src/storage.rs b/archived-contracts/dao/lp_shdswap/src/storage.rs new file mode 100644 index 0000000..3a88bc1 --- /dev/null +++ b/archived-contracts/dao/lp_shdswap/src/storage.rs @@ -0,0 +1,10 @@ +use shade_protocol::{ + c_std::{Addr, Uint128}, + contract_interfaces::dao::lp_shdswap, + secret_storage_plus::{Item, Map}, +}; + +pub const CONFIG: Item = Item::new("config"); +pub const VIEWING_KEY: Item = Item::new("viewing_key"); +pub const SELF_ADDRESS: Item = Item::new("self_address"); +pub const UNBONDING: Map = Map::new("unbonding"); diff --git a/archived-contracts/dao/lp_shdswap/src/test.rs b/archived-contracts/dao/lp_shdswap/src/test.rs new file mode 100644 index 0000000..04225b9 --- /dev/null +++ b/archived-contracts/dao/lp_shdswap/src/test.rs @@ -0,0 +1,46 @@ +/* +#[cfg(test)] +pub mod tests { + use shade_protocol::c_std::{ + testing::{ + mock_dependencies, mock_env, MockStorage, MockApi, MockQuerier + }, + Addr, + coins, from_binary, StdError, Uint128, + DepsMut, + }; + use shade_protocol::{ + treasury::{ + QueryAnswer, InstantiateMsg, ExecuteMsg, + QueryMsg, + }, + asset::Contract, + }; + + use crate::{ + contract::{ + init, handle, query, + }, + }; + + fn create_contract(address: &str, code_hash: &str) -> Contract { + let env = mock_env(address.to_string(), &[]); + return Contract{ + address: info.sender, + code_hash: code_hash.to_string() + } + } + + fn dummy_init(admin: String, viewing_key: String) -> Extern { + let mut deps = mock_dependencies(20, &[]); + let msg = InstantiateMsg { + admin: Option::from(Addr(admin.clone())), + viewing_key, + }; + let env = mock_env(admin, &coins(1000, "earth")); + let _res = init(&mut deps, env, info, msg).unwrap(); + + return deps + } +} +*/ diff --git a/archived-contracts/dao/rewards_emission/.cargo/config b/archived-contracts/dao/rewards_emission/.cargo/config new file mode 100644 index 0000000..882fe08 --- /dev/null +++ b/archived-contracts/dao/rewards_emission/.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/archived-contracts/dao/rewards_emission/.circleci/config.yml b/archived-contracts/dao/rewards_emission/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/archived-contracts/dao/rewards_emission/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/dao/rewards_emission/Cargo.toml b/archived-contracts/dao/rewards_emission/Cargo.toml new file mode 100644 index 0000000..24091b0 --- /dev/null +++ b/archived-contracts/dao/rewards_emission/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "rewards_emission" +version = "0.1.0" +authors = ["Jack Swenson "] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +cosmwasm-schema = { git = "https://github.com/CosmWasm/cosmwasm", commit = "1e05e7e" } + +shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = ["rewards_emission", "snip20", "dao", "utils", "dao-utils", "chrono"] } +schemars = "0.7" diff --git a/archived-contracts/dao/rewards_emission/Makefile b/archived-contracts/dao/rewards_emission/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/archived-contracts/dao/rewards_emission/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/archived-contracts/dao/rewards_emission/README.md b/archived-contracts/dao/rewards_emission/README.md new file mode 100644 index 0000000..55ef630 --- /dev/null +++ b/archived-contracts/dao/rewards_emission/README.md @@ -0,0 +1,65 @@ +# sSCRT Staking Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Init](#Init) + * [Admin](#Admin) + * Messages + * [UpdateConfig](#UpdateConfig) + * [Receive](#Receive) + * [Unbond](#Unbond) + * [Claim](#Claim) + * Queries + * [GetConfig](#GetConfig) + * [Delegations](#Delegations) + * [Delegation](#Delegation) +# Introduction +The sSCRT Staking contract receives sSCRT, redeems it for SCRT, then stakes it with a validator that falls within the criteria it has been configured with. The configured `treasury` will receive all funds from claiming rewards/unbonding. + +# Sections + +## Init +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|owner | Addr | contract owner/admin; a valid bech32 address; +|treasury | Addre | contract designated to receive all outgoing funds +|sscrt | Contract | sSCRT Snip-20 contract to accept for redemption/staking, all other funds will error +|validator_bounds | ValidatorBounds | criteria defining an acceptable validator to stake with + +## Admin + +### Messages +#### UpdateConfig +Updates the given values +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|owner | Addr | contract owner/admin; a valid bech32 address; +|treasury | Addre | contract designated to receive all outgoing funds +|sscrt | Contract | sSCRT Snip-20 contract to accept for redemption/staking, all other funds will error +|validator_bounds | ValidatorBounds | criteria defining an acceptable validator to stake with + +##### Response +```json +{ + "update_config": { + "status": "success" + } +} +``` + + +### Queries + +#### GetConfig +Gets the contract's configuration variables +##### Response +```json +{ + "config": { + "config": { + "owner": "Owner address", + } + } +} +``` diff --git a/archived-contracts/dao/rewards_emission/src/contract.rs b/archived-contracts/dao/rewards_emission/src/contract.rs new file mode 100644 index 0000000..1af1c0e --- /dev/null +++ b/archived-contracts/dao/rewards_emission/src/contract.rs @@ -0,0 +1,96 @@ +use shade_protocol::c_std::{ + shd_entry_point, + to_binary, + Addr, + Binary, + Deps, + DepsMut, + Env, + MessageInfo, + Response, + StdResult, +}; + +use shade_protocol::contract_interfaces::dao::rewards_emission::{ + Config, + ExecuteMsg, + InstantiateMsg, + QueryMsg, +}; + +use shade_protocol::snip20::helpers::fetch_snip20; +//use shade_protocol::contract_interfaces::dao::adapter; + +use crate::{execute, query, storage::*}; + +#[shd_entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let mut admins: Vec = msg.admins + .iter() + //TODO change unwrap + .map(|a| deps.api.addr_validate(&a).ok().unwrap()) + .collect(); + + if !admins.contains(&info.sender) { + admins.push(info.sender); + } + + let config = Config { + admins, + treasury: deps.api.addr_validate(&msg.treasury)?, + }; + + CONFIG.save(deps.storage, &config)?; + SELF_ADDRESS.save(deps.storage, &env.contract.address)?; + VIEWING_KEY.save(deps.storage, &msg.viewing_key)?; + TOKEN.save( + deps.storage, + &fetch_snip20(&msg.token.into_valid(deps.api)?, &deps.querier)?, + )?; + + Ok(Response::new()) +} + +#[shd_entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg { + ExecuteMsg::Receive { + sender, + from, + amount, + msg, + .. + } => execute::receive(deps, env, info, sender, from, amount, msg), + ExecuteMsg::UpdateConfig { config } => execute::try_update_config(deps, env, info, config), + ExecuteMsg::RegisterRewards { + token, + distributor, + amount, + cycle, + expiration, + } => execute::register_rewards( + deps, + env, + info, + token, + distributor, + amount, + cycle, + expiration, + ), + ExecuteMsg::RefillRewards {} => execute::refill_rewards(deps, env, info), + } +} + +#[shd_entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => to_binary(&query::config(deps)?), + //QueryMsg::PendingAllowance { asset } => to_binary(&query::pending_allowance(deps, asset)?), + } +} diff --git a/archived-contracts/dao/rewards_emission/src/execute.rs b/archived-contracts/dao/rewards_emission/src/execute.rs new file mode 100644 index 0000000..12322c3 --- /dev/null +++ b/archived-contracts/dao/rewards_emission/src/execute.rs @@ -0,0 +1,184 @@ +use shade_protocol::c_std::{ + to_binary, + MessageInfo, + Binary, + Env, + DepsMut, + Response, + Addr, + StdError, + StdResult, + Uint128, +}; + +use shade_protocol::snip20::helpers::{ + send_from_msg, +}; + +use shade_protocol::{ + contract_interfaces::{ + dao::{ + rewards_emission::{Config, ExecuteAnswer, Reward}, + }, + }, + utils::{ + asset::{Contract}, + generic_response::ResponseStatus, + cycle::{Cycle, exceeds_cycle, utc_now, parse_utc_datetime}, + }, +}; + +use crate::{ + storage::*, +}; + +pub fn receive( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _sender: Addr, + _from: Addr, + _amount: Uint128, + _msg: Option, +) -> StdResult { + //TODO: forward to distributor (quick fix mechanism) + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Receive { + status: ResponseStatus::Success, + })?)) +} + +pub fn try_update_config( + deps: DepsMut, + _env: Env, + info: MessageInfo, + config: Config, +) -> StdResult { + let cur_config = CONFIG.load(deps.storage)?; + + if !cur_config.admins.contains(&info.sender) { + return Err(StdError::generic_err("unauthorized")); + } + + CONFIG.save(deps.storage, &config)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { + status: ResponseStatus::Success, + })?)) +} + +pub fn refill_rewards( + deps: DepsMut, + env: Env, + info: MessageInfo, +) -> StdResult { + + let config = CONFIG.load(deps.storage)?; + let mut messages = vec![]; + + if let Some(mut reward) = REWARD.may_load(deps.storage, info.sender.clone())? { + + let token = TOKEN.load(deps.storage)?; + let now = utc_now(&env); + + // Check expiration + if let Some(expiry) = reward.expiration.clone() { + if now > parse_utc_datetime(&expiry)? { + return Err(StdError::generic_err(format!("Rewards expired on {}", expiry))); + } + } + + if exceeds_cycle(&now, &parse_utc_datetime(&reward.last_refresh.clone())?, reward.cycle.clone()) { + reward.last_refresh = now.to_rfc3339(); + REWARD.save(deps.storage, info.sender, &reward)?; + // Send from treasury + messages.push(send_from_msg( + config.treasury.clone(), + reward.distributor.address.clone(), + reward.amount, + None, + None, + None, + &token.contract.clone(), + )?); + } + else { + return Err(StdError::generic_err(format!("Last rewards were requested on {}", reward.last_refresh))); + } + } + else { + return Err(StdError::generic_err("No rewards for you")); + } + + Ok(Response::new() + .add_messages(messages) + .set_data(to_binary(&ExecuteAnswer::RefillRewards { + status: ResponseStatus::Success, + })?) + ) +} + +pub fn register_rewards( + deps: DepsMut, + env: Env, + info: MessageInfo, + token: Addr, + distributor: Contract, + amount: Uint128, + cycle: Cycle, + expiration: Option, +) -> StdResult { + + if token != TOKEN.load(deps.storage)?.contract.address { + return Err(StdError::generic_err("Invalid token")); + } + + REWARD.save(deps.storage, info.sender, &Reward { + distributor, + amount, + cycle, + //TODO change to null/zero for first refresh + last_refresh: utc_now(&env).to_rfc3339(), + expiration, + })?; + + Ok(Response::new() + .set_data(to_binary(&ExecuteAnswer::RegisterReward{ + status: ResponseStatus::Success, + })?) + ) +} + +/* +pub fn update( + deps: DepsMut, + env: Env, + info: MessageInfo, + asset: Addr, +) -> StdResult { + Ok(Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Update { + status: ResponseStatus::Success, + })?)) +} + +pub fn claim( + deps: DepsMut, + _env: Env, + asset: Addr, +) -> StdResult { + match asset_r(deps.storage).may_load(&asset.as_str().as_bytes())? { + Some(_) => Ok(Response { + messages: vec![], + log: vec![], + data: Some(to_binary(&adapter::ExecuteAnswer::Claim { + status: ResponseStatus::Success, + amount: Uint128::zero(), + })?), + }), + None => Err(StdError::generic_err(format!( + "Unrecognized Asset {}", + asset + ))), + } +} +*/ diff --git a/archived-contracts/dao/rewards_emission/src/lib.rs b/archived-contracts/dao/rewards_emission/src/lib.rs new file mode 100644 index 0000000..cce0227 --- /dev/null +++ b/archived-contracts/dao/rewards_emission/src/lib.rs @@ -0,0 +1,7 @@ +pub mod contract; +pub mod execute; +pub mod query; +pub mod storage; + +#[cfg(test)] +mod test; diff --git a/archived-contracts/dao/rewards_emission/src/query.rs b/archived-contracts/dao/rewards_emission/src/query.rs new file mode 100644 index 0000000..08b727c --- /dev/null +++ b/archived-contracts/dao/rewards_emission/src/query.rs @@ -0,0 +1,57 @@ +use shade_protocol::c_std::{ + Deps, + StdResult, +}; + +use shade_protocol::{ + contract_interfaces::dao::rewards_emission::QueryAnswer, +}; + + + + +use crate::storage::*; + +pub fn config(deps: Deps) -> StdResult { + Ok(QueryAnswer::Config { + config: CONFIG.load(deps.storage)?, + }) +} + +/* +pub fn pending_allowance( + deps: Deps, + asset: Addr, +) -> StdResult { + let token = TOKEN.load(deps.storage)?; + let config = CONFIG.load(deps.storage)?; + + let allowance = allowance_query( + &deps.querier, + config.treasury, + SELF_ADDRESS.load(deps.storage)?, + VIEWING_KEY.load(deps.storage)?, + &token.contract.clone(), + )? + .allowance; + + Ok(QueryAnswer::PendingAllowance { amount: allowance }) +} + +pub fn balance( + deps: Deps, + asset: Addr, +) -> StdResult { + let token = TOKEN.may_load(deps.storage)?; + + let balance = balance_query( + &deps.querier, + SELF_ADDRESS.load(deps.storage)?, + VIEWING_KEY.load(deps.storage)?, + &token.contract.clone(), + )? + .amount; + + Ok(adapter::QueryAnswer::Balance { amount: balance }) +} +*/ diff --git a/archived-contracts/dao/rewards_emission/src/storage.rs b/archived-contracts/dao/rewards_emission/src/storage.rs new file mode 100644 index 0000000..53e8510 --- /dev/null +++ b/archived-contracts/dao/rewards_emission/src/storage.rs @@ -0,0 +1,14 @@ +use shade_protocol::c_std::{Addr}; + +use shade_protocol::contract_interfaces::{dao::rewards_emission, snip20::helpers::Snip20Asset}; + +use shade_protocol::{ + secret_storage_plus::{Map, Item}, +}; + +pub const CONFIG: Item = Item::new("config"); +pub const SELF_ADDRESS: Item = Item::new("self_address"); +pub const VIEWING_KEY: Item = Item::new("viewing_key"); +pub const TOKEN: Item = Item::new("token"); +pub const REWARD: Map = Map::new("rewards"); + diff --git a/archived-contracts/dao/rewards_emission/src/test.rs b/archived-contracts/dao/rewards_emission/src/test.rs new file mode 100644 index 0000000..04225b9 --- /dev/null +++ b/archived-contracts/dao/rewards_emission/src/test.rs @@ -0,0 +1,46 @@ +/* +#[cfg(test)] +pub mod tests { + use shade_protocol::c_std::{ + testing::{ + mock_dependencies, mock_env, MockStorage, MockApi, MockQuerier + }, + Addr, + coins, from_binary, StdError, Uint128, + DepsMut, + }; + use shade_protocol::{ + treasury::{ + QueryAnswer, InstantiateMsg, ExecuteMsg, + QueryMsg, + }, + asset::Contract, + }; + + use crate::{ + contract::{ + init, handle, query, + }, + }; + + fn create_contract(address: &str, code_hash: &str) -> Contract { + let env = mock_env(address.to_string(), &[]); + return Contract{ + address: info.sender, + code_hash: code_hash.to_string() + } + } + + fn dummy_init(admin: String, viewing_key: String) -> Extern { + let mut deps = mock_dependencies(20, &[]); + let msg = InstantiateMsg { + admin: Option::from(Addr(admin.clone())), + viewing_key, + }; + let env = mock_env(admin, &coins(1000, "earth")); + let _res = init(&mut deps, env, info, msg).unwrap(); + + return deps + } +} +*/ diff --git a/archived-contracts/governance/.cargo/config b/archived-contracts/governance/.cargo/config new file mode 100644 index 0000000..882fe08 --- /dev/null +++ b/archived-contracts/governance/.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/archived-contracts/governance/.circleci/config.yml b/archived-contracts/governance/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/archived-contracts/governance/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/governance/Cargo.toml b/archived-contracts/governance/Cargo.toml new file mode 100644 index 0000000..06fdeba --- /dev/null +++ b/archived-contracts/governance/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "governance" +version = "0.1.0" +authors = ["Guy Garcia "] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ + "governance-impl", + "snip20_staking", + "query_auth" +] } +schemars = "0.7" + +[dev-dependencies] +rstest = "0.15" +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["multi-test", "admin"] } +serde_json = { version = "1.0.67" } +shade-multi-test = { version = "0.1.0", path = "../../packages/multi_test", features = [ "governance", "snip20", "query_auth", "admin" ] } diff --git a/archived-contracts/governance/Makefile b/archived-contracts/governance/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/archived-contracts/governance/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/archived-contracts/governance/src/contract.rs b/archived-contracts/governance/src/contract.rs new file mode 100644 index 0000000..0c20214 --- /dev/null +++ b/archived-contracts/governance/src/contract.rs @@ -0,0 +1,482 @@ +use crate::{ + handle::{ + assembly::{try_add_assembly, try_assembly_proposal, try_assembly_vote, try_set_assembly}, + assembly_msg::{ + try_add_assembly_msg, + try_add_assembly_msg_assemblies, + try_set_assembly_msg, + }, + authorized, + contract::{try_add_contract, try_add_contract_assemblies, try_set_contract}, + migration::{try_migrate, try_migrate_data, try_receive_migration_data}, + profile::{try_add_profile, try_set_profile}, + proposal::{ + try_cancel, + try_claim_funding, + try_receive_funding, + try_receive_vote, + try_trigger, + try_update, + }, + try_set_config, + try_set_runtime_state, + }, + query, +}; +use shade_protocol::{ + c_std::{ + shd_entry_point, + to_binary, + Addr, + Binary, + Deps, + DepsMut, + Env, + MessageInfo, + Reply, + Response, + StdResult, + SubMsg, + }, + contract_interfaces::governance::{ + assembly::{Assembly, AssemblyMsg}, + contract::AllowedContract, + stored_id::ID, + Config, + ExecuteMsg, + InstantiateMsg, + QueryMsg, + MSG_VARIABLE, + }, + governance::{errors::Error, AuthQuery, QueryData, RuntimeState}, + query_auth::helpers::{authenticate_permit, authenticate_vk, PermitAuthentication}, + snip20::helpers::register_receive, + utils::{ + asset::Contract, + flexible_msg::FlexibleMsg, + pad_handle_result, + pad_query_result, + storage::plus::ItemStorage, + }, +}; + +// Used to pad up responses for better privacy. +pub const RESPONSE_BLOCK_SIZE: usize = 256; + +#[shd_entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let self_contract = Contract { + address: env.contract.address, + code_hash: env.contract.code_hash.clone(), + }; + + let migrated_from: Option; + + if let Some(migrator) = msg.migrator { + ID::set_assembly(deps.storage, migrator.assembly)?; + ID::set_profile(deps.storage, migrator.profile)?; + ID::set_assembly_msg(deps.storage, migrator.assembly_msg)?; + ID::set_contract(deps.storage, migrator.contract)?; + migrated_from = Some(migrator.source); + } else { + // Setups IDs + ID::set_assembly(deps.storage, 1)?; + ID::set_profile(deps.storage, 1)?; + ID::set_assembly_msg(deps.storage, 0)?; + ID::set_contract(deps.storage, 0)?; + migrated_from = None; + } + + // Setup config + Config { + query: msg.query_auth, + treasury: msg.treasury, + vote_token: msg.vote_token.clone(), + funding_token: msg.funding_token.clone(), + migrated_from, + migrated_to: None, + } + .save(deps.storage)?; + + let mut messages = vec![]; + if let Some(vote_token) = msg.vote_token.clone() { + messages.push(SubMsg::new(register_receive( + env.contract.code_hash.clone(), + None, + &vote_token, + )?)); + } + if let Some(funding_token) = msg.funding_token.clone() { + messages.push(SubMsg::new(register_receive( + env.contract.code_hash.clone(), + None, + &funding_token, + )?)); + } + + // Only initialize the data if not migrating + if let Some(assemblies) = msg.assemblies { + // Setup public profile + assemblies.public_profile.save(deps.storage, 0)?; + + if assemblies.public_profile.funding.is_some() { + if msg.funding_token.is_none() { + return Err(Error::missing_funding_token(vec![])); + } + } + + if assemblies.public_profile.token.is_some() { + if msg.vote_token.is_none() { + return Err(Error::missing_voting_token(vec![])); + } + } + + // Setup public assembly + Assembly { + name: "public".to_string(), + metadata: "All inclusive assembly, acts like traditional governance".to_string(), + members: vec![], + profile: 0, + } + .save(deps.storage, 0)?; + + // Setup admin profile + assemblies.admin_profile.save(deps.storage, 1)?; + + if assemblies.admin_profile.funding.is_some() { + if msg.funding_token.is_none() { + return Err(Error::missing_funding_token(vec![])); + } + } + + if assemblies.admin_profile.token.is_some() { + if msg.vote_token.is_none() { + return Err(Error::missing_voting_token(vec![])); + } + } + + // Setup admin assembly + Assembly { + name: "admin".to_string(), + metadata: "Assembly of DAO admins.".to_string(), + members: assemblies.admin_members, + profile: 1, + } + .save(deps.storage, 1)?; + + // Setup generic command + AssemblyMsg { + name: "blank message".to_string(), + assemblies: vec![0, 1], + msg: FlexibleMsg { + msg: MSG_VARIABLE.to_string(), + arguments: 1, + }, + } + .save(deps.storage, 0)?; + + // Setup self contract + AllowedContract { + name: "Governance".to_string(), + metadata: "Current governance contract, this one".to_string(), + assemblies: None, + contract: self_contract.clone(), + } + .save(deps.storage, 0)?; + } + + // Set runtime + RuntimeState::Normal.save(deps.storage)?; + + Ok(Response::new() + .add_submessages(messages) + .add_attributes(vec![ + (ADDRESS_ATTRIBUTE, self_contract.address.to_string()), + (CODE_HASH_ATTRIBUTE, self_contract.code_hash), + ])) +} + +#[shd_entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg { + ExecuteMsg::Trigger { .. } // Will be deprecated + | ExecuteMsg::Cancel { .. } // Will also be deprecated + | ExecuteMsg::Update { .. } // Gets halted + | ExecuteMsg::Receive { .. } // Gets halted + | ExecuteMsg::ClaimFunding { .. } // Gets halted + | ExecuteMsg::AssemblyVote { .. } // Gets halted + | ExecuteMsg::ReceiveBalance { .. } // Gets halted + | ExecuteMsg::AssemblyProposal { .. } // Gets halted with special permissions + | ExecuteMsg::MigrateData { .. } + | ExecuteMsg::ReceiveMigrationData { .. } => {} + // Only callable by itself + _ => authorized(deps.storage, &env, &info)?, + } + + pad_handle_result( + match msg { + // State setups + ExecuteMsg::SetConfig { + query_auth, + treasury, + vote_token, + funding_token, + .. + } => try_set_config( + deps, + env, + info, + query_auth, + treasury, + vote_token, + funding_token, + ), + + ExecuteMsg::SetRuntimeState { state, .. } => { + try_set_runtime_state(deps, env, info, state) + } + + // Proposals + ExecuteMsg::Trigger { proposal, .. } => try_trigger(deps, env, info, proposal), + ExecuteMsg::Cancel { proposal, .. } => try_cancel(deps, env, info, proposal), + ExecuteMsg::Update { proposal, .. } => try_update(deps, env, info, proposal), + ExecuteMsg::Receive { + sender, + from, + amount, + msg, + memo, + .. + } => try_receive_funding(deps, env, info, sender, from, amount, msg, memo), + ExecuteMsg::ClaimFunding { id } => try_claim_funding(deps, env, info, id), + + ExecuteMsg::ReceiveBalance { + sender, + msg, + balance, + memo, + } => try_receive_vote(deps, env, info, sender, msg, balance, memo), + + // Assemblies + ExecuteMsg::AssemblyVote { proposal, vote, .. } => { + try_assembly_vote(deps, env, info, proposal, vote) + } + + ExecuteMsg::AssemblyProposal { + assembly, + title, + metadata, + msgs, + .. + } => try_assembly_proposal(deps, env, info, assembly, title, metadata, msgs), + + ExecuteMsg::AddAssembly { + name, + metadata, + members, + profile, + .. + } => try_add_assembly(deps, env, info, name, metadata, members, profile), + + ExecuteMsg::SetAssembly { + id, + name, + metadata, + members, + profile, + .. + } => try_set_assembly(deps, env, info, id, name, metadata, members, profile), + + // Assembly Msgs + ExecuteMsg::AddAssemblyMsg { + name, + msg, + assemblies, + .. + } => try_add_assembly_msg(deps, env, info, name, msg, assemblies), + + ExecuteMsg::SetAssemblyMsg { + id, + name, + msg, + assemblies, + .. + } => try_set_assembly_msg(deps, env, info, id, name, msg, assemblies), + + ExecuteMsg::AddAssemblyMsgAssemblies { id, assemblies } => { + try_add_assembly_msg_assemblies(deps, env, info, id, assemblies) + } + + // Profiles + ExecuteMsg::AddProfile { profile, .. } => try_add_profile(deps, env, info, profile), + + ExecuteMsg::SetProfile { id, profile, .. } => { + try_set_profile(deps, env, info, id, profile) + } + + // Contracts + ExecuteMsg::AddContract { + name, + metadata, + contract, + assemblies, + .. + } => try_add_contract(deps, env, info, name, metadata, contract, assemblies), + + ExecuteMsg::SetContract { + id, + name, + metadata, + contract, + disable_assemblies, + assemblies, + .. + } => try_set_contract( + deps, + env, + info, + id, + name, + metadata, + contract, + disable_assemblies, + assemblies, + ), + + ExecuteMsg::AddContractAssemblies { id, assemblies } => { + try_add_contract_assemblies(deps, env, info, id, assemblies) + } + + // Migration + ExecuteMsg::Migrate { + id, + label, + code_hash, + } => try_migrate(deps, env, info, id, label, code_hash), + + ExecuteMsg::MigrateData { data, total } => { + try_migrate_data(deps, env, info, data, total) + } + + ExecuteMsg::ReceiveMigrationData { data } => { + try_receive_migration_data(deps, env, info, data) + } + }, + RESPONSE_BLOCK_SIZE, + ) +} + +#[shd_entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + pad_query_result( + match msg { + QueryMsg::TotalProposals {} => to_binary(&query::total_proposals(deps)?), + + QueryMsg::Proposals { start, end } => to_binary(&query::proposals(deps, start, end)?), + + QueryMsg::TotalAssemblies {} => to_binary(&query::total_assemblies(deps)?), + + QueryMsg::Assemblies { start, end } => to_binary(&query::assemblies(deps, start, end)?), + + QueryMsg::TotalAssemblyMsgs {} => to_binary(&query::total_assembly_msgs(deps)?), + + QueryMsg::AssemblyMsgs { start, end } => { + to_binary(&query::assembly_msgs(deps, start, end)?) + } + + QueryMsg::TotalProfiles {} => to_binary(&query::total_profiles(deps)?), + + QueryMsg::Profiles { start, end } => to_binary(&query::profiles(deps, start, end)?), + + QueryMsg::TotalContracts {} => to_binary(&query::total_contracts(deps)?), + + QueryMsg::Contracts { start, end } => to_binary(&query::contracts(deps, start, end)?), + + QueryMsg::Config {} => to_binary(&query::config(deps)?), + + QueryMsg::WithVK { user, key, query } => { + // Query VK info + let authenticator = Config::load(deps.storage)?.query; + if !authenticate_vk(user.clone(), key, &deps.querier, &authenticator)? { + return Err(Error::bad_vk(vec![])); + } + + auth_queries(deps, query, user) + } + + QueryMsg::WithPermit { permit, query } => { + // Query Permit info + let authenticator = Config::load(deps.storage)?.query; + let res: PermitAuthentication = + authenticate_permit(permit, &deps.querier, authenticator)?; + + if res.revoked { + return Err(Error::bad_pkey(vec![])); + } + + auth_queries(deps, query, res.sender) + } + }, + RESPONSE_BLOCK_SIZE, + ) +} + +pub fn auth_queries(deps: Deps, msg: AuthQuery, user: Addr) -> StdResult { + to_binary(&match msg { + AuthQuery::Proposals { pagination } => query::user_proposals(deps, user, pagination)?, + AuthQuery::AssemblyVotes { pagination } => { + query::user_assembly_votes(deps, user, pagination)? + } + AuthQuery::Funding { pagination } => query::user_funding(deps, user, pagination)?, + AuthQuery::Votes { pagination } => query::user_votes(deps, user, pagination)?, + }) +} + +const MIGRATION_REPLY: u64 = 0; +// const PROPOSAL_REPLY: u64 = 1; +const ADDRESS_ATTRIBUTE: &str = "instantiated-address"; +const CODE_HASH_ATTRIBUTE: &str = "instantiated-code-hash"; +#[shd_entry_point] +pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> StdResult { + match msg.id { + MIGRATION_REPLY => { + // Get the returned address and code_hash + let res = msg.result.unwrap(); + let wasm = res + .events + .iter() + .find(|event| event.ty == "wasm") + .ok_or_else(|| Error::bad_event(vec![]))?; + let address = deps.api.addr_validate( + &wasm + .attributes + .iter() + .find(|attribute| attribute.key == ADDRESS_ATTRIBUTE) + .ok_or_else(|| Error::missing_migration_event(vec!["address"]))? + .value, + )?; + let code_hash = &wasm + .attributes + .iter() + .find(|attribute| attribute.key == CODE_HASH_ATTRIBUTE) + .ok_or_else(|| Error::missing_migration_event(vec!["code-hash"]))? + .value; + + let mut config = Config::load(deps.storage)?; + config.migrated_to = Some(Contract { + address, + code_hash: code_hash.to_string(), + }); + config.save(deps.storage)?; + } + // TODO: on receiving a response, subtract 1 and update that proposals status to failed + _ => return Err(Error::wrong_reply(vec![&msg.id.to_string()])), + } + + Ok(Response::new()) +} diff --git a/archived-contracts/governance/src/handle/assembly.rs b/archived-contracts/governance/src/handle/assembly.rs new file mode 100644 index 0000000..9314fcf --- /dev/null +++ b/archived-contracts/governance/src/handle/assembly.rs @@ -0,0 +1,256 @@ +use crate::handle::authorize_assembly; +use shade_protocol::{ + c_std::{ + from_binary, + to_binary, + Addr, + Binary, + DepsMut, + Env, + MessageInfo, + Response, + StdResult, + Uint128, + }, + contract_interfaces::governance::{ + assembly::{Assembly, AssemblyMsg}, + contract::AllowedContract, + profile::Profile, + proposal::{Proposal, ProposalMsg, Status}, + stored_id::{UserID, ID}, + vote::Vote, + ExecuteAnswer, + MSG_VARIABLE, + }, + governance::errors::Error, + utils::generic_response::ResponseStatus, +}; + +pub fn try_assembly_vote( + deps: DepsMut, + env: Env, + info: MessageInfo, + proposal: u32, + vote: Vote, +) -> StdResult { + authorize_assembly( + deps.storage, + &info, + Proposal::assembly(deps.storage, proposal)?, + )?; + + let sender = info.sender; + + // Check if proposal in assembly voting + if let Status::AssemblyVote { end, .. } = Proposal::status(deps.storage, proposal)? { + if end <= env.block.time.seconds() { + return Err(Error::voting_ended(vec![&end.to_string()])); + } + } else { + return Err(Error::not_assembly_voting(vec![])); + } + + let mut tally = Proposal::assembly_votes(deps.storage, proposal)?; + + // Assembly votes can only be = 1 uint + if vote.total_count()? != Uint128::new(1) { + return Err(Error::assembly_vote_qty(vec![])); + } + + // Check if user voted + if let Some(old_vote) = Proposal::assembly_vote(deps.storage, proposal, &sender)? { + tally = tally.checked_sub(&old_vote)?; + } + + Proposal::save_assembly_vote(deps.storage, proposal, &sender, &vote)?; + Proposal::save_assembly_votes(deps.storage, proposal, &tally.checked_add(&vote)?)?; + + // Save data for user queries + UserID::add_assembly_vote(deps.storage, sender.clone(), proposal.clone())?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::AssemblyVote { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn try_assembly_proposal( + deps: DepsMut, + env: Env, + info: MessageInfo, + assembly_id: u16, + title: String, + metadata: String, + msgs: Option>, +) -> StdResult { + // Get assembly + let assembly_data = authorize_assembly(deps.storage, &info, assembly_id)?; + + // Get profile + // Check if assembly is enabled + let profile = Profile::data(deps.storage, assembly_data.profile)?; + + let status: Status; + + // Check if assembly voting + if let Some(vote_settings) = Profile::assembly_voting(deps.storage, assembly_data.profile)? { + status = Status::AssemblyVote { + start: env.block.time.seconds(), + end: env.block.time.seconds() + vote_settings.deadline, + } + } + // Check if funding + else if let Some(fund_settings) = Profile::funding(deps.storage, assembly_data.profile)? { + status = Status::Funding { + amount: Uint128::zero(), + start: env.block.time.seconds(), + end: env.block.time.seconds() + fund_settings.deadline, + } + } + // Check if token voting + else if let Some(vote_settings) = Profile::public_voting(deps.storage, assembly_data.profile)? + { + status = Status::Voting { + start: env.block.time.seconds(), + end: env.block.time.seconds() + vote_settings.deadline, + } + } + // Else push directly to passed + else { + status = Status::Passed { + start: env.block.time.seconds(), + end: env.block.time.seconds() + profile.cancel_deadline, + } + } + + let processed_msgs: Option>; + if let Some(msgs) = msgs.clone() { + let mut new_msgs = vec![]; + for msg in msgs.iter() { + // Check if msg is allowed in assembly + let assembly_msg = AssemblyMsg::data(deps.storage, msg.assembly_msg)?; + if !assembly_msg.assemblies.contains(&assembly_id) { + return Err(Error::msg_not_in_assembly(vec![])); + } + + // Check if msg is allowed in contract + let contract = AllowedContract::data(deps.storage, msg.target)?; + if let Some(assemblies) = contract.assemblies { + if !assemblies.contains(&msg.target) { + return Err(Error::msg_not_in_contract(vec![])); + } + } + + let vars: Vec = from_binary(&msg.msg)?; + let binary_msg = + Binary::from(assembly_msg.msg.create_msg(vars, MSG_VARIABLE)?.as_bytes()); + + new_msgs.push(ProposalMsg { + target: msg.target, + assembly_msg: msg.assembly_msg, + msg: binary_msg, + send: msg.send.clone(), + }); + } + processed_msgs = Some(new_msgs); + } else { + processed_msgs = None; + } + + let prop = Proposal { + proposer: info.sender, + title, + metadata, + msgs: processed_msgs, + assembly: assembly_id, + assembly_vote_tally: None, + public_vote_tally: None, + status, + status_history: vec![], + funders: None, + }; + + prop.save(deps.storage)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::AssemblyProposal { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn try_add_assembly( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + name: String, + metadata: String, + members: Vec, + profile: u16, +) -> StdResult { + let id = ID::add_assembly(deps.storage)?; + + // Check that profile exists + if profile > ID::profile(deps.storage)? { + return Err(Error::item_not_found(vec![&profile.to_string(), "Profile"])); + } + + Assembly { + name, + metadata, + members, + profile, + } + .save(deps.storage, id)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::AddAssembly { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn try_set_assembly( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + id: u16, + name: Option, + metadata: Option, + members: Option>, + profile: Option, +) -> StdResult { + let mut assembly = match Assembly::may_load(deps.storage, id)? { + None => return Err(Error::item_not_found(vec![&id.to_string(), "Assembly"])), + Some(c) => c, + }; + + if let Some(name) = name { + assembly.name = name; + } + + if let Some(metadata) = metadata { + assembly.metadata = metadata + } + + if let Some(members) = members { + assembly.members = members + } + + if let Some(profile) = profile { + // Check that profile exists + if profile > ID::profile(deps.storage)? { + return Err(Error::item_not_found(vec![&profile.to_string(), "Profile"])); + } + assembly.profile = profile + } + + assembly.save(deps.storage, id)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::SetAssembly { + status: ResponseStatus::Success, + })?), + ) +} diff --git a/archived-contracts/governance/src/handle/assembly_msg.rs b/archived-contracts/governance/src/handle/assembly_msg.rs new file mode 100644 index 0000000..a74d2c1 --- /dev/null +++ b/archived-contracts/governance/src/handle/assembly_msg.rs @@ -0,0 +1,105 @@ +use shade_protocol::{ + c_std::{to_binary, DepsMut, Env, MessageInfo, Response, StdResult}, + contract_interfaces::governance::{ + assembly::AssemblyMsg, + stored_id::ID, + ExecuteAnswer, + MSG_VARIABLE, + }, + governance::errors::Error, + utils::{flexible_msg::FlexibleMsg, generic_response::ResponseStatus}, +}; + +pub fn try_add_assembly_msg( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + name: String, + msg: String, + assemblies: Vec, +) -> StdResult { + let id = ID::add_assembly_msg(deps.storage)?; + + // Check that assemblys exist + for assembly in assemblies.iter() { + if *assembly > ID::assembly(deps.storage)? { + return Err(Error::item_not_found(vec![ + &assembly.to_string(), + "Assembly", + ])); + } + } + + AssemblyMsg { + name, + assemblies, + msg: FlexibleMsg::new(msg, MSG_VARIABLE), + } + .save(deps.storage, id)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::AddAssemblyMsg { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn try_set_assembly_msg( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + id: u16, + name: Option, + msg: Option, + assemblies: Option>, +) -> StdResult { + let mut assembly_msg = match AssemblyMsg::may_load(deps.storage, id)? { + None => return Err(Error::item_not_found(vec![&id.to_string(), "AssemblyMsg"])), + Some(c) => c, + }; + + if let Some(name) = name { + assembly_msg.name = name; + } + + if let Some(msg) = msg { + assembly_msg.msg = FlexibleMsg::new(msg, MSG_VARIABLE); + } + + if let Some(assemblies) = assemblies { + assembly_msg.assemblies = assemblies; + } + + assembly_msg.save(deps.storage, id)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::SetAssemblyMsg { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn try_add_assembly_msg_assemblies( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + id: u16, + assemblies: Vec, +) -> StdResult { + let mut assembly_msg = AssemblyMsg::data(deps.storage, id)?; + + let assembly_id = ID::assembly(deps.storage)?; + for assembly in assemblies.iter() { + if assembly < &assembly_id && !assembly_msg.assemblies.contains(assembly) { + assembly_msg.assemblies.push(assembly.clone()); + } + } + + AssemblyMsg::save_data(deps.storage, id, assembly_msg)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::SetAssemblyMsg { + status: ResponseStatus::Success, + })?), + ) +} diff --git a/archived-contracts/governance/src/handle/contract.rs b/archived-contracts/governance/src/handle/contract.rs new file mode 100644 index 0000000..c6749b3 --- /dev/null +++ b/archived-contracts/governance/src/handle/contract.rs @@ -0,0 +1,133 @@ +use shade_protocol::{ + c_std::{to_binary, DepsMut, Env, MessageInfo, Response, StdResult}, + contract_interfaces::governance::{contract::AllowedContract, stored_id::ID, ExecuteAnswer}, + governance::errors::Error, + utils::{asset::Contract, generic_response::ResponseStatus}, +}; + +pub fn try_add_contract( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + name: String, + metadata: String, + contract: Contract, + assemblies: Option>, +) -> StdResult { + let id = ID::add_contract(deps.storage)?; + + if let Some(ref assemblies) = assemblies { + let assembly_id = ID::assembly(deps.storage)?; + for assembly in assemblies.iter() { + if assembly > &assembly_id { + return Err(Error::item_not_found(vec![ + &assembly.to_string(), + "Assembly", + ])); + } + } + } + + AllowedContract { + name, + metadata, + contract, + assemblies, + } + .save(deps.storage, id)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::AddContract { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn try_set_contract( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + id: u16, + name: Option, + metadata: Option, + contract: Option, + disable_assemblies: bool, + assemblies: Option>, +) -> StdResult { + if id > ID::contract(deps.storage)? { + return Err(Error::item_not_found(vec![&id.to_string(), "Contract"])); + } + + let mut allowed_contract = AllowedContract::load(deps.storage, id)?; + + if let Some(name) = name { + allowed_contract.name = name; + } + + if let Some(metadata) = metadata { + allowed_contract.metadata = metadata; + } + + if let Some(contract) = contract { + allowed_contract.contract = contract; + } + + if disable_assemblies { + allowed_contract.assemblies = None; + } else { + if let Some(assemblies) = assemblies { + let assembly_id = ID::assembly(deps.storage)?; + for assembly in assemblies.iter() { + if assembly > &assembly_id { + return Err(Error::item_not_found(vec![ + &assembly.to_string(), + "Assembly", + ])); + } + } + allowed_contract.assemblies = Some(assemblies); + } + } + + allowed_contract.save(deps.storage, id)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::AddContract { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn try_add_contract_assemblies( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + id: u16, + assemblies: Vec, +) -> StdResult { + if id > ID::contract(deps.storage)? { + return Err(Error::item_not_found(vec![&id.to_string(), "Contract"])); + } + + let mut allowed_contract = AllowedContract::data(deps.storage, id)?; + + if let Some(mut old_assemblies) = allowed_contract.assemblies { + let assembly_id = ID::assembly(deps.storage)?; + for assembly in assemblies.iter() { + if assembly <= &assembly_id && !old_assemblies.contains(assembly) { + old_assemblies.push(assembly.clone()); + } + } + allowed_contract.assemblies = Some(old_assemblies); + } else { + return Err(Error::contract_disabled(vec![])); + } + + AllowedContract::save_data(deps.storage, id, allowed_contract)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::AddContractAssemblies { + status: ResponseStatus::Success, + })?), + ) +} diff --git a/archived-contracts/governance/src/handle/migration.rs b/archived-contracts/governance/src/handle/migration.rs new file mode 100644 index 0000000..5657634 --- /dev/null +++ b/archived-contracts/governance/src/handle/migration.rs @@ -0,0 +1,217 @@ +use shade_protocol::{ + c_std::{to_binary, DepsMut, Env, MessageInfo, Response, StdResult, SubMsg}, + governance::{ + assembly::{Assembly, AssemblyMsg}, + contract::AllowedContract, + errors::Error, + profile::Profile, + stored_id::ID, + Config, + ExecuteAnswer, + ExecuteMsg::ReceiveMigrationData, + InstantiateMsg, + MigrationData, + MigrationDataAsk, + MigrationInit, + RuntimeState, + }, + utils::{ + generic_response::ResponseStatus, + storage::plus::ItemStorage, + ExecuteCallback, + InstantiateCallback, + }, + Contract, +}; +use std::cmp::min; + +pub fn try_migrate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + id: u64, + mut label: String, + code_hash: String, +) -> StdResult { + // TODO: maybe randomly generate migration label + ID::init_migration(deps.storage)?; + + let config = Config::load(deps.storage)?; + + RuntimeState::Migrated {}.save(deps.storage)?; + + label.push_str(&env.block.time.nanos().to_string()); + + let res = Response::new(); + Ok(res + .add_submessage(SubMsg::reply_on_success( + InstantiateMsg { + treasury: config.treasury, + query_auth: config.query, + assemblies: None, + funding_token: config.funding_token, + vote_token: config.vote_token, + migrator: Some(MigrationInit { + source: Contract { + address: env.contract.address, + code_hash: env.contract.code_hash, + }, + assembly: ID::assembly(deps.storage)?, + assembly_msg: ID::assembly_msg(deps.storage)?, + profile: ID::profile(deps.storage)?, + contract: ID::contract(deps.storage)?, + }), + } + .to_cosmos_msg(label, id, code_hash, vec![])?, + 0, + )) + .set_data(to_binary(&ExecuteAnswer::Migrate { + status: ResponseStatus::Success, + })?)) +} + +pub fn try_migrate_data( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + data: MigrationDataAsk, + total: u16, +) -> StdResult { + let res = Response::new(); + + let config = Config::load(deps.storage)?; + + match RuntimeState::load(deps.storage)? { + // Fail if not migrating + RuntimeState::Normal | RuntimeState::SpecificAssemblies { .. } => { + return Err(Error::migration_not_started(vec![])); + } + RuntimeState::Migrated => { + if let Some(target) = config.migrated_to { + let res_data: MigrationData; + + // Go over the migration data asks, there might be a cleaner way to do this + match data { + MigrationDataAsk::Assembly => { + let mut assemblies = vec![]; + + // Get the next id to pick + let current_id = ID::assembly_migration(deps.storage)?; + let last_id = min(current_id + total, ID::assembly(deps.storage)?); + + // iterate from next over to last + for i in current_id..=last_id { + assemblies.push((i, Assembly::load(deps.storage, i)?)); + } + + ID::set_assembly_migration(deps.storage, last_id)?; + + res_data = MigrationData::Assembly { data: assemblies }; + } + MigrationDataAsk::AssemblyMsg => { + let mut assembly_msgs = vec![]; + + let current_id = ID::assembly_msg_migration(deps.storage)?; + let last_id = min(current_id + total, ID::assembly_msg(deps.storage)?); + + for i in current_id..=last_id { + assembly_msgs.push((i, AssemblyMsg::load(deps.storage, i)?)); + } + + ID::set_assembly_msg_migration(deps.storage, last_id)?; + + res_data = MigrationData::AssemblyMsg { + data: assembly_msgs, + } + } + MigrationDataAsk::Profile => { + let mut profiles = vec![]; + + let current_id = ID::profile_migration(deps.storage)?; + let last_id = min(current_id + total, ID::profile(deps.storage)?); + + for i in current_id..=last_id { + profiles.push((i, Profile::load(deps.storage, i)?)); + } + + ID::set_profile_migration(deps.storage, last_id)?; + + res_data = MigrationData::Profile { data: profiles }; + } + MigrationDataAsk::Contract => { + let mut contracts = vec![]; + + let current_id = ID::contract_migration(deps.storage)?; + let last_id = min(current_id + total, ID::contract(deps.storage)?); + + for i in current_id..=last_id { + contracts.push((i, AllowedContract::load(deps.storage, i)?)); + } + + ID::set_contract_migration(deps.storage, last_id)?; + + res_data = MigrationData::Contract { data: contracts }; + } + }; + + return Ok(res + .add_message( + ReceiveMigrationData { data: res_data }.to_cosmos_msg(&target, vec![])?, + ) + .set_data(to_binary(&ExecuteAnswer::MigrateData { + status: ResponseStatus::Success, + })?)); + } else { + return Err(Error::migration_tartet(vec![])); + } + } + }; +} + +pub fn try_receive_migration_data( + deps: DepsMut, + _env: Env, + info: MessageInfo, + data: MigrationData, +) -> StdResult { + let res = Response::new(); + + let config = Config::load(deps.storage)?; + + if let Some(from) = config.migrated_from { + if from.address != info.sender { + return Err(Error::no_migrator(vec![])); + } + + match data { + MigrationData::Assembly { data } => { + for item in data { + item.1.save(deps.storage, item.0)?; + } + } + MigrationData::AssemblyMsg { data } => { + for item in data { + item.1.save(deps.storage, item.0)?; + } + } + MigrationData::Profile { data } => { + for item in data { + item.1.save(deps.storage, item.0)?; + } + } + MigrationData::Contract { data } => { + for item in data { + item.1.save(deps.storage, item.0)?; + } + } + } + } else { + return Err(Error::migration_tartet(vec![])); + } + + Ok( + res.set_data(to_binary(&ExecuteAnswer::ReceiveMigrationData { + status: ResponseStatus::Success, + })?), + ) +} diff --git a/archived-contracts/governance/src/handle/mod.rs b/archived-contracts/governance/src/handle/mod.rs new file mode 100644 index 0000000..59b65fe --- /dev/null +++ b/archived-contracts/governance/src/handle/mod.rs @@ -0,0 +1,135 @@ +use shade_protocol::{ + c_std::{to_binary, Addr, DepsMut, Env, MessageInfo, Response, StdResult, Storage, SubMsg}, + contract_interfaces::governance::{Config, ExecuteAnswer, RuntimeState}, + governance::{ + assembly::{Assembly, AssemblyData}, + errors::Error, + profile::Profile, + }, + snip20::helpers::register_receive, + utils::{asset::Contract, generic_response::ResponseStatus, storage::plus::ItemStorage}, +}; + +pub mod assembly; +pub mod assembly_msg; +pub mod contract; +pub mod migration; +pub mod profile; +pub mod proposal; + +/// Checks that state can be updated +pub fn assembly_state_valid(storage: &dyn Storage, assembly: u16) -> StdResult<()> { + match RuntimeState::load(storage)? { + RuntimeState::Normal => {} + RuntimeState::SpecificAssemblies { assemblies } => { + if !assemblies.contains(&assembly) { + return Err(Error::assemblies_limited(vec![])); + } + } + RuntimeState::Migrated { .. } => return Err(Error::migrated(vec![])), + }; + + Ok(()) +} + +/// Authorizes the assembly, returns assembly data to avoid redundant loading +pub fn authorize_assembly( + storage: &dyn Storage, + info: &MessageInfo, + assembly: u16, +) -> StdResult { + assembly_state_valid(storage, assembly)?; + + let data = Assembly::data(storage, assembly)?; + + // Check that the user is in the non-public assembly + if data.profile != 0 && !data.members.contains(&info.sender) { + return Err(Error::not_in_assembly(vec![ + info.sender.as_str(), + &assembly.to_string(), + ])); + }; + + // Check if enabled + if !Profile::data(storage, data.profile)?.enabled { + return Err(Error::profile_disabled(vec![&data.profile.to_string()])); + } + + Ok(data) +} + +/// Checks that the message sender is self and also not migrated +pub fn authorized(storage: &dyn Storage, env: &Env, info: &MessageInfo) -> StdResult<()> { + if info.sender != env.contract.address { + return Err(Error::not_self(vec![])); + } else if let RuntimeState::Migrated { .. } = RuntimeState::load(storage)? { + return Err(Error::migrated(vec![])); + } + + Ok(()) +} + +pub fn try_set_config( + deps: DepsMut, + env: Env, + _info: MessageInfo, + query_auth: Option, + treasury: Option, + vote_token: Option, + funding_token: Option, +) -> StdResult { + let mut messages = vec![]; + let mut config = Config::load(deps.storage)?; + + // Vote and funding tokens cannot be set to none after being set + if let Some(vote_token) = vote_token { + config.vote_token = Some(vote_token.clone()); + messages.push(SubMsg::new(register_receive( + env.contract.code_hash.clone(), + None, + &vote_token, + )?)); + } + + if let Some(funding_token) = funding_token { + config.funding_token = Some(funding_token.clone()); + messages.push(SubMsg::new(register_receive( + env.contract.code_hash.clone(), + None, + &funding_token, + )?)); + } + + if let Some(treasury) = treasury { + config.treasury = treasury; + } + + if let Some(query_auth) = query_auth { + config.query = query_auth; + } + + config.save(deps.storage)?; + Ok(Response::new() + .set_data(to_binary(&ExecuteAnswer::SetConfig { + status: ResponseStatus::Success, + })?) + .add_submessages(messages)) +} + +pub fn try_set_runtime_state( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + state: RuntimeState, +) -> StdResult { + if let RuntimeState::Migrated { .. } = state { + return Err(Error::migrated(vec![])); + } + + state.save(deps.storage)?; + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::SetRuntimeState { + status: ResponseStatus::Success, + })?), + ) +} diff --git a/archived-contracts/governance/src/handle/profile.rs b/archived-contracts/governance/src/handle/profile.rs new file mode 100644 index 0000000..6c8bc7b --- /dev/null +++ b/archived-contracts/governance/src/handle/profile.rs @@ -0,0 +1,77 @@ +use shade_protocol::{ + c_std::{to_binary, DepsMut, Env, MessageInfo, Response, StdResult}, + contract_interfaces::governance::{ + profile::{Profile, UpdateProfile}, + stored_id::ID, + ExecuteAnswer, + }, + governance::errors::Error, + utils::generic_response::ResponseStatus, +}; + +pub fn try_add_profile( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + profile: Profile, +) -> StdResult { + let id = ID::add_profile(deps.storage)?; + profile.save(deps.storage, id)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::AddProfile { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn try_set_profile( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + id: u16, + new_profile: UpdateProfile, +) -> StdResult { + let mut profile = match Profile::may_load(deps.storage, id)? { + None => return Err(Error::item_not_found(vec![&id.to_string(), "Profile"])), + Some(p) => p, + }; + + if let Some(name) = new_profile.name { + profile.name = name; + } + + if let Some(enabled) = new_profile.enabled { + profile.enabled = enabled; + } + + if new_profile.disable_assembly { + profile.assembly = None; + } else if let Some(assembly) = new_profile.assembly { + profile.assembly = Some(assembly.update_profile(&profile.assembly)?) + } + + if new_profile.disable_funding { + profile.funding = None; + } else if let Some(funding) = new_profile.funding { + profile.funding = Some(funding.update_profile(&profile.funding)?) + } + + if new_profile.disable_token { + profile.token = None; + } else if let Some(token) = new_profile.token { + profile.token = Some(token.update_profile(&profile.token)?) + } + + if let Some(cancel_deadline) = new_profile.cancel_deadline { + profile.cancel_deadline = cancel_deadline; + } + + profile.save(deps.storage, id)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::SetProfile { + status: ResponseStatus::Success, + })?), + ) +} diff --git a/archived-contracts/governance/src/handle/proposal.rs b/archived-contracts/governance/src/handle/proposal.rs new file mode 100644 index 0000000..bf4c30c --- /dev/null +++ b/archived-contracts/governance/src/handle/proposal.rs @@ -0,0 +1,541 @@ +use crate::handle::assembly_state_valid; +use shade_protocol::{ + c_std::{ + from_binary, + to_binary, + Addr, + Binary, + DepsMut, + Env, + MessageInfo, + Response, + StdResult, + SubMsg, + Uint128, + WasmMsg, + }, + contract_interfaces::{ + governance::{ + assembly::Assembly, + contract::AllowedContract, + profile::{Count, Profile, VoteProfile}, + proposal::{Funding, Proposal, Status}, + stored_id::UserID, + vote::{ReceiveBalanceMsg, TalliedVotes, Vote}, + Config, + ExecuteAnswer, + }, + staking::snip20_staking, + }, + governance::errors::Error, + snip20::helpers::send_msg, + utils::{asset::Contract, generic_response::ResponseStatus, storage::plus::ItemStorage, Query}, +}; + +pub fn try_trigger( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + proposal: u32, +) -> StdResult { + let mut messages = vec![]; + + let status = Proposal::status(deps.storage, proposal)?; + if let Status::Passed { .. } = status { + let mut history = Proposal::status_history(deps.storage, proposal)?; + history.push(status); + Proposal::save_status_history(deps.storage, proposal, history)?; + Proposal::save_status(deps.storage, proposal, Status::Success)?; + + // Trigger the msg + let proposal_msg = Proposal::msg(deps.storage, proposal)?; + if let Some(prop_msgs) = proposal_msg { + for (_i, prop_msg) in prop_msgs.iter().enumerate() { + let contract = AllowedContract::data(deps.storage, prop_msg.target)?.contract; + let msg = WasmMsg::Execute { + contract_addr: contract.address.into(), + code_hash: contract.code_hash, + msg: prop_msg.msg.clone(), + funds: prop_msg.send.clone(), + }; + // TODO: set to reply on error where ID is propID + 1 + // TODO: set proposal status to success + messages.push(SubMsg::new(msg)); + } + } + } else { + return Err(Error::not_passed(vec![])); + } + + Ok(Response::new() + .add_submessages(messages) + .set_data(to_binary(&ExecuteAnswer::Trigger { + status: ResponseStatus::Success, + })?)) +} + +pub fn try_cancel( + deps: DepsMut, + env: Env, + _info: MessageInfo, + proposal: u32, +) -> StdResult { + // Check if passed, and check if current time > cancel time + let status = Proposal::status(deps.storage, proposal)?; + if let Status::Passed { start: _, end } = status { + if env.block.time.seconds() < end { + return Err(Error::cannot_cancel(vec![&end.to_string()])); + } + let mut history = Proposal::status_history(deps.storage, proposal)?; + history.push(status); + Proposal::save_status_history(deps.storage, proposal, history)?; + Proposal::save_status(deps.storage, proposal, Status::Canceled)?; + } else { + return Err(Error::cannot_cancel(vec![&(-1).to_string()])); + } + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Cancel { + status: ResponseStatus::Success, + })?)) +} + +fn validate_votes(votes: Vote, total_power: Uint128, settings: VoteProfile) -> Status { + let tally = TalliedVotes::tally(votes); + + let threshold = match settings.threshold { + Count::Percentage { percent } => total_power.multiply_ratio(percent, Uint128::new(10000)), + Count::LiteralCount { count } => count, + }; + + let yes_threshold = match settings.yes_threshold { + Count::Percentage { percent } => { + (tally.yes + tally.no).multiply_ratio(percent, Uint128::new(10000)) + } + Count::LiteralCount { count } => count, + }; + + let veto_threshold = match settings.veto_threshold { + Count::Percentage { percent } => { + (tally.yes + tally.no).multiply_ratio(percent, Uint128::new(10000)) + } + Count::LiteralCount { count } => count, + }; + + let new_status: Status; + + if tally.total < threshold { + new_status = Status::Expired; + } else if tally.veto >= veto_threshold { + new_status = Status::Vetoed { + slash_percent: Uint128::zero(), + }; + } else if tally.yes < yes_threshold { + new_status = Status::Rejected; + } else { + new_status = Status::Success; + } + + return new_status; +} + +pub fn try_update( + deps: DepsMut, + env: Env, + _info: MessageInfo, + proposal: u32, +) -> StdResult { + // TODO: see if this can get cleaned up + + let mut history = Proposal::status_history(deps.storage, proposal)?; + let status = Proposal::status(deps.storage, proposal)?; + let mut new_status: Status; + + let assembly = Proposal::assembly(deps.storage, proposal)?; + let profile = Assembly::data(deps.storage, assembly)?.profile; + + // Halt all proposal updates + assembly_state_valid(deps.storage, assembly)?; + + let mut messages = vec![]; + + match status.clone() { + Status::AssemblyVote { start: _, end } => { + if end > env.block.time.seconds() { + return Err(Error::cannot_update(vec!["AssemblyVote", &end.to_string()])); + } + + let votes = Proposal::assembly_votes(deps.storage, proposal)?; + + // Total power is equal to the total amount of assembly members + let total_power = + Uint128::new(Assembly::data(deps.storage, assembly)?.members.len() as u128); + + // Try to load, if not then assume it was updated after proposal creation but before section end + let mut vote_conclusion: Status; + if let Some(settings) = Profile::assembly_voting(deps.storage, profile)? { + vote_conclusion = validate_votes(votes, total_power, settings); + } else { + vote_conclusion = Status::Success + } + + if let Status::Vetoed { .. } = vote_conclusion { + // Cant veto an assembly vote + vote_conclusion = Status::Rejected; + } + + // Try to load the next steps, if all are none then pass + if let Status::Success = vote_conclusion { + if let Some(setting) = Profile::funding(deps.storage, profile)? { + vote_conclusion = Status::Funding { + amount: Uint128::zero(), + start: env.block.time.seconds(), + end: env.block.time.seconds() + setting.deadline, + } + } else if let Some(setting) = Profile::public_voting(deps.storage, profile)? { + vote_conclusion = Status::Voting { + start: env.block.time.seconds(), + end: env.block.time.seconds() + setting.deadline, + } + } else { + vote_conclusion = Status::Passed { + start: env.block.time.seconds(), + end: env.block.time.seconds() + + Profile::data(deps.storage, profile)?.cancel_deadline, + } + } + } + + new_status = vote_conclusion; + } + Status::Funding { amount, end, .. } => { + // This helps combat the possibility of the profile changing + // before another proposal is finished + if let Some(setting) = Profile::funding(deps.storage, profile)? { + // Check if deadline or funding limit reached + if amount >= setting.required { + new_status = Status::Passed { + start: env.block.time.seconds(), + end: env.block.time.seconds() + + Profile::data(deps.storage, profile)?.cancel_deadline, + } + } else if end > env.block.time.seconds() { + return Err(Error::cannot_update(vec!["Funding", &end.to_string()])); + } else { + new_status = Status::Expired; + } + } else { + new_status = Status::Passed { + start: env.block.time.seconds(), + end: env.block.time.seconds() + + Profile::data(deps.storage, profile)?.cancel_deadline, + } + } + + if let Status::Passed { .. } = new_status { + if let Some(setting) = Profile::public_voting(deps.storage, profile)? { + new_status = Status::Voting { + start: env.block.time.seconds(), + end: env.block.time.seconds() + setting.deadline, + } + } + } + } + Status::Voting { start: _, end } => { + if end > env.block.time.seconds() { + return Err(Error::cannot_update(vec!["Voting", &end.to_string()])); + } + + let config = Config::load(deps.storage)?; + let votes = Proposal::public_votes(deps.storage, proposal)?; + + let query: snip20_staking::QueryAnswer = snip20_staking::QueryMsg::TotalStaked {} + .query(&deps.querier, &config.vote_token.unwrap())?; + + // Get total staking power + let total_power = match query { + snip20_staking::QueryAnswer::TotalStaked { tokens, .. } => tokens.into(), + _ => return Err(Error::unexpected_query_response(vec![])), + }; + + let mut vote_conclusion: Status; + + if let Some(settings) = Profile::public_voting(deps.storage, profile)? { + vote_conclusion = validate_votes(votes, total_power, settings); + } else { + vote_conclusion = Status::Success + } + + if let Status::Vetoed { .. } = vote_conclusion { + // Send the funding amount to the treasury + if let Some(profile) = Profile::funding(deps.storage, profile)? { + // Look for the history and find funding + for s in history.iter() { + // Check if it has funding history + if let Status::Funding { amount, .. } = s { + let loss = profile.veto_deposit_loss.clone(); + vote_conclusion = Status::Vetoed { + slash_percent: loss, + }; + + let send_amount = amount.multiply_ratio(100000u128, loss); + if send_amount != Uint128::zero() { + let config = Config::load(deps.storage)?; + // Update slash amount + messages.push(send_msg( + config.treasury.into(), + Uint128::new(send_amount.u128()), + None, + None, + None, + &config.funding_token.unwrap(), + )?); + } + break; + } + } + } + } else if let Status::Success = vote_conclusion { + vote_conclusion = Status::Passed { + start: env.block.time.seconds(), + end: env.block.time.seconds() + + Profile::data(deps.storage, profile)?.cancel_deadline, + } + } + + new_status = vote_conclusion; + } + _ => return Err(Error::state_update(vec![])), + } + + // Add old status to history + history.push(status); + Proposal::save_status_history(deps.storage, proposal, history)?; + // Save new status + Proposal::save_status(deps.storage, proposal, new_status.clone())?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Update { + status: ResponseStatus::Success, + })?)) +} + +pub fn try_receive_funding( + deps: DepsMut, + env: Env, + info: MessageInfo, + _sender: Addr, + from: Addr, + amount: Uint128, + msg: Option, + _memo: Option, +) -> StdResult { + // Check if sent token is the funding token + let funding_token: Contract; + if let Some(token) = Config::load(deps.storage)?.funding_token { + funding_token = token.clone(); + if info.sender != token.address { + return Err(Error::missing_funding_token(vec![])); + } + } else { + return Err(Error::missing_funding_token(vec![])); + } + + // Check if msg contains the proposal information + let proposal: u32; + if let Some(msg) = msg { + proposal = from_binary(&msg)?; + } else { + return Err(Error::funding_msg_not_set(vec![])); + } + + // Check if proposal is in funding stage + let mut return_amount = Uint128::zero(); + + let status = Proposal::status(deps.storage, proposal)?; + if let Status::Funding { + amount: funded, + start, + end, + } = status + { + // Check if proposal funding stage is set or funding limit already set + if env.block.time.seconds() >= end { + return Err(Error::funding_limit_reached(vec![])); + } + + let mut new_fund = amount + funded; + + let assembly = Proposal::assembly(deps.storage, proposal)?; + + // Validate that this action is possible + assembly_state_valid(deps.storage, assembly)?; + + let profile = Assembly::data(deps.storage, assembly)?.profile; + if let Some(funding_profile) = Profile::funding(deps.storage, profile)? { + if funding_profile.required == funded { + return Err(Error::completely_funded(vec![])); + } + + if funding_profile.required < new_fund { + return_amount = new_fund.checked_sub(funding_profile.required)?; + new_fund = funding_profile.required; + } + } else { + return Err(Error::no_funding_profile(vec![])); + } + + // Store the funder information and update the current funding data + Proposal::save_status(deps.storage, proposal, Status::Funding { + amount: new_fund, + start, + end, + })?; + + // Either add or update funder + let mut funder_amount = amount.checked_sub(return_amount)?; + let mut funders = Proposal::funders(deps.storage, proposal)?; + if funders.contains(&from) { + funder_amount += Proposal::funding(deps.storage, proposal, &from)?.amount; + } else { + funders.push(from.clone()); + Proposal::save_funders(deps.storage, proposal, funders)?; + } + Proposal::save_funding(deps.storage, proposal, &from, Funding { + amount: funder_amount, + claimed: false, + })?; + + // Add funding info to cross search + UserID::add_funding(deps.storage, from.clone(), proposal.clone())?; + } else { + return Err(Error::no_funding_state(vec![])); + } + + let mut messages = vec![]; + if return_amount != Uint128::zero() { + messages.push(send_msg( + from.into(), + return_amount.into(), + None, + None, + None, + &funding_token, + )?); + } + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Receive { + status: ResponseStatus::Success, + })?)) +} + +pub fn try_claim_funding( + deps: DepsMut, + _env: Env, + info: MessageInfo, + id: u32, +) -> StdResult { + let reduction = match Proposal::status(deps.storage, id)? { + Status::AssemblyVote { .. } | Status::Funding { .. } | Status::Voting { .. } => { + return Err(Error::funding_not_claimable(vec![])); + } + Status::Vetoed { slash_percent } => slash_percent, + _ => Uint128::zero(), + }; + + let funding = Proposal::funding(deps.storage, id, &info.sender)?; + + if funding.claimed { + return Err(Error::funding_claimed(vec![])); + } + + let return_amount = funding.amount.checked_sub( + funding + .amount + .multiply_ratio(reduction, Uint128::new(10000)), + )?; + + if return_amount == Uint128::zero() { + return Err(Error::funding_nothing(vec![])); + } + + let funding_token = match Config::load(deps.storage)?.funding_token { + None => return Err(Error::missing_funding_token(vec![])), + Some(token) => token, + }; + + Ok(Response::new() + .add_message(send_msg( + info.sender.into(), + return_amount.into(), + None, + None, + None, + &funding_token, + )?) + .set_data(to_binary(&ExecuteAnswer::ClaimFunding { + status: ResponseStatus::Success, + })?)) +} + +pub fn try_receive_vote( + deps: DepsMut, + env: Env, + info: MessageInfo, + sender: Addr, + msg: Option, + balance: Uint128, + _memo: Option, +) -> StdResult { + if let Some(token) = Config::load(deps.storage)?.vote_token { + if info.sender != token.address { + return Err(Error::sender_funding(vec![])); + } + } else { + return Err(Error::missing_funding_token(vec![])); + } + + let vote: Vote; + let proposal: u32; + if let Some(msg) = msg { + let decoded_msg: ReceiveBalanceMsg = from_binary(&msg)?; + vote = decoded_msg.vote; + proposal = decoded_msg.proposal; + + // Verify that total does not exceed balance + let total_votes = vote.yes.checked_add( + vote.no + .checked_add(vote.abstain.checked_add(vote.no_with_veto)?)?, + )?; + + if total_votes > balance { + return Err(Error::voting_balance(vec![])); + } + } else { + return Err(Error::voting_msg(vec![])); + } + + // Check if proposal in assembly voting + if let Status::Voting { end, .. } = Proposal::status(deps.storage, proposal)? { + if end <= env.block.time.seconds() { + return Err(Error::voting_time(vec![&end.to_string()])); + } + } else { + return Err(Error::voting_not_state(vec![])); + } + + let mut tally = Proposal::public_votes(deps.storage, proposal)?; + + // Check if user voted + if let Some(old_vote) = Proposal::public_vote(deps.storage, proposal, &sender)? { + tally = tally.checked_sub(&old_vote)?; + } + + Proposal::save_public_vote(deps.storage, proposal, &sender, &vote)?; + Proposal::save_public_votes(deps.storage, proposal, &tally.checked_add(&vote)?)?; + UserID::add_vote(deps.storage, sender.clone(), proposal)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::ReceiveBalance { + status: ResponseStatus::Success, + })?), + ) +} diff --git a/archived-contracts/governance/src/lib.rs b/archived-contracts/governance/src/lib.rs new file mode 100644 index 0000000..dda1443 --- /dev/null +++ b/archived-contracts/governance/src/lib.rs @@ -0,0 +1,8 @@ +pub mod contract; +pub struct QueryAuth; + +pub mod handle; +pub mod query; + +#[cfg(test)] +pub mod tests; diff --git a/archived-contracts/governance/src/query.rs b/archived-contracts/governance/src/query.rs new file mode 100644 index 0000000..824d6a5 --- /dev/null +++ b/archived-contracts/governance/src/query.rs @@ -0,0 +1,229 @@ +use shade_protocol::{ + c_std::{Addr, Deps, StdResult}, + contract_interfaces::governance::{ + assembly::{Assembly, AssemblyMsg}, + contract::AllowedContract, + profile::Profile, + proposal::Proposal, + stored_id::ID, + Config, + QueryAnswer, + }, + governance::{errors::Error, stored_id::UserID, Pagination, ResponseWithID}, + utils::storage::plus::ItemStorage, +}; +use std::cmp::min; + +pub fn config(deps: Deps) -> StdResult { + Ok(QueryAnswer::Config { + config: Config::load(deps.storage)?, + }) +} + +pub fn total_proposals(deps: Deps) -> StdResult { + Ok(QueryAnswer::Total { + total: ID::proposal(deps.storage)?.checked_add(1).unwrap(), + }) +} + +pub fn proposals(deps: Deps, start: u32, end: u32) -> StdResult { + let mut items = vec![]; + let total = ID::proposal(deps.storage)?; + + if start > total { + return Err(Error::item_not_found(vec![&start.to_string(), "Proposal"])); + } + + for i in start..=min(end, total) { + items.push(Proposal::load(deps.storage, i)?); + } + + Ok(QueryAnswer::Proposals { props: items }) +} + +pub fn total_profiles(deps: Deps) -> StdResult { + Ok(QueryAnswer::Total { + total: ID::profile(deps.storage)?.checked_add(1).unwrap() as u32, + }) +} + +pub fn profiles(deps: Deps, start: u16, end: u16) -> StdResult { + let mut items = vec![]; + let total = ID::profile(deps.storage)?; + + if start > total { + return Err(Error::item_not_found(vec![&start.to_string(), "Profile"])); + } + + for i in start..=min(end, total) { + items.push(Profile::load(deps.storage, i)?); + } + + Ok(QueryAnswer::Profiles { profiles: items }) +} + +pub fn total_assemblies(deps: Deps) -> StdResult { + Ok(QueryAnswer::Total { + total: ID::assembly(deps.storage)?.checked_add(1).unwrap() as u32, + }) +} + +pub fn assemblies(deps: Deps, start: u16, end: u16) -> StdResult { + let mut items = vec![]; + let total = ID::assembly(deps.storage)?; + + if start > total { + return Err(Error::item_not_found(vec![&start.to_string(), "Assembly"])); + } + + for i in start..=min(end, total) { + items.push(Assembly::load(deps.storage, i)?); + } + + Ok(QueryAnswer::Assemblies { assemblies: items }) +} + +pub fn total_assembly_msgs(deps: Deps) -> StdResult { + Ok(QueryAnswer::Total { + total: ID::assembly_msg(deps.storage)?.checked_add(1).unwrap() as u32, + }) +} + +pub fn assembly_msgs(deps: Deps, start: u16, end: u16) -> StdResult { + let mut items = vec![]; + let total = ID::assembly_msg(deps.storage)?; + + if start > total { + return Err(Error::item_not_found(vec![ + &start.to_string(), + "AssemblyMsg", + ])); + } + + for i in start..=min(end, total) { + items.push(AssemblyMsg::load(deps.storage, i)?); + } + + Ok(QueryAnswer::AssemblyMsgs { msgs: items }) +} + +pub fn total_contracts(deps: Deps) -> StdResult { + Ok(QueryAnswer::Total { + total: ID::contract(deps.storage)?.checked_add(1).unwrap() as u32, + }) +} + +pub fn contracts(deps: Deps, start: u16, end: u16) -> StdResult { + let mut items = vec![]; + let total = ID::contract(deps.storage)?; + + if start > total { + return Err(Error::item_not_found(vec![&start.to_string(), "Contract"])); + } + + for i in start..=min(end, total) { + items.push(AllowedContract::load(deps.storage, i)?); + } + + Ok(QueryAnswer::Contracts { contracts: items }) +} + +pub fn user_proposals(deps: Deps, user: Addr, pagination: Pagination) -> StdResult { + let total = UserID::total_proposals(deps.storage, user.clone())?; + + let start = pagination + .amount + .checked_mul(pagination.page as u32) + .unwrap(); + let mut props = vec![]; + + for i in start..start + pagination.amount { + let id = match UserID::proposal(deps.storage, user.clone(), i) { + Ok(id) => id, + Err(_) => break, + }; + + props.push(ResponseWithID { + prop_id: id, + data: Proposal::load(deps.storage, id)?, + }); + } + + Ok(QueryAnswer::UserProposals { props, total }) +} + +pub fn user_assembly_votes( + deps: Deps, + user: Addr, + pagination: Pagination, +) -> StdResult { + let total = UserID::total_assembly_votes(deps.storage, user.clone())?; + + let start = pagination + .amount + .checked_mul(pagination.page as u32) + .unwrap(); + let mut votes = vec![]; + + for i in start..start + pagination.amount { + let id = match UserID::assembly_vote(deps.storage, user.clone(), i) { + Ok(id) => id, + Err(_) => break, + }; + + votes.push(ResponseWithID { + prop_id: id, + data: Proposal::assembly_vote(deps.storage, id, &user)?.unwrap(), + }); + } + + Ok(QueryAnswer::UserAssemblyVotes { votes, total }) +} + +pub fn user_funding(deps: Deps, user: Addr, pagination: Pagination) -> StdResult { + let total = UserID::total_funding(deps.storage, user.clone())?; + + let start = pagination + .amount + .checked_mul(pagination.page as u32) + .unwrap(); + let mut funds = vec![]; + + for i in start..start + pagination.amount { + let id = match UserID::funding(deps.storage, user.clone(), i as u32) { + Ok(id) => id, + Err(_) => break, + }; + + funds.push(ResponseWithID { + prop_id: id, + data: Proposal::funding(deps.storage, id, &user)?, + }); + } + + Ok(QueryAnswer::UserFunding { funds, total }) +} + +pub fn user_votes(deps: Deps, user: Addr, pagination: Pagination) -> StdResult { + let total = UserID::total_votes(deps.storage, user.clone())?; + + let start = pagination + .amount + .checked_mul(pagination.page as u32) + .unwrap(); + let mut votes = vec![]; + + for i in start..start + pagination.amount { + let id = match UserID::votes(deps.storage, user.clone(), i) { + Ok(id) => id, + Err(_) => break, + }; + + votes.push(ResponseWithID { + prop_id: id, + data: Proposal::public_vote(deps.storage, id, &user)?.unwrap(), + }); + } + + Ok(QueryAnswer::UserVotes { votes, total }) +} diff --git a/archived-contracts/governance/src/tests/handle/assembly.rs b/archived-contracts/governance/src/tests/handle/assembly.rs new file mode 100644 index 0000000..9dae98f --- /dev/null +++ b/archived-contracts/governance/src/tests/handle/assembly.rs @@ -0,0 +1,105 @@ +use crate::tests::{admin_only_governance, get_assemblies}; +use shade_protocol::{c_std::Addr, contract_interfaces::governance, utils::ExecuteCallback}; + +#[test] +fn add_assembly() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + governance::ExecuteMsg::AddAssembly { + name: "Other assembly".to_string(), + metadata: "some data".to_string(), + members: vec![], + profile: 1, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let assemblies = get_assemblies(&mut chain, &gov, 0, 2).unwrap(); + + assert_eq!(assemblies.len(), 3); +} + +#[test] +fn unauthorised_add_assembly() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + assert!( + governance::ExecuteMsg::AddAssembly { + name: "Other assembly".to_string(), + metadata: "some data".to_string(), + members: vec![], + profile: 1, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + Addr::unchecked("random"), + &[] + ) + .is_err() + ) +} + +#[test] +fn set_assembly() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + let old_assembly = get_assemblies(&mut chain, &gov, 1, 2).unwrap()[0].clone(); + + governance::ExecuteMsg::SetAssembly { + id: 1, + name: Some("Random name".to_string()), + metadata: Some("data".to_string()), + members: None, + profile: None, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let new_assembly = get_assemblies(&mut chain, &gov, 1, 2).unwrap()[0].clone(); + + assert_ne!(new_assembly.name, old_assembly.name); + assert_ne!(new_assembly.metadata, old_assembly.metadata); + assert_eq!(new_assembly.members, old_assembly.members); + assert_eq!(new_assembly.profile, old_assembly.profile); +} + +#[test] +fn unauthorised_set_assembly() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + assert!( + governance::ExecuteMsg::SetAssembly { + id: 1, + name: Some("Random name".to_string()), + metadata: Some("data".to_string()), + members: None, + profile: None, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + Addr::unchecked("random"), + &[] + ) + .is_err() + ) +} diff --git a/archived-contracts/governance/src/tests/handle/assembly_msg.rs b/archived-contracts/governance/src/tests/handle/assembly_msg.rs new file mode 100644 index 0000000..013a371 --- /dev/null +++ b/archived-contracts/governance/src/tests/handle/assembly_msg.rs @@ -0,0 +1,102 @@ +use crate::tests::{admin_only_governance, get_assembly_msgs}; +use shade_protocol::{c_std::Addr, contract_interfaces::governance, utils::ExecuteCallback}; + +#[test] +fn add_assembly_msg() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + governance::ExecuteMsg::AddAssemblyMsg { + name: "Some Assembly name".to_string(), + msg: "{}".to_string(), + assemblies: vec![0], + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let assemblies = get_assembly_msgs(&mut chain, &gov, 0, 1).unwrap(); + + assert_eq!(assemblies.len(), 2); +} + +#[test] +fn unauthorised_add_assembly_msg() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + assert!( + governance::ExecuteMsg::AddAssemblyMsg { + name: "Some Assembly name".to_string(), + msg: "{}".to_string(), + assemblies: vec![0], + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + Addr::unchecked("random"), + &[] + ) + .is_err() + ); +} + +#[test] +fn set_assembly_msg() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + let original_msg = get_assembly_msgs(&mut chain, &gov, 0, 1).unwrap()[0].clone(); + + governance::ExecuteMsg::SetAssemblyMsg { + id: 0, + name: Some("New name".to_string()), + msg: None, + assemblies: None, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let assemblies = get_assembly_msgs(&mut chain, &gov, 0, 1).unwrap(); + + assert_eq!(assemblies.len(), 1); + + assert_ne!(original_msg.name, assemblies[0].name); + assert_eq!(original_msg.assemblies, assemblies[0].assemblies); + assert_eq!(original_msg.msg, assemblies[0].msg); +} + +#[test] +fn unauthorised_set_assembly_msg() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + assert!( + governance::ExecuteMsg::SetAssemblyMsg { + id: 0, + name: Some("New name".to_string()), + msg: None, + assemblies: None, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + Addr::unchecked("random"), + &[] + ) + .is_err() + ); +} diff --git a/archived-contracts/governance/src/tests/handle/contract.rs b/archived-contracts/governance/src/tests/handle/contract.rs new file mode 100644 index 0000000..46c4a7e --- /dev/null +++ b/archived-contracts/governance/src/tests/handle/contract.rs @@ -0,0 +1,301 @@ +use crate::tests::{admin_only_governance, get_contract}; +use shade_protocol::{ + c_std::Addr, + contract_interfaces::governance, + utils::{asset::Contract, ExecuteCallback}, +}; + +#[test] +fn add_contract() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + governance::ExecuteMsg::AddContract { + name: "Contract".to_string(), + metadata: "some description".to_string(), + contract: Contract { + address: Addr::unchecked("contract"), + code_hash: "hash".to_string(), + }, + assemblies: None, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let contracts = get_contract(&mut chain, &gov, 0, 1).unwrap(); + + assert_eq!(contracts.len(), 2); +} +#[test] +fn unauthorised_add_contract() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + assert!( + governance::ExecuteMsg::AddContract { + name: "Contract".to_string(), + metadata: "some description".to_string(), + contract: Contract { + address: Addr::unchecked("contract"), + code_hash: "hash".to_string(), + }, + assemblies: None, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + Addr::unchecked("random"), + &[] + ) + .is_err() + ); +} +#[test] +fn set_contract() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + governance::ExecuteMsg::AddContract { + name: "Contract".to_string(), + metadata: "some description".to_string(), + contract: Contract { + address: Addr::unchecked("contract"), + code_hash: "hash".to_string(), + }, + assemblies: None, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let old_contract = get_contract(&mut chain, &gov, 1, 1).unwrap()[0].clone(); + + governance::ExecuteMsg::SetContract { + id: 1, + name: Some("New name".to_string()), + metadata: Some("New desc".to_string()), + contract: Some(Contract { + address: Addr::unchecked("new contract"), + code_hash: "other hash".to_string(), + }), + disable_assemblies: false, + assemblies: None, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let new_contract = get_contract(&mut chain, &gov, 1, 1).unwrap()[0].clone(); + + assert_ne!(old_contract.name, new_contract.name); + assert_ne!(old_contract.metadata, new_contract.metadata); + assert_ne!(old_contract.contract.address, new_contract.contract.address); + assert_ne!( + old_contract.contract.code_hash, + new_contract.contract.code_hash + ); +} + +#[test] +fn disable_contract_assemblies() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + governance::ExecuteMsg::AddContract { + name: "Contract".to_string(), + metadata: "some description".to_string(), + contract: Contract { + address: Addr::unchecked("contract"), + code_hash: "hash".to_string(), + }, + assemblies: Some(vec![0]), + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let old_contract = get_contract(&mut chain, &gov, 1, 1).unwrap()[0].clone(); + + governance::ExecuteMsg::SetContract { + id: 1, + name: Some("New name".to_string()), + metadata: Some("New desc".to_string()), + contract: Some(Contract { + address: Addr::unchecked("new contract"), + code_hash: "other hash".to_string(), + }), + disable_assemblies: true, + assemblies: None, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let new_contract = get_contract(&mut chain, &gov, 1, 1).unwrap()[0].clone(); + + assert_ne!(old_contract.name, new_contract.name); + assert_ne!(old_contract.metadata, new_contract.metadata); + assert_ne!(old_contract.contract.address, new_contract.contract.address); + assert_ne!( + old_contract.contract.code_hash, + new_contract.contract.code_hash + ); + assert_ne!(old_contract.assemblies, new_contract.assemblies); +} + +#[test] +fn enable_contract_assemblies() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + governance::ExecuteMsg::AddContract { + name: "Contract".to_string(), + metadata: "some description".to_string(), + contract: Contract { + address: Addr::unchecked("contract"), + code_hash: "hash".to_string(), + }, + assemblies: None, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let old_contract = get_contract(&mut chain, &gov, 1, 1).unwrap()[0].clone(); + + governance::ExecuteMsg::SetContract { + id: 1, + name: Some("New name".to_string()), + metadata: Some("New desc".to_string()), + contract: Some(Contract { + address: Addr::unchecked("new contract"), + code_hash: "other hash".to_string(), + }), + disable_assemblies: false, + assemblies: Some(vec![0]), + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let new_contract = get_contract(&mut chain, &gov, 1, 1).unwrap()[0].clone(); + + assert_ne!(old_contract.name, new_contract.name); + assert_ne!(old_contract.metadata, new_contract.metadata); + assert_ne!(old_contract.contract.address, new_contract.contract.address); + assert_ne!( + old_contract.contract.code_hash, + new_contract.contract.code_hash + ); + assert_ne!(old_contract.assemblies, new_contract.assemblies); +} + +#[test] +fn unauthorised_set_contract() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + assert!( + governance::ExecuteMsg::SetContract { + id: 1, + name: Some("New name".to_string()), + metadata: Some("New desc".to_string()), + contract: Some(Contract { + address: Addr::unchecked("new contract"), + code_hash: "other hash".to_string(), + }), + disable_assemblies: false, + assemblies: None, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + Addr::unchecked("random"), + &[] + ) + .is_err() + ); +} +#[test] +fn add_contract_assemblies() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + governance::ExecuteMsg::AddContract { + name: "Contract".to_string(), + metadata: "some description".to_string(), + contract: Contract { + address: Addr::unchecked("contract"), + code_hash: "hash".to_string(), + }, + assemblies: Some(vec![0]), + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let old_contract = get_contract(&mut chain, &gov, 1, 1).unwrap()[0].clone(); + + governance::ExecuteMsg::AddContractAssemblies { + id: 1, + assemblies: vec![1], + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let new_contract = get_contract(&mut chain, &gov, 1, 1).unwrap()[0].clone(); + + assert_ne!(old_contract.assemblies, new_contract.assemblies); +} diff --git a/archived-contracts/governance/src/tests/handle/migration.rs b/archived-contracts/governance/src/tests/handle/migration.rs new file mode 100644 index 0000000..55b8c0e --- /dev/null +++ b/archived-contracts/governance/src/tests/handle/migration.rs @@ -0,0 +1,310 @@ +// Migrate to new contract +// Send data +// Receive data + +use crate::tests::handle::runstate::init_gov; +use shade_protocol::{ + c_std::{Addr, ContractInfo}, + governance, + governance::{profile::Profile, ExecuteMsg, MigrationDataAsk, QueryAnswer, QueryMsg}, + multi_test::App, + utils::{ExecuteCallback, Query}, +}; + +#[test] +fn migrate() { + let (mut chain, gov, _snip20, gov_id) = init_gov().unwrap(); + + for i in 0..20 { + // Generate multiple assemblies to migrate + governance::ExecuteMsg::AddAssembly { + name: format!("Assembly {}", i), + metadata: "some data".to_string(), + members: vec![ + Addr::unchecked("alpha"), + Addr::unchecked("beta"), + Addr::unchecked("charlie"), + ], + profile: 1, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + // Generate multiple profiles + governance::ExecuteMsg::AddProfile { + profile: Profile { + name: format!("Profile {}", i), + enabled: false, + assembly: None, + funding: None, + token: None, + cancel_deadline: 0, + }, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + // Generate multiple assembly msgs + governance::ExecuteMsg::AddAssemblyMsg { + name: format!("AssemblyMsg {}", i), + msg: "{}".to_string(), + assemblies: vec![], + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + // Generate multiple contracts + governance::ExecuteMsg::AddContract { + name: format!("Contract {}", i), + metadata: "".to_string(), + contract: Default::default(), + assemblies: None, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + } + + governance::ExecuteMsg::Migrate { + id: gov_id, + label: "new_governance".to_string(), + code_hash: gov.code_hash.clone(), + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let answer: governance::QueryAnswer = governance::QueryMsg::Config {} + .test_query(&gov, &chain) + .unwrap(); + + let new_gov = match answer { + QueryAnswer::Config { config } => { + if let Some(contract) = config.migrated_to { + ContractInfo { + address: contract.address, + code_hash: contract.code_hash, + } + } else { + panic!("No migration target") + } + } + _ => panic!("Not the expected response"), + }; + + // Check that totals are well initialized + assert_query_totals(QueryMsg::TotalAssemblies {}, &chain, &gov, &new_gov); + assert_query_totals(QueryMsg::TotalAssemblyMsgs {}, &chain, &gov, &new_gov); + assert_query_totals(QueryMsg::TotalContracts {}, &chain, &gov, &new_gov); + assert_query_totals(QueryMsg::TotalProfiles {}, &chain, &gov, &new_gov); + + // Check that gov is not well initialized + assert_migrated_items(&chain, &gov, &new_gov, false); + + // Make sure that it can handle exact migration + ExecuteMsg::MigrateData { + data: MigrationDataAsk::Assembly, + total: 22, + } + .test_exec(&gov, &mut chain, Addr::unchecked("anyone"), &[]) + .unwrap(); + + // Handle fractional migrations + ExecuteMsg::MigrateData { + data: MigrationDataAsk::Profile, + total: 11, + } + .test_exec(&gov, &mut chain, Addr::unchecked("anyone"), &[]) + .unwrap(); + ExecuteMsg::MigrateData { + data: MigrationDataAsk::Profile, + total: 11, + } + .test_exec(&gov, &mut chain, Addr::unchecked("anyone"), &[]) + .unwrap(); + + // Handle amount overflow + ExecuteMsg::MigrateData { + data: MigrationDataAsk::AssemblyMsg, + total: 40, + } + .test_exec(&gov, &mut chain, Addr::unchecked("anyone"), &[]) + .unwrap(); + + ExecuteMsg::MigrateData { + data: MigrationDataAsk::Contract, + total: 25, + } + .test_exec(&gov, &mut chain, Addr::unchecked("anyone"), &[]) + .unwrap(); + + // Finally assert that everything is fine + assert_migrated_items(&chain, &gov, &new_gov, true); +} + +fn assert_query_totals(query: QueryMsg, chain: &App, gov1: &ContractInfo, gov2: &ContractInfo) { + let query1 = query.test_query(&gov1, &chain).unwrap(); + let query2 = query.test_query(&gov2, &chain).unwrap(); + if let QueryAnswer::Total { total: total1 } = query1 { + if let QueryAnswer::Total { total: total2 } = query2 { + assert_eq!(total1, total2); + } else { + panic!("Expected something") + } + } else { + panic!("Expected something") + } +} + +fn assert_migrated_items( + chain: &App, + gov1: &ContractInfo, + gov2: &ContractInfo, + should_equal: bool, +) { + ///////// ASSEMBLIES + let query = QueryMsg::Assemblies { start: 0, end: 25 }; + let query1 = query.test_query(&gov1, &chain).unwrap(); + let query2_try = query.test_query(&gov2, &chain); + + // Should error out cause item is not found + if !should_equal { + assert!(query2_try.is_err()); + } else if let QueryAnswer::Assemblies { + assemblies: assemblies1, + } = query1 + { + let query2 = query2_try.unwrap(); + if let QueryAnswer::Assemblies { + assemblies: assemblies2, + } = query2 + { + assert_eq!(assemblies1.len(), assemblies2.len()); + if should_equal { + for (i, assembly) in assemblies1.iter().enumerate() { + assert_eq!(assembly.clone(), assemblies2[i]); + } + } + } else { + panic!("Expected something") + } + } else { + panic!("Expected something") + } + + ///////// ASSEMBLY MSGS + let query = QueryMsg::AssemblyMsgs { start: 0, end: 25 }; + let query1 = query.test_query(&gov1, &chain).unwrap(); + let query2_try = query.test_query(&gov2, &chain); + + // Should error out cause item is not found + if !should_equal { + assert!(query2_try.is_err()); + } else if let QueryAnswer::AssemblyMsgs { msgs: msgs1 } = query1 { + let query2 = query2_try.unwrap(); + if let QueryAnswer::AssemblyMsgs { msgs: msgs2 } = query2 { + assert_eq!(msgs1.len(), msgs2.len()); + if should_equal { + for (i, msg) in msgs1.iter().enumerate() { + assert_eq!(msg.clone(), msgs2[i]); + } + } + } else { + panic!("Expected something") + } + } else { + panic!("Expected something") + } + + ///////// PROFILES + let query = QueryMsg::Profiles { start: 0, end: 25 }; + let query1 = query.test_query(&gov1, &chain).unwrap(); + let query2_try = query.test_query(&gov2, &chain); + + // Should error out cause item is not found + if !should_equal { + assert!(query2_try.is_err()); + } else if let QueryAnswer::Profiles { + profiles: profiles1, + } = query1 + { + let query2 = query2_try.unwrap(); + if let QueryAnswer::Profiles { + profiles: profiles2, + } = query2 + { + assert_eq!(profiles1.len(), profiles2.len()); + if should_equal { + for (i, profile) in profiles1.iter().enumerate() { + assert_eq!(profile.clone(), profiles2[i]); + } + } + } else { + panic!("Expected something") + } + } else { + panic!("Expected something") + } + + ///////// CONTRACTS + let query = QueryMsg::Contracts { start: 0, end: 25 }; + let query1 = query.test_query(&gov1, &chain).unwrap(); + let query2_try = query.test_query(&gov2, &chain); + + // Should error out cause item is not found + if !should_equal { + assert!(query2_try.is_err()); + } else if let QueryAnswer::Contracts { + contracts: contracts1, + } = query1 + { + let query2 = query2_try.unwrap(); + if let QueryAnswer::Contracts { + contracts: contracts2, + } = query2 + { + assert_eq!(contracts1.len(), contracts2.len()); + if should_equal { + for (i, contract) in contracts1.iter().enumerate() { + assert_eq!(contract.clone(), contracts2[i]); + } + } + } else { + panic!("Expected something") + } + } else { + panic!("Expected something") + } +} diff --git a/archived-contracts/governance/src/tests/handle/mod.rs b/archived-contracts/governance/src/tests/handle/mod.rs new file mode 100644 index 0000000..5347087 --- /dev/null +++ b/archived-contracts/governance/src/tests/handle/mod.rs @@ -0,0 +1,127 @@ +pub mod assembly; +pub mod assembly_msg; +pub mod contract; +pub mod migration; +pub mod profile; +pub mod proposal; +pub mod runstate; + +use crate::tests::{admin_only_governance, get_config, handle::proposal::init_funding_token}; +use shade_protocol::{ + c_std::Addr, + contract_interfaces::governance, + utils::{asset::Contract, ExecuteCallback}, +}; + +#[test] +fn set_config_msg() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + let old_config = get_config(&mut chain, &gov).unwrap(); + + let snip20 = init_funding_token(&mut chain, None, None).unwrap(); + + governance::ExecuteMsg::SetConfig { + query_auth: None, + treasury: Some(Addr::unchecked("random")), + funding_token: Some(Contract { + address: snip20.address.clone(), + code_hash: snip20.code_hash.clone(), + }), + vote_token: Some(Contract { + address: snip20.address, + code_hash: snip20.code_hash, + }), + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let new_config = get_config(&mut chain, &gov).unwrap(); + + assert_ne!(old_config.treasury, new_config.treasury); + assert_ne!(old_config.funding_token, new_config.funding_token); + assert_ne!(old_config.vote_token, new_config.vote_token); +} + +#[test] +fn unauthorised_set_config_msg() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + assert!( + governance::ExecuteMsg::SetConfig { + query_auth: None, + treasury: None, + funding_token: None, + vote_token: None, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + Addr::unchecked("random"), + &[] + ) + .is_err() + ); +} + +#[test] +fn reject_disable_config_tokens() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + let snip20 = init_funding_token(&mut chain, None, None).unwrap(); + + governance::ExecuteMsg::SetConfig { + query_auth: None, + treasury: Some(Addr::unchecked("random")), + funding_token: Some(Contract { + address: snip20.address.clone(), + code_hash: snip20.code_hash.clone(), + }), + vote_token: Some(Contract { + address: snip20.address, + code_hash: snip20.code_hash, + }), + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let old_config = get_config(&mut chain, &gov).unwrap(); + + governance::ExecuteMsg::SetConfig { + query_auth: None, + treasury: None, + funding_token: None, + vote_token: None, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let new_config = get_config(&mut chain, &gov).unwrap(); + + assert_eq!(old_config.treasury, new_config.treasury); + assert_eq!(old_config.funding_token, new_config.funding_token); + assert_eq!(old_config.vote_token, new_config.vote_token); +} diff --git a/archived-contracts/governance/src/tests/handle/profile.rs b/archived-contracts/governance/src/tests/handle/profile.rs new file mode 100644 index 0000000..ad09a3c --- /dev/null +++ b/archived-contracts/governance/src/tests/handle/profile.rs @@ -0,0 +1,472 @@ +use crate::tests::{admin_only_governance, get_profiles}; +use shade_protocol::{ + c_std::{Addr, Uint128}, + contract_interfaces::{ + governance, + governance::profile::{ + Count, + Profile, + UpdateFundProfile, + UpdateProfile, + UpdateVoteProfile, + }, + }, + utils::ExecuteCallback, +}; + +#[test] +fn add_profile() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + governance::ExecuteMsg::AddProfile { + profile: Profile { + name: "Other Profile".to_string(), + enabled: false, + assembly: None, + funding: None, + token: None, + cancel_deadline: 0, + }, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let profiles = get_profiles(&mut chain, &gov, 0, 10).unwrap(); + + assert_eq!(profiles.len(), 3); +} +#[test] +fn unauthorised_add_profile() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + assert!( + governance::ExecuteMsg::AddProfile { + profile: Profile { + name: "Other Profile".to_string(), + enabled: false, + assembly: None, + funding: None, + token: None, + cancel_deadline: 0, + }, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + Addr::unchecked("random"), + &[] + ) + .is_err() + ); +} + +#[test] +fn set_profile() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + let old_profile = get_profiles(&mut chain, &gov, 1, 1).unwrap()[0].clone(); + + governance::ExecuteMsg::SetProfile { + id: 1, + profile: UpdateProfile { + name: Some("New Name".to_string()), + enabled: None, + disable_assembly: false, + assembly: None, + disable_funding: false, + funding: None, + disable_token: false, + token: None, + cancel_deadline: None, + }, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let new_profile = get_profiles(&mut chain, &gov, 1, 1).unwrap()[0].clone(); + + assert_ne!(new_profile.name, old_profile.name); + assert_eq!(new_profile.assembly, old_profile.assembly); + assert_eq!(new_profile.funding, old_profile.funding); + assert_eq!(new_profile.token, old_profile.token); + assert_eq!(new_profile.enabled, old_profile.enabled); + assert_eq!(new_profile.cancel_deadline, old_profile.cancel_deadline); +} + +#[test] +fn unauthorised_set_profile() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + assert!( + governance::ExecuteMsg::SetProfile { + id: 1, + profile: UpdateProfile { + name: Some("New Name".to_string()), + enabled: None, + disable_assembly: false, + assembly: None, + disable_funding: false, + funding: None, + disable_token: false, + token: None, + cancel_deadline: None, + }, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + Addr::unchecked("random"), + &[] + ) + .is_err() + ); +} + +#[test] +fn set_profile_disable_assembly() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + governance::ExecuteMsg::SetProfile { + id: 1, + profile: UpdateProfile { + name: None, + enabled: None, + disable_assembly: false, + assembly: Some(UpdateVoteProfile { + deadline: Some(0), + threshold: Some(Count::LiteralCount { + count: Uint128::zero(), + }), + yes_threshold: Some(Count::LiteralCount { + count: Uint128::zero(), + }), + veto_threshold: Some(Count::LiteralCount { + count: Uint128::zero(), + }), + }), + disable_funding: false, + funding: None, + disable_token: false, + token: None, + cancel_deadline: None, + }, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let old_profile = get_profiles(&mut chain, &gov, 1, 1).unwrap()[0].clone(); + + governance::ExecuteMsg::SetProfile { + id: 1, + profile: UpdateProfile { + name: None, + enabled: None, + disable_assembly: true, + assembly: None, + disable_funding: false, + funding: None, + disable_token: false, + token: None, + cancel_deadline: None, + }, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let new_profile = get_profiles(&mut chain, &gov, 1, 1).unwrap()[0].clone(); + + assert_eq!(new_profile.name, old_profile.name); + assert_ne!(new_profile.assembly, old_profile.assembly); + assert_eq!(new_profile.funding, old_profile.funding); + assert_eq!(new_profile.token, old_profile.token); + assert_eq!(new_profile.enabled, old_profile.enabled); + assert_eq!(new_profile.cancel_deadline, old_profile.cancel_deadline); +} + +#[test] +fn set_profile_set_incomplete_assembly() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + assert!( + governance::ExecuteMsg::SetProfile { + id: 1, + profile: UpdateProfile { + name: None, + enabled: None, + disable_assembly: false, + assembly: Some(UpdateVoteProfile { + deadline: Some(0), + threshold: None, + yes_threshold: None, + veto_threshold: Some(Count::LiteralCount { + count: Uint128::zero(), + }), + }), + disable_funding: false, + funding: None, + disable_token: false, + token: None, + cancel_deadline: None, + }, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[] + ) + .is_err() + ); +} + +#[test] +fn set_profile_disable_token() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + governance::ExecuteMsg::SetProfile { + id: 1, + profile: UpdateProfile { + name: None, + enabled: None, + disable_assembly: false, + assembly: None, + disable_funding: false, + funding: None, + disable_token: false, + token: Some(UpdateVoteProfile { + deadline: Some(0), + threshold: Some(Count::LiteralCount { + count: Uint128::zero(), + }), + yes_threshold: Some(Count::LiteralCount { + count: Uint128::zero(), + }), + veto_threshold: Some(Count::LiteralCount { + count: Uint128::zero(), + }), + }), + cancel_deadline: None, + }, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let old_profile = get_profiles(&mut chain, &gov, 1, 1).unwrap()[0].clone(); + + governance::ExecuteMsg::SetProfile { + id: 1, + profile: UpdateProfile { + name: None, + enabled: None, + disable_assembly: false, + assembly: None, + disable_funding: false, + funding: None, + disable_token: true, + token: None, + cancel_deadline: None, + }, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let new_profile = get_profiles(&mut chain, &gov, 1, 1).unwrap()[0].clone(); + + assert_eq!(new_profile.name, old_profile.name); + assert_eq!(new_profile.assembly, old_profile.assembly); + assert_eq!(new_profile.funding, old_profile.funding); + assert_ne!(new_profile.token, old_profile.token); + assert_eq!(new_profile.enabled, old_profile.enabled); + assert_eq!(new_profile.cancel_deadline, old_profile.cancel_deadline); +} + +#[test] +fn set_profile_set_incomplete_token() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + assert!( + governance::ExecuteMsg::SetProfile { + id: 1, + profile: UpdateProfile { + name: None, + enabled: None, + disable_assembly: false, + assembly: None, + disable_funding: false, + funding: None, + disable_token: false, + token: Some(UpdateVoteProfile { + deadline: Some(0), + threshold: None, + yes_threshold: None, + veto_threshold: Some(Count::LiteralCount { + count: Uint128::zero(), + }), + }), + cancel_deadline: None, + }, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[] + ) + .is_err() + ); +} + +#[test] +fn set_profile_disable_funding() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + governance::ExecuteMsg::SetProfile { + id: 1, + profile: UpdateProfile { + name: None, + enabled: None, + disable_assembly: false, + assembly: None, + disable_funding: false, + funding: Some(UpdateFundProfile { + deadline: Some(0), + required: Some(Uint128::zero()), + privacy: Some(true), + veto_deposit_loss: Some(Uint128::zero()), + }), + disable_token: false, + token: None, + cancel_deadline: None, + }, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let old_profile = get_profiles(&mut chain, &gov, 1, 1).unwrap()[0].clone(); + + governance::ExecuteMsg::SetProfile { + id: 1, + profile: UpdateProfile { + name: None, + enabled: None, + disable_assembly: false, + assembly: None, + disable_funding: true, + funding: None, + disable_token: false, + token: None, + cancel_deadline: None, + }, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + let new_profile = get_profiles(&mut chain, &gov, 1, 1).unwrap()[0].clone(); + + assert_eq!(new_profile.name, old_profile.name); + assert_eq!(new_profile.assembly, old_profile.assembly); + assert_ne!(new_profile.funding, old_profile.funding); + assert_eq!(new_profile.token, old_profile.token); + assert_eq!(new_profile.enabled, old_profile.enabled); + assert_eq!(new_profile.cancel_deadline, old_profile.cancel_deadline); +} + +#[test] +fn set_profile_set_incomplete_fuding() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + assert!( + governance::ExecuteMsg::SetProfile { + id: 1, + profile: UpdateProfile { + name: None, + enabled: None, + disable_assembly: false, + assembly: None, + disable_funding: false, + funding: Some(UpdateFundProfile { + deadline: Some(0), + required: None, + privacy: Some(true), + veto_deposit_loss: None, + }), + disable_token: false, + token: None, + cancel_deadline: None, + }, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[] + ) + .is_err() + ); +} diff --git a/archived-contracts/governance/src/tests/handle/proposal/assembly_voting.rs b/archived-contracts/governance/src/tests/handle/proposal/assembly_voting.rs new file mode 100644 index 0000000..2ac8fdd --- /dev/null +++ b/archived-contracts/governance/src/tests/handle/proposal/assembly_voting.rs @@ -0,0 +1,844 @@ +use crate::tests::{get_proposals, init_chain}; +use shade_multi_test::multi::governance::Governance; +use shade_protocol::{ + c_std::{Addr, ContractInfo, StdResult, Uint128}, + contract_interfaces::{ + governance, + governance::{ + profile::{Count, Profile, VoteProfile}, + proposal::Status, + vote::Vote, + InstantiateMsg, + }, + query_auth, + }, + governance::AssemblyInit, + multi_test::App, + utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable}, +}; + +pub fn init_assembly_governance_with_proposal() -> StdResult<(App, ContractInfo)> { + let (mut chain, auth) = init_chain(); + + query_auth::ExecuteMsg::SetViewingKey { + key: "password".to_string(), + padding: None, + } + .test_exec(&auth, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + query_auth::ExecuteMsg::SetViewingKey { + key: "password".to_string(), + padding: None, + } + .test_exec(&auth, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + query_auth::ExecuteMsg::SetViewingKey { + key: "password".to_string(), + padding: None, + } + .test_exec(&auth, &mut chain, Addr::unchecked("charlie"), &[]) + .unwrap(); + + let gov = InstantiateMsg { + treasury: Addr::unchecked("treasury"), + query_auth: Contract { + address: auth.address, + code_hash: auth.code_hash, + }, + funding_token: None, + vote_token: None, + assemblies: Some(AssemblyInit { + admin_members: vec![ + Addr::unchecked("alpha"), + Addr::unchecked("beta"), + Addr::unchecked("charlie"), + ], + admin_profile: Profile { + name: "admin".to_string(), + enabled: true, + assembly: Some(VoteProfile { + deadline: 10000, + threshold: Count::LiteralCount { + count: Uint128::new(2), + }, + yes_threshold: Count::LiteralCount { + count: Uint128::new(2), + }, + veto_threshold: Count::LiteralCount { + count: Uint128::new(3), + }, + }), + funding: None, + token: None, + cancel_deadline: 0, + }, + public_profile: Profile { + name: "public".to_string(), + enabled: false, + assembly: None, + funding: None, + token: None, + cancel_deadline: 0, + }, + }), + migrator: None, + } + .test_init( + Governance::default(), + &mut chain, + Addr::unchecked("admin"), + "governance", + &[], + ) + .unwrap(); + + governance::ExecuteMsg::AssemblyProposal { + assembly: 1, + title: "Title".to_string(), + metadata: "Text only proposal".to_string(), + msgs: None, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + Ok((chain, gov)) +} + +#[test] +fn assembly_voting() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + assert_eq!(prop.title, "Title".to_string()); + assert_eq!(prop.metadata, "Text only proposal".to_string()); + assert_eq!(prop.proposer, Addr::unchecked("alpha")); + assert_eq!(prop.assembly, 1); + + match prop.status { + Status::AssemblyVote { .. } => assert!(true), + _ => assert!(false), + }; +} + +#[test] +fn update_before_deadline() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + assert!( + governance::ExecuteMsg::Update { + proposal: 0, + padding: None + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .is_err() + ); +} + +#[test] +fn update_after_deadline() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + assert!( + governance::ExecuteMsg::Update { + proposal: 0, + padding: None + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .is_ok() + ); +} + +#[test] +fn invalid_vote() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + assert!( + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::new(1), + no: Uint128::new(1), + no_with_veto: Default::default(), + abstain: Default::default() + }, + padding: None + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .is_err() + ); +} + +#[test] +fn unauthorised_vote() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + assert!( + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::zero(), + no: Uint128::new(1), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }, + padding: None + } + .test_exec(&gov, &mut chain, Addr::unchecked("foxtrot"), &[]) + .is_err() + ); +} + +#[test] +fn vote_after_deadline() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + assert!( + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::zero(), + no: Uint128::new(1), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }, + padding: None + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .is_err() + ); +} + +#[test] +fn vote_yes() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::new(1), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::AssemblyVote { .. } => assert!(true), + _ => assert!(false), + }; + + assert_eq!( + prop.assembly_vote_tally, + Some(Vote { + yes: Uint128::new(1), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }) + ) +} + +#[test] +fn vote_abstain() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::new(1), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::AssemblyVote { .. } => assert!(true), + _ => assert!(false), + }; + + assert_eq!( + prop.assembly_vote_tally, + Some(Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::new(1) + }) + ) +} + +#[test] +fn vote_no() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::zero(), + no: Uint128::new(1), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::AssemblyVote { .. } => assert!(true), + _ => assert!(false), + }; + + assert_eq!( + prop.assembly_vote_tally, + Some(Vote { + yes: Uint128::zero(), + no: Uint128::new(1), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }) + ) +} + +#[test] +fn vote_veto() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::new(1), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::AssemblyVote { .. } => assert!(true), + _ => assert!(false), + }; + + assert_eq!( + prop.assembly_vote_tally, + Some(Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::new(1), + abstain: Uint128::zero() + }) + ) +} + +#[test] +fn vote_passed() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::new(1), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::new(1), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Passed { .. } => assert!(true), + _ => assert!(false), + }; +} + +#[test] +fn vote_abstained() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::new(1), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::new(1), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Rejected { .. } => assert!(true), + _ => assert!(false), + }; +} + +#[test] +fn vote_rejected() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::zero(), + no: Uint128::new(1), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::zero(), + no: Uint128::new(1), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Rejected { .. } => assert!(true), + _ => assert!(false), + }; +} + +#[test] +fn vote_vetoed() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::new(1), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::new(1), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + // NOTE: assembly votes cannot be vetoed + Status::Rejected { .. } => assert!(true), + _ => assert!(false), + }; +} + +#[test] +fn vote_no_quorum() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::new(1), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + assert_eq!(prop.status, Status::Expired); +} + +#[test] +fn vote_total() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::new(1), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::new(1), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::new(1), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("charlie"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::AssemblyVote { .. } => assert!(true), + _ => assert!(false), + }; + + assert_eq!( + prop.assembly_vote_tally, + Some(Vote { + yes: Uint128::new(2), + no: Uint128::zero(), + no_with_veto: Uint128::new(1), + abstain: Uint128::zero() + }) + ) +} + +#[test] +fn update_vote() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::new(1), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + assert_eq!( + prop.assembly_vote_tally, + Some(Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::new(1), + abstain: Uint128::zero() + }) + ); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::new(1), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + assert_eq!( + prop.assembly_vote_tally, + Some(Vote { + yes: Uint128::new(1), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }) + ); +} + +#[test] +fn vote_count() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::new(1), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::new(1), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Passed { .. } => assert!(true), + _ => assert!(false), + }; +} + +#[test] +fn vote_count_percentage() { + let (mut chain, auth) = init_chain(); + + let gov = InstantiateMsg { + treasury: Addr::unchecked("treasury"), + query_auth: Contract { + address: auth.address, + code_hash: auth.code_hash, + }, + funding_token: None, + vote_token: None, + assemblies: Some(AssemblyInit { + admin_members: vec![ + Addr::unchecked("alpha"), + Addr::unchecked("beta"), + Addr::unchecked("charlie"), + ], + admin_profile: Profile { + name: "admin".to_string(), + enabled: true, + assembly: Some(VoteProfile { + deadline: 10000, + threshold: Count::Percentage { percent: 6500 }, + yes_threshold: Count::Percentage { percent: 6500 }, + veto_threshold: Count::Percentage { percent: 6500 }, + }), + funding: None, + token: None, + cancel_deadline: 0, + }, + public_profile: Profile { + name: "public".to_string(), + enabled: false, + assembly: None, + funding: None, + token: None, + cancel_deadline: 0, + }, + }), + migrator: None, + } + .test_init( + Governance::default(), + &mut chain, + Addr::unchecked("admin"), + "governance", + &[], + ) + .unwrap(); + + governance::ExecuteMsg::AssemblyProposal { + assembly: 1, + title: "Title".to_string(), + metadata: "Text only proposal".to_string(), + msgs: None, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::new(1), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::new(1), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Passed { .. } => assert!(true), + _ => assert!(false), + }; +} diff --git a/archived-contracts/governance/src/tests/handle/proposal/funding.rs b/archived-contracts/governance/src/tests/handle/proposal/funding.rs new file mode 100644 index 0000000..6e79fb2 --- /dev/null +++ b/archived-contracts/governance/src/tests/handle/proposal/funding.rs @@ -0,0 +1,781 @@ +use crate::tests::{get_proposals, handle::proposal::init_funding_token, init_chain}; +use shade_multi_test::multi::{governance::Governance, snip20::Snip20}; +use shade_protocol::{ + c_std::{to_binary, Addr, ContractInfo, StdResult, Uint128}, + contract_interfaces::{ + governance, + governance::{ + profile::{Count, FundProfile, Profile, UpdateProfile, UpdateVoteProfile}, + proposal::Status, + vote::Vote, + InstantiateMsg, + }, + query_auth, + snip20, + }, + governance::AssemblyInit, + multi_test::App, + utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +pub fn init_funding_governance_with_proposal() +-> StdResult<(App, ContractInfo, ContractInfo, ContractInfo)> { + let (mut chain, auth) = init_chain(); + + // Register snip20 + let snip20 = snip20::InstantiateMsg { + name: "funding_token".to_string(), + admin: None, + symbol: "FND".to_string(), + decimals: 6, + initial_balances: Some(vec![ + snip20::InitialBalance { + address: "alpha".into(), + amount: Uint128::new(10000), + }, + snip20::InitialBalance { + address: "beta".into(), + amount: Uint128::new(10000), + }, + snip20::InitialBalance { + address: "charlie".into(), + amount: Uint128::new(10000), + }, + ]), + prng_seed: Default::default(), + config: None, + query_auth: None, + } + .test_init( + Snip20::default(), + &mut chain, + Addr::unchecked("admin"), + "funding_token", + &[], + ) + .unwrap(); + + let gov = InstantiateMsg { + treasury: Addr::unchecked("treasury"), + query_auth: Contract { + address: auth.address.clone(), + code_hash: auth.code_hash.clone(), + }, + + assemblies: Some(AssemblyInit { + admin_members: vec![ + Addr::unchecked("alpha"), + Addr::unchecked("beta"), + Addr::unchecked("charlie"), + ], + admin_profile: Profile { + name: "admin".to_string(), + enabled: true, + assembly: None, + funding: Some(FundProfile { + deadline: 1000, + required: Uint128::new(2000), + privacy: false, + veto_deposit_loss: Default::default(), + }), + token: None, + cancel_deadline: 0, + }, + public_profile: Profile { + name: "public".to_string(), + enabled: false, + assembly: None, + funding: None, + token: None, + cancel_deadline: 0, + }, + }), + funding_token: Some(Contract { + address: snip20.address.clone(), + code_hash: snip20.code_hash.clone(), + }), + vote_token: None, + migrator: None, + } + .test_init( + Governance::default(), + &mut chain, + Addr::unchecked("admin"), + "governance", + &[], + ) + .unwrap(); + + governance::ExecuteMsg::AssemblyProposal { + assembly: 1, + title: "Title".to_string(), + metadata: "Text only proposal".to_string(), + msgs: None, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + snip20::ExecuteMsg::SetViewingKey { + key: "password".to_string(), + padding: None, + } + .test_exec(&snip20, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + query_auth::ExecuteMsg::SetViewingKey { + key: "password".to_string(), + padding: None, + } + .test_exec(&auth, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + snip20::ExecuteMsg::SetViewingKey { + key: "password".to_string(), + padding: None, + } + .test_exec(&snip20, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + snip20::ExecuteMsg::SetViewingKey { + key: "password".to_string(), + padding: None, + } + .test_exec(&snip20, &mut chain, Addr::unchecked("charlie"), &[]) + .unwrap(); + + Ok((chain, gov, snip20, auth)) +} + +#[test] +fn assembly_to_funding_transition() { + let (mut chain, gov, _snip20, _auth) = init_funding_governance_with_proposal().unwrap(); + + governance::ExecuteMsg::SetProfile { + id: 1, + profile: UpdateProfile { + name: None, + enabled: None, + disable_assembly: false, + assembly: Some(UpdateVoteProfile { + deadline: Some(1000), + threshold: Some(Count::LiteralCount { + count: Uint128::new(1), + }), + yes_threshold: Some(Count::LiteralCount { + count: Uint128::new(1), + }), + veto_threshold: Some(Count::LiteralCount { + count: Uint128::new(1), + }), + }), + disable_funding: false, + funding: None, + disable_token: false, + token: None, + cancel_deadline: None, + }, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + governance::ExecuteMsg::AssemblyProposal { + assembly: 1, + title: "Title".to_string(), + metadata: "Text only proposal".to_string(), + msgs: None, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 1, + vote: Vote { + yes: Uint128::new(1), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 1, + vote: Vote { + yes: Uint128::new(1), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + governance::ExecuteMsg::Update { + proposal: 1, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 1, 2).unwrap()[0].clone(); + + assert_eq!(prop.title, "Title".to_string()); + assert_eq!(prop.metadata, "Text only proposal".to_string()); + assert_eq!(prop.proposer, Addr::unchecked("alpha")); + assert_eq!(prop.assembly, 1); + + // Check that history works + match prop.status_history[0] { + Status::AssemblyVote { .. } => assert!(true), + _ => assert!(false), + } + + match prop.status { + Status::Funding { .. } => assert!(true), + _ => assert!(false), + }; +} +#[test] +fn fake_funding_token() { + let (mut chain, gov, snip20, _) = init_funding_governance_with_proposal().unwrap(); + + let other = snip20::InstantiateMsg { + name: "funding_token".to_string(), + admin: None, + symbol: "FND".to_string(), + decimals: 6, + initial_balances: Some(vec![ + snip20::InitialBalance { + address: "alpha".into(), + amount: Uint128::new(10000), + }, + snip20::InitialBalance { + address: "beta".into(), + amount: Uint128::new(10000), + }, + snip20::InitialBalance { + address: "charlie".into(), + amount: Uint128::new(10000), + }, + ]), + prng_seed: Default::default(), + config: None, + query_auth: None, + } + .test_init( + Snip20::default(), + &mut chain, + Addr::unchecked("admin"), + "other_snip20", + &[], + ) + .unwrap(); + + governance::ExecuteMsg::SetConfig { + query_auth: None, + treasury: None, + funding_token: Some(Contract { + address: other.address.clone(), + code_hash: other.code_hash, + }), + vote_token: None, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + assert!( + snip20::ExecuteMsg::Send { + recipient: gov.address.into(), + recipient_code_hash: None, + amount: Uint128::new(100), + msg: None, + memo: None, + padding: None + } + .test_exec( + // Sender is self + &snip20, + &mut chain, + Addr::unchecked("alpha"), + &[] + ) + .is_err() + ); +} +#[test] +fn funding_proposal_without_msg() { + let (mut chain, gov, snip20, _auth) = init_funding_governance_with_proposal().unwrap(); + + assert!( + snip20::ExecuteMsg::Send { + recipient: gov.address.into(), + recipient_code_hash: None, + amount: Uint128::new(100), + msg: None, + memo: None, + padding: None + } + .test_exec( + // Sender is self + &snip20, + &mut chain, + Addr::unchecked("alpha"), + &[] + ) + .is_err() + ); +} +#[test] +fn funding_proposal() { + let (mut chain, gov, snip20, _auth) = init_funding_governance_with_proposal().unwrap(); + + snip20::ExecuteMsg::Send { + recipient: gov.address.clone().into(), + recipient_code_hash: None, + amount: Uint128::new(100), + msg: Some(to_binary(&0).unwrap()), + memo: None, + padding: None, + } + .test_exec( + // Sender is self + &snip20, + &mut chain, + Addr::unchecked("alpha"), + &[], + ) + .unwrap(); + + snip20::ExecuteMsg::Send { + recipient: gov.address.clone().into(), + recipient_code_hash: None, + amount: Uint128::new(100), + msg: Some(to_binary(&0).unwrap()), + memo: None, + padding: None, + } + .test_exec( + // Sender is self + &snip20, + &mut chain, + Addr::unchecked("beta"), + &[], + ) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Funding { amount, .. } => assert_eq!(amount, Uint128::new(200)), + _ => assert!(false), + }; +} +#[test] +fn funding_proposal_after_deadline() { + let (mut chain, gov, snip20, _auth) = init_funding_governance_with_proposal().unwrap(); + + chain.update_block(|block| block.time = block.time.plus_seconds(10000)); + + assert!( + snip20::ExecuteMsg::Send { + recipient: gov.address.into(), + recipient_code_hash: None, + amount: Uint128::new(100), + msg: Some(to_binary(&0).unwrap()), + memo: None, + padding: None + } + .test_exec( + // Sender is self + &snip20, + &mut chain, + Addr::unchecked("alpha"), + &[] + ) + .is_err() + ) +} +#[test] +fn update_while_funding() { + let (mut chain, gov, _snip20, _auth) = init_funding_governance_with_proposal().unwrap(); + + assert!( + governance::ExecuteMsg::Update { + proposal: 0, + padding: None + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .is_err() + ); +} +#[test] +fn update_when_fully_funded() { + let (mut chain, gov, snip20, _auth) = init_funding_governance_with_proposal().unwrap(); + + snip20::ExecuteMsg::Send { + recipient: gov.address.clone().into(), + recipient_code_hash: None, + amount: Uint128::new(1000), + msg: Some(to_binary(&0).unwrap()), + memo: None, + padding: None, + } + .test_exec( + // Sender is self + &snip20, + &mut chain, + Addr::unchecked("alpha"), + &[], + ) + .unwrap(); + + snip20::ExecuteMsg::Send { + recipient: gov.address.clone().into(), + recipient_code_hash: None, + amount: Uint128::new(1000), + msg: Some(to_binary(&0).unwrap()), + memo: None, + padding: None, + } + .test_exec( + // Sender is self + &snip20, + &mut chain, + Addr::unchecked("beta"), + &[], + ) + .unwrap(); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Passed { .. } => assert!(true), + _ => assert!(false), + }; +} +#[test] +fn update_after_failed_funding() { + let (mut chain, gov, snip20, _auth) = init_funding_governance_with_proposal().unwrap(); + + snip20::ExecuteMsg::Send { + recipient: gov.address.clone().into(), + recipient_code_hash: None, + amount: Uint128::new(1000), + msg: Some(to_binary(&0).unwrap()), + memo: None, + padding: None, + } + .test_exec( + // Sender is self + &snip20, + &mut chain, + Addr::unchecked("alpha"), + &[], + ) + .unwrap(); + + chain.update_block(|block| block.time = block.time.plus_seconds(10000)); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Expired {} => assert!(true), + _ => assert!(false), + }; +} +#[test] +fn claim_when_not_finished() { + let (mut chain, gov, snip20, _auth) = init_funding_governance_with_proposal().unwrap(); + + snip20::ExecuteMsg::Send { + recipient: gov.address.into(), + recipient_code_hash: None, + amount: Uint128::new(1000), + msg: Some(to_binary(&0).unwrap()), + memo: None, + padding: None, + } + .test_exec( + // Sender is self + &snip20, + &mut chain, + Addr::unchecked("alpha"), + &[], + ) + .unwrap(); + + assert!( + governance::ExecuteMsg::ClaimFunding { id: 0 } + .test_exec( + // Sender is self + &snip20, + &mut chain, + Addr::unchecked("alpha"), + &[] + ) + .is_err() + ); +} +#[test] +fn claim_after_failing() { + let (mut chain, gov, snip20, _auth) = init_funding_governance_with_proposal().unwrap(); + + snip20::ExecuteMsg::Send { + recipient: gov.address.clone().into(), + recipient_code_hash: None, + amount: Uint128::new(1000), + msg: Some(to_binary(&0).unwrap()), + memo: None, + padding: None, + } + .test_exec( + // Sender is self + &snip20, + &mut chain, + Addr::unchecked("alpha"), + &[], + ) + .unwrap(); + + chain.update_block(|block| block.time = block.time.plus_seconds(10000)); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + governance::ExecuteMsg::ClaimFunding { id: 0 } + .test_exec( + // Sender is self + &gov, + &mut chain, + Addr::unchecked("alpha"), + &[], + ) + .unwrap(); + + let query: snip20::QueryAnswer = snip20::QueryMsg::Balance { + address: "alpha".into(), + key: "password".to_string(), + } + .test_query(&snip20, &chain) + .unwrap(); + + match query { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, Uint128::new(10000)) + } + _ => assert!(false), + }; +} +#[test] +fn claim_after_passing() { + let (mut chain, gov, snip20, _auth) = init_funding_governance_with_proposal().unwrap(); + + snip20::ExecuteMsg::Send { + recipient: gov.address.clone().into(), + recipient_code_hash: None, + amount: Uint128::new(2000), + msg: Some(to_binary(&0).unwrap()), + memo: None, + padding: None, + } + .test_exec( + // Sender is self + &snip20, + &mut chain, + Addr::unchecked("alpha"), + &[], + ) + .unwrap(); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + governance::ExecuteMsg::ClaimFunding { id: 0 } + .test_exec( + // Sender is self + &gov, + &mut chain, + Addr::unchecked("alpha"), + &[], + ) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + assert_eq!( + prop.funders.unwrap()[0], + (Addr::unchecked("alpha"), Uint128::new(2000)) + ); + + let query: snip20::QueryAnswer = snip20::QueryMsg::Balance { + address: "alpha".into(), + key: "password".to_string(), + } + .test_query(&snip20, &chain) + .unwrap(); + + match query { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, Uint128::new(10000)) + } + _ => assert!(false), + }; +} + +fn init_funding_governance_with_proposal_with_privacy() +-> StdResult<(App, ContractInfo, ContractInfo, ContractInfo)> { + let (mut chain, auth) = init_chain(); + + // Register snip20 + let snip20 = init_funding_token( + &mut chain, + Some(vec![ + snip20::InitialBalance { + address: "alpha".into(), + amount: Uint128::new(10000), + }, + snip20::InitialBalance { + address: "beta".into(), + amount: Uint128::new(10000), + }, + snip20::InitialBalance { + address: "charlie".into(), + amount: Uint128::new(10000), + }, + ]), + Some(&auth), + )?; + + // Register governance + let gov = InstantiateMsg { + treasury: Addr::unchecked("treasury"), + query_auth: Contract { + address: auth.address.clone(), + code_hash: auth.code_hash.clone(), + }, + assemblies: Some(AssemblyInit { + admin_members: vec![ + Addr::unchecked("alpha"), + Addr::unchecked("beta"), + Addr::unchecked("charlie"), + ], + admin_profile: Profile { + name: "admin".to_string(), + enabled: true, + assembly: None, + funding: Some(FundProfile { + deadline: 1000, + required: Uint128::new(2000), + privacy: true, + veto_deposit_loss: Default::default(), + }), + token: None, + cancel_deadline: 0, + }, + public_profile: Profile { + name: "public".to_string(), + enabled: false, + assembly: None, + funding: None, + token: None, + cancel_deadline: 0, + }, + }), + funding_token: Some(Contract { + address: snip20.address.clone(), + code_hash: snip20.code_hash.clone(), + }), + vote_token: None, + migrator: None, + } + .test_init( + Governance::default(), + &mut chain, + Addr::unchecked("admin"), + "governance", + &[], + ) + .unwrap(); + + governance::ExecuteMsg::AssemblyProposal { + assembly: 1, + title: "Title".to_string(), + metadata: "Text only proposal".to_string(), + msgs: None, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + Ok((chain, gov, snip20, auth)) +} + +#[test] +fn funding_privacy() { + let (mut chain, gov, snip20, _auth) = + init_funding_governance_with_proposal_with_privacy().unwrap(); + + snip20::ExecuteMsg::Send { + recipient: gov.address.clone().into(), + recipient_code_hash: None, + amount: Uint128::new(2000), + msg: Some(to_binary(&0).unwrap()), + memo: None, + padding: None, + } + .test_exec( + // Sender is self + &snip20, + &mut chain, + Addr::unchecked("alpha"), + &[], + ) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + assert!(prop.funders.is_none()); +} diff --git a/archived-contracts/governance/src/tests/handle/proposal/mod.rs b/archived-contracts/governance/src/tests/handle/proposal/mod.rs new file mode 100644 index 0000000..fbf1360 --- /dev/null +++ b/archived-contracts/governance/src/tests/handle/proposal/mod.rs @@ -0,0 +1,293 @@ +pub mod assembly_voting; +pub mod funding; +pub mod voting; + +use crate::tests::{ + admin_only_governance, + get_assemblies, + get_proposals, + gov_generic_proposal, + gov_msg_proposal, +}; +use shade_multi_test::multi::snip20::Snip20; +use shade_protocol::{ + c_std::{to_binary, Addr, ContractInfo, StdResult}, + contract_interfaces::{ + governance, + governance::proposal::{ProposalMsg, Status}, + }, + multi_test::App, + query_auth, + snip20::{self, InitialBalance}, + utils::{ExecuteCallback, InstantiateCallback, MultiTestable}, +}; + +pub fn init_funding_token( + chain: &mut App, + initial_balances: Option>, + query_auth: Option<&ContractInfo>, +) -> StdResult { + let snip20 = snip20::InstantiateMsg { + name: "funding_token".to_string(), + admin: None, + symbol: "FND".to_string(), + decimals: 6, + initial_balances: initial_balances.clone(), + prng_seed: Default::default(), + config: None, + query_auth: None, + } + .test_init( + Snip20::default(), + chain, + Addr::unchecked("admin"), + "funding_token", + &[], + ) + .unwrap(); + + if let Some(balances) = initial_balances { + if let Some(auth) = query_auth { + for balance in balances { + query_auth::ExecuteMsg::SetViewingKey { + key: "password".to_string(), + padding: None, + } + .test_exec(&auth, chain, Addr::unchecked(balance.address), &[]) + .unwrap(); + } + } + } + + Ok(snip20) +} + +#[test] +fn trigger_admin_command() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + governance::ExecuteMsg::AssemblyProposal { + assembly: 1, + title: "Title".to_string(), + metadata: "Proposal metadata".to_string(), + msgs: None, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) + .unwrap(); +} + +#[test] +fn unauthorized_trigger_admin_command() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + assert!( + governance::ExecuteMsg::AssemblyProposal { + assembly: 1, + title: "Title".to_string(), + metadata: "Proposal metadata".to_string(), + msgs: None, + padding: None + } + .test_exec(&gov, &mut chain, Addr::unchecked("random"), &[]) + .is_err() + ); +} + +#[test] +fn text_only_proposal() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + governance::ExecuteMsg::AssemblyProposal { + assembly: 1, + title: "Title".to_string(), + metadata: "Text only proposal".to_string(), + msgs: None, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + assert_eq!(prop.proposer, Addr::unchecked("admin")); + assert_eq!(prop.title, "Title".to_string()); + assert_eq!(prop.metadata, "Text only proposal".to_string()); + assert_eq!(prop.msgs, None); + assert_eq!(prop.assembly, 1); + assert_eq!(prop.assembly_vote_tally, None); + assert_eq!(prop.public_vote_tally, None); + match prop.status { + Status::Passed { .. } => assert!(true), + _ => assert!(false), + }; + assert_eq!(prop.status_history.len(), 0); + assert_eq!(prop.funders, None); + + governance::ExecuteMsg::Trigger { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + assert_eq!(prop.status, Status::Success); + assert_eq!(prop.status_history.len(), 1); +} + +#[test] +fn msg_proposal() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + gov_generic_proposal( + &mut chain, + &gov, + "admin", + governance::ExecuteMsg::SetAssembly { + id: 1, + name: Some("Random name".to_string()), + metadata: None, + members: None, + profile: None, + padding: None, + }, + ) + .unwrap(); + + let old_assembly = get_assemblies(&mut chain, &gov, 1, 2).unwrap()[0].clone(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Passed { .. } => assert!(true), + _ => assert!(false), + }; + + governance::ExecuteMsg::Trigger { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + assert!(prop.msgs.is_some()); + assert_eq!(prop.status, Status::Success); + + let new_assembly = get_assemblies(&mut chain, &gov, 1, 2).unwrap()[0].clone(); + + assert_ne!(new_assembly.name, old_assembly.name); +} + +#[test] +fn multi_msg_proposal() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + gov_msg_proposal(&mut chain, &gov, "admin", vec![ + ProposalMsg { + target: 0, + assembly_msg: 0, + msg: to_binary(&vec![ + serde_json::to_string(&governance::ExecuteMsg::SetAssembly { + id: 1, + name: Some("Random name".to_string()), + metadata: None, + members: None, + profile: None, + padding: None, + }) + .unwrap(), + ]) + .unwrap(), + send: vec![], + }, + ProposalMsg { + target: 0, + assembly_msg: 0, + msg: to_binary(&vec![ + serde_json::to_string(&governance::ExecuteMsg::SetAssembly { + id: 1, + name: None, + metadata: Some("Random name".to_string()), + members: None, + profile: None, + padding: None, + }) + .unwrap(), + ]) + .unwrap(), + send: vec![], + }, + ]) + .unwrap(); + + let old_assembly = get_assemblies(&mut chain, &gov, 1, 2).unwrap()[0].clone(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Passed { .. } => assert!(true), + _ => assert!(false), + }; + + governance::ExecuteMsg::Trigger { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + assert_eq!(prop.status, Status::Success); + + let new_assembly = get_assemblies(&mut chain, &gov, 1, 2).unwrap()[0].clone(); + + assert_ne!(new_assembly.name, old_assembly.name); + assert_ne!(new_assembly.metadata, old_assembly.metadata); +} + +#[test] +fn msg_proposal_invalid_msg() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + gov_generic_proposal( + &mut chain, + &gov, + "admin", + governance::ExecuteMsg::SetAssembly { + id: 3, + name: Some("Random name".to_string()), + metadata: None, + members: None, + profile: None, + padding: None, + }, + ) + .unwrap(); + + assert!( + governance::ExecuteMsg::Trigger { + proposal: 0, + padding: None + } + .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) + .is_err() + ); + + chain.update_block(|block| block.time = block.time.plus_seconds(100000)); + + governance::ExecuteMsg::Cancel { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + assert_eq!(prop.status, Status::Canceled); +} diff --git a/archived-contracts/governance/src/tests/handle/proposal/voting.rs b/archived-contracts/governance/src/tests/handle/proposal/voting.rs new file mode 100644 index 0000000..ecc484b --- /dev/null +++ b/archived-contracts/governance/src/tests/handle/proposal/voting.rs @@ -0,0 +1,1022 @@ +use crate::tests::{get_proposals, handle::proposal::init_funding_token, init_chain}; +use shade_multi_test::multi::{governance::Governance, snip20::Snip20}; +use shade_protocol::{ + c_std::{to_binary, Addr, ContractInfo, StdResult, Uint128}, + contract_interfaces::{ + governance, + governance::{ + profile::{Count, Profile, VoteProfile}, + proposal::Status, + vote::Vote, + InstantiateMsg, + }, + snip20, + }, + governance::AssemblyInit, + multi_test::{App, AppResponse}, + query_auth, + utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable}, + AnyResult, +}; + +pub fn init_voting_governance_with_proposal() -> StdResult<(App, ContractInfo, String, ContractInfo)> +{ + let (mut chain, auth) = init_chain(); + + // Register snip20 + let _snip20 = init_funding_token( + &mut chain, + Some(vec![ + snip20::InitialBalance { + address: "alpha".into(), + amount: Uint128::new(20_000_000), + }, + snip20::InitialBalance { + address: "beta".into(), + amount: Uint128::new(20_000_000), + }, + snip20::InitialBalance { + address: "charlie".into(), + amount: Uint128::new(20_000_000), + }, + ]), + Some(&auth), + ) + .unwrap(); + + // Fake init token so it has a valid codehash + let stkd_tkn = snip20::InstantiateMsg { + name: "token".to_string(), + admin: None, + symbol: "TKN".to_string(), + decimals: 6, + initial_balances: None, + prng_seed: to_binary("some seed").unwrap(), + config: None, + query_auth: None, + } + .test_init( + Snip20::default(), + &mut chain, + Addr::unchecked("admin"), + "staked_token", + &[], + ) + .unwrap(); + // Assume they got 20_000_000 total staked + + // Register governance + query_auth::ExecuteMsg::SetViewingKey { + key: "password".to_string(), + padding: None, + } + .test_exec(&auth, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + query_auth::ExecuteMsg::SetViewingKey { + key: "password".to_string(), + padding: None, + } + .test_exec(&auth, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + query_auth::ExecuteMsg::SetViewingKey { + key: "password".to_string(), + padding: None, + } + .test_exec(&auth, &mut chain, Addr::unchecked("charlie"), &[]) + .unwrap(); + + let gov = InstantiateMsg { + treasury: Addr::unchecked("treasury"), + query_auth: Contract { + address: auth.address.clone(), + code_hash: auth.code_hash.clone(), + }, + assemblies: Some(AssemblyInit { + admin_members: vec![ + Addr::unchecked("alpha"), + Addr::unchecked("beta"), + Addr::unchecked("charlie"), + ], + admin_profile: Profile { + name: "admin".to_string(), + enabled: true, + assembly: None, + funding: None, + token: Some(VoteProfile { + deadline: 10000, + threshold: Count::LiteralCount { + count: Uint128::new(10_000_000), + }, + yes_threshold: Count::LiteralCount { + count: Uint128::new(15_000_000), + }, + veto_threshold: Count::LiteralCount { + count: Uint128::new(15_000_000), + }, + }), + cancel_deadline: 0, + }, + public_profile: Profile { + name: "public".to_string(), + enabled: false, + assembly: None, + funding: None, + token: None, + cancel_deadline: 0, + }, + }), + funding_token: None, + vote_token: Some(Contract { + address: stkd_tkn.address.clone(), + code_hash: stkd_tkn.code_hash.clone(), + }), + migrator: None, + } + .test_init( + Governance::default(), + &mut chain, + Addr::unchecked("admin"), + "governance", + &[], + ) + .unwrap(); + + governance::ExecuteMsg::AssemblyProposal { + assembly: 1, + title: "Title".to_string(), + metadata: "Text only proposal".to_string(), + msgs: None, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + Ok((chain, gov, stkd_tkn.address.to_string(), auth)) +} + +pub fn vote( + gov: &ContractInfo, + chain: &mut App, + stkd: &str, + voter: &str, + vote: governance::vote::ReceiveBalanceMsg, + balance: Uint128, +) -> AnyResult { + governance::ExecuteMsg::ReceiveBalance { + sender: Addr::unchecked(voter), + msg: Some(to_binary(&vote).unwrap()), + balance, + memo: None, + } + .test_exec(gov, chain, Addr::unchecked(stkd), &[]) +} + +#[test] +fn voting() { + let (mut chain, gov, _, _auth) = init_voting_governance_with_proposal().unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + assert_eq!(prop.title, "Title".to_string()); + assert_eq!(prop.metadata, "Text only proposal".to_string()); + assert_eq!(prop.proposer, Addr::unchecked("alpha")); + assert_eq!(prop.assembly, 1); + + match prop.status { + Status::Voting { .. } => assert!(true), + _ => assert!(false), + }; +} + +#[test] +fn update_before_deadline() { + let (mut chain, _gov, _, auth) = init_voting_governance_with_proposal().unwrap(); + + assert!( + governance::ExecuteMsg::Update { + proposal: 0, + padding: None + } + .test_exec(&auth, &mut chain, Addr::unchecked("alpha"), &[]) + .is_err() + ); +} + +// TODO +/*#[test] +fn update_after_deadline() { + let (mut chain, gov, _, _auth) = init_voting_governance_with_proposal().unwrap(); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + // TODO: will crash until i get staking back up + assert!( + governance::ExecuteMsg::Update { + proposal: 0, + padding: None + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .is_ok() + ); +}*/ + +#[test] +fn invalid_vote() { + let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "alpha", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::new(25_000_000), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_err() + ); +} + +#[test] +fn vote_after_deadline() { + let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "alpha", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::new(10_000_000), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_err() + ); +} + +#[test] +fn vote_yes() { + let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "alpha", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::new(1_000_000), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Voting { .. } => assert!(true), + _ => assert!(false), + }; + + assert_eq!( + prop.public_vote_tally, + Some(Vote { + yes: Uint128::new(1_000_000), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }) + ) +} + +#[test] +fn vote_abstain() { + let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "alpha", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::new(1_000_000) + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Voting { .. } => assert!(true), + _ => assert!(false), + }; + + assert_eq!( + prop.public_vote_tally, + Some(Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::new(1_000_000) + }) + ) +} + +#[test] +fn vote_no() { + let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "alpha", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::zero(), + no: Uint128::new(1_000_000), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Voting { .. } => assert!(true), + _ => assert!(false), + }; + + assert_eq!( + prop.public_vote_tally, + Some(Vote { + yes: Uint128::zero(), + no: Uint128::new(1_000_000), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }) + ) +} + +#[test] +fn vote_veto() { + let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "alpha", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::new(1_000_000), + abstain: Uint128::zero() + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Voting { .. } => assert!(true), + _ => assert!(false), + }; + + assert_eq!( + prop.public_vote_tally, + Some(Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::new(1_000_000), + abstain: Uint128::zero() + }) + ) +} + +// TODO +/*#[test] +fn vote_passed() { + let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "alpha", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::new(10_000_000), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "beta", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::new(10_000_000), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + // Check that history works + match prop.status_history[0] { + Status::Voting { .. } => assert!(true), + _ => assert!(false), + } + + match prop.status { + Status::Passed { .. } => assert!(true), + _ => assert!(false), + }; +} + +#[test] +fn vote_abstained() { + let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "alpha", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::new(10_000_000) + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "beta", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::new(10_000_000) + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Rejected { .. } => assert!(true), + _ => assert!(false), + }; +} + +#[test] +fn vote_rejected() { + let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "alpha", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::zero(), + no: Uint128::new(10_000_000), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "beta", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::zero(), + no: Uint128::new(10_000_000), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Rejected { .. } => assert!(true), + _ => assert!(false), + }; +} + +#[test] +fn vote_vetoed() { + let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "alpha", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::new(10_000_000), + abstain: Uint128::zero() + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "beta", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::new(10_000_000), + abstain: Uint128::zero() + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Vetoed { .. } => assert!(true), + _ => assert!(false), + }; +} + +#[test] +fn vote_no_quorum() { + let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "alpha", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::new(10), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "beta", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::new(10), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Expired { .. } => assert!(true), + _ => assert!(false), + }; +} + +#[test] +fn vote_total() { + let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "alpha", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::new(10), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "beta", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::new(10), + no: Uint128::zero(), + no_with_veto: Uint128::new(10_000), + abstain: Uint128::zero() + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "charlie", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::zero(), + no: Uint128::new(23_000), + no_with_veto: Uint128::zero(), + abstain: Uint128::new(10_000), + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Voting { .. } => assert!(true), + _ => assert!(false), + }; + + assert_eq!( + prop.public_vote_tally, + Some(Vote { + yes: Uint128::new(20), + no: Uint128::new(23_000), + no_with_veto: Uint128::new(10_000), + abstain: Uint128::new(10_000) + }) + ) +}*/ + +#[test] +fn update_vote() { + let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "alpha", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::new(22_000), + abstain: Uint128::zero(), + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + assert_eq!( + prop.public_vote_tally, + Some(Vote { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::new(22_000), + abstain: Uint128::zero() + }) + ); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "alpha", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::new(10_000), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + assert_eq!( + prop.public_vote_tally, + Some(Vote { + yes: Uint128::new(10_000), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero() + }) + ); +} + +// TODO +/*#[test] +fn vote_count() { + let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "alpha", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::new(10_000_000), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "beta", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::new(10_000_000), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Passed { .. } => assert!(true), + _ => assert!(false), + }; +} + +#[test] +fn vote_count_percentage() { + let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "alpha", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::new(10_000_000), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "beta", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::new(10_000_000), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + + chain.update_block(|block| block.time = block.time.plus_seconds(30000)); + + governance::ExecuteMsg::Update { + proposal: 0, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) + .unwrap(); + + let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); + + match prop.status { + Status::Passed { .. } => assert!(true), + _ => assert!(false), + }; +}*/ diff --git a/archived-contracts/governance/src/tests/handle/runstate.rs b/archived-contracts/governance/src/tests/handle/runstate.rs new file mode 100644 index 0000000..8efd9aa --- /dev/null +++ b/archived-contracts/governance/src/tests/handle/runstate.rs @@ -0,0 +1,255 @@ +use crate::tests::{handle::init_funding_token, init_chain}; +use rstest::*; +use shade_multi_test::multi::governance::Governance; +use shade_protocol::{ + c_std::{to_binary, Addr, ContractInfo, StdResult, Uint128}, + governance, + governance::{ + profile::{Count, FundProfile, Profile, VoteProfile}, + vote::Vote, + AssemblyInit, + InstantiateMsg, + RuntimeState, + }, + multi_test::{App, AppResponse, Executor}, + snip20, + utils::{asset::Contract, ExecuteCallback, MultiTestable}, + AnyResult, +}; + +pub fn init_gov() -> StdResult<(App, ContractInfo, ContractInfo, u64)> { + let (mut chain, auth) = init_chain(); + + // Register snip20 + let snip20 = init_funding_token( + &mut chain, + Some(vec![ + snip20::InitialBalance { + address: "alpha".into(), + amount: Uint128::new(10000), + }, + snip20::InitialBalance { + address: "beta".into(), + amount: Uint128::new(10000), + }, + snip20::InitialBalance { + address: "charlie".into(), + amount: Uint128::new(10000), + }, + ]), + Some(&auth), + )?; + + // Register governance + let stored_code = chain.store_code(Governance::default().contract()); + let gov = chain + .instantiate_contract( + stored_code.clone(), + Addr::unchecked("admin"), + &InstantiateMsg { + treasury: Addr::unchecked("treasury"), + query_auth: Contract { + address: auth.address.clone(), + code_hash: auth.code_hash.clone(), + }, + assemblies: Some(AssemblyInit { + admin_members: vec![ + Addr::unchecked("alpha"), + Addr::unchecked("beta"), + Addr::unchecked("charlie"), + ], + admin_profile: Profile { + name: "admin".to_string(), + enabled: true, + assembly: Some(VoteProfile { + deadline: 1000, + threshold: Count::LiteralCount { + count: Uint128::new(1), + }, + yes_threshold: Count::LiteralCount { + count: Uint128::new(1), + }, + veto_threshold: Count::LiteralCount { + count: Uint128::new(1), + }, + }), + funding: Some(FundProfile { + deadline: 1000, + required: Uint128::new(1000), + privacy: true, + veto_deposit_loss: Default::default(), + }), + token: None, + cancel_deadline: 0, + }, + public_profile: Profile { + name: "public".to_string(), + enabled: false, + assembly: None, + funding: None, + token: None, + cancel_deadline: 0, + }, + }), + funding_token: Some(Contract { + address: snip20.address.clone(), + code_hash: snip20.code_hash.clone(), + }), + vote_token: None, + migrator: None, + }, + &vec![], + "governance", + None, + ) + .unwrap(); + + Ok((chain, gov, snip20, stored_code.code_id)) +} + +fn update_proposal(chain: &mut App, gov: &ContractInfo, proposal: u32) -> AnyResult { + governance::ExecuteMsg::Update { + proposal, + padding: None, + } + .test_exec(&gov, chain, Addr::unchecked("beta"), &[]) +} + +fn create_proposal(chain: &mut App, gov: &ContractInfo, assembly: u16) -> AnyResult { + governance::ExecuteMsg::AssemblyProposal { + assembly, + title: "Title".to_string(), + metadata: "Text only proposal".to_string(), + msgs: None, + padding: None, + } + .test_exec(&gov, chain, Addr::unchecked("alpha"), &[]) +} + +fn assembly_vote(chain: &mut App, gov: &ContractInfo, proposal: u32) -> AnyResult { + governance::ExecuteMsg::AssemblyVote { + proposal, + vote: Vote { + yes: Uint128::new(1), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, chain, Addr::unchecked("alpha"), &[]) +} + +fn fund_proposal( + chain: &mut App, + gov: &ContractInfo, + snip20: &ContractInfo, + proposal: u32, +) -> AnyResult { + snip20::ExecuteMsg::Send { + recipient: gov.address.clone().into(), + recipient_code_hash: None, + amount: Uint128::new(100), + msg: Some(to_binary(&proposal).unwrap()), + memo: None, + padding: None, + } + .test_exec( + // Sender is self + &snip20, + chain, + Addr::unchecked("alpha"), + &[], + ) +} + +// Use RS test to run all the expected functions under all of the states +#[rstest] +#[case(RuntimeState::Normal, 1, true)] +#[case(RuntimeState::SpecificAssemblies { assemblies: vec![1] }, 2, false)] +#[case(RuntimeState::SpecificAssemblies { assemblies: vec![1] }, 1, true)] +#[case(RuntimeState::Migrated, 1, false)] +fn runstate_states(#[case] state: RuntimeState, #[case] assembly: u16, #[case] expect: bool) { + let (mut chain, gov, snip20, gov_id) = init_gov().unwrap(); + + governance::ExecuteMsg::AddAssembly { + name: "Other assembly".to_string(), + metadata: "some data".to_string(), + members: vec![ + Addr::unchecked("alpha"), + Addr::unchecked("beta"), + Addr::unchecked("charlie"), + ], + profile: 1, + padding: None, + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + + // Proposal for claiming funding + create_proposal(&mut chain, &gov, assembly).unwrap(); + assembly_vote(&mut chain, &gov, 0).unwrap(); + chain.update_block(|block| block.time = block.time.plus_seconds(10000)); + update_proposal(&mut chain, &gov, 0).unwrap(); + fund_proposal(&mut chain, &gov, &snip20, 0).unwrap(); + chain.update_block(|block| block.time = block.time.plus_seconds(10000)); + update_proposal(&mut chain, &gov, 0).unwrap(); + + // Proposal for updating state + create_proposal(&mut chain, &gov, assembly).unwrap(); + assembly_vote(&mut chain, &gov, 1).unwrap(); + chain.update_block(|block| block.time = block.time.plus_seconds(10000)); + + // Proposal for voting + create_proposal(&mut chain, &gov, assembly).unwrap(); + + match state { + RuntimeState::Normal => {} + RuntimeState::SpecificAssemblies { assemblies } => { + governance::ExecuteMsg::SetRuntimeState { + state: RuntimeState::SpecificAssemblies { assemblies }, + padding: None, + } + .test_exec(&gov, &mut chain, gov.address.clone(), &[]) + .unwrap(); + } + RuntimeState::Migrated => { + governance::ExecuteMsg::Migrate { + id: gov_id, + label: "migrated".to_string(), + code_hash: gov.code_hash.clone(), + } + .test_exec( + // Sender is self + &gov, + &mut chain, + gov.address.clone(), + &[], + ) + .unwrap(); + } + } + + // try to create proposal + assert_eq!(expect, create_proposal(&mut chain, &gov, assembly).is_ok()); + + // Claim funding TODO: should this get halted? + assert_eq!( + true, + governance::ExecuteMsg::ClaimFunding { id: 0 } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .is_ok() + ); + + assert_eq!(expect, update_proposal(&mut chain, &gov, 1).is_ok()); + + assert_eq!(expect, assembly_vote(&mut chain, &gov, 2).is_ok()); + + // TODO: not working but progress to user voting +} diff --git a/archived-contracts/governance/src/tests/mod.rs b/archived-contracts/governance/src/tests/mod.rs new file mode 100644 index 0000000..af5b492 --- /dev/null +++ b/archived-contracts/governance/src/tests/mod.rs @@ -0,0 +1,214 @@ +pub mod handle; +pub mod query; + +use shade_multi_test::multi::{ + admin::init_admin_auth, + governance::Governance, + query_auth::QueryAuth, +}; +use shade_protocol::{ + c_std::{to_binary, Addr, Binary, ContractInfo, StdError, StdResult}, + contract_interfaces::{ + governance, + governance::{ + assembly::{Assembly, AssemblyMsg}, + contract::AllowedContract, + profile::Profile, + proposal::{Proposal, ProposalMsg}, + Config, + }, + }, + governance::AssemblyInit, + multi_test::App, + query_auth, + utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +pub fn init_chain() -> (App, ContractInfo) { + let mut chain = App::default(); + + let admin = init_admin_auth(&mut chain, &Addr::unchecked("admin")); + + let auth = query_auth::InstantiateMsg { + admin_auth: Contract { + address: admin.address.clone(), + code_hash: admin.code_hash.clone(), + }, + prng_seed: Binary::from("random".as_bytes()), + } + .test_init( + QueryAuth::default(), + &mut chain, + Addr::unchecked("admin"), + "query_auth", + &[], + ) + .unwrap(); + + (chain, auth) +} + +pub fn admin_only_governance() -> StdResult<(App, ContractInfo)> { + let (mut chain, auth) = init_chain(); + + let gov = governance::InstantiateMsg { + treasury: Addr::unchecked("treasury".to_string()), + query_auth: Contract { + address: auth.address, + code_hash: auth.code_hash, + }, + assemblies: Some(AssemblyInit { + admin_members: vec![Addr::unchecked("admin".to_string())], + admin_profile: Profile { + name: "admin".to_string(), + enabled: true, + assembly: None, + funding: None, + token: None, + cancel_deadline: 0, + }, + public_profile: Profile { + name: "public".to_string(), + enabled: false, + assembly: None, + funding: None, + token: None, + cancel_deadline: 0, + }, + }), + funding_token: None, + vote_token: None, + migrator: None, + } + .test_init( + Governance::default(), + &mut chain, + Addr::unchecked("admin"), + "gov", + &[], + ) + .unwrap(); + + Ok((chain, gov)) +} + +pub fn gov_generic_proposal( + chain: &mut App, + gov: &ContractInfo, + sender: &str, + msg: governance::ExecuteMsg, +) -> StdResult<()> { + gov_msg_proposal(chain, gov, sender, vec![ProposalMsg { + target: 0, + assembly_msg: 0, + msg: to_binary(&vec![serde_json::to_string(&msg).unwrap()]).unwrap(), + send: vec![], + }]) +} + +pub fn gov_msg_proposal( + chain: &mut App, + gov: &ContractInfo, + sender: &str, + msgs: Vec, +) -> StdResult<()> { + governance::ExecuteMsg::AssemblyProposal { + assembly: 1, + title: "Title".to_string(), + metadata: "Proposal metadata".to_string(), + msgs: Some(msgs), + padding: None, + } + .test_exec(gov, chain, Addr::unchecked(sender), &[]) + .unwrap(); + + Ok(()) +} + +pub fn get_assembly_msgs( + chain: &mut App, + gov: &ContractInfo, + start: u16, + end: u16, +) -> StdResult> { + let query: governance::QueryAnswer = + governance::QueryMsg::AssemblyMsgs { start, end }.test_query(&gov, &chain)?; + + let msgs = match query { + governance::QueryAnswer::AssemblyMsgs { msgs } => msgs, + _ => return Err(StdError::generic_err("Returned wrong enum")), + }; + + Ok(msgs) +} + +pub fn get_contract( + chain: &mut App, + gov: &ContractInfo, + start: u16, + end: u16, +) -> StdResult> { + let query: governance::QueryAnswer = + governance::QueryMsg::Contracts { start, end }.test_query(&gov, &chain)?; + + match query { + governance::QueryAnswer::Contracts { contracts } => Ok(contracts), + _ => return Err(StdError::generic_err("Returned wrong enum")), + } +} + +pub fn get_profiles( + chain: &mut App, + gov: &ContractInfo, + start: u16, + end: u16, +) -> StdResult> { + let query: governance::QueryAnswer = + governance::QueryMsg::Profiles { start, end }.test_query(&gov, &chain)?; + + match query { + governance::QueryAnswer::Profiles { profiles } => Ok(profiles), + _ => return Err(StdError::generic_err("Returned wrong enum")), + } +} + +pub fn get_assemblies( + chain: &mut App, + gov: &ContractInfo, + start: u16, + end: u16, +) -> StdResult> { + let query: governance::QueryAnswer = + governance::QueryMsg::Assemblies { start, end }.test_query(&gov, &chain)?; + + match query { + governance::QueryAnswer::Assemblies { assemblies } => Ok(assemblies), + _ => return Err(StdError::generic_err("Returned wrong enum")), + } +} + +pub fn get_proposals( + chain: &mut App, + gov: &ContractInfo, + start: u32, + end: u32, +) -> StdResult> { + let query: governance::QueryAnswer = + governance::QueryMsg::Proposals { start, end }.test_query(&gov, &chain)?; + + match query { + governance::QueryAnswer::Proposals { props } => Ok(props), + _ => return Err(StdError::generic_err("Returned wrong enum")), + } +} + +pub fn get_config(chain: &mut App, gov: &ContractInfo) -> StdResult { + let query: governance::QueryAnswer = governance::QueryMsg::Config {} + .test_query(&gov, &chain) + .unwrap(); + + match query { + governance::QueryAnswer::Config { config } => Ok(config), + _ => return Err(StdError::generic_err("Returned wrong enum")), + } +} diff --git a/archived-contracts/governance/src/tests/query/mod.rs b/archived-contracts/governance/src/tests/query/mod.rs new file mode 100644 index 0000000..ec85dc4 --- /dev/null +++ b/archived-contracts/governance/src/tests/query/mod.rs @@ -0,0 +1,2 @@ +pub mod public; +pub mod user; diff --git a/archived-contracts/governance/src/tests/query/public.rs b/archived-contracts/governance/src/tests/query/public.rs new file mode 100644 index 0000000..8b8e9d3 --- /dev/null +++ b/archived-contracts/governance/src/tests/query/public.rs @@ -0,0 +1,180 @@ +use crate::tests::{ + admin_only_governance, + get_assemblies, + get_assembly_msgs, + get_config, + get_contract, + get_profiles, +}; +use shade_protocol::{contract_interfaces::governance, utils::Query}; + +#[test] +fn query_total_assembly_msg() { + let (chain, gov) = admin_only_governance().unwrap(); + + let query: governance::QueryAnswer = governance::QueryMsg::TotalAssemblyMsgs {} + .test_query(&gov, &chain) + .unwrap(); + + let total = match query { + governance::QueryAnswer::Total { total } => total, + _ => 0, + }; + + assert_eq!(total, 1); +} + +#[test] +fn query_assembly_msg() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + let assemblies = get_assembly_msgs(&mut chain, &gov, 0, 0).unwrap(); + + assert_eq!(assemblies.len(), 1); +} + +#[test] +fn query_assembly_msg_large_end() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + let assemblies = get_assembly_msgs(&mut chain, &gov, 0, 10).unwrap(); + + assert_eq!(assemblies.len(), 1); +} + +#[test] +fn query_assembly_msg_wrong_index() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + assert!(get_assembly_msgs(&mut chain, &gov, 5, 10).is_err()); +} + +#[test] +fn query_total_contracts() { + let (chain, gov) = admin_only_governance().unwrap(); + + let query: governance::QueryAnswer = governance::QueryMsg::TotalContracts {} + .test_query(&gov, &chain) + .unwrap(); + + let total = match query { + governance::QueryAnswer::Total { total } => total, + _ => 0, + }; + + assert_eq!(total, 1); +} + +#[test] +fn query_contracts() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + let contracts = get_contract(&mut chain, &gov, 0, 0).unwrap(); + + assert_eq!(contracts.len(), 1); +} + +#[test] +fn query_contracts_large_end() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + let contracts = get_contract(&mut chain, &gov, 0, 10).unwrap(); + + assert_eq!(contracts.len(), 1); +} + +#[test] +fn query_contracts_wrong_index() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + assert!(get_contract(&mut chain, &gov, 5, 10).is_err()); +} + +#[test] +fn query_total_profiles() { + let (chain, gov) = admin_only_governance().unwrap(); + + let query: governance::QueryAnswer = governance::QueryMsg::TotalProfiles {} + .test_query(&gov, &chain) + .unwrap(); + + let total = match query { + governance::QueryAnswer::Total { total } => total, + _ => 0, + }; + + assert_eq!(total, 2); +} + +#[test] +fn query_profiles() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + let profiles = get_profiles(&mut chain, &gov, 0, 0).unwrap(); + + assert_eq!(profiles.len(), 1); +} + +#[test] +fn query_profiles_large_end() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + let profiles = get_profiles(&mut chain, &gov, 0, 10).unwrap(); + + assert_eq!(profiles.len(), 2); +} + +#[test] +fn query_profiles_wrong_index() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + assert!(get_profiles(&mut chain, &gov, 5, 10).is_err()); +} + +#[test] +fn query_total_assemblies() { + let (chain, gov) = admin_only_governance().unwrap(); + + let query: governance::QueryAnswer = governance::QueryMsg::TotalAssemblies {} + .test_query(&gov, &chain) + .unwrap(); + + let total = match query { + governance::QueryAnswer::Total { total } => total, + _ => 0, + }; + + assert_eq!(total, 2); +} + +#[test] +fn query_assemblies() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + let assemblies = get_assemblies(&mut chain, &gov, 0, 0).unwrap(); + + assert_eq!(assemblies.len(), 1); +} + +#[test] +fn query_assemblies_large_end() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + let assemblies = get_assemblies(&mut chain, &gov, 0, 10).unwrap(); + + assert_eq!(assemblies.len(), 2); +} + +#[test] +fn query_assemblies_wrong_index() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + assert!(get_assemblies(&mut chain, &gov, 5, 10).is_err()); +} + +#[test] +fn query_config() { + let (mut chain, gov) = admin_only_governance().unwrap(); + + get_config(&mut chain, &gov).unwrap(); +} diff --git a/archived-contracts/governance/src/tests/query/user.rs b/archived-contracts/governance/src/tests/query/user.rs new file mode 100644 index 0000000..3cfc65f --- /dev/null +++ b/archived-contracts/governance/src/tests/query/user.rs @@ -0,0 +1,268 @@ +use crate::tests::{ + handle::proposal::{ + assembly_voting::init_assembly_governance_with_proposal, + funding::init_funding_governance_with_proposal, + voting::{init_voting_governance_with_proposal, vote}, + }, + init_chain, +}; +use shade_multi_test::multi::governance::Governance; +use shade_protocol::{ + c_std::{to_binary, Addr, StdResult, Uint128}, + contract_interfaces::{ + governance::{self, profile::Profile, vote::Vote, AuthQuery, Pagination, QueryAnswer}, + query_auth, + snip20, + }, + governance::AssemblyInit, + utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +#[test] +fn proposals() { + let (mut chain, auth) = init_chain(); + + query_auth::ExecuteMsg::SetViewingKey { + key: "password".to_string(), + padding: None, + } + .test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) + .unwrap(); + + let msg = governance::InstantiateMsg { + treasury: Addr::unchecked("treasury".to_string()), + query_auth: Contract { + address: auth.address, + code_hash: auth.code_hash, + }, + assemblies: Some(AssemblyInit { + admin_members: vec![Addr::unchecked("admin".to_string())], + admin_profile: Profile { + name: "admin".to_string(), + enabled: true, + assembly: None, + funding: None, + token: None, + cancel_deadline: 0, + }, + public_profile: Profile { + name: "public".to_string(), + enabled: false, + assembly: None, + funding: None, + token: None, + cancel_deadline: 0, + }, + }), + funding_token: None, + vote_token: None, + migrator: None, + }; + + let gov = msg + .test_init( + Governance::default(), + &mut chain, + Addr::unchecked("admin"), + "governance", + &[], + ) + .unwrap(); + + governance::ExecuteMsg::AssemblyProposal { + assembly: 1, + title: "Title".to_string(), + metadata: "Text".to_string(), + msgs: None, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) + .unwrap(); + + let query: governance::QueryAnswer = governance::QueryMsg::WithVK { + user: Addr::unchecked("admin"), + key: "password".to_string(), + query: AuthQuery::Proposals { + pagination: Pagination { + page: 0, + amount: 10, + }, + }, + } + .test_query(&gov, &chain) + .unwrap(); + + match query { + QueryAnswer::UserProposals { props, total } => { + assert_eq!(total, 0); + assert_eq!(props.len(), 1); + } + _ => assert!(false), + } + + governance::ExecuteMsg::AssemblyProposal { + assembly: 1, + title: "Title".to_string(), + metadata: "Text".to_string(), + msgs: None, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) + .unwrap(); + + let query: governance::QueryAnswer = governance::QueryMsg::WithVK { + user: Addr::unchecked("admin"), + key: "password".to_string(), + query: AuthQuery::Proposals { + pagination: Pagination { + page: 0, + amount: 10, + }, + }, + } + .test_query(&gov, &chain) + .unwrap(); + + match query { + QueryAnswer::UserProposals { props, total } => { + assert_eq!(total, 1); + assert_eq!(props.len(), 2); + } + _ => assert!(false), + } + + let query: StdResult = governance::QueryMsg::WithVK { + user: Addr::unchecked("admin"), + key: "not_password".to_string(), + query: AuthQuery::Proposals { + pagination: Pagination { + page: 0, + amount: 10, + }, + }, + } + .test_query(&gov, &chain); + assert!(query.is_err()) +} + +#[test] +fn assembly_votes() { + let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); + + governance::ExecuteMsg::AssemblyVote { + proposal: 0, + vote: Vote { + yes: Uint128::new(1), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + }, + padding: None, + } + .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + let query: governance::QueryAnswer = governance::QueryMsg::WithVK { + user: Addr::unchecked("alpha"), + key: "password".to_string(), + query: AuthQuery::AssemblyVotes { + pagination: Pagination { + page: 0, + amount: 10, + }, + }, + } + .test_query(&gov, &chain) + .unwrap(); + + match query { + QueryAnswer::UserAssemblyVotes { votes, total } => { + assert_eq!(total, 0); + assert_eq!(votes.len(), 1); + } + _ => assert!(false), + } +} + +#[test] +fn funding() { + let (mut chain, gov, snip20, _) = init_funding_governance_with_proposal().unwrap(); + + snip20::ExecuteMsg::Send { + recipient: gov.address.clone().into(), + recipient_code_hash: None, + amount: Uint128::new(100), + msg: Some(to_binary(&0).unwrap()), + memo: None, + padding: None, + } + .test_exec(&snip20, &mut chain, Addr::unchecked("alpha"), &[]) + .unwrap(); + + let query: governance::QueryAnswer = governance::QueryMsg::WithVK { + user: Addr::unchecked("alpha"), + key: "password".to_string(), + query: AuthQuery::Funding { + pagination: Pagination { + page: 0, + amount: 10, + }, + }, + } + .test_query(&gov, &chain) + .unwrap(); + + match query { + QueryAnswer::UserFunding { funds, total } => { + assert_eq!(total, 0); + assert_eq!(funds.len(), 1); + } + _ => assert!(false), + } +} + +#[test] +fn votes() { + let (mut chain, gov, stkd_tkn, _) = init_voting_governance_with_proposal().unwrap(); + + assert!( + vote( + &gov, + &mut chain, + stkd_tkn.as_str(), + "alpha", + governance::vote::ReceiveBalanceMsg { + vote: Vote { + yes: Uint128::new(1_000_000), + no: Default::default(), + no_with_veto: Default::default(), + abstain: Default::default(), + }, + proposal: 0 + }, + Uint128::new(20_000_000) + ) + .is_ok() + ); + + let query: governance::QueryAnswer = governance::QueryMsg::WithVK { + user: Addr::unchecked("alpha"), + key: "password".to_string(), + query: AuthQuery::Votes { + pagination: Pagination { + page: 0, + amount: 10, + }, + }, + } + .test_query(&gov, &chain) + .unwrap(); + + match query { + QueryAnswer::UserVotes { votes, total } => { + assert_eq!(total, 0); + assert_eq!(votes.len(), 1); + } + _ => assert!(false), + } +} diff --git a/archived-contracts/mint/.cargo/config b/archived-contracts/mint/.cargo/config new file mode 100644 index 0000000..882fe08 --- /dev/null +++ b/archived-contracts/mint/.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/archived-contracts/mint/.circleci/config.yml b/archived-contracts/mint/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/archived-contracts/mint/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/mint/Cargo.toml b/archived-contracts/mint/Cargo.toml new file mode 100644 index 0000000..c19b32a --- /dev/null +++ b/archived-contracts/mint/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "mint" +version = "0.1.0" +authors = [ + "Guy Garcia ", + "Jackson Swenson ", +] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ "mint", "oracles", "band", "dex", "snip20", "ensemble", "chrono" ] } +schemars = "0.7" + +[dev-dependencies] +contract_harness = { version = "0.1.0", path = "../../packages/contract_harness", features = ["mint", "snip20", "mock_band", "oracle"] } diff --git a/archived-contracts/mint/Makefile b/archived-contracts/mint/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/archived-contracts/mint/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/archived-contracts/mint/README.md b/archived-contracts/mint/README.md new file mode 100644 index 0000000..e7d2c66 --- /dev/null +++ b/archived-contracts/mint/README.md @@ -0,0 +1,215 @@ + +# Mint Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Init](#Init) + * [Admin](#Admin) + * Messages + * [UpdateConfig](#UpdateConfig) + * [UpdateMintLimit](#UpdateMintLimit) + * [RegisterAsset](#RegisterAsset) + * [RemoveAsset](#RemoveAsset) + * [User](#User) + * Messages + * [Receive](#Receive) + * Queries + * [GetNativeAsset](#GetNativeAsset) + * [GetConfig](#GetConfig) + * [GetMintLimit](#GetMintLimit) + * [GetSupportedAssets](#GetSupportedAssets) + * [GetAsset](#GetAsset) +# Introduction +Contract responsible to mint a paired snip20 asset + +# Sections + +## Init +##### Request +|Name |Type |Description | optional | +|-----------------|------------|-------------------------------------------------------------------------------|----------| +|admin | string | New contract owner; SHOULD be a valid bech32 address | yes | +|native_asset | Contract | Asset to mint | no | +|oracle | Contract | Oracle contract | no | +|peg | String | Symbol to peg to when querying oracle (defaults to native_asset symbol) | yes | +|treasury | Contract | Treasury contract | yes | +|secondary_burn | Addrr | Where non-burnable assets will go | yes | +|start_epoch | String | The starting epoch | yes | +|epoch_frequency | String | The frequency in which the mint limit resets, if 0 then no limit is enforced | yes | +|epoch_mint_limit | String | The limit of uTokens to mint per epoch | yes | +## Admin + +### Messages +#### UpdateConfig +Updates the given values +##### Request +|Name |Type |Description | optional | +|---------------|------------|-------------------------------------------------------|----------| +|admin | string | New contract admin; SHOULD be a valid bech32 address | yes | +|oracle | Contract | Oracle contract | yes | +|treasury | Contract | Treasury contract | yes | +|secondary_burn | Addrr | Where non-burnable assets will go | yes | +##### Response +```json +{ + "update_config": { + "status": "success" + } +} +``` + +#### UpdateMintLimit +Updates the mint limit and epoch time +##### Request +|Name |Type |Description | optional | +|-----------------|----------|--------------------------------------------------------------------------------|----------| +|start_epoch | String | The starting epoch | yes | +|epoch_frequency | String | The frequency in which the mint limit resets, if 0 then no limit is enforced | yes | +|epoch_mint_limit | String | The limit of uTokens to mint per epoch | yes | +##### Response +```json +{ + "update_mint_limit": { + "status": "success" + } +} +``` + +#### RegisterAsset +Registers a supported asset. The asset must be SNIP-20 compliant since [RegisterReceive](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md#RegisterReceive) is called. + +##### Request +|Name |Type |Description | optional | +|------------|--------|-------------------------------------|----------| +|contract | Contract | Type explained [here](#Contract) | no | +##### Response +```json +{ + "register_asset": { + "status": "success" + } +} +``` + +#### RemoveAsset +Remove a registered asset. +##### Request +|Name |Type |Description | optional | +|------------|--------|--------------------------------|----------| +|address | String | The asset to remove's address | no | +##### Response +```json +{ + "remove_asset": { + "status": "success" + } +} +``` + +##User + +### Messages + +#### Receive +To mint the user must use a supported asset's send function and send the amount over to the contract's address. The contract will take care of the rest. + +In the msg field of a snip20 send command you must send a base64 encoded json like this one +```json +{"minimum_expected_amount": "Uint128" } +``` + +### Queries + +#### GetNativeAsset +Gets the contract's minted asset +#### Response +```json +{ + "native_asset": { + "asset": "Snip20Asset Object", + "peg": "Pegged symbol" + } +} +``` + +#### GetConfig +Gets the contract's configuration variables +##### Response +```json +{ + "config": { + "config": { + "admin": "Owner address", + "oracle": { + "address": "Asset contract address", + "code_hash": "Asset callback code hash" + }, + "treasury": { + "address": "Asset contract address", + "code_hash": "Asset callback code hash" + }, + "secondary_burn": "Optional burn address", + "activated": "Boolean of contract's actviation status" + } + } +} +``` + +#### GetMintLimit +Gets the contract's configuration variables +##### Response +```json +{ + "limit": { + "mint_limit": { + "frequency": "Frequency per epoch reset", + "mint_capacity": "Mint capacity per epoch", + "total_minted": "Total minted in current epoch", + "next_epoch": "Timestamp for the next epoch" + } + } +} +``` + +#### GetSupportedAssets +Get all the contract's supported assets. +##### Response +```json +{ + "supported_assets": { + "assets": ["asset address"] + } +} +``` + +#### GetAsset +Get specific information on a supported asset. +##### Request +|Name |Type |Description | optional | +|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| +|contract | string | Snip20 contract address; SHOULD be a valid bech32 address, but contracts may use a different naming scheme as well | no | +##### Response +```json +{ + "asset": { + "asset": { + "Snip20Asset": { + "contract": "Asset contract", + "token_info": "Token info as per Snip20", + "token_config": "Optional information about the config if the Snip20 supports it" + }, + "burned": "Total burned on this contract" + } + } +} +``` + +## Contract +Type used in many of the admin commands +```json +{ + "config": { + "address": "Asset contract address", + "code_hash": "Asset callback code hash" + } +} +``` \ No newline at end of file diff --git a/archived-contracts/mint/src/contract.rs b/archived-contracts/mint/src/contract.rs new file mode 100644 index 0000000..adb5cb7 --- /dev/null +++ b/archived-contracts/mint/src/contract.rs @@ -0,0 +1,110 @@ +use shade_protocol::{ + c_std::{ + shd_entry_point, + to_binary, + Api, + Binary, + Deps, + DepsMut, + Env, + MessageInfo, + Querier, + Response, + StdResult, + Storage, + }, + snip20::helpers::{token_config, token_info}, +}; + +use shade_protocol::contract_interfaces::{ + mint::mint::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}, + snip20::helpers::Snip20Asset, +}; + +use crate::{ + handle, + query, + state::{asset_list_w, asset_peg_w, config_w, limit_w, native_asset_w}, +}; + +#[shd_entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let state = Config { + admin: match msg.admin { + None => info.sender.clone(), + Some(admin) => admin, + }, + oracle: msg.oracle, + treasury: msg.treasury, + secondary_burn: msg.secondary_burn, + limit: msg.limit, + activated: true, + }; + + config_w(deps.storage).save(&state)?; + + let token_info = token_info(&deps.querier, &msg.native_asset)?; + + let token_config = token_config(&deps.querier, &msg.native_asset)?; + + let peg = match msg.peg { + Some(p) => p, + None => token_info.symbol.clone(), + }; + asset_peg_w(deps.storage).save(&peg)?; + + deps.api.debug("Setting native asset"); + native_asset_w(deps.storage).save(&Snip20Asset { + contract: msg.native_asset.clone(), + token_info, + token_config: Option::from(token_config), + })?; + + asset_list_w(deps.storage).save(&vec![])?; + + deps.api + .debug(&format!("Contract was initialized by {}", info.sender)); + + Ok(Response::new()) +} + +#[shd_entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg { + ExecuteMsg::UpdateConfig { config } => handle::try_update_config(deps, env, info, config), + ExecuteMsg::RegisterAsset { + contract, + capture, + fee, + unlimited, + } => handle::try_register_asset(deps, &env, info, &contract, capture, fee, unlimited), + ExecuteMsg::RemoveAsset { address } => handle::try_remove_asset(deps, &env, address), + ExecuteMsg::Receive { + sender, + from, + amount, + msg, + .. + } => handle::try_burn(deps, env, info, sender, from, amount, msg), + } +} + +#[shd_entry_point] +pub fn query(deps: Deps, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::NativeAsset {} => to_binary(&query::native_asset(deps)?), + QueryMsg::SupportedAssets {} => to_binary(&query::supported_assets(deps)?), + QueryMsg::Asset { contract } => to_binary(&query::asset(deps, contract)?), + QueryMsg::Config {} => to_binary(&query::config(deps)?), + QueryMsg::Limit {} => to_binary(&query::limit(deps)?), + QueryMsg::Mint { + offer_asset, + amount, + } => to_binary(&query::mint(deps, offer_asset, amount)?), + } +} diff --git a/archived-contracts/mint/src/handle.rs b/archived-contracts/mint/src/handle.rs new file mode 100644 index 0000000..cf4b35c --- /dev/null +++ b/archived-contracts/mint/src/handle.rs @@ -0,0 +1,491 @@ +use shade_protocol::{ + c_std::{ + from_binary, + to_binary, + Addr, + Api, + Binary, + CosmosMsg, + Deps, + DepsMut, + Env, + MessageInfo, + Querier, + QuerierWrapper, + Response, + StdError, + StdResult, + Storage, + Uint128, + }, + chrono::prelude::*, + contract_interfaces::{ + mint::mint::{Config, HandleAnswer, Limit, MintMsgHook, SupportedAsset}, + oracles::{band::ReferenceData, oracle::QueryMsg::Price}, + snip20::helpers::Snip20Asset, + }, + snip20::helpers::{self, burn_msg, mint_msg, send_msg, token_config, token_info, TokenConfig}, + utils::{asset::Contract, generic_response::ResponseStatus, Query}, +}; +use std::{borrow::BorrowMut, cmp::Ordering, convert::TryFrom, fmt::format}; + +use crate::state::{ + asset_list_w, asset_peg_r, assets_r, assets_w, config_r, config_w, limit_r, limit_refresh_r, + limit_refresh_w, limit_w, minted_r, minted_w, native_asset_r, total_burned_w, +}; + +pub fn try_burn( + deps: DepsMut, + env: Env, + info: MessageInfo, + _sender: Addr, + from: Addr, + amount: Uint128, + msg: Option, +) -> StdResult { + let config = config_r(deps.storage).load()?; + // Check if contract enabled + if !config.activated { + return Err(StdError::generic_err("unauthorized")); + } + + let mint_asset = native_asset_r(deps.storage).load()?; + + // Prevent sender to be native asset + if mint_asset.contract.address == info.sender { + return Err(StdError::generic_err( + "Sender cannot be the same as the native asset.", + )); + } + + // Check that sender is a supported snip20 asset + let burn_asset = match assets_r(deps.storage).may_load(info.sender.to_string().as_bytes())? { + Some(supported_asset) => { + deps.api.debug(&format!( + "Found Burn Asset: {} {}", + &supported_asset.asset.token_info.symbol, + info.sender.to_string() + )); + supported_asset + } + None => { + return Err(StdError::not_found(info.sender)); + } + }; + + let mut input_amount = amount; + let mut messages = vec![]; + + if burn_asset.fee > Uint128::zero() { + let fee_amount = calculate_portion(input_amount, burn_asset.fee); + // Reduce input by fee + input_amount = input_amount.checked_sub(fee_amount)?; + + // Fee to treasury + messages.push(send_msg( + config.treasury.clone(), + fee_amount.into(), + None, + None, + None, + &burn_asset.asset.contract, + )?); + } + + // This will calculate the total mint value + let amount_to_mint: Uint128 = + mint_amount(deps.as_ref(), input_amount, &burn_asset, &mint_asset)?; + + if let Some(limit) = config.limit { + // Limit Refresh Check + try_limit_refresh(deps.storage, &deps.querier, env, info, limit)?; + + // Check & adjust limit if a limited asset + if !burn_asset.unlimited { + let minted = minted_r(deps.storage).load()?; + if (amount_to_mint + minted) > limit_r(deps.storage).load()? { + return Err(StdError::generic_err("Limit Exceeded")); + } + + minted_w(deps.storage).save(&(amount_to_mint + minted))?; + } + } + + let mut burn_amount = input_amount; + + // Ignore capture if the set capture is 0 + if burn_asset.capture > Uint128::zero() { + let capture_amount = calculate_portion(amount, burn_asset.capture); + + // Capture to treasury + messages.push(send_msg( + config.treasury.into(), + capture_amount.into(), + None, + None, + None, + &burn_asset.asset.contract, + )?); + + burn_amount = input_amount.checked_sub(capture_amount)?; + } + + if burn_amount > Uint128::zero() { + // Try to burn + if let Some(token_config) = &burn_asset.asset.token_config { + if token_config.burn_enabled { + messages.push(burn_msg( + burn_amount.into(), + None, + None, + &burn_asset.asset.contract, + )?); + } else if let Some(recipient) = config.secondary_burn { + messages.push(send_msg( + recipient, + burn_amount.into(), + None, + None, + None, + &burn_asset.asset.contract, + )?); + } + } else if let Some(recipient) = config.secondary_burn { + messages.push(send_msg( + recipient, + burn_amount.into(), + None, + None, + None, + &burn_asset.asset.contract, + )?); + } + } + + total_burned_w(deps.storage).update( + burn_asset.asset.contract.address.to_string().as_bytes(), + |burned| -> StdResult { + match burned { + Some(burned) => Ok(burned + burn_amount), + None => Ok(burn_amount), + } + }, + )?; + + if let Some(message) = msg { + let msg: MintMsgHook = from_binary(&message)?; + + // Check Slippage + if amount_to_mint < msg.minimum_expected_amount { + return Err(StdError::generic_err( + "Mint amount is less than the minimum expected.", + )); + } + }; + + messages.push(mint_msg( + from, + amount_to_mint.into(), + None, + None, + &mint_asset.contract, + )?); + + Ok(Response::new().set_data(to_binary(&HandleAnswer::Mint { + status: ResponseStatus::Success, + amount: amount_to_mint, + })?)) +} + +pub fn try_limit_refresh( + storage: &mut dyn Storage, + querier: &QuerierWrapper, + env: Env, + info: MessageInfo, + limit: Limit, +) -> StdResult { + match DateTime::parse_from_rfc3339(&limit_refresh_r(storage).load()?) { + Ok(parsed) => { + let naive = NaiveDateTime::from_timestamp(env.block.time.seconds() as i64, 0); + let now: DateTime = DateTime::from_utc(naive, Utc); + let last_refresh: DateTime = parsed.with_timezone(&Utc); + + let mut fresh_amount = Uint128::zero(); + + let native_asset = native_asset_r(storage).load()?; + + let token_info = token_info(querier, &native_asset.contract)?; + + let supply = match token_info.total_supply { + Some(s) => s.into(), + None => return Err(StdError::generic_err("Could not get native token supply")), + }; + + // get amount to add, 0 if not in need of refresh + match limit { + Limit::Daily { + supply_portion, + days, + } => { + // Slight error in annual limit if (days / 365) is not a whole number + if now.num_days_from_ce() as u128 - days.u128() + >= last_refresh.num_days_from_ce() as u128 + { + fresh_amount = calculate_portion(supply, supply_portion); + } + } + Limit::Monthly { + supply_portion, + months, + } => { + if now.year() > last_refresh.year() || now.month() > last_refresh.month() { + /* If its a new year or new month, add (year_diff * 12) to the later (now) month + * 12-2021 <-> 1-2022 becomes a comparison between 12 <-> (1 + 12) + * resulting in a difference of 1 month + */ + let year_diff = now.year() - last_refresh.year(); + + if (now.month() + (year_diff * 12) as u32) - last_refresh.month() + >= months.u128() as u32 + { + fresh_amount = calculate_portion(supply, supply_portion); + } + } + } + } + + if fresh_amount > Uint128::zero() { + let minted = minted_r(storage).load()?; + + limit_w(storage).update(|state| -> StdResult { + // Stack with previous unminted limit + Ok(state.checked_sub(minted)? + fresh_amount) + })?; + limit_refresh_w(storage).save(&now.to_rfc3339())?; + minted_w(storage).save(&Uint128::zero())?; + } + + Ok(fresh_amount) + } + Err(e) => return Err(StdError::generic_err("Failed to parse previous datetime")), + } +} + +pub fn try_update_config( + deps: DepsMut, + env: Env, + info: MessageInfo, + config: Config, +) -> StdResult { + let cur_config = config_r(deps.storage).load()?; + + // Admin-only + if info.sender != cur_config.admin { + return Err(StdError::generic_err("unauthorized")); + } + + config_w(deps.storage).save(&config)?; + + Ok( + Response::new().set_data(to_binary(&HandleAnswer::UpdateConfig { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn try_register_asset( + deps: DepsMut, + env: &Env, + info: MessageInfo, + contract: &Contract, + capture: Option, + fee: Option, + unlimited: Option, +) -> StdResult { + let config = config_r(deps.storage).load()?; + // Check if admin + if info.sender != config.admin { + return Err(StdError::generic_err("unauthorized")); + } + // Check if contract enabled + if !config.activated { + return Err(StdError::generic_err("unauthorized")); + } + + let contract_str = contract.address.to_string(); + + // Add the new asset + let asset_info = token_info(&deps.querier, &contract)?; + + let asset_config: Option = match token_config(&deps.querier, &contract) { + Ok(c) => Option::from(c), + Err(_) => None, + }; + + deps.api + .debug(&format!("Registering {}", asset_info.symbol)); + assets_w(deps.storage).save(contract_str.as_bytes(), &SupportedAsset { + asset: Snip20Asset { + contract: contract.clone(), + token_info: asset_info, + token_config: asset_config, + }, + // If capture is not set then default to 0 + capture: match capture { + None => Uint128::zero(), + Some(value) => value, + }, + fee: match fee { + None => Uint128::zero(), + Some(value) => value, + }, + unlimited: match unlimited { + None => false, + Some(u) => u, + }, + )?; + + total_burned_w(deps.storage).save(contract_str.as_bytes(), &Uint128::zero())?; + + // Add the asset to list + asset_list_w(deps.storage).update(|mut state| -> StdResult> { + state.push(contract.clone()); + Ok(state) + })?; + + // Register contract in asset + let messages = vec![register_receive(env, contract)?]; + + Ok( + Response::new().set_data(to_binary(&HandleAnswer::RegisterAsset { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn try_remove_asset(deps: DepsMut, _env: &Env, address: Addr) -> StdResult { + let address_str = address.to_string(); + + // Remove asset from the array + asset_list_w(deps.storage).update(|mut state| -> StdResult> { + state.retain(|value| value.address != address); + Ok(state) + })?; + + // Remove supported asset + assets_w(deps.storage).remove(address_str.as_bytes()); + + // We wont remove the total burned since we want to keep track of all the burned assets + + Ok( + Response::new().set_data(to_binary(&HandleAnswer::RemoveAsset { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn register_receive(env: &Env, contract: &Contract) -> StdResult { + helpers::register_receive(env.contract.code_hash.clone(), None, contract) +} + +pub fn mint_amount( + deps: Deps, + burn_amount: Uint128, + burn_asset: &SupportedAsset, + mint_asset: &Snip20Asset, +) -> StdResult { + deps.api.debug(&format!( + "Burning {} {} for {}", + burn_amount, burn_asset.asset.token_info.symbol, mint_asset.token_info.symbol + )); + + let burn_price = oracle(deps, burn_asset.asset.token_info.symbol.clone())?; + deps.api.debug(&format!("Burn Price: {}", burn_price)); + + let mint_price = oracle(deps, asset_peg_r(deps.storage).load()?)?; + deps.api.debug(&format!("Mint Price: {}", mint_price)); + + Ok(calculate_mint( + burn_price, + burn_amount, + burn_asset.asset.token_info.decimals, + mint_price, + mint_asset.token_info.decimals, + )) +} + +pub fn calculate_mint( + burn_price: Uint128, + burn_amount: Uint128, + burn_decimals: u8, + mint_price: Uint128, + mint_decimals: u8, +) -> Uint128 { + // Math must only be made in integers + // in_decimals = x + // target_decimals = y + // in_price = p1 * 10^18 + // target_price = p2 * 10^18 + // in_amount = a1 * 10^x + // return = a2 * 10^y + + // (a1 * 10^x) * (p1 * 10^18) = (a2 * 10^y) * (p2 * 10^18) + + // (p1 * 10^18) + // (a1 * 10^x) * -------------- = (a2 * 10^y) + // (p2 * 10^18) + + let burn_value = burn_amount.multiply_ratio(burn_price, mint_price); + + // burn_value * 10^(y - x) = (a2 * 10^y) + let difference: i32 = mint_decimals as i32 - burn_decimals as i32; + + // To avoid a mess of different types doing math + match difference.cmp(&0) { + Ordering::Greater => { + Uint128::new(burn_value.u128() * 10u128.pow(u32::try_from(difference).unwrap())) + } + Ordering::Less => { + burn_value.multiply_ratio(1u128, 10u128.pow(u32::try_from(difference.abs()).unwrap())) + } + Ordering::Equal => burn_value, + } +} + +/* +pub fn calculate_fee_curve( + // "Centered" + base_fee: Uint128, + // How far off from where we want (abs(desired_price - cur_price)) + price_skew: Uint128, + // skew we should never reach (where fee maxes out) + asymptote: Uint128, +) -> Uint128 { + + /* aggressiveness is how sharply it turns up at the asymptote + * speed is the overall speed of increase + * how to include asymptote to push the threshold before acceleration? + * y = (x + speed) ^ (2 * aggressiveness) + */ +} +*/ + +pub fn calculate_portion(amount: Uint128, portion: Uint128) -> Uint128 { + /* amount: total amount sent to burn (uSSCRT/uSILK/uSHD) + * portion: percent * 10^18 e.g. 5_320_000_000_000_000_000 = 5.32% = .0532 + * + * return portion = amount * portion / 10^18 + */ + if portion == Uint128::zero() { + return Uint128::zero(); + } + + amount.multiply_ratio(portion, 10u128.pow(18)) +} + +fn oracle(deps: Deps, symbol: String) -> StdResult { + let config: Config = config_r(deps.storage).load()?; + let answer: ReferenceData = Price { symbol }.query(&deps.querier, &config.oracle)?; + + Ok(Uint128::from(answer.rate)) +} diff --git a/archived-contracts/mint/src/lib.rs b/archived-contracts/mint/src/lib.rs new file mode 100644 index 0000000..a69b81f --- /dev/null +++ b/archived-contracts/mint/src/lib.rs @@ -0,0 +1,4 @@ +pub mod contract; +pub mod handle; +pub mod query; +pub mod state; diff --git a/archived-contracts/mint/src/query.rs b/archived-contracts/mint/src/query.rs new file mode 100644 index 0000000..234284d --- /dev/null +++ b/archived-contracts/mint/src/query.rs @@ -0,0 +1,74 @@ +use crate::{ + handle::mint_amount, + state::{ + asset_list_r, + asset_peg_r, + assets_r, + config_r, + limit_r, + limit_refresh_r, + minted_r, + native_asset_r, + total_burned_r, + }, +}; +use shade_protocol::{ + c_std::{Addr, Deps, StdError, StdResult, Uint128}, + contract_interfaces::mint::mint::QueryAnswer, +}; + +pub fn native_asset(deps: Deps) -> StdResult { + Ok(QueryAnswer::NativeAsset { + asset: native_asset_r(deps.storage).load()?, + peg: asset_peg_r(deps.storage).load()?, + }) +} + +pub fn supported_assets(deps: Deps) -> StdResult { + Ok(QueryAnswer::SupportedAssets { + assets: asset_list_r(deps.storage).load()?, + }) +} + +pub fn asset(deps: Deps, contract: String) -> StdResult { + let assets = assets_r(deps.storage); + + match assets.may_load(contract.as_bytes())? { + Some(asset) => Ok(QueryAnswer::Asset { + asset, + burned: total_burned_r(deps.storage).load(contract.as_bytes())?, + }), + None => Err(StdError::not_found(contract)), + } +} + +pub fn config(deps: Deps) -> StdResult { + Ok(QueryAnswer::Config { + config: config_r(deps.storage).load()?, + }) +} + +pub fn limit(deps: Deps) -> StdResult { + Ok(QueryAnswer::Limit { + minted: minted_r(deps.storage).load()?, + limit: limit_r(deps.storage).load()?, + last_refresh: limit_refresh_r(deps.storage).load()?, + }) +} + +pub fn mint(deps: Deps, offer_asset: Addr, amount: Uint128) -> StdResult { + let native_asset = native_asset_r(deps.storage).load()?; + + match assets_r(deps.storage).may_load(offer_asset.to_string().as_bytes())? { + Some(asset) => { + //let fee = calculate_portion(amount, asset.fee); + //let amount = mint_amount(deps, amount.checked_sub(fee)?, &asset, &native_asset)?; + let amount = mint_amount(deps, amount, &asset, &native_asset)?; + Ok(QueryAnswer::Mint { + asset: native_asset.contract, + amount, + }) + } + None => Err(StdError::not_found(offer_asset.to_string())), + } +} diff --git a/archived-contracts/mint/src/state.rs b/archived-contracts/mint/src/state.rs new file mode 100644 index 0000000..a739abc --- /dev/null +++ b/archived-contracts/mint/src/state.rs @@ -0,0 +1,107 @@ +use shade_protocol::c_std::Uint128; +use shade_protocol::c_std::Storage; +use shade_protocol::storage::{ + bucket, + bucket_read, + singleton, + singleton_read, + Bucket, + ReadonlyBucket, + ReadonlySingleton, + Singleton, +}; +use shade_protocol::{ + contract_interfaces::{ + mint::mint::{Config, SupportedAsset}, + snip20::helpers::Snip20Asset, + }, + utils::asset::Contract, +}; + +pub static CONFIG: &[u8] = b"config"; +pub static LIMIT: &[u8] = b"mint_limit"; +pub static LIMIT_REFRESH: &[u8] = b"limit_refresh"; +pub static MINTED: &[u8] = b"minted"; +pub static NATIVE_ASSET: &[u8] = b"native_asset"; +pub static ASSET_PEG: &[u8] = b"asset_peg"; +pub static ASSET: &[u8] = b"assets"; +pub static ASSET_LIST: &[u8] = b"asset_list"; +pub static BURN_COUNT: &[u8] = b"burn_count"; + +pub fn config_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, CONFIG) +} + +pub fn config_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, CONFIG) +} + +/* Daily limit as (limit * total_supply) at the time of refresh + */ +pub fn limit_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, LIMIT) +} + +pub fn limit_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, LIMIT) +} + +/* RFC-3339 datetime str, last time limit was refreshed + */ +pub fn limit_refresh_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, LIMIT_REFRESH) +} + +pub fn limit_refresh_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, LIMIT_REFRESH) +} + +/* Amount minted this cycle (daily) + */ +pub fn minted_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, MINTED) +} + +pub fn minted_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, MINTED) +} + +pub fn native_asset_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, NATIVE_ASSET) +} + +pub fn native_asset_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, NATIVE_ASSET) +} + +pub fn asset_peg_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, ASSET_PEG) +} + +pub fn asset_peg_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, ASSET_PEG) +} + +pub fn asset_list_w(storage: &mut dyn Storage) -> Singleton> { + singleton(storage, ASSET_LIST) +} + +pub fn asset_list_r(storage: &dyn Storage) -> ReadonlySingleton> { + singleton_read(storage, ASSET_LIST) +} + +pub fn assets_r(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, ASSET) +} + +pub fn assets_w(storage: &mut dyn Storage) -> Bucket { + bucket(storage, ASSET) +} + +pub fn total_burned_r(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, BURN_COUNT) +} + +pub fn total_burned_w(storage: &mut dyn Storage) -> Bucket { + bucket(storage, BURN_COUNT) +} diff --git a/archived-contracts/mint/tests/integration.rs b/archived-contracts/mint/tests/integration.rs new file mode 100644 index 0000000..ce398eb --- /dev/null +++ b/archived-contracts/mint/tests/integration.rs @@ -0,0 +1,260 @@ +use shade_protocol::c_std::{ + coins, + from_binary, + to_binary, + Binary, + Env, + DepsMut, + Response, + Addr, + Response, + StdError, + StdResult, +}; + +use shade_protocol::c_std::Uint128; +use shade_protocol::{ + contract_interfaces::{ + snip20, + mint::mint::{ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg}, + oracles::band::{BandQuery, ReferenceData}, + }, + utils::{ + asset::Contract, + price::{normalize_price, translate_price}, + }, +}; + +use snip20_reference_impl; +use mock_band; +use oracle; + +use mint::{ + contract::{handle, instantiate, query}, + handle::{calculate_mint, calculate_portion, try_burn}, +}; + +use contract_harness::harness::{ + mint::Mint, + mock_band::MockBand, + oracle::Oracle, + snip20_reference_impl::Snip20ReferenceImpl as Snip20 +}; + +use shade_protocol::fadroma::{ + ensemble::{ContractEnsemble, ContractHarness, MockDeps, MockEnv}, +}; +use shade_protocol::fadroma::core::ContractLink; + +fn test_ensemble( + offer_price: Uint128, + offer_amount: Uint128, + mint_price: Uint128, + expected_amount: Uint128, +) { + let mut ensemble = ContractEnsemble::new(50); + + let reg_oracle = ensemble.register(Box::new(Oracle)); + let reg_mint = ensemble.register(Box::new(Mint)); + let reg_snip20 = ensemble.register(Box::new(Snip20)); + let reg_band = ensemble.register(Box::new(MockBand)); + + let sscrt = ensemble + .instantiate( + reg_snip20.id, + &snip20::InstantiateMsg { + name: "secretSCRT".into(), + admin: Some("admin".into()), + symbol: "SSCRT".into(), + decimals: 6, + initial_balances: None, + prng_seed: to_binary("").ok().unwrap(), + config: None, + }, + MockEnv::new("admin", ContractLink { + address: Addr::unchecked("sscrt".into()), + code_hash: reg_snip20.code_hash.clone(), + }), + ) + .unwrap().instance; + + let shade = ensemble + .instantiate( + reg_snip20.id, + &snip20::InstantiateMsg { + name: "Shade".into(), + admin: Some("admin".into()), + symbol: "SHD".into(), + decimals: 8, + initial_balances: None, + prng_seed: to_binary("").ok().unwrap(), + config: None, + }, + MockEnv::new("admin", ContractLink { + address: Addr::unchecked("shade".into()), + code_hash: reg_snip20.code_hash.clone(), + }), + ) + .unwrap().instance; + + let band = ensemble + .instantiate( + reg_band.id, + &shade_protocol::contract_interfaces::oracles::band::InstantiateMsg {}, + MockEnv::new("admin", ContractLink { + address: Addr::unchecked("band".into()), + code_hash: reg_band.code_hash.clone(), + }), + ) + .unwrap().instance; + + let oracle = ensemble + .instantiate( + reg_oracle.id, + &shade_protocol::contract_interfaces::oracles::oracle::InstantiateMsg { + admin: Some(Addr::unchecked("admin".into())), + band: Contract { + address: band.address.clone(), + code_hash: band.code_hash.clone(), + }, + sscrt: Contract { + address: sscrt.address.clone(), + code_hash: sscrt.code_hash.clone(), + }, + }, + MockEnv::new("admin", ContractLink { + address: Addr::unchecked("oracle".into()), + code_hash: reg_oracle.code_hash.clone(), + }), + ) + .unwrap().instance; + + let mint = ensemble + .instantiate( + reg_mint.id, + &shade_protocol::contract_interfaces::mint::mint::InstantiateMsg { + admin: Some(Addr::unchecked("admin".into())), + oracle: Contract { + address: oracle.address.clone(), + code_hash: oracle.code_hash.clone(), + }, + native_asset: Contract { + address: shade.address.clone(), + code_hash: shade.code_hash.clone(), + }, + peg: None, + treasury: Addr::unchecked("admin".into()), + secondary_burn: None, + limit: None, + }, + MockEnv::new("admin", ContractLink { + address: Addr::unchecked("mint".into()), + code_hash: reg_mint.code_hash, + }), + ) + .unwrap().instance; + + // Setup price feeds + ensemble + .execute( + &mock_band::contract::ExecuteMsg::MockPrice { + symbol: "SCRT".into(), + price: offer_price, + }, + MockEnv::new("admin", band.clone()), + ) + .unwrap(); + ensemble + .execute( + &mock_band::contract::ExecuteMsg::MockPrice { + symbol: "SHD".into(), + price: mint_price, + }, + MockEnv::new("admin", band.clone()), + ) + .unwrap(); + + // Register sSCRT burn + ensemble + .execute( + &shade_protocol::contract_interfaces::mint::mint::ExecuteMsg::RegisterAsset { + contract: Contract { + address: sscrt.address.clone(), + code_hash: sscrt.code_hash.clone(), + }, + capture: None, + fee: None, + unlimited: None, + }, + MockEnv::new("admin", mint.clone()), + ) + .unwrap(); + + // Check mint query + let (asset, amount) = match ensemble + .query( + mint.address.clone(), + &shade_protocol::contract_interfaces::mint::mint::QueryMsg::Mint { + offer_asset: sscrt.address.clone(), + amount: Uint128::new(offer_amount.u128()), + }, + ) + .unwrap() + { + shade_protocol::contract_interfaces::mint::mint::QueryAnswer::Mint { asset, amount } => { + (asset, amount) + } + _ => ( + Contract { + address: Addr::unchecked("".into()), + code_hash: "".into(), + }, + Uint128::new(0), + ), + }; + + assert_eq!(asset, Contract { + address: shade.address.clone(), + code_hash: shade.code_hash.clone(), + }); + + assert_eq!(amount, Uint128::new(expected_amount.u128())); +} + +macro_rules! mint_int_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (offer_price, offer_amount, mint_price, expected_amount) = $value; + test_ensemble(offer_price, offer_amount, mint_price, expected_amount); + } + )* + } +} +mint_int_tests! { + mint_int_0: ( + Uint128::new(10u128.pow(18)), // $1 + Uint128::new(10u128.pow(6)), // 1sscrt + Uint128::new(10u128.pow(18)), // $1 + Uint128::new(10u128.pow(8)), // 1 SHD + ), + mint_int_1: ( + Uint128::new(2 * 10u128.pow(18)), // $2 + Uint128::new(10u128.pow(6)), // 1 sscrt + Uint128::new(10u128.pow(18)), // $1 + Uint128::new(2 * 10u128.pow(8)), // 2 SHD + ), + mint_int_2: ( + Uint128::new(1 * 10u128.pow(18)), // $1 + Uint128::new(4 * 10u128.pow(6)), // 4 sscrt + Uint128::new(10u128.pow(18)), // $1 + Uint128::new(4 * 10u128.pow(8)), // 4 SHD + ), + mint_int_3: ( + Uint128::new(10 * 10u128.pow(18)), // $10 + Uint128::new(30 * 10u128.pow(6)), // 30 sscrt + Uint128::new(5 * 10u128.pow(18)), // $5 + Uint128::new(60 * 10u128.pow(8)), // 60 SHD + ), +} diff --git a/archived-contracts/mint/tests/unit.rs b/archived-contracts/mint/tests/unit.rs new file mode 100644 index 0000000..44da68d --- /dev/null +++ b/archived-contracts/mint/tests/unit.rs @@ -0,0 +1,113 @@ +use shade_protocol::c_std::Uint128; +use shade_protocol::c_std::{ + self, + coins, + from_binary, + to_binary, + Binary, + Env, + DepsMut, + Response, + Addr, + Response, + StdError, + StdResult, +}; + +use shade_protocol::{ + contract_interfaces::{ + mint::mint::{ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg}, + oracles::band::{BandQuery, ReferenceData}, + }, + utils::{ + asset::Contract, + price::{normalize_price, translate_price}, + }, +}; + +#[test] +fn capture_calc() { + let amount = Uint128::new(1_000_000_000_000_000_000u128); + //10% + let capture = Uint128::new(100_000_000_000_000_000u128); + let expected = Uint128::new(100_000_000_000_000_000u128); + let value = mint::handle::calculate_portion(amount, capture); + assert_eq!(value, expected); +} + +macro_rules! mint_algorithm_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (in_price, in_amount, in_decimals, target_price, target_decimals, expected_value) = $value; + assert_eq!(mint::handle::calculate_mint(in_price, in_amount, in_decimals, target_price, target_decimals), expected_value); + } + )* + } +} + +mint_algorithm_tests! { + mint_simple_0: ( + // In this example the "sent" value is 1 with 6 decimal places + // The mint value will be 1 with 3 decimal places + Uint128::new(1_000_000_000_000_000_000), //Burn price + Uint128::new(1_000_000), //Burn amount + 6u8, //Burn decimals + Uint128::new(1_000_000_000_000_000_000), //Mint price + 3u8, //Mint decimals + Uint128::new(1_000), //Expected value + ), + mint_simple_1: ( + // In this example the "sent" value is 1 with 8 decimal places + // The mint value will be 1 with 3 decimal places + Uint128::new(1_000_000_000_000_000_000), //Burn price + Uint128::new(1_000_000), //Burn amount + 6u8, //Burn decimals + Uint128::new(1_000_000_000_000_000_000), //Mint price + 8u8, //Mint decimals + Uint128::new(100_000_000), //Expected value + ), + mint_complex_0: ( + // In this example the "sent" value is 1.8 with 6 decimal places + // The mint value will be 3.6 with 12 decimal places + Uint128::new(2_000_000_000_000_000_000), + Uint128::new(1_800_000), + 6u8, + Uint128::new(1_000_000_000_000_000_000), + 12u8, + Uint128::new(3_600_000_000_000), + ), + mint_complex_1: ( + // In amount is 50.000 valued at 20 + // target price is 100$ with 6 decimals + Uint128::new(20_000_000_000_000_000_000), + Uint128::new(50_000), + 3u8, + Uint128::new(100_000_000_000_000_000_000), + 6u8, + Uint128::new(10_000_000), + ), + mint_complex_2: ( + // In amount is 10,000,000 valued at 100 + // Target price is $10 with 6 decimals + Uint128::new(1_000_000_000_000_000_000_000), + Uint128::new(100_000_000_000_000), + 8u8, + Uint128::new(10_000_000_000_000_000_000), + 6u8, + Uint128::new(100_000_000_000_000), + ), + /* + mint_overflow_0: ( + // In amount is 1,000,000,000,000,000,000,000,000 valued at 1,000 + // Target price is $5 with 6 decimals + Uint128::new(1_000_000_000_000_000_000_000), + Uint128::new(100_000_000_000_000_000_000_000_000_000_000), + 8u8, + Uint128::new(5_000_000_000_000_000_000), + 6u8, + Uint128::new(500_000_000_000_000_000_000_000_000_000_000_000), + ), + */ +} diff --git a/archived-contracts/mint_router/.cargo/config b/archived-contracts/mint_router/.cargo/config new file mode 100644 index 0000000..882fe08 --- /dev/null +++ b/archived-contracts/mint_router/.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/archived-contracts/mint_router/.circleci/config.yml b/archived-contracts/mint_router/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/archived-contracts/mint_router/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/mint_router/Cargo.toml b/archived-contracts/mint_router/Cargo.toml new file mode 100644 index 0000000..9557470 --- /dev/null +++ b/archived-contracts/mint_router/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "mint_router" +version = "0.1.0" +authors = ["Jackson Swenson "] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ + "mint_router", + "oracles", + "band", + "mint", + "dex", + "chrono", +] } +schemars = "0.7" diff --git a/archived-contracts/mint_router/Makefile b/archived-contracts/mint_router/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/archived-contracts/mint_router/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/archived-contracts/mint_router/README.md b/archived-contracts/mint_router/README.md new file mode 100644 index 0000000..d21087c --- /dev/null +++ b/archived-contracts/mint_router/README.md @@ -0,0 +1,210 @@ + +# Mint Router Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Init](#Init) + * [Admin](#Admin) + * Messages + * [UpdateConfig](#UpdateConfig) + * [UpdateMintLimit](#UpdateMintLimit) + * [RegisterAsset](#RegisterAsset) + * [RemoveAsset](#RemoveAsset) + * [User](#User) + * Messages + * [Receive](#Receive) + * Queries + * [NativeAsset](#GetNativeAsset) + * [Config](#GetConfig) + * [SupportedAssets](#GetSupportedAssets) + * [Asset](#GetAsset) +# Introduction +Contract responsible to mint a paired snip20 asset + +# Sections + +## Init +##### Request +|Name |Type |Description | optional | +|-----------------|------------|-------------------------------------------------------------------------------|----------| +|admin | string | New contract owner; SHOULD be a valid bech32 address | yes | +|oracle | Contract | Oracle contract | no | +|peg | String | Symbol to peg to when querying oracle (defaults to native_asset symbol) | yes | +|treasury | Contract | Treasury contract | yes | +|secondary_burn | Addrr | Where non-burnable assets will go | yes | +## Admin + +### Messages +#### UpdateConfig +Updates the given values +##### Request +|Name |Type |Description | optional | +|---------------|------------|-------------------------------------------------------|----------| +|admin | string | New contract admin; SHOULD be a valid bech32 address | yes | +|oracle | Contract | Oracle contract | yes | +|treasury | Contract | Treasury contract | yes | +|secondary_burn | Addrr | Where non-burnable assets will go | yes | +##### Response +```json +{ + "update_config": { + "status": "success" + } +} +``` + +#### UpdateMintLimit +Updates the mint limit and epoch time +##### Request +|Name |Type |Description | optional | +|-----------------|----------|--------------------------------------------------------------------------------|----------| +|start_epoch | String | The starting epoch | yes | +|epoch_frequency | String | The frequency in which the mint limit resets, if 0 then no limit is enforced | yes | +|epoch_mint_limit | String | The limit of uTokens to mint per epoch | yes | +##### Response +```json +{ + "update_mint_limit": { + "status": "success" + } +} +``` + +#### RegisterAsset +Registers a supported asset. The asset must be SNIP-20 compliant since [RegisterReceive](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md#RegisterReceive) is called. + +##### Request +|Name |Type |Description | optional | +|------------|--------|-------------------------------------|----------| +|contract | Contract | Type explained [here](#Contract) | no | +##### Response +```json +{ + "register_asset": { + "status": "success" + } +} +``` + +#### RemoveAsset +Remove a registered asset. +##### Request +|Name |Type |Description | optional | +|------------|--------|--------------------------------|----------| +|address | String | The asset to remove's address | no | +##### Response +```json +{ + "remove_asset": { + "status": "success" + } +} +``` + +##User + +### Messages + +#### Receive +To mint the user must use a supported asset's send function and send the amount over to the contract's address. The contract will take care of the rest. + +In the msg field of a snip20 send command you must send a base64 encoded json like this one +```json +{"minimum_expected_amount": "Uint128" } +``` + +### Queries + +#### GetNativeAsset +Gets the contract's minted asset +#### Response +```json +{ + "native_asset": { + "asset": "Snip20Asset Object", + "peg": "Pegged symbol" + } +} +``` + +#### GetConfig +Gets the contract's configuration variables +##### Response +```json +{ + "config": { + "config": { + "admin": "Owner address", + "oracle": { + "address": "Asset contract address", + "code_hash": "Asset callback code hash" + }, + "treasury": { + "address": "Asset contract address", + "code_hash": "Asset callback code hash" + }, + "secondary_burn": "Optional burn address", + "activated": "Boolean of contract's actviation status" + } + } +} +``` + +#### GetMintLimit +Gets the contract's configuration variables +##### Response +```json +{ + "limit": { + "mint_limit": { + "frequency": "Frequency per epoch reset", + "mint_capacity": "Mint capacity per epoch", + "total_minted": "Total minted in current epoch", + "next_epoch": "Timestamp for the next epoch" + } + } +} +``` + +#### GetSupportedAssets +Get all the contract's supported assets. +##### Response +```json +{ + "supported_assets": { + "assets": ["asset address"] + } +} +``` + +#### GetAsset +Get specific information on a supported asset. +##### Request +|Name |Type |Description | optional | +|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| +|contract | string | Snip20 contract address; SHOULD be a valid bech32 address, but contracts may use a different naming scheme as well | no | +##### Response +```json +{ + "asset": { + "asset": { + "Snip20Asset": { + "contract": "Asset contract", + "token_info": "Token info as per Snip20", + "token_config": "Optional information about the config if the Snip20 supports it" + }, + "burned": "Total burned on this contract" + } + } +} +``` + +## Contract +Type used in many of the admin commands +```json +{ + "config": { + "address": "Asset contract address", + "code_hash": "Asset callback code hash" + } +} +``` diff --git a/archived-contracts/mint_router/src/contract.rs b/archived-contracts/mint_router/src/contract.rs new file mode 100644 index 0000000..021d64a --- /dev/null +++ b/archived-contracts/mint_router/src/contract.rs @@ -0,0 +1,80 @@ +use shade_protocol::c_std::{ + + to_binary, + Api, + Binary, + Env, + DepsMut, + Response, + Querier, + StdResult, + Storage, + Uint128, +}; +use shade_protocol::snip20::helpers::{register_receive, token_info, token_config}; + +use shade_protocol::contract_interfaces::{ + mint::mint_router::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}, + snip20::helpers::Snip20Asset, +}; + +use crate::{ + handle, + query, + state::{config_w, current_assets_w}, +}; + +pub fn init( + deps: DepsMut, + env: Env, + info: MessageInfo, +) -> StdResult { + let config = Config { + admin: match msg.admin { + None => info.sender.clone(), + Some(admin) => admin, + }, + path: msg.path, + }; + + config_w(deps.storage).save(&config)?; + //current_assets_w(deps.storage).save(&vec![])?; + + let mut messages = vec![]; + + if config.path.len() > 0 { + //messages.append(&mut handle::update_entry_assets(deps, env, info, config.path[0].clone())?); + messages.append(&mut handle::build_path(deps, env, info, config.path.clone())?); + } + + Ok(Response::new()) +} + +pub fn handle( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> StdResult { + match msg { + ExecuteMsg::UpdateConfig { config } => handle::try_update_config(deps, env, info, config), + ExecuteMsg::Receive { + sender, + from, + amount, + msg, + .. + } => handle::receive(deps, env, info, sender, from, amount, msg), + } +} + +pub fn query( + deps: Deps, + msg: QueryMsg, +) -> StdResult { + match msg { + QueryMsg::Config {} => to_binary(&query::config(deps)?), + QueryMsg::Assets {} => to_binary(&query::assets(deps)?), + QueryMsg::Route { asset, amount } => to_binary(&query::route(deps, asset, amount)?), + } +} diff --git a/archived-contracts/mint_router/src/handle.rs b/archived-contracts/mint_router/src/handle.rs new file mode 100644 index 0000000..851ca19 --- /dev/null +++ b/archived-contracts/mint_router/src/handle.rs @@ -0,0 +1,230 @@ +use shade_protocol::{ + c_std::{ + from_binary, + to_binary, + Addr, + Api, + Binary, + CosmosMsg, + DepsMut, + Env, + Querier, + Response, + StdError, + StdResult, + Storage, + Uint128, + }, + chrono::prelude::*, + contract_interfaces::{ + mint::{ + mint, + mint_router::{Config, HandleAnswer}, + }, + oracles::{band::ReferenceData, oracle::QueryMsg::Price}, + snip20::helpers::Snip20Asset, + }, + snip20::helpers::{ + burn_msg, + mint_msg, + register_receive, + send_msg, + token_config, + token_info_query, + TokenConfig, + }, + utils::{asset::Contract, generic_response::ResponseStatus, Query}, +}; +use std::{cmp::Ordering, convert::TryFrom}; + +use crate::state::{ + asset_path_r, + asset_path_w, + config_r, + config_w, + current_assets_w, + final_asset_r, + final_asset_w, + registered_asset_r, + registered_asset_w, +}; + +pub fn receive( + deps: DepsMut, + env: Env, + info: MessageInfo, + sender: Addr, + from: Addr, + amount: Uint128, + msg: Option, +) -> StdResult { + let mut messages = vec![]; + let asset_paths = asset_path_r(deps.storage); + + let mut input_asset = + registered_asset_r(deps.storage).load(&info.sender.to_string().as_bytes())?; + let mut input_amount = amount; + + let final_asset = final_asset_r(deps.storage).load()?; + + while input_asset.address != final_asset { + let mint = asset_paths.load(input_asset.address.to_string().as_bytes())?; + let (output_asset, output_amount) = match (mint::QueryMsg::Mint { + offer_asset: input_asset.address.clone(), + amount: input_amount, + } + .query(&deps.querier, mint.clone())?) + { + mint::QueryAnswer::Mint { asset, amount } => (asset, amount), + _ => { + return Err(StdError::generic_err("Failed to get mint asset/amount")); + } + }; + + if output_asset.address == final_asset { + // Send with the msg for slippage + messages.push(send_msg( + mint.address.to_string(), + input_amount.into(), + msg.clone(), + None, + None, + input_asset.clone * (), + )?); + } else { + // ignore slippage for intermediate steps + messages.push(send_msg( + mint.address.to_string(), + input_amount.into(), + None, + None, + None, + input_asset.clone(), + )?); + } + + input_asset = output_asset; + input_amount = output_amount; + } + + messages.push(send_msg( + from.to_string(), + input_amount.into(), + None, + None, + None, + input_asset.clone(), + )?); + + Ok(Response::new().set_data(to_binary(&HandleAnswer::Mint { + status: ResponseStatus::Success, + amount, + })?)) +} + +pub fn try_update_config( + deps: DepsMut, + env: Env, + info: MessageInfo, + config: Config, +) -> StdResult { + let cur_config = config_r(deps.storage).load()?; + + // Admin-only + if info.sender != cur_config.admin { + return Err(StdError::generic_err("unauthorized")); + } + + let mut messages = vec![]; + + if cur_config.path != config.path { + messages.append(&mut build_path(deps, env, info, config.path.clone())?); + } + + config_w(deps.storage).save(&config)?; + + Ok( + Response::new().set_data(to_binary(&HandleAnswer::UpdateConfig { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn build_path( + deps: DepsMut, + env: Env, + info: MessageInfo, + path: Vec, +) -> StdResult> { + let mut messages = vec![]; + let mut all_assets = vec![]; + + for mint in path.clone() { + let entry_assets = match (mint::QueryMsg::SupportedAssets {}.query( + &deps.querier, + mint.code_hash.clone(), + mint.address.clone(), + )?) { + mint::QueryAnswer::SupportedAssets { assets } => assets, + _ => { + return Err(StdError::generic_err("Failed to get entry assets")); + } + }; + + all_assets.append(&mut entry_assets.clone()); + + // Make sure all new assets are registered + for asset in entry_assets.clone() { + // Register receive if it hasn't been before + if (registered_asset_r(deps.storage).may_load(&asset.address.to_string().as_bytes())?) + .is_none() + { + messages.push(register_receive( + env.contract.code_hash.clone(), + None, + asset, + )?); + registered_asset_w(deps.storage) + .save(&asset.address.to_string().as_bytes(), &asset)?; + } + + // Set this assets node to the current mint contract + asset_path_w(deps.storage).save(&asset.address.to_string().as_bytes(), &mint)?; + } + } + + let exit = path.last().unwrap(); + let final_asset = match (mint::QueryMsg::NativeAsset {}.query( + &deps.querier, + exit.code_hash.clone(), + exit.address.clone(), + )?) { + mint::QueryAnswer::NativeAsset { asset, peg } => asset.contract, + _ => { + return Err(StdError::generic_err("Failed to get final asset")); + } + }; + + // Ensure final asset is registered + if (registered_asset_r(deps.storage).may_load(&final_asset.address.to_string().as_bytes())?) + .is_none() + { + messages.push(register_receive( + env.contract.code_hash.clone(), + None, + &final_asset, + )?); + registered_asset_w(deps.storage) + .save(&final_asset.address.to_string().as_bytes(), &final_asset)?; + } + + // remove final asset to prevent circles + if let Some(index) = all_assets.iter().position(|a| *a == final_asset) { + all_assets.remove(index); + } + + final_asset_w(deps.storage).save(&final_asset.address)?; + current_assets_w(deps.storage).save(&all_assets)?; + + Ok(messages) +} diff --git a/archived-contracts/mint_router/src/lib.rs b/archived-contracts/mint_router/src/lib.rs new file mode 100644 index 0000000..e321ad6 --- /dev/null +++ b/archived-contracts/mint_router/src/lib.rs @@ -0,0 +1,49 @@ +pub mod contract; +pub mod handle; +pub mod query; +pub mod state; + +#[cfg(test)] +mod test; + +#[cfg(target_arch = "wasm32")] +mod wasm { + use super::contract; + use shade_protocol::c_std::{ + do_handle, + do_init, + do_query, + ExternalApi, + ExternalQuerier, + ExternalStorage, + }; + + #[no_mangle] + extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { + do_init( + &contract::init::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { + do_handle( + &contract::handle::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn query(msg_ptr: u32) -> u32 { + do_query( + &contract::query::, + msg_ptr, + ) + } + + // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available + // automatically because we `use cosmwasm_std`. +} diff --git a/archived-contracts/mint_router/src/query.rs b/archived-contracts/mint_router/src/query.rs new file mode 100644 index 0000000..99f4181 --- /dev/null +++ b/archived-contracts/mint_router/src/query.rs @@ -0,0 +1,60 @@ +use crate::state::{asset_path_r, config_r, current_assets_r, final_asset_r, registered_asset_r}; +use shade_protocol::{ + c_std::{Addr, Deps, StdError, StdResult, Uint128}, + contract_interfaces::mint::{ + mint, + mint_router::{PathNode, QueryAnswer}, + }, + snip20::helpers::token_info_query, + utils::Query, +}; + +pub fn config(deps: Deps) -> StdResult { + Ok(QueryAnswer::Config { + config: config_r(deps.storage).load()?, + }) +} + +pub fn assets(deps: Deps) -> StdResult { + Ok(QueryAnswer::Assets { + assets: current_assets_r(deps.storage).load()?, + }) +} + +pub fn route(deps: Deps, asset: Addr, amount: Uint128) -> StdResult { + let mut path = vec![]; + let mut input_asset = registered_asset_r(deps.storage).load(&asset.to_string().as_bytes())?; + let mut input_amount = amount; + + let final_asset = final_asset_r(deps.storage).load()?; + + while input_asset.address != final_asset { + let mint = asset_path_r(deps.storage).load(&input_asset.address.to_string().as_bytes())?; + let (output_asset, output_amount) = match (mint::QueryMsg::Mint { + offer_asset: input_asset.address.clone(), + amount: input_amount, + } + .query(&deps.querier, mint.code_hash.clone(), mint.address.clone())?) + { + mint::QueryAnswer::Mint { asset, amount } => (asset, amount), + _ => { + return Err(StdError::generic_err("Failed to get native asset")); + } + }; + + path.push(PathNode { + input_asset: input_asset.address.clone(), + input_amount, + + mint: mint.address, + + output_asset: output_asset.address.clone(), + output_amount, + }); + + input_asset = output_asset; + input_amount = output_amount; + } + + Ok(QueryAnswer::Route { path }) +} diff --git a/archived-contracts/mint_router/src/state.rs b/archived-contracts/mint_router/src/state.rs new file mode 100644 index 0000000..8588e4f --- /dev/null +++ b/archived-contracts/mint_router/src/state.rs @@ -0,0 +1,76 @@ +use shade_protocol::c_std::Uint128; +use shade_protocol::c_std::{Addr, Storage}; +use shade_protocol::storage::{ + bucket, + bucket_read, + singleton, + singleton_read, + Bucket, + ReadonlyBucket, + ReadonlySingleton, + Singleton, +}; +use shade_protocol::{ + contract_interfaces::{mint::mint_router::Config, snip20::helpers::Snip20Asset}, + utils::asset::Contract, +}; + +pub static CONFIG: &[u8] = b"config"; +pub static REGISTERED_ASSETS: &[u8] = b"registered_assets"; +pub static CURRENT_ASSETS: &[u8] = b"current_assets"; +pub static ASSET_PATH: &[u8] = b"asset_path"; +pub static FINAL_ASSET: &[u8] = b"final_asset"; +pub static USER: &[u8] = b"user"; + +pub fn config_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, CONFIG) +} + +pub fn config_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, CONFIG) +} + +pub fn registered_asset_w(storage: &mut dyn Storage) -> Bucket { + bucket(storage, REGISTERED_ASSETS) +} + +pub fn registered_asset_r(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, REGISTERED_ASSETS) +} + +/* Given a snip20 asset, gives the mint contract + * furthest down the path + */ +pub fn asset_path_w(storage: &mut dyn Storage) -> Bucket { + bucket(storage, ASSET_PATH) +} + +pub fn asset_path_r(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, ASSET_PATH) +} + +pub fn final_asset_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, FINAL_ASSET) +} + +pub fn final_asset_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, FINAL_ASSET) +} + +pub fn current_assets_w(storage: &mut dyn Storage) -> Singleton> { + singleton(storage, CURRENT_ASSETS) +} + +pub fn current_assets_r(storage: &dyn Storage) -> ReadonlySingleton> { + singleton_read(storage, CURRENT_ASSETS) +} + +/* Needs to track the originating user across receive calls + */ +pub fn user_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, USER) +} + +pub fn user_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, USER) +} diff --git a/archived-contracts/mint_router/src/test.rs b/archived-contracts/mint_router/src/test.rs new file mode 100644 index 0000000..b41a801 --- /dev/null +++ b/archived-contracts/mint_router/src/test.rs @@ -0,0 +1,414 @@ +/* +#[cfg(test)] +pub mod tests { + use shade_protocol::c_std::{ + coins, from_binary, + testing::{mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage}, + DepsMut, StdError, Uint128, + }; + use shade_protocol::mint_router::{ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg}; + + use crate::{ + contract::{handle, init, query}, + handle::{calculate_capture, calculate_mint, try_burn}, + }; + + mod mock_secret_toolkit { + + use shade_protocol::c_std::{Addr, Querier, StdResult, Uint128}; + use shade_protocol::snip20::helpers::TokenInfo; + + pub fn mock_token_info_query( + _querier: &Q, + _block_size: usize, + _callback_code_hash: String, + _contract_addr: Addr, + ) -> StdResult { + Ok(TokenInfo { + name: "Token".to_string(), + symbol: "TKN".to_string(), + decimals: 6, + total_supply: Option::from(Uint128::new(150)), + }) + } + } + + #[double] + use mock_shade_protocol::secret_toolkit::token_info_query; + use shade_protocol::utils::asset::Contract; + + fn create_contract(address: &str, code_hash: &str) -> Contract { + let env = mock_env(address.to_string(), &[]); + return Contract { + address: info.sender, + code_hash: code_hash.to_string(), + }; + } + + fn dummy_init( + admin: String, + native_asset: Contract, + oracle: Contract, + peg: Option, + treasury: Option, + capture: Option, + ) -> Extern { + let mut deps = mock_dependencies(20, &[]); + let msg = InstantiateMsg { + admin: None, + native_asset, + oracle, + peg, + treasury, + secondary_burn: None, + limit: None, + /* + start_epoch: None, + epoch_frequency: None, + epoch_mint_limit: None, + */ + }; + let env = mock_env(admin, &coins(1000, "earth")); + let _res = init(&mut deps, env, info, msg).unwrap(); + + return deps; + } + + #[test] + /* + fn proper_initialization() { + let mut deps = mock_dependencies(20, &[]); + let msg = InstantiateMsg { + admin: None, + native_asset: create_contract("", ""), + oracle: create_contract("", ""), + peg: Option::from("TKN".to_string()), + treasury: Option::from(create_contract("", "")), + // 1% + capture: Option::from(Uint128::new(100)), + }; + let env = mock_env("creator", &coins(1000, "earth")); + + // we can just call .unwrap() to assert this was a success + let res = init(&mut deps, env, info, msg).unwrap(); + assert_eq!(0, res.messages.len()); + } + */ + + /* + #[test] + fn config_update() { + let native_asset = create_contract("snip20", "hash"); + let oracle = create_contract("oracle", "hash"); + let treasury = create_contract("treasury", "hash"); + let capture = Uint128::new(100); + + let admin_env = mock_env("admin", &coins(1000, "earth")); + let mut deps = dummy_init("admin".to_string(), + native_asset, + oracle, + None, + Option::from(treasury), + Option::from(capture)); + + // new config vars + let new_oracle = Option::from(create_contract("new_oracle", "hash")); + let new_treasury = Option::from(create_contract("new_treasury", "hash")); + let new_capture = Option::from(Uint128::new(200)); + + // Update config + let update_msg = ExecuteMsg::UpdateConfig { + owner: None, + oracle: new_oracle.clone(), + treasury: new_treasury.clone(), + // 2% + capture: new_capture.clone(), + }; + let update_res = handle(&mut deps, admin_env, update_msg); + + let config_res = query(&deps, QueryMsg::GetConfig {}).unwrap(); + let value: QueryAnswer = from_binary(&config_res).unwrap(); + match value { + QueryAnswer::Config { config } => { + assert_eq!(config.oracle, new_oracle.unwrap()); + assert_eq!(config.treasury, new_treasury); + assert_eq!(config.capture, new_capture); + } + _ => { panic!("Received wrong answer") } + } + } + */ + + /* + #[test] + fn user_register_asset() { + let mut deps = dummy_init("admin".to_string(), + create_contract("", ""), + create_contract("", ""), + None, None, None); + + // User should not be allowed to add an item + let user_env = mock_env("user", &coins(1000, "earth")); + let dummy_contract = create_contract("some_contract", "some_hash"); + let msg = ExecuteMsg::RegisterAsset { + contract: dummy_contract, + }; + let res = handle(&mut deps, user_env, msg); + match res { + Err(StdError::Unauthorized { .. }) => {} + _ => panic!("Must return unauthorized error"), + } + + // Response should be an empty array + let res = query(&deps, QueryMsg::GetSupportedAssets {}).unwrap(); + let value: QueryAnswer = from_binary(&res).unwrap(); + match value { + QueryAnswer::SupportedAssets { assets } => { assert_eq!(0, assets.len()) } + _ => { panic!("Received wrong answer") } + } + } + + #[test] + fn admin_register_asset() { + let mut deps = dummy_init("admin".to_string(), + create_contract("", ""), + create_contract("", ""), + None, + None, + None); + + // Admin should be allowed to add an item + let env = mock_env("admin", &coins(1000, "earth")); + let dummy_contract = create_contract("some_contract", "some_hash"); + let msg = ExecuteMsg::RegisterAsset { + contract: dummy_contract, + }; + let _res = handle(&mut deps, env, info, msg).unwrap(); + + // Response should be an array of size 1 + let res = query(&deps, QueryMsg::GetSupportedAssets {}).unwrap(); + let value: QueryAnswer = from_binary(&res).unwrap(); + match value { + QueryAnswer::SupportedAssets { assets } => { assert_eq!(1, assets.len()) } + _ => { panic!("Received wrong answer") } + } + } + + #[test] + fn duplicate_register_asset() { + let mut deps = dummy_init("admin".to_string(), + create_contract("", ""), + create_contract("", ""), + None, + None, + None); + + let env = mock_env("admin", &coins(1000, "earth")); + let dummy_contract = create_contract("some_contract", "some_hash"); + let msg = ExecuteMsg::RegisterAsset { + contract: dummy_contract, + }; + let _res = handle(&mut deps, env, info, msg).unwrap(); + + // Should not be allowed to add an existing asset + let env = mock_env("admin", &coins(1000, "earth")); + let dummy_contract = create_contract("some_contract", "other_hash"); + let msg = ExecuteMsg::RegisterAsset { + contract: dummy_contract, + }; + let res = handle(&mut deps, env, info, msg); + match res { + Err(StdError::GenericErr { .. }) => {} + _ => panic!("Must return not found error"), + }; + } + + /* + #[test] + fn user_update_asset() { + let mut deps = dummy_init("admin".to_string(), + create_contract("", ""), + create_contract("", "")); + + // Add a supported asset + let env = mock_env("admin", &coins(1000, "earth")); + let dummy_contract = create_contract("some_contract", "some_hash"); + let msg = ExecuteMsg::RegisterAsset { + contract: dummy_contract, + }; + let _res = handle(&mut deps, env, info, msg).unwrap(); + + // users should not be allowed to update assets + let user_env = mock_env("user", &coins(1000, "earth")); + let dummy_contract = create_contract("some_contract", "some_hash"); + let new_dummy_contract = create_contract("some_other_contract", "some_hash"); + let msg = ExecuteMsg::UpdateAsset { + asset: dummy_contract.address, + contract: new_dummy_contract, + }; + let res = handle(&mut deps, user_env, msg); + match res { + Err(StdError::Unauthorized { .. }) => {} + _ => panic!("Must return unauthorized error"), + }; + } + */ + + /* + #[test] + fn admin_update_asset() { + let mut deps = dummy_init("admin".to_string(), + create_contract("", ""), + create_contract("", "")); + + // Add a supported asset + let env = mock_env("admin", &coins(1000, "earth")); + let dummy_contract = create_contract("some_contract", "some_hash"); + let msg = ExecuteMsg::RegisterAsset { + contract: dummy_contract, + }; + let _res = handle(&mut deps, env, info, msg).unwrap(); + + // admins can update assets + let env = mock_env("admin", &coins(1000, "earth")); + let dummy_contract = create_contract("some_contract", "some_hash"); + let new_dummy_contract = create_contract("some_other_contract", "some_hash"); + let msg = ExecuteMsg::UpdateAsset { + asset: dummy_contract.address, + contract: new_dummy_contract, + }; + let _res = handle(&mut deps, env, info, msg).unwrap(); + + // Response should be new dummy contract + let res = query(&deps, QueryMsg::GetAsset { contract: "some_other_contract".to_string() }).unwrap(); + let value: QueryAnswer = from_binary(&res).unwrap(); + match value { + QueryAnswer::Asset { asset, burned } => { assert_eq!("some_other_contract".to_string(), asset.contract.address.to_string()) } + _ => { panic!("Received wrong answer") } + }; + } + */ + + #[test] + fn receiving_an_asset() { + let mut deps = dummy_init("admin".to_string(), + create_contract("", ""), + create_contract("", ""), + None, None, None); + + // Add a supported asset + let env = mock_env("admin", &coins(1000, "earth")); + let dummy_contract = create_contract("some_contract", "some_hash"); + let msg = ExecuteMsg::RegisterAsset { + contract: dummy_contract, + }; + let _res = handle(&mut deps, env, info, msg).unwrap(); + + // Contract tries to send funds + let env = mock_env("some_contract", &coins(1000, "earth")); + let dummy_contract = create_contract("some_owner", "some_hash"); + + let msg = ExecuteMsg::Receive { + sender: dummy_contract.address, + from: Default::default(), + amount: Uint128::new(100), + msg: None, + memo: None + }; + + let res = handle(&mut deps, env, info, msg); + match res { + Err(err) => { + match err { + StdError::NotFound { .. } => {panic!("Not found");} + StdError::Unauthorized { .. } => {panic!("Unauthorized");} + _ => {} + } + } + _ => {} + } + } + + #[test] + fn receiving_an_asset_from_non_supported_asset() { + let mut deps = dummy_init("admin".to_string(), + create_contract("", ""), + create_contract("", ""), + None, + None, + None, + ); + + // Add a supported asset + let env = mock_env("admin", &coins(1000, "earth")); + let dummy_contract = create_contract("some_contract", "some_hash"); + let msg = ExecuteMsg::RegisterAsset { + contract: dummy_contract, + }; + let _res = handle(&mut deps, env, info, msg).unwrap(); + + // Contract tries to send funds + let env = mock_env("some_other_contract", &coins(1000, "earth")); + let dummy_contract = create_contract("some_owner", "some_hash"); + let msg = ExecuteMsg::Receive { + sender: dummy_contract.address, + from: Default::default(), + amount: Uint128::new(100), + msg: None, + memo: None + }; + let res = handle(&mut deps, env, info, msg); + match res { + Err(StdError::NotFound { .. }) => {} + _ => {panic!("Must return not found error")}, + } + } + */ + #[test] + fn capture_calc() { + let amount = Uint128::new(1_000_000_000_000_000_000); + //10% + let capture = Uint128::new(100_000_000_000_000_000); + let expected = Uint128::new(100_000_000_000_000_000); + let value = calculate_capture(amount, capture); + assert_eq!(value, expected); + } + #[test] + fn mint_algorithm_simple() { + // In this example the "sent" value is 1 with 6 decimal places + // The mint value will be 1 with 3 decimal places + let price = Uint128::new(1_000_000_000_000_000_000); + let in_amount = Uint128::new(1_000_000); + let expected_value = Uint128::new(1_000); + let value = calculate_mint(price, in_amount, 6, price, 3); + + assert_eq!(value, expected_value); + } + + #[test] + fn mint_algorithm_complex_1() { + // In this example the "sent" value is 1.8 with 6 decimal places + // The mint value will be 3.6 with 12 decimal places + let in_price = Uint128::new(2_000_000_000_000_000_000); + let target_price = Uint128::new(1_000_000_000_000_000_000); + let in_amount = Uint128::new(1_800_000); + let expected_value = Uint128::new(3_600_000_000_000); + let value = calculate_mint(in_price, in_amount, 6, target_price, 12); + + assert_eq!(value, expected_value); + } + + #[test] + fn mint_algorithm_complex_2() { + // In amount is 50.000 valued at 20 + // target price is 100$ with 6 decimals + let in_price = Uint128::new(20_000_000_000_000_000_000); + let target_price = Uint128::new(100_000_000_000_000_000_000); + let in_amount = Uint128::new(50_000); + let expected_value = Uint128::new(10_000_000); + let value = calculate_mint(in_price, in_amount, 3, target_price, 6); + + assert_eq!(value, expected_value); + } +} +*/ diff --git a/archived-contracts/mock_band/.cargo/config b/archived-contracts/mock_band/.cargo/config new file mode 100644 index 0000000..882fe08 --- /dev/null +++ b/archived-contracts/mock_band/.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/archived-contracts/mock_band/.circleci/config.yml b/archived-contracts/mock_band/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/archived-contracts/mock_band/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/mock_band/Cargo.toml b/archived-contracts/mock_band/Cargo.toml new file mode 100644 index 0000000..54a4287 --- /dev/null +++ b/archived-contracts/mock_band/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "mock_band" +version = "0.1.0" +authors = [ + "Jack Swenson ", + "Guy Garcia ", +] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +bincode = "1.3.1" +shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ + "band", +] } +schemars = "0.7" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/archived-contracts/mock_band/Makefile b/archived-contracts/mock_band/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/archived-contracts/mock_band/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/archived-contracts/mock_band/README.md b/archived-contracts/mock_band/README.md new file mode 100644 index 0000000..8fad675 --- /dev/null +++ b/archived-contracts/mock_band/README.md @@ -0,0 +1,21 @@ +# Mock Band Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [User](#User) + * Queries + * [GetReferenceData](#GetReferenceData) +# Introduction +The Mocked Band contract is used to test locally when there is no official band contract available + +### Queries + +#### GetReferenceData +Get a hardcoded sample from an ETH query for testing locally +##### Response +```json +{ + "rate": "3119154999999000000000", + "last_updated_base": 1628548483, + "last_updated_quote": 3377610 +} +``` diff --git a/archived-contracts/mock_band/src/contract.rs b/archived-contracts/mock_band/src/contract.rs new file mode 100644 index 0000000..d5d219b --- /dev/null +++ b/archived-contracts/mock_band/src/contract.rs @@ -0,0 +1,110 @@ +use shade_protocol::c_std::{ + to_binary, + Api, + Binary, + Env, + DepsMut, + Response, + Querier, + StdError, + StdResult, + Storage, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use shade_protocol::contract_interfaces::oracles::band::{InstantiateMsg, ReferenceData}; +use shade_protocol::c_std::Uint128; + +use shade_protocol::storage::{bucket, bucket_read, Bucket, ReadonlyBucket}; + +pub static PRICE: &[u8] = b"prices"; + +pub fn price_r(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, PRICE) +} + +pub fn price_w(storage: &mut dyn Storage) -> Bucket { + bucket(storage, PRICE) +} + +pub fn init( + _deps: DepsMut, + _env: Env, + _msg: InstantiateMsg, +) -> StdResult { + Ok(Response::default()) +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + MockPrice { symbol: String, price: Uint128 }, +} + +pub fn handle( + deps: DepsMut, + _env: Env, + msg: ExecuteMsg, +) -> StdResult { + return match msg { + ExecuteMsg::MockPrice { symbol, price } => { + price_w(deps.storage).save(symbol.as_bytes(), &price)?; + Ok(Response::default()) + } + }; +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + GetReferenceData { + base_symbol: String, + quote_symbol: String, + }, + GetReferenceDataBulk { + base_symbols: Vec, + quote_symbols: Vec, + }, +} +pub fn query( + deps: Deps, + msg: QueryMsg, +) -> StdResult { + match msg { + QueryMsg::GetReferenceData { + base_symbol, + quote_symbol: _, + } => { + if let Some(price) = price_r(deps.storage).may_load(base_symbol.as_bytes())? { + return to_binary(&ReferenceData { + rate: price, + last_updated_base: 0, + last_updated_quote: 0, + }); + } + Err(StdError::generic_err("Missing Price Feed")) + } + QueryMsg::GetReferenceDataBulk { + base_symbols, + quote_symbols: _, + } => { + let mut results = Vec::new(); + + for sym in base_symbols { + if let Some(price) = price_r(deps.storage).may_load(sym.as_bytes())? { + results.push(ReferenceData { + rate: price, + last_updated_base: 0, + last_updated_quote: 0, + }); + } else { + return Err(StdError::GenericErr { + msg: "Missing Price Feed".to_string(), + backtrace: None, + }); + } + } + to_binary(&results) + } + } +} diff --git a/archived-contracts/mock_band/src/execute.rs b/archived-contracts/mock_band/src/execute.rs new file mode 100644 index 0000000..9949c6c --- /dev/null +++ b/archived-contracts/mock_band/src/execute.rs @@ -0,0 +1,424 @@ +use shade_protocol::{ + admin::helpers::{validate_admin, AdminPermissions}, + c_std::{ + to_binary, + Addr, + Binary, + Coin, + CosmosMsg, + Deps, + DepsMut, + DistributionMsg, + Env, + MessageInfo, + Response, + StakingMsg, + StdError, + StdResult, + Uint128, + Validator, + }, +}; + +use shade_protocol::snip20::helpers::redeem_msg; + +use shade_protocol::{ + dao::{ + adapter, + scrt_staking::{Config, ExecuteAnswer, ValidatorBounds}, + }, + utils::{ + asset::{scrt_balance, Contract}, + generic_response::ResponseStatus, + wrap::{unwrap, wrap_and_send}, + }, +}; + +use crate::{ + query, + storage::{CONFIG, SELF_ADDRESS, UNBONDING}, +}; + +pub fn receive( + deps: DepsMut, + env: Env, + info: MessageInfo, + _sender: Addr, + _from: Addr, + amount: Uint128, + _msg: Option, +) -> StdResult { + //panic!("scrt staking Received {}", amount); + + let config = CONFIG.load(deps.storage)?; + + if info.sender != config.sscrt.address { + return Err(StdError::generic_err("Only accepts sSCRT")); + } + + let validator = choose_validator(deps, env.block.time.seconds())?; + + Ok(Response::new() + .add_messages(vec![ + redeem_msg(amount, None, None, &config.sscrt)?, + CosmosMsg::Staking(StakingMsg::Delegate { + validator: validator.address.clone(), + amount: Coin { + amount, + denom: "uscrt".to_string(), + }, + }), + ]) + .set_data(to_binary(&ExecuteAnswer::Receive { + status: ResponseStatus::Success, + validator, + })?)) +} + +pub fn try_update_config( + deps: DepsMut, + _env: Env, + info: MessageInfo, + config: Config, +) -> StdResult { + let cur_config = CONFIG.load(deps.storage)?; + + validate_admin( + &deps.querier, + AdminPermissions::ScrtStakingAdmin, + &info.sender, + &cur_config.admin_auth, + )?; + + // Save new info + CONFIG.save(deps.storage, &config)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { + status: ResponseStatus::Success, + })?), + ) +} + +/* Claim rewards and restake, hold enough for pending unbondings + * Send reserves unbonded funds to treasury + */ +pub fn update(deps: DepsMut, env: Env, _info: MessageInfo, asset: Addr) -> StdResult { + let mut messages = vec![]; + //let asset = deps.api.addr_validate(asset.as_str())?; + + let config = CONFIG.load(deps.storage)?; + + if asset != config.sscrt.address { + return Err(StdError::generic_err("Unrecognized Asset")); + } + + let scrt_balance = scrt_balance(deps, SELF_ADDRESS.load(deps.storage)?)?; + + // Claim Rewards + let rewards = query::rewards(deps.as_ref())?; + if !rewards.is_zero() { + messages.append(&mut withdraw_rewards(deps.as_ref())?); + } + + let mut stake_amount = rewards + scrt_balance; + let unbonding = UNBONDING.load(deps.storage)?; + + // Don't restake funds that unbonded + if unbonding < stake_amount { + stake_amount = stake_amount - unbonding; + } else { + stake_amount = Uint128::zero(); + } + + if stake_amount > Uint128::zero() { + let validator = choose_validator(deps, env.block.time.seconds())?; + messages.push(CosmosMsg::Staking(StakingMsg::Delegate { + validator: validator.address.clone(), + amount: Coin { + amount: stake_amount, + denom: "uscrt".to_string(), + }, + })); + } + + Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Update { + status: ResponseStatus::Success, + }, + )?)) +} + +pub fn unbond( + deps: DepsMut, + _env: Env, + info: MessageInfo, + asset: Addr, + amount: Uint128, +) -> StdResult { + /* Unbonding to the scrt staking contract + * Once scrt is on balance sheet, treasury can claim + * and this contract will take all scrt->sscrt and send + */ + + //let asset = deps.api.addr_validate(asset.as_str())?; + let config = CONFIG.load(deps.storage)?; + + if validate_admin( + &deps.querier, + AdminPermissions::ScrtStakingAdmin, + &info.sender, + &config.admin_auth, + ) + .is_err() + && config.owner != info.sender + { + return Err(StdError::generic_err("Unauthorized")); + } + + if asset != config.sscrt.address { + return Err(StdError::generic_err("Unrecognized Asset")); + } + + let self_address = SELF_ADDRESS.load(deps.storage)?; + let delegations = query::delegations(deps.as_ref())?; + + let delegated = Uint128::new( + delegations + .iter() + .map(|d| d.amount.amount.u128()) + .sum::(), + ); + let scrt_balance = scrt_balance(deps, self_address)?; + let rewards = query::rewards(deps.as_ref())?; + + let mut messages = vec![]; + + if !rewards.is_zero() { + messages.append(&mut withdraw_rewards(deps.as_ref())?); + } + + let mut undelegated = vec![]; + + let prev_unbonding = UNBONDING.load(deps.storage)?; + + let mut total = delegated + scrt_balance + rewards + delegated; + total -= prev_unbonding; + + if total - prev_unbonding < amount { + return Err(StdError::generic_err(format!( + "Total Unbond amount {} greater than delegated {}; rew {}, bal {}", + amount, delegated, rewards, scrt_balance + ))); + } + + let mut unbonding = amount; + let mut total_unbonding = amount + prev_unbonding; + let reserves = scrt_balance + rewards; + + if total_unbonding.is_zero() { + return Ok( + Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Unbond { + status: ResponseStatus::Success, + amount: unbonding, + })?), + ); + } + + // Send full unbonding + if total_unbonding <= reserves { + messages.append(&mut wrap_and_send( + unbonding, + config.owner, + config.sscrt, + None, + )?); + total_unbonding = Uint128::zero(); + } + // Send all reserves + else if !reserves.is_zero() { + messages.append(&mut wrap_and_send( + reserves, + config.owner, + config.sscrt, + None, + )?); + total_unbonding -= reserves; + } + + UNBONDING.save(deps.storage, &total_unbonding)?; + + while !total_unbonding.is_zero() { + // Unbond from largest validator first + let max_delegation = delegations.iter().max_by_key(|d| { + if undelegated.contains(&d.validator) { + Uint128::zero() + } else { + d.amount.amount + } + }); + + // No more delegated funds to unbond + match max_delegation { + None => { + break; + } + Some(delegation) => { + if undelegated.contains(&delegation.validator) + || delegation.amount.amount.clone() == Uint128::zero() + { + break; + } + + // This delegation isn't enough to fully unbond + if delegation.amount.amount.clone() < unbonding + && !delegation.amount.amount.clone().is_zero() + { + messages.push(CosmosMsg::Staking(StakingMsg::Undelegate { + validator: delegation.validator.clone(), + amount: delegation.amount.clone(), + })); + unbonding = unbonding - delegation.amount.amount.clone(); + undelegated.push(delegation.validator.clone()); + } else if !delegation.amount.amount.clone().is_zero() { + messages.push(CosmosMsg::Staking(StakingMsg::Undelegate { + validator: delegation.validator.clone(), + amount: Coin { + denom: delegation.amount.denom.clone(), + amount: unbonding, + }, + })); + unbonding = Uint128::zero(); + undelegated.push(delegation.validator.clone()); + } + } + } + } + + Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Unbond { + status: ResponseStatus::Success, + amount: unbonding, + }, + )?)) +} + +pub fn withdraw_rewards(deps: Deps) -> StdResult> { + let mut messages = vec![]; + let address = SELF_ADDRESS.load(deps.storage)?; + + for delegation in deps.querier.query_all_delegations(address.clone())? { + messages.push(CosmosMsg::Distribution( + DistributionMsg::WithdrawDelegatorReward { + validator: delegation.validator, + }, + )); + } + + Ok(messages) +} + +pub fn unwrap_and_stake( + _deps: DepsMut, + amount: Uint128, + validator: Validator, + token: Contract, +) -> StdResult> { + Ok(vec![ + // unwrap + unwrap(amount, token.clone())?, + // Stake + CosmosMsg::Staking(StakingMsg::Delegate { + validator: validator.address.clone(), + amount: Coin { + amount, + denom: "uscrt".to_string(), + }, + }), + ]) +} + +/* Claims completed unbondings, wraps them, + * and returns them to treasury + */ +pub fn claim(deps: DepsMut, _env: Env, _info: MessageInfo, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + //let asset = deps.api.addr_validate(asset.as_str())?; + if asset != config.sscrt.address { + return Err(StdError::generic_err("Unrecognized Asset")); + } + + let mut messages = vec![]; + + let unbond_amount = UNBONDING.load(deps.storage)?; + let claim_amount; + + let scrt_balance = scrt_balance(deps.as_ref(), SELF_ADDRESS.load(deps.storage)?)?; + + if scrt_balance >= unbond_amount { + claim_amount = unbond_amount; + } else { + // Claim Rewards + let rewards = query::rewards(deps.as_ref())?; + + if !rewards.is_zero() { + //assert!(false, "withdraw rewards"); + messages.append(&mut withdraw_rewards(deps.as_ref())?); + } + + if rewards + scrt_balance >= unbond_amount { + claim_amount = unbond_amount; + } else { + claim_amount = rewards + scrt_balance; + } + } + + if !claim_amount.is_zero() { + messages.append(&mut wrap_and_send( + claim_amount, + config.owner, + config.sscrt, + None, + )?); + + //assert!(false, "u - claim_amount: {} - {}", unbond_amount, claim_amount); + let u = UNBONDING.load(deps.storage)?; + UNBONDING.save(deps.storage, &(u - claim_amount))?; + } + + Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Claim { + status: ResponseStatus::Success, + amount: claim_amount, + }, + )?)) +} + +pub fn choose_validator(deps: DepsMut, seed: u64) -> StdResult { + let mut validators = deps.querier.query_all_validators()?; + + // filter down to viable candidates + if let Some(bounds) = (CONFIG.load(deps.storage)?).validator_bounds { + let mut candidates = vec![]; + + for validator in validators { + if is_validator_inbounds(&validator, &bounds) { + candidates.push(validator); + } + } + + validators = candidates; + } + + if validators.is_empty() { + return Err(StdError::generic_err("No validators within bounds")); + } + + // seed will likely be env.block.time.seconds() + Ok(validators[(seed % validators.len() as u64) as usize].clone()) +} + +pub fn is_validator_inbounds(validator: &Validator, bounds: &ValidatorBounds) -> bool { + validator.commission <= bounds.max_commission && validator.commission >= bounds.min_commission +} diff --git a/archived-contracts/mock_band/src/lib.rs b/archived-contracts/mock_band/src/lib.rs new file mode 100644 index 0000000..2943dbb --- /dev/null +++ b/archived-contracts/mock_band/src/lib.rs @@ -0,0 +1 @@ +pub mod contract; diff --git a/archived-contracts/mock_secretswap_pair/.cargo/config b/archived-contracts/mock_secretswap_pair/.cargo/config new file mode 100644 index 0000000..882fe08 --- /dev/null +++ b/archived-contracts/mock_secretswap_pair/.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/archived-contracts/mock_secretswap_pair/.circleci/config.yml b/archived-contracts/mock_secretswap_pair/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/archived-contracts/mock_secretswap_pair/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/mock_secretswap_pair/Cargo.toml b/archived-contracts/mock_secretswap_pair/Cargo.toml new file mode 100644 index 0000000..00380ef --- /dev/null +++ b/archived-contracts/mock_secretswap_pair/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "mock_secretswap_pair" +version = "0.1.0" +authors = ["Jack Swenson "] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +bincode = "1.3.1" +shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ + "dex", +] } +schemars = "0.7" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/archived-contracts/mock_secretswap_pair/Makefile b/archived-contracts/mock_secretswap_pair/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/archived-contracts/mock_secretswap_pair/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/archived-contracts/mock_secretswap_pair/README.md b/archived-contracts/mock_secretswap_pair/README.md new file mode 100644 index 0000000..8fad675 --- /dev/null +++ b/archived-contracts/mock_secretswap_pair/README.md @@ -0,0 +1,21 @@ +# Mock Band Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [User](#User) + * Queries + * [GetReferenceData](#GetReferenceData) +# Introduction +The Mocked Band contract is used to test locally when there is no official band contract available + +### Queries + +#### GetReferenceData +Get a hardcoded sample from an ETH query for testing locally +##### Response +```json +{ + "rate": "3119154999999000000000", + "last_updated_base": 1628548483, + "last_updated_quote": 3377610 +} +``` diff --git a/archived-contracts/mock_secretswap_pair/src/contract.rs b/archived-contracts/mock_secretswap_pair/src/contract.rs new file mode 100644 index 0000000..c9127e9 --- /dev/null +++ b/archived-contracts/mock_secretswap_pair/src/contract.rs @@ -0,0 +1,176 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use shade_protocol::{ + c_std::{ + to_binary, + Addr, + Api, + Binary, + Deps, + DepsMut, + Env, + Querier, + Response, + StdError, + StdResult, + Storage, + Uint128, + }, + contract_interfaces::{ + dex::{ + dex, + secretswap::{ + Asset, + AssetInfo, + PairQuery, + PairResponse, + PoolResponse, + SimulationResponse, + Token, + }, + }, + oracles::band::{InstantiateMsg, ReferenceData}, + }, + utils::asset::Contract, +}; + +use shade_protocol::storage::{singleton, singleton_read, ReadonlySingleton, Singleton}; + +pub static PAIR_INFO: &[u8] = b"pair_info"; +pub static POOL: &[u8] = b"pool"; + +pub fn pair_info_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, PAIR_INFO) +} + +pub fn pair_info_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, PAIR_INFO) +} + +pub fn pool_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, POOL) +} + +pub fn pool_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, POOL) +} + +pub fn init(_deps: DepsMut, _env: Env, _msg: InstantiateMsg) -> StdResult { + Ok(Response::default()) +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + MockPool { + token_a: Contract, + amount_a: Uint128, + token_b: Contract, + amount_b: Uint128, + }, +} + +pub fn handle(deps: DepsMut, _env: Env, msg: ExecuteMsg) -> StdResult { + return match msg { + ExecuteMsg::MockPool { + token_a, + amount_a, + token_b, + amount_b, + } => { + let asset_infos = vec![ + AssetInfo { + token: Token { + contract_addr: token_a.address, + token_code_hash: token_a.code_hash, + viewing_key: "SecretSwap".to_string(), + }, + }, + AssetInfo { + token: Token { + contract_addr: token_b.address, + token_code_hash: token_b.code_hash, + viewing_key: "SecretSwap".to_string(), + }, + }, + ]; + pool_w(deps.storage).save(&PoolResponse { + assets: vec![ + Asset { + amount: amount_a, + info: asset_infos[0].clone(), + }, + Asset { + amount: amount_b, + info: asset_infos[1].clone(), + }, + ], + total_share: Uint128::zero(), + })?; + + pair_info_w(deps.storage).save(&PairResponse { + asset_infos, + contract_addr: Addr::unchecked("".to_string()), + liquidity_token: Addr::unchecked("".to_string()), + token_code_hash: "".to_string(), + asset0_volume: Uint128::zero(), + asset1_volume: Uint128::zero(), + factory: Contract { + address: Addr::unchecked("".to_string()), + code_hash: "".to_string(), + }, + })?; + Ok(Response::default()) + } + }; +} + +pub fn query(deps: Deps, msg: PairQuery) -> StdResult { + match msg { + PairQuery::Pool {} => to_binary(&pool_r(deps.storage).load()?), + PairQuery::Pair {} => to_binary(&pair_info_r(deps.storage).load()?), + PairQuery::Simulation { offer_asset } => { + let pool = pool_r(deps.storage).load()?; + + if pool.assets[0].info == offer_asset.info { + /* + let take_amount = dex::pool_take_amount( + offer_asset.amount, + pool.assets[0].amount, + pool.assets[1].amount, + ); + + return Err(StdError::generic_err( + format!("INPUT 0 pools input: {}, give: {}, take: {}", + offer_asset.amount, + pool.assets[0].amount, + pool.assets[1].amount + ) + )); + */ + let resp = SimulationResponse { + return_amount: dex::pool_take_amount( + offer_asset.amount, + pool.assets[0].amount, + pool.assets[1].amount, + ), + spread_amount: Uint128::zero(), + commission_amount: Uint128::zero(), + }; + return to_binary(&resp); + } else if pool.assets[1].info == offer_asset.info { + return to_binary(&SimulationResponse { + return_amount: dex::pool_take_amount( + offer_asset.amount, + pool.assets[1].amount, + pool.assets[0].amount, + ), + spread_amount: Uint128::zero(), + commission_amount: Uint128::zero(), + }); + } + + return Err(StdError::generic_err("Not a token on this pair")); + } + } +} diff --git a/archived-contracts/mock_secretswap_pair/src/lib.rs b/archived-contracts/mock_secretswap_pair/src/lib.rs new file mode 100644 index 0000000..d7d6c20 --- /dev/null +++ b/archived-contracts/mock_secretswap_pair/src/lib.rs @@ -0,0 +1,43 @@ +pub mod contract; + +#[cfg(target_arch = "wasm32")] +mod wasm { + use super::contract; + use shade_protocol::c_std::{ + do_handle, + do_init, + do_query, + ExternalApi, + ExternalQuerier, + ExternalStorage, + }; + + #[no_mangle] + extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { + do_init( + &contract::init::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { + do_handle( + &contract::handle::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn query(msg_ptr: u32) -> u32 { + do_query( + &contract::query::, + msg_ptr, + ) + } + + // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available + // automatically because we `use cosmwasm_std`. +} diff --git a/archived-contracts/mock_sienna_pair/Cargo.toml b/archived-contracts/mock_sienna_pair/Cargo.toml new file mode 100644 index 0000000..5508629 --- /dev/null +++ b/archived-contracts/mock_sienna_pair/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "mock_sienna_pair" +version = "0.1.0" +authors = ["Jack Swenson "] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +cosmwasm-schema = "1.1.5" +shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ + "dex", +] } diff --git a/archived-contracts/mock_sienna_pair/src/contract.rs b/archived-contracts/mock_sienna_pair/src/contract.rs new file mode 100644 index 0000000..6344be6 --- /dev/null +++ b/archived-contracts/mock_sienna_pair/src/contract.rs @@ -0,0 +1,303 @@ +use shade_protocol::{ + c_std::{ + shd_entry_point, from_binary, to_binary, + Addr, Binary, Decimal, Deps, DepsMut, + Env, MessageInfo, Response, StdError, + StdResult, QuerierWrapper, Uint128, + }, + contract_interfaces::{ + dex::{ + dex::pool_take_amount, + sienna::{ + self, + Pair, + TokenType, + }, + }, + snip20::helpers::{balance_query, send_msg, set_viewing_key_msg}, + }, + cosmwasm_schema::cw_serde, + utils::{ + asset::Contract, ExecuteCallback, InstantiateCallback, + storage::plus::{Item, ItemStorage}, + }, +}; +pub use shade_protocol::dex::sienna::{ + PairQuery as QueryMsg, + PairInfoResponse, + SimulationResponse, + ReceiverCallbackMsg, +}; + +#[cw_serde] +pub struct Config { + pub address: Addr, + pub viewing_key: String, + pub commission: Decimal, +} + +impl ItemStorage for Config { + const ITEM: Item<'static, Self> = Item::new("item-config"); +} + +#[cw_serde] +pub struct PairInfo { + pub token_0: Contract, + pub token_1: Contract, +} + +impl ItemStorage for PairInfo { + const ITEM: Item<'static, Self> = Item::new("item-pair"); +} + +#[cw_serde] +pub struct InstantiateMsg { + pub token_0: Contract, + pub token_1: Contract, + pub viewing_key: String, + pub commission: Decimal, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[shd_entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg +) -> StdResult { + let pair_info = PairInfo { + token_0: msg.token_0.clone(), + token_1: msg.token_1.clone(), + }; + pair_info.save(deps.storage)?; + + let config = Config { + address: env.contract.address, + viewing_key: msg.viewing_key.clone(), + commission: msg.commission, + }; + config.save(deps.storage)?; + + let messages = vec![ + set_viewing_key_msg( + msg.viewing_key.clone(), + None, + &msg.token_0, + )?, + set_viewing_key_msg( + msg.viewing_key, + None, + &msg.token_1, + )?, + ]; + Ok(Response::default() + .add_messages(messages)) +} + +#[cw_serde] +pub enum ExecuteMsg { + MockPool { + token_a: Contract, + token_b: Contract, + }, + // SNIP20 receiver interface + Receive { + sender: Addr, + from: Addr, + msg: Option, + amount: Uint128, + }, +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[shd_entry_point] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg +) -> StdResult { + match msg { + ExecuteMsg::MockPool { + token_a, + token_b, + } => { + let pair_info = PairInfo { + token_0: token_a, + token_1: token_b, + }; + + pair_info.save(deps.storage)?; + Ok(Response::default()) + } + + // Swap + ExecuteMsg::Receive { + from, + msg, + amount, + .. + } => { + let msg = msg.ok_or_else(|| { + StdError::generic_err("Receiver callback \"msg\" parameter cannot be empty.") + })?; + + match from_binary(&msg)? { + ReceiverCallbackMsg::Swap { expected_return, to } => { + let config = Config::load(deps.storage)?; + let pair = PairInfo::load(deps.storage)?; + + let (in_token, out_token) = if info.sender == pair.token_0.address { + (pair.token_0, pair.token_1) + } else if info.sender == pair.token_1.address { + (pair.token_1, pair.token_0) + } else { + return Err(StdError::generic_err("unauthorized")); + }; + + let (in_pool, out_pool) = query_pool_amounts( + &deps.querier, + &config, + in_token.clone(), + out_token.clone(), + )?; + + // Sienna takes commission before swap + let swap_amount = amount - (amount * config.commission); + let return_amount = pool_take_amount( + swap_amount, + in_pool - amount, // amount has already been added to this pool + out_pool, + ); + + if return_amount < expected_return.unwrap_or(Uint128::zero()) { + return Err(StdError::generic_err( + "Operation fell short of expected_return" + )); + } + + // send tokens + let return_addr = to.unwrap_or(from); + return Ok(Response::default() + .add_message(send_msg( + return_addr, + return_amount, + None, + None, + None, + &out_token, + )?)) + }, + } + } + } + +} + +#[shd_entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::PairInfo => { + let config = Config::load(deps.storage)?; + let pair_info = PairInfo::load(deps.storage)?; + let (amount_0, amount_1) = query_pool_amounts( + &deps.querier, + &config, + pair_info.token_0.clone(), + pair_info.token_1.clone(), + )?; + + to_binary(&PairInfoResponse { + pair_info: sienna::PairInfo { + liquidity_token: Contract { + address: Addr::unchecked("lp_token"), + code_hash: "hash".to_string(), + }, + factory: Contract { + address: Addr::unchecked("factory"), + code_hash: "hash".to_string(), + }, + pair: Pair { + token_0: TokenType::CustomToken { + contract_addr: pair_info.token_0.address, + token_code_hash: pair_info.token_0.code_hash, + }, + token_1: TokenType::CustomToken { + contract_addr: pair_info.token_1.address, + token_code_hash: pair_info.token_1.code_hash, + } + }, + amount_0, + amount_1, + total_liquidity: Uint128::zero(), + contract_version: 0, + }, + }) + }, + QueryMsg::SwapSimulation { offer } => { + let config = Config::load(deps.storage)?; + let pair = PairInfo::load(deps.storage)?; + let token_0 = pair.token_0; + let token_1 = pair.token_1; + + let (in_token, out_token) = match offer.token { + TokenType::CustomToken { contract_addr, .. } => { + if contract_addr == token_0.address { + (token_0, token_1) + } else if contract_addr == token_1.address { + (token_1, token_0) + } else { + return Err(StdError::generic_err(format!( + "The supplied token {}, is not managed by this contract", + contract_addr + ))) + } + }, + _ => { + return Err(StdError::generic_err("Only CustomToken supported")); + } + }; + + let (amount_0, amount_1) = query_pool_amounts( + &deps.querier, + &config, + in_token.clone(), + out_token.clone(), + )?; + + // Sienna takes commission before swap + let commission = offer.amount * config.commission; + let swap_amount = offer.amount - commission; + + return to_binary(&SimulationResponse { + return_amount: pool_take_amount( + swap_amount, + amount_0, + amount_1, + ), + spread_amount: Uint128::zero(), + commission_amount: commission, + }); + + } + } +} + +fn query_pool_amounts( + querier: &QuerierWrapper, + config: &Config, + token_0: Contract, + token_1: Contract, +) -> StdResult<(Uint128, Uint128)> { + Ok(( + balance_query(querier, config.address.clone(), config.viewing_key.clone(), &token_0)?, + balance_query(querier, config.address.clone(), config.viewing_key.clone(), &token_1)?, + )) +} diff --git a/archived-contracts/oracle/.cargo/config b/archived-contracts/oracle/.cargo/config new file mode 100644 index 0000000..882fe08 --- /dev/null +++ b/archived-contracts/oracle/.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/archived-contracts/oracle/.circleci/config.yml b/archived-contracts/oracle/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/archived-contracts/oracle/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/oracle/Cargo.toml b/archived-contracts/oracle/Cargo.toml new file mode 100644 index 0000000..7a11ce9 --- /dev/null +++ b/archived-contracts/oracle/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "oracle" +version = "0.1.0" +authors = [ + "Guy Garcia ", + "Jackson Swenson ", +] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +bincode = "1.3.1" +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ + "oracles", + "dex", +] } +schemars = "0.7" + +[dev-dependencies] +fadroma = { branch = "v100", git = "https://github.com/a-stgeorge/fadroma-staking.git", features = ["ensemble", "scrt"] } +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ + "oracles", + "dex", + "ensemble" +] } diff --git a/archived-contracts/oracle/Makefile b/archived-contracts/oracle/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/archived-contracts/oracle/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/archived-contracts/oracle/README.md b/archived-contracts/oracle/README.md new file mode 100644 index 0000000..c215ea3 --- /dev/null +++ b/archived-contracts/oracle/README.md @@ -0,0 +1,142 @@ +# Oracle Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Init](#Init) + * [User](#User) + * Queries + * [GetScrtPrice](#GetScrtPrice) +# Introduction +The oracle contract is used to query the price of different currencies + +# Sections + +## Init + +##### Request + +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|admin | string | New contract admin; SHOULD be a valid bech32 address, but contracts may use a different naming scheme as well | yes | +|sscrt | Contract | sSCRT snip20 token contract | no | +|band | Contract | Band protocol contract | no | + +## User + +### Messages + +#### UpdateConfig +Updates the given values + +##### Request + +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|owner | string | New contract owner; SHOULD be a valid bech32 address, but contracts may use a different naming scheme as well | yes | +|silk | Contract | Silk contract | no | +|oracle | Contract | Oracle contract | no | + +##### Response + +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|status | string | Always 'success' | no | + +###### Example + +```json +{ + "update_config": { + "status": "success" + } +} +``` + +#### RegisterSswapPair +Registers a Secret Swap pair that can then be queried + +##### Request + +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|pair | Contract | A Secret Swap Pair contract where one of the tokens must be sSCRT | no | + +##### Response + +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|status | string | Always 'success' | no | + +###### Example + +```json +{ + "register_sswap_pair": { + "status": "success" + } +} +``` + +### Queries + +#### Price +Get asset price according to band protocol or a registered SecretSwap pair + +##### Request + +|Name |Type |Description | optional | +|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| +|symbol | string | Asset abbreviation e.g. BTC/ETH/SCRT; | no | + +##### Response + +|Name |Type |Description | optional | +|-------------------|--------|----------------------------------------------------------------------------------------------------------------|----------| +|rate | u128 | The exchange rate of the asset against USD | no | +|last_updated_base | u64 | UNIX timestamp of when the base asset price was last updated (0 for SecretSwap pairs) | no | +|last_updated_quote | u64 | UNIX timestamp of when the quote asset price was last updated (0 for SecretSwap pairs) | no | + +###### Example + +```json +{ + "rate": 1470000000000000000, + "last_updated_base": 1628569146, + "last_updated_quote": 3377610 +} +``` + +#### Config +Get the current config + +#### Prices +Get prices of list of assets +##### Request + +|Name |Type |Description | optional | +|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| +|symbols | list | list of asset symbols e.g. BTC/ETH/SCRT; | no | +##### Response + +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|owner | string | Contract owner | no | +|band | Contract | Band contract | no | +|sscrt | Contract | sSCRT contract | no | + +###### Example + +Addresses are fictional. + +```json +{ + [ + { + "rate": "1470000000000000000", + "last_updated_base": 1628569146, + "last_updated_quote": 3377610 + }, + ... + ] +} +``` + diff --git a/archived-contracts/oracle/src/contract.rs b/archived-contracts/oracle/src/contract.rs new file mode 100644 index 0000000..d82e9e8 --- /dev/null +++ b/archived-contracts/oracle/src/contract.rs @@ -0,0 +1,72 @@ +use crate::{handle, query, state::config_w}; +use shade_protocol::c_std::{ + + to_binary, + Api, + Binary, + Env, + DepsMut, + Response, + Querier, + StdResult, + Storage, +}; +use shade_protocol::contract_interfaces::oracles::oracle::{ + ExecuteMsg, + InstantiateMsg, + OracleConfig, + QueryMsg, +}; + +pub fn init( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let state = OracleConfig { + admin: match msg.admin { + None => info.sender.clone(), + Some(admin) => admin, + }, + band: msg.band, + sscrt: msg.sscrt, + }; + + config_w(deps.storage).save(&state)?; + + deps.api.debug("Contract was initialized by {}", info.sender); + + Ok(Response::default()) +} + +pub fn handle( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> StdResult { + match msg { + ExecuteMsg::UpdateConfig { admin, band } => { + handle::try_update_config(deps, env, info, admin, band) + } + ExecuteMsg::RegisterPair { pair } => handle::register_pair(deps, env, info, pair), + ExecuteMsg::UnregisterPair { symbol, pair } => { + handle::unregister_pair(deps, env, info, symbol, pair) + } + ExecuteMsg::RegisterIndex { symbol, basket } => { + handle::register_index(deps, env, info, symbol, basket) + } + } +} + +pub fn query( + deps: Deps, + msg: QueryMsg, +) -> StdResult { + match msg { + QueryMsg::Config {} => to_binary(&query::config(deps)?), + QueryMsg::Price { symbol } => to_binary(&query::price(deps, symbol)?), + QueryMsg::Prices { symbols } => to_binary(&query::prices(deps, symbols)?), + } +} diff --git a/archived-contracts/oracle/src/handle.rs b/archived-contracts/oracle/src/handle.rs new file mode 100644 index 0000000..494f45f --- /dev/null +++ b/archived-contracts/oracle/src/handle.rs @@ -0,0 +1,313 @@ +use crate::state::{config_r, config_w, dex_pairs_r, dex_pairs_w, index_r, index_w}; +use shade_protocol::c_std::{ + to_binary, + Api, + Env, + DepsMut, + Response, + Addr, + Querier, + StdError, + StdResult, + Storage, +}; +use shade_protocol::{ + snip20::helpers::{token_info_query, TokenInfo}, +}; +use shade_protocol::{ + contract_interfaces::{ + dex::{dex, secretswap, sienna}, + oracles::oracle::{HandleAnswer, IndexElement}, + snip20::helpers::Snip20Asset, + }, + utils::{asset::Contract, generic_response::ResponseStatus}, +}; + +pub fn register_pair( + deps: DepsMut, + env: Env, + info: MessageInfo, + pair: Contract, +) -> StdResult { + let config = config_r(deps.storage).load()?; + if info.sender != config.admin { + return Err(StdError::generic_err("unauthorized")); + } + + let mut trading_pair: Option = None; + let mut token_data: Option<(Contract, TokenInfo)> = None; + + if secretswap::is_pair(deps, pair.clone())? { + let td = fetch_token_paired_to_sscrt_on_sswap(deps, config.sscrt.address, &pair.clone())?; + token_data = Some(td.clone()); + + trading_pair = Some(dex::TradingPair { + contract: pair.clone(), + asset: Snip20Asset { + contract: td.clone().0, + token_info: td.clone().1, + token_config: None, + }, + dex: dex::Dex::SecretSwap, + }); + } else if sienna::is_pair(deps, pair.clone())? { + let td = fetch_token_paired_to_sscrt_on_sienna(deps, config.sscrt.address, &pair)?; + token_data = Some(td.clone()); + + trading_pair = Some(dex::TradingPair { + contract: pair.clone(), + asset: Snip20Asset { + contract: td.clone().0, + token_info: td.1, + token_config: None, + }, + dex: dex::Dex::SiennaSwap, + }); + } + + if let Some(tp) = trading_pair { + if let Some(td) = token_data { + // If symbol would override an index + if let Some(_) = index_r(deps.storage).may_load(td.1.symbol.as_bytes())? { + return Err(StdError::generic_err( + "Symbol already registered as an index", + )); + } + + if let Some(mut pairs) = dex_pairs_r(deps.storage).may_load(td.1.symbol.as_bytes())? { + //TODO: Check pair already registered + pairs.push(tp.clone()); + dex_pairs_w(deps.storage).save(td.1.symbol.as_bytes(), &pairs)?; + } else { + dex_pairs_w(deps.storage).save(td.1.symbol.as_bytes(), &vec![tp.clone()])?; + } + + return Ok(Response { + messages: vec![], + log: vec![], + data: Some(to_binary(&HandleAnswer::RegisterPair { + status: ResponseStatus::Success, + symbol: td.1.symbol, + pair: tp, + })?), + }); + } + return Err(StdError::generic_err("Failed to extract token data")); + } + + Err(StdError::generic_err("Failed to extract Trading Pair")) +} + +pub fn unregister_pair( + deps: DepsMut, + env: Env, + info: MessageInfo, + symbol: String, + pair: Contract, +) -> StdResult { + let config = config_r(deps.storage).load()?; + if info.sender != config.admin { + return Err(StdError::generic_err("unauthorized")); + } + + if let Some(mut pair_list) = dex_pairs_r(deps.storage).may_load(symbol.as_bytes())? { + if let Some(i) = pair_list + .iter() + .position(|p| p.contract.address == pair.address) + { + pair_list.remove(i); + + dex_pairs_w(deps.storage).save(symbol.as_bytes(), &pair_list)?; + + return Ok(Response { + messages: vec![], + log: vec![], + data: Some(to_binary(&HandleAnswer::UnregisterPair { + status: ResponseStatus::Success, + })?), + }); + } + } + + Err(StdError::generic_err("Pair not found")) +} + +/// +/// Will fetch token Contract along with TokenInfo for {symbol} in pair argument. +/// Pair argument must represent Secret Swap contract for {symbol}/sSCRT or sSCRT/{symbol}. +/// +fn fetch_token_paired_to_sscrt_on_sswap( + deps: DepsMut, + sscrt_addr: Addr, + pair: &Contract, +) -> StdResult<(Contract, TokenInfo)> { + // Query for snip20's in the pair + let response: secretswap::PairResponse = secretswap::PairQuery::Pair {}.query( + &deps.querier, + pair.code_hash.clone(), + pair.address.clone(), + )?; + + let mut token_contract = Contract { + address: response.asset_infos[0].token.contract_addr.clone(), + code_hash: response.asset_infos[0].token.token_code_hash.clone(), + }; + + // if thats sscrt, switch it + if token_contract.address == sscrt_addr { + token_contract = Contract { + address: response.asset_infos[1].token.contract_addr.clone(), + code_hash: response.asset_infos[1].token.token_code_hash.clone(), + } + } + // if neither is sscrt + else if response.asset_infos[1].token.contract_addr != sscrt_addr { + return Err(StdError::NotFound { + kind: "Not an sSCRT Pair".to_string(), + backtrace: None, + }); + } + + let token_info = token_info_query( + &deps.querier, + 1, + token_contract.code_hash.clone(), + token_contract.address.clone(), + )?; + + Ok((token_contract, token_info)) +} + +fn fetch_token_paired_to_sscrt_on_sienna( + deps: DepsMut, + sscrt_addr: Addr, + pair: &Contract, +) -> StdResult<(Contract, TokenInfo)> { + // Query for snip20's in the pair + let response: sienna::PairInfoResponse = (sienna::PairQuery::PairInfo).query( + &deps.querier, + pair.code_hash.clone(), + pair.address.clone(), + )?; + + let mut token_contract = match response.pair_info.pair.token_0 { + sienna::TokenType::CustomToken { + contract_addr, + token_code_hash, + } => Contract { + address: contract_addr, + code_hash: token_code_hash, + }, + sienna::TokenType::NativeToken { denom } => { + return Err(StdError::generic_err( + "Sienna Native Token pairs not supported", + )); + } + }; + + // if thats sscrt, switch it + if token_contract.address == sscrt_addr { + token_contract = match response.pair_info.pair.token_1 { + sienna::TokenType::CustomToken { + contract_addr, + token_code_hash, + } => Contract { + address: contract_addr, + code_hash: token_code_hash, + }, + sienna::TokenType::NativeToken { denom: _ } => { + return Err(StdError::generic_err( + "Sienna Native Token pairs not supported", + )); + } + }; + } + // if its not, make sure other is sscrt + else { + match response.pair_info.pair.token_1 { + sienna::TokenType::CustomToken { + contract_addr, + token_code_hash, + } => { + if contract_addr != sscrt_addr { + // if we get here, neither the first or second tokens were sscrt + return Err(StdError::NotFound { + kind: "Not an SSCRT Pair".to_string(), + backtrace: None, + }); + } + } + sienna::TokenType::NativeToken { denom: _ } => { + return Err(StdError::generic_err( + "Sienna Native Token pairs not supported", + )); + } + } + } + + let token_info = token_info_query( + &deps.querier, + 1, + token_contract.code_hash.clone(), + token_contract.address.clone(), + )?; + + Ok((token_contract, token_info)) +} + +pub fn register_index( + deps: DepsMut, + env: Env, + info: MessageInfo, + symbol: String, + basket: Vec, +) -> StdResult { + let config = config_r(deps.storage).load()?; + if info.sender != config.admin { + return Err(StdError::generic_err("unauthorized")); + } + + if let Some(pairs) = dex_pairs_r(deps.storage).may_load(symbol.as_bytes())? { + if pairs.len() > 0 { + return Err(StdError::generic_err( + "Symbol collides with an existing Dex pair", + )); + } + } + + index_w(deps.storage).save(symbol.as_bytes(), &basket)?; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::RegisterIndex { + status: ResponseStatus::Success, + })?)) +} + +pub fn try_update_config( + deps: DepsMut, + env: Env, + info: MessageInfo, + admin: Option, + band: Option, +) -> StdResult { + let config = config_r(deps.storage).load()?; + if info.sender != config.admin { + return Err(StdError::generic_err("unauthorized")); + } + + // Save new info + let mut config = config_w(deps.storage); + config.update(|mut state| { + if let Some(admin) = admin { + state.admin = admin; + } + if let Some(band) = band { + state.band = band; + } + + Ok(state) + })?; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::UpdateConfig { + status: ResponseStatus::Success, + })?)) +} diff --git a/archived-contracts/oracle/src/lib.rs b/archived-contracts/oracle/src/lib.rs new file mode 100644 index 0000000..c5689f2 --- /dev/null +++ b/archived-contracts/oracle/src/lib.rs @@ -0,0 +1,49 @@ +pub mod contract; +pub mod handle; +pub mod query; +pub mod state; + +#[cfg(test)] +pub mod test; + +#[cfg(target_arch = "wasm32")] +mod wasm { + use super::contract; + use shade_protocol::c_std::{ + do_handle, + do_init, + do_query, + ExternalApi, + ExternalQuerier, + ExternalStorage, + }; + + #[no_mangle] + extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { + do_init( + &contract::init::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { + do_handle( + &contract::handle::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn query(msg_ptr: u32) -> u32 { + do_query( + &contract::query::, + msg_ptr, + ) + } + + // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available + // automatically because we `use cosmwasm_std`. +} diff --git a/archived-contracts/oracle/src/query.rs b/archived-contracts/oracle/src/query.rs new file mode 100644 index 0000000..c3498ee --- /dev/null +++ b/archived-contracts/oracle/src/query.rs @@ -0,0 +1,177 @@ +use crate::state::{config_r, dex_pairs_r, index_r}; +use shade_protocol::c_std::{Uint128, Uint512}; +use shade_protocol::c_std::{self, Api, DepsMut, Querier, StdError, StdResult, Storage}; +use shade_protocol::contract_interfaces::{ + dex::dex, + oracles::{ + band, + oracle::{IndexElement, QueryAnswer}, + }, +}; +use std::convert::TryFrom; + +pub fn config(deps: Deps) -> StdResult { + Ok(QueryAnswer::Config { + config: config_r(deps.storage).load()?, + }) +} + +pub fn price( + deps: Deps, + symbol: String, +) -> StdResult { + let config = config_r(deps.storage).load()?; + if symbol == "SSCRT" { + return band::reference_data(deps, "SCRT".to_string(), "USD".to_string(), config.band); + } + + if let Some(dex_pairs) = dex_pairs_r(deps.storage).may_load(symbol.as_bytes())? { + if dex_pairs.len() > 0 { + return Ok(band::ReferenceData { + rate: dex::aggregate_price(&deps, dex_pairs, config.sscrt, config.band)?, + last_updated_base: 0, + last_updated_quote: 0, + }); + } + } + + // Index + if let Some(index) = index_r(deps.storage).may_load(symbol.as_bytes())? { + return Ok(band::ReferenceData { + rate: eval_index(deps, index)?, + last_updated_base: 0, + last_updated_quote: 0, + }); + } + + // symbol/USD price from BAND + band::reference_data(deps, symbol, "USD".to_string(), config.band) +} + +pub fn prices( + deps: Deps, + symbols: Vec, +) -> StdResult> { + let mut band_symbols = vec![]; + let mut band_quotes = vec![]; + let mut results = vec![Uint128::zero(); symbols.len()]; + + let config = config_r(deps.storage).load()?; + + for (i, sym) in symbols.iter().enumerate() { + // Aggregate DEX pair prices + if let Some(dex_pairs) = dex_pairs_r(deps.storage).may_load(sym.as_bytes())? { + if dex_pairs.len() > 0 { + results[i] = dex::aggregate_price( + &deps, + dex_pairs, + config.sscrt.clone(), + config.band.clone(), + )?; + } + } + // Index + else if let Some(index) = index_r(deps.storage).may_load(sym.as_bytes())? { + results[i] = eval_index(deps, index)?; + } + // BAND + else { + band_symbols.push(sym.clone()); + band_quotes.push("USD".to_string()); + } + } + + // Query all the band prices + let ref_data = band::reference_data_bulk( + deps, + band_symbols.clone(), + band_quotes, + config_r(deps.storage).load()?.band, + )?; + + for (data, sym) in ref_data.iter().zip(band_symbols.iter()) { + let result_index = symbols + .iter() + .enumerate() + .find(|&s| *s.1 == *sym) + .unwrap() + .0; + results[result_index] = data.rate; + } + + Ok(results + .iter() + .map(|r| Uint128::new(r.u128())) + .collect()) +} + +pub fn eval_index( + deps: Deps, + index: Vec, +) -> StdResult { + let mut weight_sum = Uint512::zero(); + let mut price = Uint512::zero(); + + let mut band_bases = vec![]; + let mut band_quotes = vec![]; + let mut band_weights = vec![]; + let config = config_r(deps.storage).load()?; + + for element in index { + weight_sum += Uint512::from(element.weight.u128()); + + // Get dex prices + if let Some(dex_pairs) = dex_pairs_r(deps.storage).may_load(element.symbol.as_bytes())? { + return Err(StdError::generic_err(format!( + "EVAL INDEX DEX PAIRS {}", + element.symbol + ))); + + // NOTE: unreachable? + // price += + // dex::aggregate_price(deps, dex_pairs, config.sscrt.clone(), config.band.clone())? + // .multiply_ratio(element.weight, 10u128.pow(18)) + } + // Nested index + else if let Some(sub_index) = + index_r(deps.storage).may_load(element.symbol.as_bytes())? + { + // TODO: make sure no circular deps + return Err(StdError::generic_err(format!( + "EVAL NESTED INDEX {}", + element.symbol + ))); + // NOTE: unreachable? + // price += eval_index(&deps, sub_index)?.multiply_ratio(element.weight, 10u128.pow(18)) + } + // Setup to query for all at once from BAND + else { + band_weights.push(element.weight); + band_bases.push(element.symbol.clone()); + band_quotes.push("USD".to_string()); + } + } + + if band_bases.len() > 0 { + let ref_data = band::reference_data_bulk( + deps, + band_bases, + band_quotes, + config_r(deps.storage).load()?.band, + )?; + + for (reference, weight) in ref_data.iter().zip(band_weights.iter()) { + price += Uint512::from(reference.rate.u128()) * Uint512::from(weight.u128()) + / Uint512::from(10u128.pow(18)); + } + } + + Ok(Uint128::new( + Uint128::try_from( + price + .checked_mul(Uint512::from(10u128.pow(18)))? + .checked_div(weight_sum)?, + )? + .u128(), + )) +} diff --git a/archived-contracts/oracle/src/state.rs b/archived-contracts/oracle/src/state.rs new file mode 100644 index 0000000..8cc83ca --- /dev/null +++ b/archived-contracts/oracle/src/state.rs @@ -0,0 +1,45 @@ +use shade_protocol::c_std::Storage; +use shade_protocol::storage::{ + bucket, + bucket_read, + singleton, + singleton_read, + Bucket, + ReadonlyBucket, + ReadonlySingleton, + Singleton, +}; +use shade_protocol::contract_interfaces::{ + dex::dex, + oracles::oracle::{IndexElement, OracleConfig}, +}; + +pub static CONFIG_KEY: &[u8] = b"config"; +pub static DEX_PAIRS: &[u8] = b"dex_pairs"; +pub static SSWAP_PAIRS: &[u8] = b"sswap_pairs"; +pub static SIENNA_PAIRS: &[u8] = b"sienna_pairs"; +pub static INDEX: &[u8] = b"index"; + +pub fn config_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, CONFIG_KEY) +} + +pub fn config_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, CONFIG_KEY) +} + +pub fn dex_pairs_r(storage: &dyn Storage) -> ReadonlyBucket> { + bucket_read(storage, DEX_PAIRS) +} + +pub fn dex_pairs_w(storage: &mut dyn Storage) -> Bucket> { + bucket(storage, DEX_PAIRS) +} + +pub fn index_r(storage: &dyn Storage) -> ReadonlyBucket> { + bucket_read(storage, INDEX) +} + +pub fn index_w(storage: &mut dyn Storage) -> Bucket> { + bucket(storage, INDEX) +} diff --git a/archived-contracts/oracle/src/test.rs b/archived-contracts/oracle/src/test.rs new file mode 100644 index 0000000..88790b3 --- /dev/null +++ b/archived-contracts/oracle/src/test.rs @@ -0,0 +1,48 @@ +use crate::contract::{handle, init, query}; +use shade_protocol::c_std::{ + coins, + from_binary, + Binary, + Env, + DepsMut, + Response, + Addr, + Response, + StdError, + StdResult, +}; +use shade_protocol::fadroma::{ + ensemble::{ContractEnsemble, ContractHarness, MockDeps, MockEnv}, +}; + +pub struct Oracle; + +impl ContractHarness for Oracle { + // Use the method from the default implementation + fn init(&self, deps: &mut MockDeps, env: Env, msg: Binary) -> StdResult { + init( + deps, + env, + from_binary(&msg)?, + //mint::DefaultImpl, + ) + } + + fn handle(&self, deps: &mut MockDeps, env: Env, msg: Binary) -> StdResult { + handle( + deps, + env, + from_binary(&msg)?, + //mint::DefaultImpl, + ) + } + + // Override with some hardcoded value for the ease of testing + fn query(&self, deps: &MockDeps, msg: Binary) -> StdResult { + query( + deps, + from_binary(&msg)?, + //mint::DefaultImpl, + ) + } +} diff --git a/archived-contracts/oracle/tests/integration.rs b/archived-contracts/oracle/tests/integration.rs new file mode 100644 index 0000000..6c26b03 --- /dev/null +++ b/archived-contracts/oracle/tests/integration.rs @@ -0,0 +1,18 @@ +//! This integration test tries to run and call the generated wasm. +//! It depends on a Wasm build being available, which you can create with `cargo wasm`. +//! Then running `cargo integration-test` will validate we can properly call into that generated Wasm. +//! +//! You can easily convert unit tests to integration tests. +//! 1. First copy them over verbatum, +//! 2. Then change +//! let mut deps = mock_dependencies(20, &[]); +//! to +//! let mut deps = mock_instance(WASM, &[]); +//! 3. If you access raw storage, where ever you see something like: +//! deps.storage.get(CONFIG_KEY).expect("no data stored"); +//! replace it with: +//! deps.with_storage(|store| { +//! let data = store.get(CONFIG_KEY).expect("no data stored"); +//! //... +//! }); +//! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) diff --git a/archived-contracts/peg_stability/Cargo.toml b/archived-contracts/peg_stability/Cargo.toml new file mode 100644 index 0000000..a42b9e9 --- /dev/null +++ b/archived-contracts/peg_stability/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "peg_stability" +version = "0.1.0" +authors = [ + "jackb7", +] +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["peg_stability", "admin"] } +shade-oracles = { git = "https://github.com/securesecrets/shade-oracle.git" } +cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" } +#shade-admin = { git = "https://github.com/securesecrets/shadeadmin", branch = "cosmwasm-v1-refactor", optional = true } +schemars = "0.7" + +[dev-dependencies] +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["multi-test"] } +shade-multi-test = { version = "0.1.0", path = "../../packages/multi_test", features = [ "snip20", "peg_stability", "admin"] } +#shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["ensemble"] } +#contract_harness = { version = "0.1.0", path = "../../packages/contract_harness" } diff --git a/archived-contracts/peg_stability/Makefile b/archived-contracts/peg_stability/Makefile new file mode 100644 index 0000000..c49ed5d --- /dev/null +++ b/archived-contracts/peg_stability/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 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/archived-contracts/peg_stability/src/contract.rs b/archived-contracts/peg_stability/src/contract.rs new file mode 100644 index 0000000..3ab798d --- /dev/null +++ b/archived-contracts/peg_stability/src/contract.rs @@ -0,0 +1,94 @@ +use crate::{handle, query}; +use shade_protocol::{ + c_std::{ + shd_entry_point, + to_binary, + Binary, + Deps, + DepsMut, + Env, + MessageInfo, + Response, + StdResult, + }, + contract_interfaces::{ + peg_stability::{Config, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryMsg, ViewingKey}, + }, + snip20::helpers::set_viewing_key_msg, + utils::{ + generic_response::ResponseStatus, + storage::plus::{GenericItemStorage, ItemStorage}, + }, +}; + +#[shd_entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let config = Config { + admin_auth: msg.admin_auth.clone(), + snip20: msg.snip20.clone(), + pairs: vec![], + oracle: msg.oracle.clone(), + treasury: msg.treasury.clone(), + symbols: vec![], + payback: msg.payback, + self_addr: env.contract.address.clone(), + dump_contract: msg.dump_contract, + }; + config.save(deps.storage)?; + ViewingKey::save(deps.storage, &msg.viewing_key.clone())?; + Ok(Response::new() + .add_message(set_viewing_key_msg( + msg.viewing_key.to_string(), + None, + &msg.snip20, + )?) + .set_data(to_binary(&ExecuteAnswer::Init { + status: ResponseStatus::Success, + })?)) +} + +#[shd_entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg { + ExecuteMsg::UpdateConfig { + admin_auth, + snip20, + oracle, + treasury, + payback, + dump_contract, + .. + } => handle::try_update_config( + deps, + env, + info, + admin_auth, + snip20, + oracle, + treasury, + payback, + dump_contract, + ), + ExecuteMsg::SetPairs { pairs, .. } => handle::try_set_pairs(deps, env, info, pairs), + ExecuteMsg::AppendPairs { pairs, .. } => handle::try_append_pairs(deps, env, info, pairs), + ExecuteMsg::RemovePair { pair_address, .. } => { + handle::try_remove_pair(deps, env, info, pair_address) + } + ExecuteMsg::Swap { .. } => handle::try_swap(deps, env, info), + } +} + +#[shd_entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetConfig {} => to_binary(&query::get_config(deps)?), + QueryMsg::Balance {} => to_binary(&query::get_balance(deps)?), + QueryMsg::GetPairs {} => to_binary(&query::get_pairs(deps)?), + QueryMsg::Profitable {} => to_binary(&query::profitable(deps)?), + } +} diff --git a/archived-contracts/peg_stability/src/handle.rs b/archived-contracts/peg_stability/src/handle.rs new file mode 100644 index 0000000..bc395b4 --- /dev/null +++ b/archived-contracts/peg_stability/src/handle.rs @@ -0,0 +1,233 @@ +use crate::query::calculate_profit; +use shade_protocol::{ + admin::helpers::{validate_admin, AdminPermissions}, + c_std::{to_binary, Decimal, DepsMut, Env, MessageInfo, Response, StdError, StdResult}, + contract_interfaces::{ + peg_stability::{CalculateRes, Config, ExecuteAnswer, ViewingKey}, + sky::cycles::ArbPair, + }, + snip20::helpers::{send_msg, set_viewing_key_msg, token_info, TokenInfo}, + utils::{ + asset::Contract, + generic_response::ResponseStatus, + storage::plus::{GenericItemStorage, ItemStorage}, + }, +}; + +pub fn try_update_config( + deps: DepsMut, + _env: Env, + info: MessageInfo, + admin_auth: Option, + snip20: Option, + treasury: Option, + oracle: Option, + payback: Option, + dump_contract: Option, +) -> StdResult { + //Admin-only + let mut config = Config::load(deps.storage)?; + validate_admin( + &deps.querier, + AdminPermissions::StabilityAdmin, + info.sender.to_string(), + &config.admin_auth, + )?; + let mut messages = vec![]; + if let Some(admin_auth) = admin_auth { + config.admin_auth = admin_auth; + } + if let Some(snip20) = snip20 { + if !(config.pairs.len() == 0) { + return Err(StdError::generic_err( + "You must remove all pairs before chaning the snip20 asset", + )); + } + config.snip20 = snip20; + let viewing_key = ViewingKey::load(deps.storage)?; + messages.push(set_viewing_key_msg(viewing_key, None, &config.snip20)?) + } + if let Some(treasury) = treasury { + config.treasury = treasury; + } + if let Some(oracle) = oracle { + config.oracle = oracle; + } + if let Some(payback) = payback { + config.payback = payback; + } + if let Some(dump_contract) = dump_contract { + config.dump_contract = dump_contract; + } + Ok(Response::new() + .add_messages(messages) + .set_data(to_binary(&ExecuteAnswer::UpdateConfig { + config, + status: ResponseStatus::Success, + })?)) +} + +pub fn try_set_pairs( + deps: DepsMut, + _env: Env, + info: MessageInfo, + pairs: Vec, +) -> StdResult { + //Admin-only + let mut config = Config::load(deps.storage)?; + validate_admin( + &deps.querier, + AdminPermissions::StabilityAdmin, + info.sender.to_string(), + &config.admin_auth, + )?; + if pairs.is_empty() { + return Err(StdError::generic_err("Must pass at least one pair")); + } + let token0_info: TokenInfo = token_info(&deps.querier, &pairs[0].token0)?; + let token1_info: TokenInfo = token_info(&deps.querier, &pairs[0].token1)?; + let other_asset; + if config.snip20 == pairs[0].token0 { + config.symbols = vec![token0_info.symbol, token1_info.symbol]; + other_asset = pairs[0].token1.clone(); + } else { + config.symbols = vec![token1_info.symbol, token0_info.symbol]; + other_asset = pairs[0].token0.clone(); + } + for pair in pairs.clone() { + pair.validate_pair()?; + if !((pair.token0 == config.snip20 && pair.token1 == other_asset) + || (pair.token0 == other_asset && pair.token1 == config.snip20)) + { + return Err(StdError::generic_err( + "pairs must have the same assets as the rest of the pairs", + )); + } + } + config.pairs = pairs; + config.save(deps.storage)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::SetPairs { + pairs: config.pairs, + status: ResponseStatus::Success, + })?), + ) +} + +pub fn try_append_pairs( + deps: DepsMut, + env: Env, + info: MessageInfo, + pairs: Vec, +) -> StdResult { + let mut config = Config::load(deps.storage)?; + if config.pairs.is_empty() { + return Ok(try_set_pairs(deps, env, info, pairs)?); + } else if pairs.is_empty() { + return Err(StdError::generic_err("Must pass at least 1 pair")); + } + //Admin-only + validate_admin( + &deps.querier, + AdminPermissions::StabilityAdmin, + info.sender.to_string(), + &config.admin_auth, + )?; + let other_asset; + if config.snip20 == config.pairs[0].token0 { + other_asset = config.pairs[0].token1.clone(); + } else { + other_asset = config.pairs[0].token0.clone(); + } + for pair in pairs.clone() { + pair.validate_pair()?; + if !((pair.token0 == config.snip20 && pair.token1 == other_asset) + || (pair.token0 == other_asset && pair.token1 == config.snip20)) + { + return Err(StdError::generic_err( + "pairs must have the same assets as the rest of the pairs", + )); + } + } + config.pairs.append(&mut pairs.clone()); + config.save(deps.storage)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::AppendPairs { + pairs: config.pairs, + status: ResponseStatus::Success, + })?), + ) +} + +pub fn try_remove_pair( + deps: DepsMut, + _env: Env, + info: MessageInfo, + pair_address: String, +) -> StdResult { + //Admin-only + let mut config = Config::load(deps.storage)?; + validate_admin( + &deps.querier, + AdminPermissions::StabilityAdmin, + info.sender.to_string(), + &config.admin_auth, + )?; + if config.pairs.len() == 0 { + return Err(StdError::generic_err("No pairs to remove")); + } + for (i, pair) in config.pairs.iter().enumerate() { + match pair.pair_contract.clone() { + Some(contract) => { + if contract.address == pair_address { + config.pairs.remove(i); + config.save(deps.storage)?; + return Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::RemovePair { + pairs: config.pairs, + status: ResponseStatus::Success, + })?), + ); + } + } + None => continue, + } + } + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::RemovePair { + pairs: config.pairs, + status: ResponseStatus::Failure, + })?), + ) +} + +pub fn try_swap(deps: DepsMut, _env: Env, info: MessageInfo) -> StdResult { + let res: CalculateRes = calculate_profit(deps.as_ref())?; + let other_asset; + if res.config.snip20 == res.config.pairs[0].token0 { + other_asset = res.config.pairs[0].token1.clone(); + } else { + other_asset = res.config.pairs[0].token0.clone(); + } + let messages = vec![ + res.config.pairs[res.index].to_cosmos_msg(res.offer, res.min_expected)?, + send_msg( + res.config.dump_contract.address, + res.min_expected - res.payback, + None, + None, + None, + &other_asset, + )?, + send_msg(info.sender, res.payback, None, None, None, &other_asset)?, + ]; + Ok(Response::new() + .add_messages(messages) + .set_data(to_binary(&ExecuteAnswer::Swap { + profit: res.profit, + payback: res.payback, + status: ResponseStatus::Success, + })?)) +} diff --git a/archived-contracts/peg_stability/src/lib.rs b/archived-contracts/peg_stability/src/lib.rs new file mode 100644 index 0000000..9bb1910 --- /dev/null +++ b/archived-contracts/peg_stability/src/lib.rs @@ -0,0 +1,6 @@ +pub mod contract; +pub mod handle; +pub mod query; + +#[cfg(test)] +pub mod tests; diff --git a/archived-contracts/peg_stability/src/query.rs b/archived-contracts/peg_stability/src/query.rs new file mode 100644 index 0000000..dee9575 --- /dev/null +++ b/archived-contracts/peg_stability/src/query.rs @@ -0,0 +1,199 @@ + +use shade_protocol::{ + c_std::{Deps, Isqrt, StdError, StdResult, Uint128, Uint256}, + contract_interfaces::{ + peg_stability::{CalculateRes, Config, QueryAnswer, ViewingKey}, + sky::cycles::Offer, + snip20, + }, + snip20::helpers::balance_query, + utils::{ + callback::Query, + storage::plus::{GenericItemStorage, ItemStorage}, + }, +}; +use std::convert::TryFrom; + +pub fn get_config(deps: Deps) -> StdResult { + Ok(QueryAnswer::Config { + config: Config::load(deps.storage)?, + }) +} + +pub fn get_balance(deps: Deps) -> StdResult { + let viewing_key = ViewingKey::load(deps.storage)?; + let config = Config::load(deps.storage)?; + + let res = snip20::QueryMsg::Balance { + address: config.self_addr.clone().to_string(), + key: viewing_key, + } + .query(&deps.querier, &config.snip20)?; + + match res { + snip20::QueryAnswer::Balance { amount } => Ok(QueryAnswer::Balance { snip20_bal: amount }), + _ => Err(StdError::generic_err("snip20 bal query failed")), + } +} + +pub fn get_pairs(deps: Deps) -> StdResult { + Ok(QueryAnswer::GetPairs { + pairs: Config::load(deps.storage)?.pairs, + }) +} + +pub fn profitable(deps: Deps) -> StdResult { + let res: CalculateRes = calculate_profit(deps)?; + Ok(QueryAnswer::Profitable { + profit: res.profit, + payback: res.payback, + }) +} + +pub fn calculate_profit(deps: Deps) -> StdResult { + let config = Config::load(deps.storage)?; + if config.pairs.len() < 1 { + return Err(StdError::generic_err("Must have pairs saved")); + } + /*let res: Vec = router::QueryMsg::GetPrices { + keys: config.symbols, + } + .query(&deps.querier, &config.oracle)? + .prices; + let prices = vec![ + Uint128::new(res[0].data.rate.u128()), + Uint128::new(res[1].data.rate.u128()), + ];*/ + let prices = vec![Uint128::zero(); 2]; + let mut max_swap_amount = Uint128::zero(); + let mut index = 0usize; + let snip20_dec; + let other_dec; + if config.pairs[0].token0 == config.snip20 { + snip20_dec = config.pairs[0].token0_decimals.u128() as u32; + other_dec = config.pairs[0].token1_decimals.u128() as u32; + } else { + snip20_dec = config.pairs[0].token1_decimals.u128() as u32; + other_dec = config.pairs[0].token0_decimals.u128() as u32; + } + for (i, pair) in config.pairs.iter().enumerate() { + let (t0_amount, t1_amount) = pair.clone().pool_amounts(deps)?; + let mut temp; + if config.snip20 == pair.token0 { + temp = calculate_swap_amount( + t0_amount.checked_mul(Uint128::new(10).pow(18 - snip20_dec.clone()))?, + t1_amount.checked_mul(Uint128::new(10).pow(18 - other_dec.clone()))?, + prices[0], + prices[1], + ); + } else { + temp = calculate_swap_amount( + t1_amount.checked_mul(Uint128::new(10).pow(18 - snip20_dec.clone()))?, + t0_amount.checked_mul(Uint128::new(10).pow(18 - other_dec.clone()))?, + prices[0], + prices[1], + ); + } + temp = temp / Uint128::new(10).pow(18 - snip20_dec); + if temp > max_swap_amount { + max_swap_amount = temp; + index = i; + } + } + let balance = balance_query( + &deps.querier, + config.self_addr.clone(), + ViewingKey::load(deps.storage)?, + &config.snip20, + )?; + if max_swap_amount > balance { + max_swap_amount = balance; + } + let initial_value = Uint256::from(max_swap_amount) + / Uint256::from(Uint128::new(10).pow(snip20_dec)) + * Uint256::from(prices[0]); + let offer = Offer { + asset: config.snip20.clone(), + amount: max_swap_amount.clone(), + }; + let swap_res = config.pairs[index] + .clone() + .simulate_swap(deps, offer.clone())?; + let after_swap = Uint256::from(swap_res) / Uint256::from(Uint128::new(10).pow(other_dec)) + * Uint256::from(prices[1]); + if after_swap > initial_value { + let profit = Uint128::try_from(after_swap - initial_value)?; + let payback = profit / prices[1] * config.payback; + return Ok(CalculateRes { + profit, + payback, + index, + config, + offer, + min_expected: swap_res, + }); + } + Ok(CalculateRes { + profit: Uint128::zero(), + payback: Uint128::zero(), + index: 0usize, + config, + offer, + min_expected: Uint128::zero(), + }) +} + +fn calculate_swap_amount( + poolsell: Uint128, + poolbuy: Uint128, + pricesell: Uint128, + pricebuy: Uint128, +) -> Uint128 { + let nom = Uint256::from(pricebuy.isqrt()) + * Uint256::from(poolbuy.isqrt()) + * Uint256::from(poolsell.isqrt()); + let denominator = Uint256::from(pricesell.isqrt()); + let right = nom / denominator; + let res = right.checked_sub(Uint256::from(poolsell)); + match res { + Ok(amount) => match Uint128::try_from(amount) { + Ok(amount) => amount, + Err(_error) => Uint128::MAX, + }, + Err(_error) => Uint128::zero(), + } +} + +#[cfg(test)] +mod test { + use crate::query::calculate_swap_amount; + use shade_protocol::{ + c_std::{Uint128}, + }; + + #[test] + fn test_swapamount1() { + assert_eq!( + calculate_swap_amount( + Uint128::new(10_000_000_000_000_000_000_000), + Uint128::new(100_000_000_000_000_000_000_000), + Uint128::new(10_000_000_000_000_000_000), + Uint128::new(1_000_000_000_000_000_000), + ) / Uint128::new(10).pow(12), + Uint128::zero() + ) + } + + #[test] + fn test_swapamount2() { + assert_eq!( + calculate_swap_amount( + Uint128::new(10_000_000_000_000_000_000_000), + Uint128::new(100_000_000_000_000_000_000_000), + Uint128::new(10_000_000_000_000_000_000), + Uint128::new(1_100_000_000_000_000_000), + ) / Uint128::new(10).pow(13), + Uint128::new(48_808_848) + ) + } +} diff --git a/archived-contracts/peg_stability/src/tests/handle.rs b/archived-contracts/peg_stability/src/tests/handle.rs new file mode 100644 index 0000000..ab1ef55 --- /dev/null +++ b/archived-contracts/peg_stability/src/tests/handle.rs @@ -0,0 +1,59 @@ +/*#[test] +pub fn update_config_err_1() { + assert!(true); +} + +#[test] +pub fn update_config_err_2() { + assert!(true); +} + +#[test] +pub fn update_config_success() { + assert!(true); +} + +#[test] +pub fn set_pairs_err_1() { + assert!(true); +} + +#[test] +pub fn set_pairs_err_2() { + assert!(true); +} + +#[test] +pub fn set_pairs_success() { + assert!(true); +} + +#[test] +pub fn append_pairs_err() { + assert!(true); +} + +#[test] +pub fn append_pairs_success() { + assert!(true); +} + +#[test] +pub fn remove_pair_err_1() { + assert!(true); +} + +#[test] +pub fn remove_pair_err_2() { + assert!(true); +} + +#[test] +pub fn remove_pair_success() { + assert!(true); +} + +#[test] +pub fn swap_success() { + assert!(true); +}*/ diff --git a/archived-contracts/peg_stability/src/tests/mod.rs b/archived-contracts/peg_stability/src/tests/mod.rs new file mode 100644 index 0000000..e45a6b4 --- /dev/null +++ b/archived-contracts/peg_stability/src/tests/mod.rs @@ -0,0 +1,113 @@ +pub mod handle; +pub mod query; + +use shade_multi_test::multi::{admin::Admin, peg_stability::PegStability, snip20::Snip20}; +use shade_protocol::{ + admin, + c_std::{Addr, Binary, ContractInfo, Decimal, Uint128}, + contract_interfaces::peg_stability, + multi_test::{App, Executor}, + snip20, + utils::{asset::Contract, MultiTestable}, +}; + +pub fn init_chain() -> (App, ContractInfo) { + let mut chain = App::default(); + + let stored_code = chain.store_code(Admin::default().contract()); + let admin = chain + .instantiate_contract( + stored_code, + Addr::unchecked("admin"), + &admin::InstantiateMsg { super_admin: None }, + &[], + "admin", + None, + ) + .unwrap(); + + //Instantiate band + //Instantiate oracle + (chain, admin) +} + +pub fn ps_no_oracle( + chain: App, + shd_admin: ContractInfo, + snip20: ContractInfo, +) -> (App, ContractInfo) { + let mut chain = chain; + + let stored_code = chain.store_code(PegStability::default().contract()); + let init_msg = peg_stability::InstantiateMsg { + admin_auth: Contract { + address: shd_admin.address, + code_hash: shd_admin.code_hash, + }, + snip20: Contract { + address: snip20.address, + code_hash: snip20.code_hash, + }, + oracle: Contract::default(), + treasury: Contract { + address: Addr::unchecked("admin"), + code_hash: "".into(), + }, + payback: Decimal::percent(15), + viewing_key: "SecureSoftware".into(), + dump_contract: Contract::default(), + }; + let pstable = chain + .instantiate_contract( + stored_code, + Addr::unchecked("admin"), + &init_msg, + &[], + "admin", + None, + ) + .unwrap(); + + (chain, pstable) +} + +pub fn init_snip20( + chain: App, + name: String, + symbol: String, + decimals: u128, +) -> (App, ContractInfo) { + let mut chain = chain; + + let stored_code = chain.store_code(Snip20::default().contract()); + let init_msg = snip20::InstantiateMsg { + name, + admin: Some("admin".into()), + query_auth: None, + symbol, + decimals: decimals as u8, + initial_balances: Some(vec![snip20::InitialBalance { + address: "admin".into(), + amount: Uint128::new(1_000_000_000_000_000 * 10 ^ decimals), + }]), + prng_seed: Binary::default(), + config: None, + }; + let snip20 = chain + .instantiate_contract( + stored_code, + Addr::unchecked("admin"), + &init_msg, + &[], + "admin", + None, + ) + .unwrap(); + + (chain, snip20) +} + +/*#[test] +pub fn test_test() { + assert!(true); +}*/ diff --git a/archived-contracts/peg_stability/src/tests/query.rs b/archived-contracts/peg_stability/src/tests/query.rs new file mode 100644 index 0000000..7aa053c --- /dev/null +++ b/archived-contracts/peg_stability/src/tests/query.rs @@ -0,0 +1,29 @@ +/*#[test] +pub fn get_config() { + assert!(true); +} + +#[test] +pub fn get_balance() { + assert!(true); +} + +#[test] +pub fn get_pairs() { + assert!(true); +} + +#[test] +pub fn profitable_err_1() { + assert!(true); +} + +#[test] +pub fn profitable_err_2() { + assert!(true); +} + +#[test] +pub fn profitable() { + assert!(true); +}*/ diff --git a/archived-contracts/sky/.cargo/config b/archived-contracts/sky/.cargo/config new file mode 100644 index 0000000..c1e7c50 --- /dev/null +++ b/archived-contracts/sky/.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" \ No newline at end of file diff --git a/archived-contracts/sky/.circleci/config.yml b/archived-contracts/sky/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/archived-contracts/sky/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/sky/Cargo.toml b/archived-contracts/sky/Cargo.toml new file mode 100644 index 0000000..aa47a17 --- /dev/null +++ b/archived-contracts/sky/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "sky" +version = "0.1.0" +authors = [ + "jackb7", +] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["sky", "sky-utils", "adapter", "math", "admin"] } +#shade-admin = { git = "https://github.com/securesecrets/shadeadmin", branch = "cosmwasm-v1-refactor", optional = true } +schemars = "0.7" + +[dev-dependencies] +#shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["ensemble"] } +#contract_harness = { version = "0.1.0", path = "../../packages/contract_harness" } diff --git a/archived-contracts/sky/Makefile b/archived-contracts/sky/Makefile new file mode 100644 index 0000000..c49ed5d --- /dev/null +++ b/archived-contracts/sky/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 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/archived-contracts/sky/src/contract.rs b/archived-contracts/sky/src/contract.rs new file mode 100644 index 0000000..6973237 --- /dev/null +++ b/archived-contracts/sky/src/contract.rs @@ -0,0 +1,161 @@ +use crate::{execute, query}; +use shade_protocol::{ + c_std::{ + shd_entry_point, + to_binary, + Binary, + Decimal, + Deps, + DepsMut, + Env, + MessageInfo, + Response, + StdError, + StdResult, + SubMsg, + }, + contract_interfaces::{ + dao::adapter, + sky::{Config, Cycles, ExecuteMsg, InstantiateMsg, QueryMsg, SelfAddr, ViewingKeys}, + }, + snip20::helpers::set_viewing_key_msg, + utils::storage::plus::ItemStorage, +}; + +#[shd_entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let state = Config { + shade_admin: msg.shade_admin, + shd_token: msg.shd_token.clone(), + silk_token: msg.silk_token.clone(), + sscrt_token: msg.sscrt_token.clone(), + treasury: msg.treasury, + payback_rate: msg.payback_rate, + }; + + if msg.payback_rate == Decimal::zero() { + return Err(StdError::generic_err("payback rate cannot be zero")); + } + + state.save(deps.storage)?; + SelfAddr(env.contract.address).save(deps.storage)?; + Cycles(vec![]).save(deps.storage)?; + + deps.api + .debug(&format!("Contract was initialized by {}", info.sender)); + + let messages = vec![ + SubMsg::new(set_viewing_key_msg( + msg.viewing_key.clone().to_string(), + None, + &msg.shd_token.clone(), + )?), + SubMsg::new(set_viewing_key_msg( + msg.viewing_key.clone().to_string(), + None, + &msg.silk_token.clone(), + )?), + SubMsg::new(set_viewing_key_msg( + msg.viewing_key.clone().to_string(), + None, + &msg.sscrt_token.clone(), + )?), + ]; + + ViewingKeys(msg.viewing_key).save(deps.storage)?; + + Ok(Response::new().add_submessages(messages)) +} + +#[shd_entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg { + ExecuteMsg::UpdateConfig { + shade_admin, + shd_token, + silk_token, + sscrt_token, + treasury, + payback_rate, + .. + } => execute::try_update_config( + deps, + env, + info, + shade_admin, + shd_token, + silk_token, + sscrt_token, + treasury, + payback_rate, + ), + ExecuteMsg::SetCycles { cycles, .. } => execute::try_set_cycles(deps, env, info, cycles), + ExecuteMsg::AppendCycles { cycle, .. } => execute::try_append_cycle(deps, env, info, cycle), + ExecuteMsg::UpdateCycle { cycle, index, .. } => { + execute::try_update_cycle(deps, env, info, cycle, index) + } + ExecuteMsg::RemoveCycle { index, .. } => execute::try_remove_cycle(deps, env, info, index), + ExecuteMsg::ArbCycle { amount, index, .. } => { + execute::try_arb_cycle(deps, env, info, amount, index) + } + ExecuteMsg::ArbAllCycles { amount, .. } => { + execute::try_arb_all_cycles(deps, env, info, amount) + } + ExecuteMsg::Adapter(adapter) => match adapter { + adapter::SubExecuteMsg::Unbond { asset, amount } => { + let asset = deps.api.addr_validate(&asset)?; + execute::try_adapter_unbond(deps, env, info, asset, amount) + } + adapter::SubExecuteMsg::Claim { asset } => { + let asset = deps.api.addr_validate(&asset)?; + execute::try_adapter_claim(deps, env, asset) + } + adapter::SubExecuteMsg::Update { asset } => { + let asset = deps.api.addr_validate(&asset)?; + execute::try_adapter_update(deps, env, asset) + } + }, + } +} + +#[shd_entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetConfig {} => to_binary(&query::config(deps)?), + QueryMsg::Balance {} => to_binary(&query::get_balances(deps)?), + QueryMsg::GetCycles {} => to_binary(&query::get_cycles(deps)?), + QueryMsg::IsCycleProfitable { amount, index } => { + to_binary(&query::cycle_profitability(deps, amount, index)?) + } + QueryMsg::IsAnyCycleProfitable { amount } => { + to_binary(&query::any_cycles_profitable(deps, amount)?) + } + QueryMsg::Adapter(adapter) => match adapter { + adapter::SubQueryMsg::Balance { asset } => to_binary(&query::adapter_balance( + deps, + deps.api.addr_validate(&asset)?, + )?), + adapter::SubQueryMsg::Claimable { asset } => to_binary(&query::adapter_claimable( + deps, + deps.api.addr_validate(&asset)?, + )?), + adapter::SubQueryMsg::Unbonding { asset } => to_binary(&query::adapter_unbonding( + deps, + deps.api.addr_validate(&asset)?, + )?), + adapter::SubQueryMsg::Unbondable { asset } => to_binary(&query::adapter_unbondable( + deps, + deps.api.addr_validate(&asset)?, + )?), + adapter::SubQueryMsg::Reserves { asset } => to_binary(&query::adapter_reserves( + deps, + deps.api.addr_validate(&asset)?, + )?), + }, + } +} diff --git a/archived-contracts/sky/src/execute.rs b/archived-contracts/sky/src/execute.rs new file mode 100644 index 0000000..afea57a --- /dev/null +++ b/archived-contracts/sky/src/execute.rs @@ -0,0 +1,433 @@ +use crate::query::{any_cycles_profitable, cycle_profitability}; +use shade_protocol::{ + admin::helpers::{validate_admin, AdminPermissions}, + c_std::{ + to_binary, + Addr, + Decimal, + DepsMut, + Env, + MessageInfo, + Response, + StdError, + StdResult, + SubMsg, + Uint128, + }, + contract_interfaces::{ + dao::adapter, + sky::{ + self, + cycles::{Cycle, Offer}, + Config, + Cycles, + ExecuteAnswer, + ViewingKeys, + }, + }, + snip20::helpers::{send_msg, set_viewing_key_msg}, + utils::{ + asset::Contract, + generic_response::ResponseStatus, + storage::plus::ItemStorage, + ExecuteCallback, + }, +}; + +pub fn try_update_config( + deps: DepsMut, + _env: Env, + info: MessageInfo, + shade_admin: Option, + shd_token: Option, + silk_token: Option, + sscrt_token: Option, + treasury: Option, + payback_rate: Option, +) -> StdResult { + //Admin-only + let mut config = Config::load(deps.storage)?; + validate_admin( + &deps.querier, + AdminPermissions::SkyAdmin, + info.sender.to_string(), + &config.shade_admin, + )?; + + let mut messages = vec![]; + + if let Some(shade_admin) = shade_admin { + config.shade_admin = shade_admin; + } + if let Some(shd_token) = shd_token { + config.shd_token = shd_token; + messages.push(SubMsg::new(set_viewing_key_msg( + ViewingKeys::load(deps.storage)?.0, + None, + &config.shd_token.clone(), + )?)); + } + if let Some(silk_token) = silk_token { + config.silk_token = silk_token; + messages.push(SubMsg::new(set_viewing_key_msg( + ViewingKeys::load(deps.storage)?.0, + None, + &config.silk_token.clone(), + )?)); + } + if let Some(sscrt_token) = sscrt_token { + config.sscrt_token = sscrt_token; + messages.push(SubMsg::new(set_viewing_key_msg( + ViewingKeys::load(deps.storage)?.0, + None, + &config.sscrt_token.clone(), + )?)); + } + if let Some(treasury) = treasury { + config.treasury = treasury; + } + if let Some(payback_rate) = payback_rate { + if payback_rate == Decimal::zero() { + return Err(StdError::generic_err("payback_rate cannot be zero")); + } + config.payback_rate = payback_rate; + } + config.save(deps.storage)?; + Ok(Response::new() + .set_data(to_binary(&ExecuteAnswer::UpdateConfig { status: true })?) + .add_submessages(messages)) +} + +pub fn try_set_cycles( + deps: DepsMut, + _env: Env, + info: MessageInfo, + cycles_to_set: Vec, +) -> StdResult { + //Admin-only + let shade_admin = Config::load(deps.storage)?.shade_admin; + validate_admin( + &deps.querier, + AdminPermissions::SkyAdmin, + info.sender.to_string(), + &shade_admin, + )?; + + if cycles_to_set.clone().len() > 40 { + return Err(StdError::generic_err("Too many cycles")); + } + + // validate cycles + for cycle in cycles_to_set.clone() { + cycle.validate_cycle()?; + } + + let new_cycles = Cycles(cycles_to_set); + new_cycles.save(deps.storage)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetCycles { status: true })?)) +} + +pub fn try_append_cycle( + deps: DepsMut, + _env: Env, + info: MessageInfo, + cycles_to_add: Vec, +) -> StdResult { + //Admin-only + let shade_admin = Config::load(deps.storage)?.shade_admin; + validate_admin( + &deps.querier, + AdminPermissions::SkyAdmin, + info.sender.to_string(), + &shade_admin, + )?; + + for cycle in cycles_to_add.clone() { + cycle.validate_cycle()?; + } + + let mut cycles = Cycles::load(deps.storage)?; + + if cycles.0.clone().len() + cycles_to_add.clone().len() > 40 { + return Err(StdError::generic_err("Too many cycles")); + } + + cycles.0.append(&mut cycles_to_add.clone()); + + cycles.save(deps.storage)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::AppendCycles { status: true })?)) +} + +pub fn try_update_cycle( + deps: DepsMut, + _env: Env, + info: MessageInfo, + cycle: Cycle, + index: Uint128, +) -> StdResult { + let i = index.u128() as usize; + //Admin-only + let shade_admin = Config::load(deps.storage)?.shade_admin; + validate_admin( + &deps.querier, + AdminPermissions::SkyAdmin, + info.sender.to_string(), + &shade_admin, + )?; + + cycle.validate_cycle()?; + let mut cycles = Cycles::load(deps.storage)?; + if i > cycles.0.clone().len() - 1 { + return Err(StdError::generic_err("index out of bounds")); + } + cycles.0[i] = cycle; + cycles.save(deps.storage)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::UpdateCycle { status: true })?)) +} + +pub fn try_remove_cycle( + deps: DepsMut, + _env: Env, + info: MessageInfo, + index: Uint128, +) -> StdResult { + let i = index.u128() as usize; + //Admin-only + let shade_admin = Config::load(deps.storage)?.shade_admin; + validate_admin( + &deps.querier, + AdminPermissions::SkyAdmin, + info.sender.to_string(), + &shade_admin, + )?; + + // I'm pissed I couldn't do this in one line + let mut cycles = Cycles::load(deps.storage)?.0; + + if i > cycles.clone().len() - 1 { + return Err(StdError::generic_err("index out of bounds")); + } + + cycles.remove(i); + Cycles(cycles).save(deps.storage)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RemoveCycle { status: true })?)) +} + +pub fn try_arb_cycle( + deps: DepsMut, + _env: Env, + info: MessageInfo, + amount: Uint128, + index: Uint128, +) -> StdResult { + let mut messages = vec![]; + let mut return_swap_amounts = vec![]; + let mut payback_amount = Uint128::zero(); + let i = index.u128() as usize; + // cur_asset will keep track of the asset that we currently "have" + let mut cur_asset = Contract { + address: info.sender.clone(), + code_hash: "".to_string(), + }; + + // don't need to check for an index out of bounds since that check will happen in + // cycle_profitability + let res = cycle_profitability(deps.as_ref(), amount, index)?; // get profitability data from query + match res { + sky::QueryAnswer::IsCycleProfitable { + is_profitable, + direction, + swap_amounts, + profit, + } => { + return_swap_amounts = swap_amounts.clone(); + if direction.pair_addrs[0] // test to see which of the token attributes are the proposed starting addr + .token0 + == direction.start_addr.clone() + { + cur_asset = direction.pair_addrs[0].token0.clone(); + } else { + cur_asset = direction.pair_addrs[0].token1.clone(); + } + // if tx is unprofitable, err out + if !is_profitable { + return Err(StdError::generic_err("Unprofitable")); + } + //loop through the pairs in the cycle + for (i, arb_pair) in direction.pair_addrs.clone().iter().enumerate() { + // if it's the last pair, set our minimum expected amount, otherwise, this field + // should be zero + if direction.pair_addrs.len() - 1 == i { + messages.push(SubMsg::new(arb_pair.to_cosmos_msg( + Offer { + asset: cur_asset.clone(), + amount: swap_amounts[i], + }, + amount, + )?)); + } else { + messages.push(SubMsg::new(arb_pair.to_cosmos_msg( + Offer { + asset: cur_asset.clone(), + amount: swap_amounts[i], + }, + Uint128::zero(), + )?)); + } + // reset cur asset to the other asset held in the struct + if cur_asset == arb_pair.token0.clone() { + cur_asset = arb_pair.token1.clone(); + } else { + cur_asset = arb_pair.token0.clone(); + } + } + // calculate payback amount + payback_amount = profit * Config::load(deps.storage)?.payback_rate; + + // add the payback msg + messages.push(SubMsg::new(send_msg( + info.sender, + Uint128::new(payback_amount.u128()), + None, + None, + None, + &cur_asset.clone(), + )?)); + } + _ => {} + } + + // the final cur_asset should be the same as the start_addr + if !(cur_asset.clone() == Cycles::load(deps.storage)?.0[i].start_addr) { + return Err(StdError::generic_err( + "final asset not equal to start asset", + )); + } + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::ExecuteArbCycle { + status: true, + swap_amounts: return_swap_amounts, + payback_amount, + })?), + ) +} + +pub fn try_arb_all_cycles( + deps: DepsMut, + env: Env, + _info: MessageInfo, + amount: Uint128, +) -> StdResult { + let mut total_profit = Uint128::zero(); + let mut messages = vec![]; + let res = any_cycles_profitable(deps.as_ref(), amount)?; // get profitability data from query + match res { + sky::QueryAnswer::IsAnyCycleProfitable { + is_profitable, + profit, + .. + } => { + // loop through the data returned for each cycle + for (i, profit_bool) in is_profitable.iter().enumerate() { + // if a cycle is profitable call the try_arb_cycle fn and keep track of the + // total_profit + if profit_bool.clone() { + messages.push(SubMsg::new( + sky::ExecuteMsg::ArbCycle { + amount, + index: Uint128::from(i as u128), + padding: None, + } + .to_cosmos_msg( + &Contract { + address: env.contract.address.clone(), + code_hash: env.contract.code_hash.clone(), + }, + vec![], + )?, + )); + total_profit = total_profit.clone().checked_add(profit[i])?; + } + } + } + _ => {} + } + // calculate payback_amount + let payback_amount = total_profit * Config::load(deps.storage)?.payback_rate; + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::ArbAllCycles { + status: true, + payback_amount, + })?), + ) +} + +pub fn try_adapter_unbond( + deps: DepsMut, + _env: Env, + info: MessageInfo, + asset: Addr, + amount: Uint128, +) -> StdResult { + let config = Config::load(deps.storage)?; + // Error out if anyone other than the treasury is asking for money + if !(info.sender == config.treasury.address) { + return Err(StdError::generic_err("Unauthorized")); + } + // Error out if the treasury is asking for an asset sky doesn't account for + if !(config.shd_token.address == asset + || config.silk_token.address == asset + || config.sscrt_token.address == asset) + { + return Err(StdError::generic_err("Unrecognized asset")); + } + // initialize this var to whichever token the treasury is asking for + let contract; + if config.shd_token.address == asset { + contract = config.shd_token; + } else if config.silk_token.address == asset { + contract = config.silk_token; + } else { + contract = config.sscrt_token; + } + // send the msg + let messages = vec![send_msg( + config.treasury.address, + Uint128::new(amount.u128()), + None, + None, + None, + &contract, + )?]; + + Ok(Response::new() + .set_data(to_binary(&adapter::ExecuteAnswer::Unbond { + status: ResponseStatus::Success, + amount: Uint128::new(amount.u128()), + })?) + .add_messages(messages)) +} + +// Unessesary for sky +pub fn try_adapter_claim(_deps: DepsMut, _env: Env, _asset: Addr) -> StdResult { + Ok( + Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Claim { + status: ResponseStatus::Success, + amount: Uint128::zero(), + })?), + ) +} + +// Unessesary for sky +pub fn try_adapter_update(_deps: DepsMut, _env: Env, _asset: Addr) -> StdResult { + Ok( + Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Update { + status: ResponseStatus::Success, + })?), + ) +} diff --git a/archived-contracts/sky/src/lib.rs b/archived-contracts/sky/src/lib.rs new file mode 100644 index 0000000..a4f4984 --- /dev/null +++ b/archived-contracts/sky/src/lib.rs @@ -0,0 +1,3 @@ +pub mod contract; +pub mod execute; +pub mod query; diff --git a/archived-contracts/sky/src/query.rs b/archived-contracts/sky/src/query.rs new file mode 100644 index 0000000..bb43ec6 --- /dev/null +++ b/archived-contracts/sky/src/query.rs @@ -0,0 +1,334 @@ +use shade_protocol::{ + c_std::{Addr, Deps, StdError, StdResult, Uint128}, + contract_interfaces::{ + dao::adapter, + sky::{ + cycles::{Offer}, + Config, + Cycles, + QueryAnswer, + SelfAddr, + ViewingKeys, + }, + snip20, + }, + utils::{storage::plus::ItemStorage, Query}, +}; + +pub fn config(deps: Deps) -> StdResult { + Ok(QueryAnswer::Config { + config: Config::load(deps.storage)?, + }) +} + +pub fn get_balances(deps: Deps) -> StdResult { + let viewing_key = ViewingKeys::load(deps.storage)?.0; + let self_addr = SelfAddr::load(deps.storage)?.0; + let config = Config::load(deps.storage)?; + + // Query shd balance + let mut res = snip20::QueryMsg::Balance { + address: self_addr.clone().to_string(), + key: viewing_key.clone(), + } + .query(&deps.querier, &config.shd_token.clone())?; + + let shd_bal = match res { + snip20::QueryAnswer::Balance { amount } => amount, + _ => Uint128::zero(), + }; + + // Query silk balance + res = snip20::QueryMsg::Balance { + address: self_addr.clone().to_string(), + key: viewing_key.clone(), + } + .query(&deps.querier, &config.silk_token.clone())?; + + let silk_bal = match res { + snip20::QueryAnswer::Balance { amount } => amount, + _ => Uint128::zero(), + }; + + // Query sscrt balance + res = snip20::QueryMsg::Balance { + address: self_addr.clone().to_string(), + key: viewing_key.clone(), + } + .query(&deps.querier, &config.sscrt_token.clone())?; + + let sscrt_bal = match res { + snip20::QueryAnswer::Balance { amount } => amount, + _ => Uint128::zero(), + }; + + Ok(QueryAnswer::Balance { + shd_bal, + silk_bal, + sscrt_bal, + }) +} + +pub fn get_cycles(deps: Deps) -> StdResult { + //Need to make private eventually + Ok(QueryAnswer::GetCycles { + cycles: Cycles::load(deps.storage)?.0, + }) +} + +pub fn cycle_profitability(deps: Deps, amount: Uint128, index: Uint128) -> StdResult { + let mut cycles = Cycles::load(deps.storage)?.0; + let mut swap_amounts = vec![amount]; + let i = index.u128() as usize; + + if (i) >= cycles.len() { + return Err(StdError::generic_err("Index passed is out of bounds")); + } + + // set up inital offer + let mut current_offer = Offer { + asset: cycles[i].start_addr.clone(), + amount, + }; + + //loop through the pairs in the cycle + for arb_pair in cycles[i].pair_addrs.clone() { + // simulate swap will run a query with respect to which dex or minting that the pair says + // it is + let estimated_return = arb_pair + .clone() + .simulate_swap(deps, current_offer.clone())?; + swap_amounts.push(estimated_return.clone()); + // set up the next offer with the other token contract in the pair and the expected return + // from the last query + if current_offer.asset.code_hash.clone() == arb_pair.token0.code_hash.clone() { + current_offer = Offer { + asset: arb_pair.token1.clone(), + amount: estimated_return, + }; + } else { + current_offer = Offer { + asset: arb_pair.token0.clone(), + amount: estimated_return, + }; + } + } + + if swap_amounts.len() > cycles[i].pair_addrs.clone().len() { + return Err(StdError::generic_err("More swap amounts than arb pairs")); + } + + // if the last calculated swap is greater than the initial amount, return true + if current_offer.amount.u128() > amount.u128() { + return Ok(QueryAnswer::IsCycleProfitable { + is_profitable: true, + direction: cycles[i].clone(), + swap_amounts, + profit: current_offer.amount.checked_sub(amount)?, + }); + } + + // reset these variables in order to check the other way + swap_amounts = vec![amount]; + current_offer = Offer { + asset: cycles[i].start_addr.clone(), + amount, + }; + + // this is a fancy way of iterating through a vec in reverse + for arb_pair in cycles[i].pair_addrs.clone().iter().rev() { + // get the estimated return from the simulate swap function + let estimated_return = arb_pair + .clone() + .simulate_swap(deps, current_offer.clone())?; + swap_amounts.push(estimated_return.clone()); + // set the current offer to the other asset we are swapping into + if current_offer.asset.code_hash.clone() == arb_pair.token0.code_hash.clone() { + current_offer = Offer { + asset: arb_pair.token1.clone(), + amount: estimated_return, + }; + } else { + current_offer = Offer { + asset: arb_pair.token0.clone(), + amount: estimated_return, + }; + } + } + + // check to see if this direction was profitable + if current_offer.amount > amount { + // do an inplace reversal of the pair_addrs so that we know which way the opportunity goes + cycles[i].pair_addrs.reverse(); + return Ok(QueryAnswer::IsCycleProfitable { + is_profitable: true, + direction: cycles[i].clone(), + swap_amounts, + profit: current_offer.amount.checked_sub(amount)?, + }); + } + + // If both possible directions are unprofitable, return false + Ok(QueryAnswer::IsCycleProfitable { + is_profitable: false, + direction: cycles[0].clone(), + swap_amounts: vec![], + profit: Uint128::zero(), + }) +} + +pub fn any_cycles_profitable(deps: Deps, amount: Uint128) -> StdResult { + let cycles = Cycles::load(deps.storage)?.0; + let mut return_is_profitable = vec![]; + let mut return_directions = vec![]; + let mut return_swap_amounts = vec![]; + let mut return_profit = vec![]; + + // loop through the cycles with an index + for index in 0..cycles.len() { + // for each cycle, check its profitability + let res = cycle_profitability(deps, amount, Uint128::from(index as u128)).unwrap(); + match res { + QueryAnswer::IsCycleProfitable { + is_profitable, + direction, + swap_amounts, + profit, + } => { + if is_profitable { + // push the results to a vec + return_is_profitable.push(is_profitable); + return_directions.push(direction); + return_swap_amounts.push(swap_amounts); + return_profit.push(profit); + } + } + _ => { + return Err(StdError::generic_err("Unexpected result")); + } + } + } + + Ok(QueryAnswer::IsAnyCycleProfitable { + is_profitable: return_is_profitable, + direction: return_directions, + swap_amounts: return_swap_amounts, + profit: return_profit, + }) +} + +pub fn adapter_balance(deps: Deps, asset: Addr) -> StdResult { + let config = Config::load(deps.storage)?; + let viewing_key = ViewingKeys::load(deps.storage)?.0; + let self_addr = SelfAddr::load(deps.storage)?.0; + + let contract; + if config.shd_token.address == asset { + contract = config.shd_token.clone(); + } else if config.silk_token.address == asset { + contract = config.silk_token.clone(); + } else if config.sscrt_token.address == asset { + contract = config.sscrt_token.clone(); + } else { + return Ok(adapter::QueryAnswer::Unbondable { + amount: Uint128::zero(), + }); + } + + let res = snip20::QueryMsg::Balance { + address: self_addr.clone().to_string(), + key: viewing_key.clone(), + } + .query(&deps.querier, &contract.clone())?; + + let amount = match res { + snip20::QueryAnswer::Balance { amount } => amount, + _ => Uint128::zero(), + }; + + Ok(adapter::QueryAnswer::Unbondable { + amount: Uint128::new(amount.u128()), + }) +} + +pub fn adapter_claimable(_deps: Deps, _asset: Addr) -> StdResult { + Ok(adapter::QueryAnswer::Claimable { + amount: Uint128::zero(), + }) +} + +// Same as adapter_balance +pub fn adapter_unbondable(deps: Deps, asset: Addr) -> StdResult { + let config = Config::load(deps.storage)?; + let viewing_key = ViewingKeys::load(deps.storage)?.0; + let self_addr = SelfAddr::load(deps.storage)?.0; + + let contract; + if config.shd_token.address == asset { + contract = config.shd_token.clone(); + } else if config.silk_token.address == asset { + contract = config.silk_token.clone(); + } else if config.sscrt_token.address == asset { + contract = config.sscrt_token.clone(); + } else { + return Ok(adapter::QueryAnswer::Unbondable { + amount: Uint128::zero(), + }); + } + + let res = snip20::QueryMsg::Balance { + address: self_addr.clone().to_string(), + key: viewing_key.clone(), + } + .query(&deps.querier, &contract.clone())?; + + let amount = match res { + snip20::QueryAnswer::Balance { amount } => amount, + _ => Uint128::zero(), + }; + + Ok(adapter::QueryAnswer::Unbondable { + amount: Uint128::new(amount.u128()), + }) +} + +pub fn adapter_unbonding(_deps: Deps, _asset: Addr) -> StdResult { + Ok(adapter::QueryAnswer::Unbonding { + amount: Uint128::zero(), + }) +} + +// Same as adapter_balance +pub fn adapter_reserves(deps: Deps, asset: Addr) -> StdResult { + let config = Config::load(deps.storage)?; + let viewing_key = ViewingKeys::load(deps.storage)?.0; + let self_addr = SelfAddr::load(deps.storage)?.0; + + let contract; + if config.shd_token.address == asset { + contract = config.shd_token.clone(); + } else if config.silk_token.address == asset { + contract = config.silk_token.clone(); + } else if config.sscrt_token.address == asset { + contract = config.sscrt_token.clone(); + } else { + return Ok(adapter::QueryAnswer::Unbondable { + amount: Uint128::zero(), + }); + } + + let res = snip20::QueryMsg::Balance { + address: self_addr.clone().to_string(), + key: viewing_key.clone(), + } + .query(&deps.querier, &contract.clone())?; + + let amount = match res { + snip20::QueryAnswer::Balance { amount } => amount, + _ => Uint128::zero(), + }; + + Ok(adapter::QueryAnswer::Unbondable { + amount: Uint128::new(amount.u128()), + }) +} diff --git a/archived-contracts/sky/tests/integration.rs b/archived-contracts/sky/tests/integration.rs new file mode 100644 index 0000000..ab2798c --- /dev/null +++ b/archived-contracts/sky/tests/integration.rs @@ -0,0 +1,318 @@ +//! This integration test tries to run and call the generated wasm. +//! It depends on a Wasm build being available, which you can create with `cargo wasm`. +//! Then running `cargo integration-test` will validate we can properly call into that generated Wasm. +//! +//! You can easily convert unit tests to integration tests. +//! 1. First copy them over verbatum, +//! 2. Then change +//! let mut deps = mock_dependencies(20, &[]); +//! to +//! let mut deps = mock_instance(WASM, &[]); +//! 3. If you access raw storage, where ever you see something like: +//! deps.storage.get(CONFIG_KEY).expect("no data stored"); +//! replace it with: +//! deps.with_storage(|store| { +//! let data = store.get(CONFIG_KEY).expect("no data stored"); +//! //... +//! }); +//! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) + +/*use contract_harness::harness::snip20::Snip20; +use cosmwasm_math_compat as compat; +use cosmwasm_math_compat::Uint128; +use cosmwasm_std::{ + self, + coins, + from_binary, + to_binary, + Binary, + Env, + Extern, + HandleResponse, + Addr, + InitResponse, + StdError, + StdResult, +}; +use fadroma::{ + ensemble::{ContractEnsemble, MockEnv}, + prelude::{Callback, ContractInstantiationInfo, ContractLink}, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use shade_protocol::contract_interfaces::{ + dex::{self, shadeswap}, + snip20::{self}, +}; + +fn test_ensemble_sky(swap_amount: Uint128) { + let mut ensemble = ContractEnsemble::new(50); + + let reg_snip20 = ensemble.register(Box::new(Snip20)); + + //let reg_mock_shdswp = ensemble.register(Box::new(MockShdSwp)); + //let reg_shadeswap_exchange = ensemble.register(Box::new(ShadeswapExchange)); + //let reg_shadeswap_factory = ensemble.register(Box::new(ShadeswapFactory)); + //let reg_sienna_lp_token = ensemble.register(Box::new(SiennaLpToken)); + + println!("Deploying sscrt contract"); + + let sscrt = ensemble + .instantiate( + reg_snip20.id, + &snip20_reference_impl::msg::InitMsg { + name: "secretSCRT".into(), + admin: Some(Addr("admin".into())), + symbol: "SSCRT".into(), + decimals: 6, + initial_balances: Some(vec![snip20_reference_impl::msg::InitialBalance { + address: Addr("admin".into()), + amount: cosmwasm_std::Uint128(100000000000), // 100,000 SSCRT + }]), + prng_seed: to_binary("").ok().unwrap(), + config: None, + }, + MockEnv::new("admin", ContractLink { + address: Addr("sscrt".into()), + code_hash: reg_snip20.code_hash.clone(), + }), + ) + .unwrap(); + + println!("Sscrt contract addr: {}", sscrt.instance.address); + println!("Deploying shd contract"); + + let shd = ensemble + .instantiate( + reg_snip20.id, + &snip20_reference_impl::msg::InitMsg { + name: "Shade".into(), + admin: Some(Addr("admin".into())), + symbol: "SHD".into(), + decimals: 8, + initial_balances: Some(vec![snip20_reference_impl::msg::InitialBalance { + address: Addr("admin".into()), + amount: cosmwasm_std::Uint128(10000000000000), // 100,000 SHD + }]), + prng_seed: to_binary("").ok().unwrap(), + config: None, + }, + MockEnv::new("admin", ContractLink { + address: Addr("secret1k0jntykt7e4g3y88ltc60czgjuqdy4c9e8fzek".into()), + code_hash: reg_snip20.code_hash.clone(), + }), + ) + .unwrap(); + + println!("Shd contract addr: {}", shd.instance.address); + println!("Deploying silk contract"); + + let silk = ensemble + .instantiate( + reg_snip20.id, + &snip20_reference_impl::msg::InitMsg { + name: "Silk".into(), + admin: Some(Addr("admin".into())), + symbol: "SILK".into(), + decimals: 6, + initial_balances: Some(vec![snip20_reference_impl::msg::InitialBalance { + address: Addr("admin".into()), + amount: cosmwasm_std::Uint128(100000000000), // 100,000 SILK + }]), + prng_seed: to_binary("").ok().unwrap(), + config: None, + }, + MockEnv::new("admin", ContractLink { + address: Addr("secret14m2ffr7fyjhzv8cdknn2yp8sneht3luvsh9495".into()), + code_hash: reg_snip20.code_hash.clone(), + }), + ) + .unwrap(); + + println!("Silk contract addr: {}", silk.instance.address); + + let key = String::from("key"); + + ensemble + .execute( + &snip20_reference_impl::msg::HandleMsg::SetViewingKey { + key: key.clone(), + padding: None, + }, + MockEnv::new("admin", sscrt.instance.clone()), + ) + .unwrap(); + + ensemble + .execute( + &snip20_reference_impl::msg::HandleMsg::SetViewingKey { + key: key.clone(), + padding: None, + }, + MockEnv::new("admin", shd.instance.clone()), + ) + .unwrap(); + + ensemble + .execute( + &snip20_reference_impl::msg::HandleMsg::SetViewingKey { + key: key.clone(), + padding: None, + }, + MockEnv::new("admin", silk.instance.clone()), + ) + .unwrap(); + + let mut query_res = ensemble + .query(sscrt.instance.address.clone(), &snip20::QueryMsg::Balance { + address: "admin".into(), + key: key.clone(), + }) + .unwrap(); + + match query_res { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, Uint128::new(100000000000)) + } + _ => { + assert!(false) + } + } + + query_res = ensemble + .query(shd.instance.address.clone(), &snip20::QueryMsg::Balance { + address: "admin".into(), + key: key.clone(), + }) + .unwrap(); + + match query_res { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, Uint128::new(10000000000000)) + } + _ => { + assert!(false) + } + } + + query_res = ensemble + .query(silk.instance.address.clone(), &snip20::QueryMsg::Balance { + address: "admin".into(), + key: key.clone(), + }) + .unwrap(); + + match query_res { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, Uint128::new(100000000000)) + } + _ => { + assert!(false) + } + } + + /*println!("{}", reg_sienna_factory.code_hash); + println!("{}", reg_sienna_lp_token.code_hash); + + let sienna_factory = ensemble.instantiate( + reg_sienna_factory.id, + &factory::InitMsg { + lp_token_contract: reg_sienna_lp_token.clone(), + pair_contract: reg_sienna_exchange.clone(), + exchange_settings: factory::ExchangeSettings{ + swap_fee: factory::Fee { nom: 0, denom: 0 }, + sienna_fee: factory::Fee { nom: 0, denom: 0 }, + sienna_burner: None, + }, + admin: Some(Addr("admin".into())), + prng_seed: to_binary("").ok().unwrap(), + }, + MockEnv::new("admin", ContractLink { + address: Addr("reg_sienna_factory".into()), + code_hash: reg_sienna_factory.code_hash.clone(), + }), + ).unwrap(); + + println!("{}", silk.address); + println!("{}", shd.address); + + let mut res = ensemble.execute( + &factory::HandleMsg::CreateExchange { + pair: sienna::Pair { + token_0: sienna::TokenType::CustomToken { + contract_addr: shd.address, + token_code_hash: shd.code_hash, + }, + token_1: sienna::TokenType::CustomToken { + contract_addr: silk.address, + token_code_hash: silk.code_hash, + } + }, + entropy: to_binary("").ok().unwrap(), + }, + MockEnv::new("admin", sienna_factory.clone()), + ).unwrap();*/ + + println!("here"); + + /* let sienna_pair = ensemble.instantiate( + reg_sienna_exchange.id, + &amm_pair::InitMsg{ + pair: sienna::Pair { + token_0: sienna::TokenType::CustomToken{ + contract_addr: shd.address.clone(), + token_code_hash: shd.code_hash.clone(), + }, + token_1: sienna::TokenType::CustomToken{ + contract_addr: silk.address.clone(), + token_code_hash: silk.code_hash.clone(), + }, + }, + lp_token_contract: reg_sienna_lp_token.clone(), + factory_info: sienna_factory.clone(), + callback: Callback { + msg: to_binary("").ok().unwrap(), + contract: sienna_factory.clone(), + }, + prng_seed: to_binary("").ok().unwrap(), + entropy: to_binary("").ok().unwrap(), + }, + MockEnv::new("admin", ContractLink { + address: Addr("reg_sienna_exchange".into()), + code_hash: reg_sienna_exchange.code_hash.clone(), + }), + ).unwrap();*/ +} + +macro_rules! sky_int_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (swap_amount,) = $value; + test_ensemble_sky(swap_amount); + } + )* + } +} + +sky_int_tests! { + sky_int_0: ( + Uint128::zero(), + ), +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct InitMsg { + /// The tokens that will be managed by the exchange + pub pair: dex::sienna::Pair, + /// LP token instantiation info + pub lp_token_contract: ContractInstantiationInfo, + /// Used by the exchange contract to + /// send back its address to the factory on init + pub factory_info: ContractLink, + pub callback: Callback, + pub prng_seed: Binary, + pub entropy: Binary, +}*/ diff --git a/archived-contracts/snip20_staking/.cargo/config b/archived-contracts/snip20_staking/.cargo/config new file mode 100644 index 0000000..9519d41 --- /dev/null +++ b/archived-contracts/snip20_staking/.cargo/config @@ -0,0 +1,4 @@ +[alias] +unit-test = "test --lib --features backtraces" +integration-test = "test --test integration" +schema = "run --example schema" diff --git a/archived-contracts/snip20_staking/.circleci/config.yml b/archived-contracts/snip20_staking/.circleci/config.yml new file mode 100644 index 0000000..a6f10d6 --- /dev/null +++ b/archived-contracts/snip20_staking/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.46 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/snip20_staking/Cargo.toml b/archived-contracts/snip20_staking/Cargo.toml new file mode 100644 index 0000000..0c5b67f --- /dev/null +++ b/archived-contracts/snip20_staking/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "spip_stkd_0" +version = "0.1.0" +authors = ["Guy "] +edition = "2018" +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] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +# debug-print = ["cosmwasm-std/debug-print"] +[dependencies] +schemars = "0.8.9" +serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] } +thiserror = "1.0" + +bincode2 = "2.0.1" +subtle = { version = "2.2.3", default-features = false } +base64 = "0.12.3" +rand_chacha = { version = "0.2.2", default-features = false } +rand_core = { version = "0.5.1", default-features = false } +sha2 = { version = "0.9.1", default-features = false } + +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["snip20_staking", "snip20", "storage"] } + +[dev-dependencies] +rand = "0.8.4" diff --git a/archived-contracts/snip20_staking/README.md b/archived-contracts/snip20_staking/README.md new file mode 100644 index 0000000..7c50fc2 --- /dev/null +++ b/archived-contracts/snip20_staking/README.md @@ -0,0 +1,59 @@ +# SNIP-20 Reference Implementation + +This is an implementation of a [SNIP-20](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md), [SNIP-21](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-21.md), [SNIP-22](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-22.md), [SNIP-23](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-23.md) and [SNIP-24](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-24.md) compliant token contract. +At the time of token creation you may configure: +* Public Total Supply: If you enable this, the token's total supply will be displayed whenever a TokenInfo query is performed. DEFAULT: false +* Enable Deposit: If you enable this, you will be able to convert from SCRT to the token.* DEFAULT: false +* Enable Redeem: If you enable this, you will be able to redeem your token for SCRT.* It should be noted that if you have redeem enabled, but deposit disabled, all redeem attempts will fail unless someone has sent SCRT to the token contract. DEFAULT: false +* Enable Mint: If you enable this, any address in the list of minters will be able to mint new tokens. The admin address is the default minter, but can use the set/add/remove_minters functions to change the list of approved minting addresses. DEFAULT: false +* Enable Burn: If you enable this, addresses will be able to burn tokens. DEFAULT: false + + +\*:The conversion rate will be 1 uscrt for 1 minimum denomination of the token. This means that if your token has 6 decimal places, it will convert 1:1 with SCRT. If your token has 10 decimal places, it will have an exchange rate of 10000 SCRT for 1 token. If your token has 3 decimal places, it will have an exchange rate of 1000 tokens for 1 SCRT. You can use the exchange_rate query to view the exchange rate for the token. The query response will display either how many tokens are worth 1 SCRT, or how many SCRT are worth 1 token. That is, the response lists the symbol of the coin that has less value (either SCRT or the token), and the number of those coins that are worth 1 of the other. + +## Usage examples: + +To create a new token: + +```secretcli tx compute instantiate '{"name":"","symbol":"","admin":"","decimals":,"initial_balances":[{"address":"","amount":""}],"prng_seed":"","config":{"public_total_supply":,"enable_deposit":,"enable_redeem":,"enable_mint":,"enable_burn":}}' --label --from ``` + +The `admin` field is optional and will default to the "--from" address if you do not specify it. The `initial_balances` field is optional, and you can specify as many addresses/balances as you like. The `config` field as well as every field in the `config` is optional. Any `config` fields not specified will default to `false`. + +To deposit: ***(This is public)*** + +```secretcli tx compute execute '{"deposit": {}}' --amount 1000000uscrt --from ``` + +To send SSCRT: + +```secretcli tx compute execute '{"transfer": {"recipient": "", "amount": ""}}' --from ``` + +To set your viewing key: + +```secretcli tx compute execute '{"create_viewing_key": {"entropy": ""}}' --from ``` + +To check your balance: + +```secretcli q compute query '{"balance": {"address":"", "key":"your_viewing_key"}}'``` + +To view your transaction history: + +```secretcli q compute query '{"transfer_history": {"address": "", "key": "", "page": , "page_size": }}'``` + +To withdraw: ***(This is public)*** + +```secretcli tx compute execute '{"redeem": {"amount": ""}}' --from ``` + +To view the token contract's configuration: + +```secretcli q compute query '{"token_config": {}}'``` + +To view the deposit/redeem exchange rate: + +```secretcli q compute query '{"exchange_rate": {}}'``` + + +## Troubleshooting + +All transactions are encrypted, so if you want to see the error returned by a failed transaction, you need to use the command + +`secretcli q compute tx ` diff --git a/archived-contracts/snip20_staking/src/batch.rs b/archived-contracts/snip20_staking/src/batch.rs new file mode 100644 index 0000000..070595b --- /dev/null +++ b/archived-contracts/snip20_staking/src/batch.rs @@ -0,0 +1,55 @@ +//! Types used in batch operations + + +use shade_protocol::cosmwasm_schema::cw_serde; + +use shade_protocol::c_std::Uint128; +use shade_protocol::c_std::{Binary, Addr}; + +#[cw_serde] +pub struct TransferAction { + pub recipient: Addr, + pub amount: Uint128, + pub memo: Option, +} + +#[cw_serde] +pub struct SendAction { + pub recipient: Addr, + pub recipient_code_hash: Option, + pub amount: Uint128, + pub msg: Option, + pub memo: Option, +} + +#[cw_serde] +pub struct TransferFromAction { + pub owner: Addr, + pub recipient: Addr, + pub amount: Uint128, + pub memo: Option, +} + +#[cw_serde] +pub struct SendFromAction { + pub owner: Addr, + pub recipient: Addr, + pub recipient_code_hash: Option, + pub amount: Uint128, + pub msg: Option, + pub memo: Option, +} + +#[cw_serde] +pub struct MintAction { + pub recipient: Addr, + pub amount: Uint128, + pub memo: Option, +} + +#[cw_serde] +pub struct BurnFromAction { + pub owner: Addr, + pub amount: Uint128, + pub memo: Option, +} diff --git a/archived-contracts/snip20_staking/src/contract.rs b/archived-contracts/snip20_staking/src/contract.rs new file mode 100644 index 0000000..ef6647b --- /dev/null +++ b/archived-contracts/snip20_staking/src/contract.rs @@ -0,0 +1,4738 @@ +use crate::{ + batch, + distributors, + distributors::{ + get_distributor, + try_add_distributors, + try_set_distributors, + try_set_distributors_status, + }, + expose_balance::{try_expose_balance, try_expose_balance_with_cooldown}, + msg::{ + space_pad, + status_level_to_u8, + ContractStatusLevel, + HandleAnswer, + ExecuteMsg, + InstantiateMsg, + QueryAnswer, + QueryMsg, + QueryWithPermit, + ResponseStatus::Success, + }, + rand::sha_256, + receiver::Snip20ReceiveMsg, + stake::{ + claim_rewards, + remove_from_cooldown, + shares_per_token, + try_claim_rewards, + try_claim_unbond, + try_receive, + try_stake_rewards, + try_unbond, + try_update_stake_config, + }, + stake_queries, + state::{ + get_receiver_hash, + read_allowance, + read_viewing_key, + set_receiver_hash, + write_allowance, + write_viewing_key, + Balances, + Config, + Constants, + ReadonlyBalances, + ReadonlyConfig, + }, + state_staking::{ + DailyUnbondingQueue, + Distributors, + DistributorsEnabled, + TotalShares, + TotalTokens, + TotalUnbonding, + UnsentStakedTokens, + UserCooldown, + UserShares, + }, + transaction_history::{get_transfers, get_txs, store_claim_reward, store_mint, store_transfer}, + viewing_key::{ViewingKey, VIEWING_KEY_SIZE}, +}; +use shade_protocol::c_std::{Deps, MessageInfo, Uint128, Uint256}; +/// This contract implements SNIP-20 standard: +/// https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md +use shade_protocol::c_std::{ + from_binary, + to_binary, + Api, + Binary, + CanonicalAddr, + CosmosMsg, + Env, + DepsMut, + Addr, + Response, + Querier, + StdError, + StdResult, + Storage, +}; +use shade_protocol::{ + // permit::{validate, Permission, Permit, RevokedPermits}, + contract_interfaces::snip20::helpers::{register_receive, send_msg, token_info}, +}; +use shade_protocol::{Contract, contract_interfaces::staking::snip20_staking::{ + stake::{Cooldown, StakeConfig, VecQueue}, + ReceiveType, +}, utils::storage::default::{BucketStorage, SingletonStorage}}; +use shade_protocol::query_authentication::permit::Permit; +use shade_protocol::snip20::Permission; + +/// We make sure that responses from `execute` are padded to a multiple of this size. +pub const RESPONSE_BLOCK_SIZE: usize = 256; +pub const PREFIX_REVOKED_PERMITS: &str = "revoked_permits"; + +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + // Check name, symbol, decimals + if !is_valid_name(&msg.name) { + return Err(StdError::generic_err( + "Name is not in the expected format (3-30 UTF-8 bytes)", + )); + } + if !is_valid_symbol(&msg.symbol) { + return Err(StdError::generic_err( + "Ticker symbol is not in expected format [A-Z]{3,6}", + )); + } + + let init_config = msg.config(); + let admin = msg.admin.unwrap_or(info.sender); + + let total_supply: u128 = 0; + + let prng_seed_hashed = sha_256(&msg.prng_seed.0); + + // Set stake config + let staked_token_decimals: u8; + if let Some(decimals) = msg.decimals { + staked_token_decimals = decimals; + } else { + staked_token_decimals = token_info_query( + &deps.querier, + 256, + msg.staked_token.code_hash.clone(), + msg.staked_token.address.clone(), + )? + .decimals; + } + + let mut config = Config::from_storage(deps.storage); + config.set_constants(&Constants { + name: msg.name, + symbol: "STKD-".to_string() + &msg.symbol, + decimals: staked_token_decimals, + admin, + prng_seed: prng_seed_hashed.to_vec(), + total_supply_is_public: init_config.public_total_supply(), + contract_address: env.contract.address, + })?; + config.set_total_supply(total_supply); + config.set_contract_status(ContractStatusLevel::NormalRun); + + // Set distributors + Distributors(msg.distributors.unwrap_or_default()).save(deps.storage)?; + DistributorsEnabled(msg.limit_transfer).save(deps.storage)?; + + if staked_token_decimals * 2 > msg.share_decimals { + return Err(StdError::generic_err( + "Share decimals must be two times greater than the token decimals", + )); + } + + StakeConfig { + unbond_time: msg.unbond_time, + staked_token: msg.staked_token.clone(), + decimal_difference: msg.share_decimals - staked_token_decimals, + treasury: msg.treasury.clone(), + } + .save(deps.storage)?; + + // Set shares state to 0 + TotalShares(Uint256::zero()).save(deps.storage)?; + + // Initialize unbonding queue + DailyUnbondingQueue(VecQueue::new(vec![])).save(deps.storage)?; + + // Set tokens + TotalTokens(Uint128::zero()).save(deps.storage)?; + + TotalUnbonding(Uint128::zero()).save(deps.storage)?; + + UnsentStakedTokens(Uint128::zero()).save(deps.storage)?; + + // Register receive if necessary + let mut messages = vec![]; + if let Some(addr) = msg.treasury { + if let Some(code_hash) = msg.treasury_code_hash { + messages.push(register_receive( + env.contract.code_hash.clone(), + None, + &Contract { + address: addr, + code_hash + } + )?); + } + } + + messages.push(register_receive( + env.contract.code_hash, + None, + msg.staked_token + )?); + + Ok(Response::new()) +} + +fn pad_response(response: StdResult) -> StdResult { + response.map(|mut response| { + response.data = response.data.map(|mut data| { + space_pad(RESPONSE_BLOCK_SIZE, &mut data.0); + data + }); + response + }) +} + +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> StdResult { + let contract_status = ReadonlyConfig::from_storage(deps.storage).contract_status(); + + match contract_status { + ContractStatusLevel::NormalRun => {} // If it's a normal run just continue + _ => { + let mut not_authorized = false; + let status_code = status_level_to_u8(contract_status); + + match msg.clone() { + // This is always allowed + ExecuteMsg::SetContractStatus { .. } => {} + ExecuteMsg::UpdateStakeConfig { .. } => {} + + // If receive check that msg is not bonding or reward + ExecuteMsg::Receive { msg, .. } => { + let receive_type: ReceiveType; + if let Some(msg) = msg { + receive_type = from_binary(&msg)?; + } else { + return Err(StdError::generic_err("No receive type supplied in message")); + } + + match receive_type { + ReceiveType::Bond { .. } | ReceiveType::Reward => not_authorized = true, + _ => {} + } + } + // Relates to bonding + ExecuteMsg::StakeRewards { .. } => { + if status_code > 0 { + not_authorized = true; + } + } + + ExecuteMsg::ClaimRewards { .. } => { + if status_code > 1 { + not_authorized = true; + } + } + // If unbonding check that msg is not stop all + ExecuteMsg::Unbond { .. } => { + if status_code > 2 { + not_authorized = true; + } + } + ExecuteMsg::ClaimUnbond { .. } => { + if status_code > 2 { + not_authorized = true; + } + } + // All other msgs can only work if status is 1 or below + _ => { + if status_code > 1 { + not_authorized = true; + } + } + } + + if not_authorized { + return pad_response(Err(StdError::generic_err( + "This contract is stopped and this action is not allowed", + ))); + } + } + }; + + let response = match msg { + // Staking + ExecuteMsg::UpdateStakeConfig { + unbond_time, + disable_treasury, + treasury, + .. + } => try_update_stake_config(deps, env, info, unbond_time, disable_treasury, treasury), + ExecuteMsg::Receive { + sender, + from, + amount, + msg, + memo, + .. + } => try_receive(deps, env, info, sender, from, amount, msg, memo), + ExecuteMsg::Unbond { amount, .. } => try_unbond(deps, env, info, amount), + ExecuteMsg::ClaimUnbond { .. } => try_claim_unbond(deps, env, info), + ExecuteMsg::ClaimRewards { .. } => try_claim_rewards(deps, env, info), + ExecuteMsg::StakeRewards { .. } => try_stake_rewards(deps, env, info), + + // Balance + ExecuteMsg::ExposeBalance { + recipient, + code_hash, + msg, + memo, + .. + } => try_expose_balance(deps, env, info, recipient, code_hash, msg, memo), + ExecuteMsg::ExposeBalanceWithCooldown { + recipient, + code_hash, + msg, + memo, + .. + } => try_expose_balance_with_cooldown(deps, env, info, recipient, code_hash, msg, memo), + + // Distributors + ExecuteMsg::SetDistributorsStatus { enabled, .. } => { + try_set_distributors_status(deps, env, info, enabled) + } + ExecuteMsg::AddDistributors { distributors, .. } => { + try_add_distributors(deps, env, info, distributors) + } + ExecuteMsg::SetDistributors { distributors, .. } => { + try_set_distributors(deps, env, info, distributors) + } + + // Base + ExecuteMsg::Transfer { + recipient, + amount, + memo, + .. + } => try_transfer(deps, env, info, recipient, amount, memo), + ExecuteMsg::Send { + recipient, + recipient_code_hash, + amount, + msg, + memo, + .. + } => try_send(deps, env, info, recipient, recipient_code_hash, amount, memo, msg), + ExecuteMsg::BatchTransfer { actions, .. } => try_batch_transfer(deps, env, info, actions), + ExecuteMsg::BatchSend { actions, .. } => try_batch_send(deps, env, info, actions), + ExecuteMsg::RegisterReceive { code_hash, .. } => try_register_receive(deps, env, info, code_hash), + ExecuteMsg::CreateViewingKey { entropy, .. } => try_create_key(deps, env, info, entropy), + ExecuteMsg::SetViewingKey { key, .. } => try_set_key(deps, env, info, key), + + // Allowance + ExecuteMsg::IncreaseAllowance { + spender, + amount, + expiration, + .. + } => try_increase_allowance(deps, env, info, spender, amount, expiration), + ExecuteMsg::DecreaseAllowance { + spender, + amount, + expiration, + .. + } => try_decrease_allowance(deps, env, info, spender, amount, expiration), + ExecuteMsg::TransferFrom { + owner, + recipient, + amount, + memo, + .. + } => try_transfer_from(deps, &env, &owner, &recipient, amount, memo), + ExecuteMsg::SendFrom { + owner, + recipient, + recipient_code_hash, + amount, + msg, + memo, + .. + } => try_send_from( + deps, + env, + info, + owner, + recipient, + recipient_code_hash, + amount, + memo, + msg, + ), + ExecuteMsg::BatchTransferFrom { actions, .. } => { + try_batch_transfer_from(deps, &env, actions) + } + ExecuteMsg::BatchSendFrom { actions, .. } => try_batch_send_from(deps, env, info, actions), + + // Other + ExecuteMsg::ChangeAdmin { address, .. } => change_admin(deps, env, info, address), + ExecuteMsg::SetContractStatus { level, .. } => set_contract_status(deps, env, info, level), + ExecuteMsg::RevokePermit { permit_name, .. } => revoke_permit(deps, env, info, permit_name), + }; + + pad_response(response) +} + +pub fn query(deps: Deps, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::StakeConfig {} => stake_queries::stake_config(deps), + QueryMsg::TotalStaked {} => stake_queries::total_staked(deps), + QueryMsg::StakeRate {} => stake_queries::stake_rate(deps), + QueryMsg::Unbonding {} => stake_queries::unbonding(deps), + QueryMsg::Unfunded { start, total } => stake_queries::unfunded(deps, start, total), + QueryMsg::Distributors {} => distributors::distributors(deps), + QueryMsg::TokenInfo {} => query_token_info(deps.storage), + QueryMsg::TokenConfig {} => query_token_config(deps.storage), + QueryMsg::ContractStatus {} => query_contract_status(deps.storage), + QueryMsg::WithPermit { permit, query } => permit_queries(deps, permit, query), + _ => viewing_keys_queries(deps, msg), + } +} + +fn permit_queries( + deps: Deps, + permit: Permit, + query: QueryWithPermit, +) -> Result { + // Validate permit content + let token_address = ReadonlyConfig::from_storage(deps.storage) + .constants()? + .contract_address; + + let account = validate(deps, PREFIX_REVOKED_PERMITS, &permit, token_address)?; + + // Permit validated! We can now execute the query. + match query { + QueryWithPermit::Staked { time } => { + if !permit.check_permission(&Permission::Balance) { + return Err(StdError::generic_err(format!( + "No permission to query balance / stake, got permissions {:?}", + permit.params.permissions + ))); + } + + stake_queries::staked(deps, account, time) + } + QueryWithPermit::Balance {} => { + if !permit.check_permission(&Permission::Balance) { + return Err(StdError::generic_err(format!( + "No permission to query balance, got permissions {:?}", + permit.params.permissions + ))); + } + + query_balance(deps, &account) + } + QueryWithPermit::TransferHistory { page, page_size } => { + if !permit.check_permission(&Permission::History) { + return Err(StdError::generic_err(format!( + "No permission to query history, got permissions {:?}", + permit.params.permissions + ))); + } + + query_transfers(deps, &account, page.unwrap_or(0), page_size) + } + QueryWithPermit::TransactionHistory { page, page_size } => { + if !permit.check_permission(&Permission::History) { + return Err(StdError::generic_err(format!( + "No permission to query history, got permissions {:?}", + permit.params.permissions + ))); + } + + query_transactions(deps, &account, page.unwrap_or(0), page_size) + } + QueryWithPermit::Allowance { owner, spender } => { + if !permit.check_permission(&Permission::Allowance) { + return Err(StdError::generic_err(format!( + "No permission to query allowance, got permissions {:?}", + permit.params.permissions + ))); + } + + if account != owner && account != spender { + return Err(StdError::generic_err(format!( + "Cannot query allowance. Requires permit for either owner {:?} or spender {:?}, got permit for {:?}", + owner.as_str(), + spender.as_str(), + account.as_str() + ))); + } + + query_allowance(deps, owner, spender) + } + } +} + +pub fn viewing_keys_queries( + deps: Deps, + msg: QueryMsg, +) -> StdResult { + let (addresses, key) = msg.get_validation_params(); + + for address in addresses { + let canonical_addr = deps.api.canonical_address(address)?; + + let expected_key = read_viewing_key(deps.storage, &canonical_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::Staked { address, time, .. } => { + stake_queries::staked(deps, address, time) + } + QueryMsg::Balance { address, .. } => query_balance(deps, &address), + QueryMsg::TransferHistory { + address, + page, + page_size, + .. + } => query_transfers(deps, &address, page.unwrap_or(0), page_size), + QueryMsg::TransactionHistory { + address, + page, + page_size, + .. + } => query_transactions(deps, &address, page.unwrap_or(0), page_size), + QueryMsg::Allowance { owner, spender, .. } => query_allowance(deps, owner, spender), + _ => panic!("This query type does not require authentication"), + }; + } + } + + to_binary(&QueryAnswer::ViewingKeyError { + msg: "Wrong viewing key for this address or viewing key not set".to_string(), + }) +} + +fn query_token_info(storage: &dyn Storage) -> StdResult { + let config = ReadonlyConfig::from_storage(storage); + let constants = config.constants()?; + + let total_supply = if constants.total_supply_is_public { + Some(Uint128::new(config.total_supply())) + } else { + None + }; + + to_binary(&QueryAnswer::TokenInfo { + name: constants.name, + symbol: constants.symbol, + decimals: constants.decimals, + total_supply, + }) +} + +fn query_token_config(storage: &dyn Storage) -> StdResult { + let config = ReadonlyConfig::from_storage(storage); + let constants = config.constants()?; + + to_binary(&QueryAnswer::TokenConfig { + public_total_supply: constants.total_supply_is_public, + }) +} + +fn query_contract_status(storage: &dyn Storage) -> StdResult { + let config = ReadonlyConfig::from_storage(storage); + + to_binary(&QueryAnswer::ContractStatus { + status: config.contract_status(), + }) +} + +pub fn query_transfers( + deps: Deps, + account: &Addr, + page: u32, + page_size: u32, +) -> StdResult { + let address = deps.api.canonical_address(account)?; + let (txs, total) = get_transfers(&deps.api, deps.storage, &address, page, page_size)?; + + let result = QueryAnswer::TransferHistory { + txs, + total: Some(total), + }; + to_binary(&result) +} + +pub fn query_transactions( + deps: Deps, + account: &Addr, + page: u32, + page_size: u32, +) -> StdResult { + let address = deps.api.canonical_address(account)?; + let (txs, total) = get_txs(&deps.api, deps.storage, &address, page, page_size)?; + + let result = QueryAnswer::TransactionHistory { + txs, + total: Some(total), + }; + to_binary(&result) +} + +pub fn query_balance( + deps: Deps, + account: &Addr, +) -> StdResult { + let address = deps.api.canonical_address(account)?; + + let amount = + Uint128::new(ReadonlyBalances::from_storage(deps.storage).account_amount(&address)); + let response = QueryAnswer::Balance { amount }; + to_binary(&response) +} + +fn change_admin( + deps: DepsMut, + env: Env, + info: MessageInfo, + address: Addr, +) -> StdResult { + let mut config = Config::from_storage(deps.storage); + + check_if_admin(&config, &info.sender)?; + + let mut consts = config.constants()?; + consts.admin = address; + config.set_constants(&consts)?; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::ChangeAdmin { status: Success })?)) +} + +pub fn try_mint_impl( + storage: &mut dyn Storage, + minter: &CanonicalAddr, + recipient: &CanonicalAddr, + amount: Uint128, + denom: String, + memo: Option, + block: &shade_protocol::c_std::BlockInfo, +) -> StdResult<()> { + let raw_amount = amount.u128(); + + let mut balances = Balances::from_storage(storage); + + let mut account_balance = balances.balance(recipient); + + if let Some(new_balance) = account_balance.checked_add(raw_amount) { + account_balance = new_balance; + } else { + // This error literally can not happen, since the account's funds are a subset + // of the total supply, both are stored as u128, and we check for overflow of + // the total supply just a couple lines before. + // Still, writing this to cover all overflows. + return Err(StdError::generic_err( + "This mint attempt would increase the account's balance above the supported maximum", + )); + } + + balances.set_account_balance(recipient, account_balance); + + store_mint(storage, minter, recipient, amount, denom, memo, block)?; + + Ok(()) +} + +pub fn try_set_key( + deps: DepsMut, + env: Env, + info: MessageInfo, + key: String, +) -> StdResult { + let vk = ViewingKey(key); + + let message_sender = deps.api.canonical_address(&info.sender)?; + write_viewing_key(deps.storage, &message_sender, &vk); + + Ok(Response::new().set_data(to_binary(&HandleAnswer::SetViewingKey { status: Success })?)) +} + +pub fn try_create_key( + deps: DepsMut, + env: Env, + info: MessageInfo, + entropy: String, +) -> StdResult { + let constants = ReadonlyConfig::from_storage(deps.storage).constants()?; + let prng_seed = constants.prng_seed; + + let key = ViewingKey::new(&env, &prng_seed, (&entropy).as_ref()); + + let message_sender = deps.api.canonical_address(&info.sender)?; + write_viewing_key(deps.storage, &message_sender, &key); + + Ok(Response::new().set_data(to_binary(&HandleAnswer::CreateViewingKey { key })?)) +} + +fn set_contract_status( + deps: DepsMut, + env: Env, + info: MessageInfo, + status_level: ContractStatusLevel, +) -> StdResult { + let mut config = Config::from_storage(deps.storage); + + check_if_admin(&config, &info.sender)?; + + config.set_contract_status(status_level); + + Ok(Response::new().set_data(to_binary(&HandleAnswer::SetContractStatus { + status: Success, + })?)) +} + +pub fn query_allowance( + deps: Deps, + owner: Addr, + spender: Addr, +) -> StdResult { + let owner_address = deps.api.canonical_address(&owner)?; + let spender_address = deps.api.canonical_address(&spender)?; + + let allowance = read_allowance(deps.storage, &owner_address, &spender_address)?; + + let response = QueryAnswer::Allowance { + owner, + spender, + allowance: Uint128::new(allowance.amount), + expiration: allowance.expiration, + }; + to_binary(&response) +} + +#[allow(clippy::too_many_arguments)] +fn try_transfer_impl( + deps: DepsMut, + messages: &mut Vec, + sender: &Addr, + sender_canon: &CanonicalAddr, + recipient: &Addr, + recipient_canon: &CanonicalAddr, + amount: Uint128, + memo: Option, + block: &shade_protocol::c_std::BlockInfo, + + distributors: &Option>, + time: u64, +) -> StdResult<()> { + // Verify that this transfer is allowed + if let Some(distributors) = distributors { + if !distributors.contains(sender) && !distributors.contains(recipient) { + return Err(StdError::generic_err("unauthorized")); + } + } + + let symbol = Config::from_storage(deps.storage).constants()?.symbol; + + let stake_config = StakeConfig::load(deps.storage)?; + let claim = claim_rewards(deps.storage, &stake_config, sender, sender_canon)?; + if !claim.is_zero() { + messages.push(send_msg( + sender.clone(), + claim.into(), + None, + None, + None, + 256, + stake_config.staked_token.code_hash, + stake_config.staked_token.address, + )?); + + store_claim_reward( + deps.storage, + sender_canon, + claim, + symbol.clone(), + None, + block, + )?; + } + + perform_transfer( + deps.storage, + sender, + sender_canon, + recipient, + recipient_canon, + amount, + time, + )?; + + store_transfer( + deps.storage, + sender_canon, + sender_canon, + recipient_canon, + amount, + symbol, + memo, + block, + )?; + + Ok(()) +} + +fn try_transfer( + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: Addr, + amount: Uint128, + memo: Option, +) -> StdResult { + let sender = info.sender; + let sender_canon = deps.api.canonical_address(&sender)?; + let recipient_canon = deps.api.canonical_address(&recipient)?; + + let distributor = get_distributor(deps)?; + + let mut messages = vec![]; + + try_transfer_impl( + deps, + &mut messages, + &sender, + &sender_canon, + &recipient, + &recipient_canon, + amount, + memo, + &env.block, + &distributor, + env.block.time.seconds(), + )?; + + let res = Response { + messages, + log: vec![], + data: Some(to_binary(&HandleAnswer::Transfer { status: Success })?), + }; + Ok(res) +} + +fn try_batch_transfer( + deps: DepsMut, + env: Env, + info: MessageInfo, + actions: Vec, +) -> StdResult { + let sender = info.sender; + let sender_canon = deps.api.canonical_address(&sender)?; + + let distributor = get_distributor(deps)?; + + let mut messages = vec![]; + + for action in actions { + let recipient = action.recipient; + let recipient_canon = deps.api.canonical_address(&recipient)?; + try_transfer_impl( + deps, + &mut messages, + &sender, + &sender_canon, + &recipient, + &recipient_canon, + action.amount, + action.memo, + &env.block, + &distributor, + env.block.time.seconds(), + )?; + } + + let res = Response { + messages, + log: vec![], + data: Some(to_binary(&HandleAnswer::BatchTransfer { status: Success })?), + }; + Ok(res) +} + +#[allow(clippy::too_many_arguments)] +fn try_add_receiver_api_callback( + storage: &dyn Storage, + messages: &mut Vec, + recipient: Addr, + recipient_code_hash: Option, + msg: Option, + sender: Addr, + from: Addr, + amount: Uint128, + memo: Option, +) -> StdResult<()> { + if let Some(receiver_hash) = recipient_code_hash { + let receiver_msg = Snip20ReceiveMsg::new(sender, from, amount, memo, msg); + let callback_msg = receiver_msg.into_cosmos_msg(receiver_hash, recipient)?; + + messages.push(callback_msg); + return Ok(()); + } + + let receiver_hash = get_receiver_hash(storage, &recipient); + if let Some(receiver_hash) = receiver_hash { + let receiver_hash = receiver_hash?; + let receiver_msg = Snip20ReceiveMsg::new(sender, from, amount, memo, msg); + let callback_msg = receiver_msg.into_cosmos_msg(receiver_hash, recipient)?; + + messages.push(callback_msg); + } + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn try_send_impl( + deps: DepsMut, + messages: &mut Vec, + sender: Addr, + sender_canon: &CanonicalAddr, // redundant but more efficient + recipient: Addr, + recipient_code_hash: Option, + amount: Uint128, + memo: Option, + msg: Option, + block: &shade_protocol::c_std::BlockInfo, + + distributors: &Option>, + time: u64, +) -> StdResult<()> { + let recipient_canon = deps.api.canonical_address(&recipient)?; + try_transfer_impl( + deps, + messages, + &sender, + sender_canon, + &recipient, + &recipient_canon, + amount, + memo.clone(), + block, + distributors, + time, + )?; + + try_add_receiver_api_callback( + deps.storage, + messages, + recipient, + recipient_code_hash, + msg, + sender.clone(), + sender, + amount, + memo, + )?; + + Ok(()) +} + +fn try_send( + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: Addr, + recipient_code_hash: Option, + amount: Uint128, + memo: Option, + msg: Option, +) -> StdResult { + let mut messages = vec![]; + let sender = info.sender; + let sender_canon = deps.api.canonical_address(&sender)?; + + let distributor = get_distributor(deps)?; + + try_send_impl( + deps, + &mut messages, + sender, + &sender_canon, + recipient, + recipient_code_hash, + amount, + memo, + msg, + &env.block, + &distributor, + env.block.time.seconds(), + )?; + + let res = Response { + messages, + log: vec![], + data: Some(to_binary(&HandleAnswer::Send { status: Success })?), + }; + Ok(res) +} + +fn try_batch_send( + deps: DepsMut, + env: Env, + info: MessageInfo, + actions: Vec, +) -> StdResult { + let mut messages = vec![]; + let sender = info.sender; + let sender_canon = deps.api.canonical_address(&sender)?; + + let distributor = get_distributor(deps)?; + + for action in actions { + try_send_impl( + deps, + &mut messages, + sender.clone(), + &sender_canon, + action.recipient, + action.recipient_code_hash, + action.amount, + action.memo, + action.msg, + &env.block, + &distributor, + env.block.time.seconds(), + )?; + } + + let res = Response { + messages, + log: vec![], + data: Some(to_binary(&HandleAnswer::BatchSend { status: Success })?), + }; + Ok(res) +} + +fn try_register_receive( + deps: DepsMut, + env: Env, + info: MessageInfo, + code_hash: String, +) -> StdResult { + set_receiver_hash(deps.storage, &info.sender, code_hash); + let res = Response { + messages: vec![], + log: vec![log("register_status", "success")], + data: Some(to_binary(&HandleAnswer::RegisterReceive { + status: Success, + })?), + }; + Ok(res) +} + +fn insufficient_allowance(allowance: u128, required: u128) -> StdError { + StdError::generic_err(format!( + "insufficient allowance: allowance={}, required={}", + allowance, required + )) +} + +fn use_allowance( + storage: &mut dyn Storage, + env: &Env, + owner: &CanonicalAddr, + spender: &CanonicalAddr, + amount: u128, +) -> StdResult<()> { + let mut allowance = read_allowance(storage, owner, spender)?; + + if allowance.is_expired_at(&env.block) { + return Err(insufficient_allowance(0, amount)); + } + if let Some(new_allowance) = allowance.amount.checked_sub(amount) { + allowance.amount = new_allowance; + } else { + return Err(insufficient_allowance(allowance.amount, amount)); + } + + write_allowance(storage, owner, spender, allowance)?; + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn try_transfer_from_impl( + deps: DepsMut, + env: &Env, + spender: &Addr, + spender_canon: &CanonicalAddr, + owner: &Addr, + owner_canon: &CanonicalAddr, + recipient: &Addr, + recipient_canon: &CanonicalAddr, + amount: Uint128, + memo: Option, + + distributors: &Option>, + time: u64, +) -> StdResult<()> { + // Verify that this transfer is allowed + if let Some(distributors) = distributors { + if !distributors.contains(spender) + && !distributors.contains(owner) + && !distributors.contains(recipient) + { + return Err(StdError::generic_err("unauthorized")); + } + } + + let raw_amount = amount.u128(); + + use_allowance( + deps.storage, + env, + owner_canon, + spender_canon, + raw_amount, + )?; + + perform_transfer( + deps.storage, + owner, + owner_canon, + recipient, + recipient_canon, + amount, + time, + )?; + + let symbol = Config::from_storage(deps.storage).constants()?.symbol; + + store_transfer( + deps.storage, + owner_canon, + spender_canon, + recipient_canon, + amount, + symbol, + memo, + &env.block, + )?; + + Ok(()) +} + +fn try_transfer_from( + deps: DepsMut, + env: &Env, + owner: &Addr, + recipient: &Addr, + amount: Uint128, + memo: Option, +) -> StdResult { + let spender = &info.sender; + let spender_canon = deps.api.canonical_address(spender)?; + let owner_canon = deps.api.canonical_address(owner)?; + let recipient_canon = deps.api.canonical_address(recipient)?; + try_transfer_from_impl( + deps, + env, + spender, + &spender_canon, + owner, + &owner_canon, + recipient, + &recipient_canon, + amount, + memo, + &get_distributor(deps)?, + env.block.time.seconds(), + )?; + + let res = Response { + messages: vec![], + log: vec![], + data: Some(to_binary(&HandleAnswer::TransferFrom { status: Success })?), + }; + Ok(res) +} + +fn try_batch_transfer_from( + deps: DepsMut, + env: &Env, + actions: Vec, +) -> StdResult { + let spender = &info.sender; + let spender_canon = deps.api.canonical_address(spender)?; + + let distributor = get_distributor(deps)?; + + for action in actions { + let owner_canon = deps.api.canonical_address(&action.owner)?; + let recipient_canon = deps.api.canonical_address(&action.recipient)?; + try_transfer_from_impl( + deps, + env, + spender, + &spender_canon, + &action.owner, + &owner_canon, + &action.recipient, + &recipient_canon, + action.amount, + action.memo, + &distributor, + env.block.time.seconds(), + )?; + } + + let res = Response { + messages: vec![], + log: vec![], + data: Some(to_binary(&HandleAnswer::BatchTransferFrom { + status: Success, + })?), + }; + Ok(res) +} + +#[allow(clippy::too_many_arguments)] +fn try_send_from_impl( + deps: DepsMut, + env: Env, + info: MessageInfo, + messages: &mut Vec, + spender: &Addr, + spender_canon: &CanonicalAddr, // redundant but more efficient + owner: Addr, + recipient: Addr, + recipient_code_hash: Option, + amount: Uint128, + memo: Option, + msg: Option, + + distributors: &Option>, +) -> StdResult<()> { + let owner_canon = deps.api.canonical_address(&owner)?; + let recipient_canon = deps.api.canonical_address(&recipient)?; + try_transfer_from_impl( + deps, + &env, + spender, + spender_canon, + &owner, + &owner_canon, + &recipient, + &recipient_canon, + amount, + memo.clone(), + distributors, + env.block.time.seconds(), + )?; + + try_add_receiver_api_callback( + deps.storage, + messages, + recipient, + recipient_code_hash, + msg, + info.sender, + owner, + amount, + memo, + )?; + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn try_send_from( + deps: DepsMut, + env: Env, + info: MessageInfo, + owner: Addr, + recipient: Addr, + recipient_code_hash: Option, + amount: Uint128, + memo: Option, + msg: Option, +) -> StdResult { + let spender = &info.sender.clone(); + let spender_canon = deps.api.canonical_address(spender)?; + + let mut messages = vec![]; + try_send_from_impl( + deps, + env, + &mut messages, + spender, + &spender_canon, + owner, + recipient, + recipient_code_hash, + amount, + memo, + msg, + &get_distributor(deps)?, + )?; + + let res = Response { + messages, + log: vec![], + data: Some(to_binary(&HandleAnswer::SendFrom { status: Success })?), + }; + Ok(res) +} + +fn try_batch_send_from( + deps: DepsMut, + env: Env, + info: MessageInfo, + actions: Vec, +) -> StdResult { + let spender = &info.sender; + let spender_canon = deps.api.canonical_address(spender)?; + let mut messages = vec![]; + + let distributor = get_distributor(deps)?; + + for action in actions { + try_send_from_impl( + deps, + env.clone(), + &mut messages, + spender, + &spender_canon, + action.owner, + action.recipient, + action.recipient_code_hash, + action.amount, + action.memo, + action.msg, + &distributor, + )?; + } + + let res = Response { + messages, + log: vec![], + data: Some(to_binary(&HandleAnswer::BatchSendFrom { status: Success })?), + }; + Ok(res) +} + +fn try_increase_allowance( + deps: DepsMut, + env: Env, + info: MessageInfo, + spender: Addr, + amount: Uint128, + expiration: Option, +) -> StdResult { + let owner_address = deps.api.canonical_address(&info.sender)?; + let spender_address = deps.api.canonical_address(&spender)?; + + let mut allowance = read_allowance(deps.storage, &owner_address, &spender_address)?; + + // If the previous allowance has expired, reset the allowance. + // Without this users can take advantage of an expired allowance given to + // them long ago. + if allowance.is_expired_at(&env.block) { + allowance.amount = amount.u128(); + allowance.expiration = None; + } else { + allowance.amount = allowance.amount.saturating_add(amount.u128()); + } + + if expiration.is_some() { + allowance.expiration = expiration; + } + let new_amount = allowance.amount; + write_allowance( + deps.storage, + &owner_address, + &spender_address, + allowance, + )?; + + let res = Response { + messages: vec![], + log: vec![], + data: Some(to_binary(&HandleAnswer::IncreaseAllowance { + owner: info.sender, + spender, + allowance: Uint128::new(new_amount), + })?), + }; + Ok(res) +} + +fn try_decrease_allowance( + deps: DepsMut, + env: Env, + info: MessageInfo, + spender: Addr, + amount: Uint128, + expiration: Option, +) -> StdResult { + let owner_address = deps.api.canonical_address(&info.sender)?; + let spender_address = deps.api.canonical_address(&spender)?; + + let mut allowance = read_allowance(deps.storage, &owner_address, &spender_address)?; + + // If the previous allowance has expired, reset the allowance. + // Without this users can take advantage of an expired allowance given to + // them long ago. + if allowance.is_expired_at(&env.block) { + allowance.amount = 0; + allowance.expiration = None; + } else { + allowance.amount = allowance.amount.saturating_sub(amount.u128()); + } + + if expiration.is_some() { + allowance.expiration = expiration; + } + let new_amount = allowance.amount; + write_allowance( + deps.storage, + &owner_address, + &spender_address, + allowance, + )?; + + let res = Response { + messages: vec![], + log: vec![], + data: Some(to_binary(&HandleAnswer::DecreaseAllowance { + owner: info.sender, + spender, + allowance: Uint128::new(new_amount), + })?), + }; + Ok(res) +} + +fn perform_transfer( + store: &mut T, + from: &Addr, + from_canon: &CanonicalAddr, + to: &Addr, + to_canon: &CanonicalAddr, + amount: Uint128, + time: u64, +) -> StdResult<()> { + let mut balances = Balances::from_storage(store); + + let mut from_balance = balances.balance(from_canon); + let from_tokens = from_balance; + + if let Some(new_from_balance) = from_balance.checked_sub(amount.u128()) { + from_balance = new_from_balance; + } else { + return Err(StdError::generic_err(format!( + "insufficient funds: balance={}, required={}", + from_balance, amount + ))); + } + balances.set_account_balance(from_canon, from_balance); + + let mut to_balance = balances.balance(to_canon); + + to_balance = to_balance.checked_add(amount.u128()).ok_or_else(|| { + StdError::generic_err("This tx will literally make them too rich. Try transferring less") + })?; + balances.set_account_balance(to_canon, to_balance); + + // Transfer shares + let total_tokens = TotalTokens::load(store)?; + let total_shares = TotalShares::load(store)?; + + let config = StakeConfig::load(store)?; + + // calculate shares per token + let transfer_shares = shares_per_token(&config, &amount, &total_tokens.0, &total_shares.0)?; + + // move shares from one user to another + let mut from_shares = UserShares::load(store, from.as_str().as_bytes())?; + + from_shares.0 = from_shares.0.checked_sub(transfer_shares)?; + from_shares.save(store, from.as_str().as_bytes())?; + + let mut to_shares = + UserShares::may_load(store, to.as_str().as_bytes())?.unwrap_or(UserShares(Uint256::zero())); + to_shares.0 += transfer_shares; + to_shares.save(store, to.as_str().as_bytes())?; + + // check for what should be removed from the queue + let wrapped_amount = amount; + + // Update from cooldown + remove_from_cooldown(store, from, Uint128::new(from_tokens), wrapped_amount, time)?; + + // Update to cooldown + { + let mut to_cooldown = + UserCooldown::may_load(store, to.as_str().as_bytes())?.unwrap_or(UserCooldown { + total: Uint128::zero(), + queue: VecQueue(vec![]), + }); + // try to remove items that have already passed + to_cooldown.update(time); + // add the new cooldown + to_cooldown.add_cooldown(Cooldown { + amount: wrapped_amount, + release: time + StakeConfig::load(store)?.unbond_time, + }); + to_cooldown.save(store, to.as_str().as_bytes())?; + } + + Ok(()) +} + +fn revoke_permit( + deps: DepsMut, + env: Env, + info: MessageInfo, + permit_name: String, +) -> StdResult { + RevokedPermits::revoke_permit( + deps.storage, + PREFIX_REVOKED_PERMITS, + &info.sender, + &permit_name, + ); + + Ok(Response::new().set_data(to_binary(&HandleAnswer::RevokePermit { status: Success })?)) +} + +fn is_admin(config: &Config, account: &Addr) -> StdResult { + let consts = config.constants()?; + if &consts.admin != account { + return Ok(false); + } + + Ok(true) +} + +pub fn check_if_admin(config: &Config, account: &Addr) -> StdResult<()> { + if !is_admin(config, account)? { + return Err(StdError::generic_err( + "This is an admin command. Admin commands can only be run from admin address", + )); + } + + Ok(()) +} + +fn is_valid_name(name: &str) -> bool { + let len = name.len(); + (3..=30).contains(&len) +} + +fn is_valid_symbol(symbol: &str) -> bool { + let len = symbol.len(); + let len_is_valid = (3..=6).contains(&len); + + len_is_valid && symbol.bytes().all(|byte| (b'A'..=b'Z').contains(&byte)) +} + +// pub fn migrate( +// _deps: DepsMut, +// _env: Env, +// _msg: MigrateMsg, +// ) -> StdResult { +// Ok(MigrateResponse::default()) +// } + +#[cfg(test)] +mod staking_tests { + use super::*; + use crate::msg::{InitConfig, ResponseStatus}; + use shade_protocol::c_std::Uint256; + use shade_protocol::c_std::{ + from_binary, + testing::*, + BlockInfo, + ContractInfo, + MessageInfo, + Binary, + WasmMsg, + }; + use shade_protocol::{ + contract_interfaces::staking::snip20_staking::ReceiveType, + utils::asset::Contract, + }; + use std::any::Any; + + fn init_helper_staking() -> ( + StdResult, + Extern, + ) { + let mut deps = mock_dependencies(20, &[]); + let env = mock_env("instantiator", &[]); + let init_msg = InstantiateMsg { + name: "sec-sec".to_string(), + admin: Some(Addr::unchecked("admin".to_string())), + symbol: "SECSEC".to_string(), + decimals: Some(8), + share_decimals: 18, + prng_seed: Binary::from("lolz fun yay".as_bytes()), + config: None, + unbond_time: 10, + staked_token: Contract { + address: Addr::unchecked("token".to_string()), + code_hash: "hash".to_string(), + }, + treasury: Some(Addr::unchecked("treasury".to_string())), + treasury_code_hash: None, + limit_transfer: true, + distributors: Some(vec![Addr::unchecked("distributor".to_string())]), + }; + + (instantiate(&mut deps, env, info, init_msg), deps) + } + + // Handle tests + #[test] + fn test_handle_update_stake_config() { + let (init_result, mut deps) = init_helper_staking(); + + let handle_msg = ExecuteMsg::UpdateStakeConfig { + unbond_time: Some(100), + disable_treasury: true, + treasury: None, + padding: None, + }; + // Check that only admins can interact + let handle_result = execute(&mut deps, mock_env("not_admin", &[]), handle_msg.clone()); + assert!(handle_result.is_err()); + let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg); + assert!(handle_result.is_ok()); + + let query_balance_msg = QueryMsg::StakeConfig {}; + + let query_response = query(&deps, query_balance_msg).unwrap(); + let config = match from_binary(&query_response).unwrap() { + QueryAnswer::StakedConfig { config } => config, + _ => panic!("Unexpected result from query"), + }; + + assert_eq!(config.treasury, None); + assert_eq!(config.unbond_time, 100); + assert_eq!(config.decimal_difference, 10); + } + + fn new_staked_account( + deps: &mut Extern, + acc: &str, + pwd: &str, + stake: Uint128, + ) { + let handle_msg = ExecuteMsg::Receive { + sender: Addr(acc.to_string()), + from: Default::default(), + amount: stake, + msg: Some(to_binary(&ReceiveType::Bond { use_from: None }).unwrap()), + memo: None, + padding: None, + }; + // Bond tokens + let handle_result = execute(deps, mock_env("token", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + let handle_msg = ExecuteMsg::SetViewingKey { + key: pwd.to_string(), + padding: None, + }; + let handle_result = execute(deps, mock_env(acc, &[]), handle_msg.clone()); + } + + fn check_staked_state( + deps: &Extern, + expected_tokens: Uint128, + expected_shares: Uint256, + ) { + let query_balance_msg = QueryMsg::TotalStaked {}; + + let query_response = query(&deps, query_balance_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::TotalStaked { shares, tokens } => { + assert_eq!(tokens, expected_tokens); + assert_eq!(shares, expected_shares) + } + _ => panic!("Unexpected result from query"), + }; + } + + #[test] + fn test_handle_receive_bonding() { + let (init_result, mut deps) = init_helper_staking(); + + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked("foo".to_string()), + from: Default::default(), + amount: Uint128::new(100 * 10u128.pow(8)), + msg: Some(to_binary(&ReceiveType::Bond { use_from: None }).unwrap()), + memo: None, + padding: None, + }; + // Bond tokens with unsupported token + let handle_result = execute(&mut deps, mock_env("not_token", &[]), handle_msg.clone()); + assert!(handle_result.is_err()); + // Bond tokens + let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + let handle_msg = ExecuteMsg::SetViewingKey { + key: "key".to_string(), + padding: None, + }; + // Bond tokens with unsupported token + let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); + + check_staked_state( + &deps, + Uint128::new(100 * 10u128.pow(8)), + Uint256::from(100 * 10u128.pow(18)), + ); + + new_staked_account(&mut deps, "bar", "key", Uint128::new(100 * 10u128.pow(8))); + // Query user stake + let query_balance_msg = QueryMsg::Staked { + address: Addr::unchecked("bar".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_balance_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + .. + } => { + assert_eq!(tokens, Uint128::new(100 * 10u128.pow(8))); + assert_eq!(shares, Uint256::from(100 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::zero()); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, None); + } + _ => panic!("Unexpected result from query"), + }; + check_staked_state( + &deps, + Uint128::new(200 * 10u128.pow(8)), + Uint256::from(200 * 10u128.pow(18)), + ); + } + + #[test] + fn test_handle_unbond() { + let (init_result, mut deps) = init_helper_staking(); + + new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); + + // Query unbonding queue + let query_msg = QueryMsg::Unbonding {}; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Unbonding { total } => { + assert_eq!(total, Uint128::zero()); + } + _ => panic!("Unexpected result from query"), + }; + + // Unbond more than allowed + let handle_msg = ExecuteMsg::Unbond { + amount: Uint128::new(1000 * 10u128.pow(8)), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); + assert!(handle_result.is_err()); + + // Unbond + let handle_msg = ExecuteMsg::Unbond { + amount: Uint128::new(50 * 10u128.pow(8)), + padding: None, + }; + // Set time for ease of prediction + let mut env = mock_env("foo", &[]); + env.block.time.seconds() = 10; + let handle_result = execute(&mut deps, env, info, handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Query unbonding queue + let query_msg = QueryMsg::Unbonding {}; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Unbonding { total } => { + assert_eq!(total, Uint128::new(50 * 10u128.pow(8))); + } + _ => panic!("Unexpected result from query"), + }; + + // Query unbonding queue + let query_msg = QueryMsg::Unfunded { start: 0, total: 1 }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Unfunded { total } => { + assert_eq!(total, Uint128::new(50 * 10u128.pow(8))); + } + _ => panic!("Unexpected result from query"), + }; + + // Query user stake + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("foo".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + .. + } => { + assert_eq!(tokens, Uint128::new(50 * 10u128.pow(8))); + assert_eq!(shares, Uint256::from(50 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::zero()); + assert_eq!(unbonding, Uint128::new(50 * 10u128.pow(8))); + assert_eq!(unbonded, None); + } + _ => panic!("Unexpected result from query"), + }; + check_staked_state( + &deps, + Uint128::new(50 * 10u128.pow(8)), + Uint256::from(50 * 10u128.pow(18)), + ); + } + + #[test] + fn test_handle_fund_unbond() { + let (init_result, mut deps) = init_helper_staking(); + + new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); + + // Bond some amount + // Unbond + let handle_msg = ExecuteMsg::Unbond { + amount: Uint128::new(50 * 10u128.pow(8)), + padding: None, + }; + // Set time for ease of prediction + let mut env = mock_env("foo", &[]); + env.block.time.seconds() = 10; + let handle_result = execute(&mut deps, env, info, handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Query unbonding queue + let query_msg = QueryMsg::Unfunded { start: 0, total: 1 }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Unfunded { total } => { + assert_eq!(total, Uint128::new(50 * 10u128.pow(8))); + } + _ => panic!("Unexpected result from query"), + }; + + // Fund half the unbond + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked("treasury".to_string()), + from: Default::default(), + amount: Uint128::new(25 * 10u128.pow(8)), + msg: Some(to_binary(&ReceiveType::Unbond).unwrap()), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Query unbonding queue + let query_msg = QueryMsg::Unfunded { start: 0, total: 1 }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Unfunded { total } => { + assert_eq!(total, Uint128::new(25 * 10u128.pow(8))); + } + _ => panic!("Unexpected result from query"), + }; + + // Unbond in the middle of funding + let handle_msg = ExecuteMsg::Unbond { + amount: Uint128::new(25 * 10u128.pow(8)), + padding: None, + }; + // Set time for ease of prediction + let mut env = mock_env("foo", &[]); + env.block.time.seconds() = 10; + let handle_result = execute(&mut deps, env, info, handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Query unbonding queue + let query_msg = QueryMsg::Unfunded { start: 0, total: 1 }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Unfunded { total } => { + assert_eq!(total, Uint128::new(50 * 10u128.pow(8))); + } + _ => panic!("Unexpected result from query"), + }; + + // Overflow unbond + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked("treasury".to_string()), + from: Default::default(), + amount: Uint128::new(500 * 10u128.pow(8)), + msg: Some(to_binary(&ReceiveType::Unbond).unwrap()), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Query unbonding queue + let query_msg = QueryMsg::Unfunded { start: 0, total: 1 }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Unfunded { total } => { + assert_eq!(total, Uint128::zero()); + } + _ => panic!("Unexpected result from query"), + }; + } + + #[test] + fn test_handle_claim_unbond() { + let (init_result, mut deps) = init_helper_staking(); + + new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); + + // Bond some amount + // Unbond + let handle_msg = ExecuteMsg::Unbond { + amount: Uint128::new(25 * 10u128.pow(8)), + padding: None, + }; + // Set time for ease of prediction + let mut env = mock_env("foo", &[]); + env.block.time.seconds() = 0; + let handle_result = execute(&mut deps, env, info, handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Fund the unbond + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked("treasury".to_string()), + from: Default::default(), + amount: Uint128::new(25 * 10u128.pow(8)), + msg: Some(to_binary(&ReceiveType::Unbond).unwrap()), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Query user stake + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("foo".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + .. + } => { + assert_eq!(tokens, Uint128::new(75 * 10u128.pow(8))); + assert_eq!(shares, Uint256::from(75 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::zero()); + assert_eq!(unbonding, Uint128::new(25 * 10u128.pow(8))); + assert_eq!(unbonded, None); + } + _ => panic!("Unexpected result from query"), + }; + + // Try to claim when its funded but the date hasn't been reached + let handle_msg = ExecuteMsg::ClaimUnbond { padding: None }; + let mut env = mock_env("foo", &[]); + env.block.time.seconds() = 0; + let handle_result = execute(&mut deps, env, info, handle_msg.clone()); + assert!(handle_result.is_err()); + + // Query user stake + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("foo".to_string()), + key: "key".to_string(), + time: Some(10), + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + .. + } => { + assert_eq!(tokens, Uint128::new(75 * 10u128.pow(8))); + assert_eq!(shares, Uint256::from(75 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::zero()); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, Some(Uint128::new(25 * 10u128.pow(8)))); + } + _ => panic!("Unexpected result from query"), + }; + + // Claim + let handle_msg = ExecuteMsg::ClaimUnbond { padding: None }; + let mut env = mock_env("foo", &[]); + env.block.time.seconds() = 11; + let handle_result = execute(&mut deps, env, info, handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Query user stake + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("foo".to_string()), + key: "key".to_string(), + time: Some(10), + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + .. + } => { + assert_eq!(tokens, Uint128::new(75 * 10u128.pow(8))); + assert_eq!(shares, Uint256::from(75 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::zero()); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, Some(Uint128::zero())); + } + _ => panic!("Unexpected result from query"), + }; + + // Try to claim when its not funded and the date has been reached + let handle_msg = ExecuteMsg::Unbond { + amount: Uint128::new(25 * 10u128.pow(8)), + padding: None, + }; + // Set time for ease of prediction + let mut env = mock_env("foo", &[]); + env.block.time.seconds() = 0; + let handle_result = execute(&mut deps, env, info, handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Claim + let handle_msg = ExecuteMsg::ClaimUnbond { padding: None }; + let mut env = mock_env("foo", &[]); + env.block.time.seconds() = 11; + let handle_result = execute(&mut deps, env, info, handle_msg.clone()); + assert!(handle_result.is_err()); + } + + #[test] + fn test_handle_fund_and_claim_rewards() { + let (init_result, mut deps) = init_helper_staking(); + + // Foo should get 2x more rewards than bar + new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); + new_staked_account(&mut deps, "bar", "key", Uint128::new(50 * 10u128.pow(8))); + + // Claim rewards + let handle_msg = ExecuteMsg::ClaimRewards { padding: None }; + + let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); + assert!(handle_result.is_err()); + + // Add rewards; foo should get 50 tkn and bar 25 + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked("treasury".to_string()), + from: Default::default(), + amount: Uint128::new(75 * 10u128.pow(8)), + msg: Some(to_binary(&ReceiveType::Reward).unwrap()), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Query user stake + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("foo".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + .. + } => { + assert_eq!(tokens, Uint128::new(100 * 10u128.pow(8))); + assert_eq!(shares, Uint256::from(100 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::new(50 * 10u128.pow(8))); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, None); + } + _ => panic!("Unexpected result from query"), + }; + + // Query user stake + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("bar".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + .. + } => { + assert_eq!(tokens, Uint128::new(50 * 10u128.pow(8))); + assert_eq!(shares, Uint256::from(50 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::new(25 * 10u128.pow(8))); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, None); + } + _ => panic!("Unexpected result from query"), + }; + + // Total tokens should be total staked plus the rewards + check_staked_state( + &deps, + Uint128::new(225 * 10u128.pow(8)), + Uint256::from(150 * 10u128.pow(18)), + ); + + // Claim rewards + let handle_msg = ExecuteMsg::ClaimRewards { padding: None }; + + let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("foo".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + .. + } => { + assert_eq!(tokens, Uint128::new(100 * 10u128.pow(8))); + assert!(shares < Uint256::from(100 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::zero()); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, None); + } + _ => panic!("Unexpected result from query"), + }; + } + + #[test] + fn test_handle_stake_rewards() { + let (init_result, mut deps) = init_helper_staking(); + + new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); + + // Add rewards + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked("treasury".to_string()), + from: Default::default(), + amount: Uint128::new(50 * 10u128.pow(8)), + msg: Some(to_binary(&ReceiveType::Reward).unwrap()), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Check account to confirm it works + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("foo".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + .. + } => { + assert_eq!(tokens, Uint128::new(100 * 10u128.pow(8))); + assert_eq!(shares, Uint256::from(100 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::new(50 * 10u128.pow(8))); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, None); + } + _ => panic!("Unexpected result from query"), + }; + + let handle_msg = ExecuteMsg::StakeRewards { padding: None }; + let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("foo".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + .. + } => { + assert_eq!(tokens, Uint128::new(150 * 10u128.pow(8))); + assert_eq!(shares, Uint256::from(100 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::zero()); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, None); + } + _ => panic!("Unexpected result from query"), + }; + } + + #[test] + fn test_handle_unbond_with_rewards() { + let (init_result, mut deps) = init_helper_staking(); + + // Foo should get 2x more rewards than bar + new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); + new_staked_account(&mut deps, "bar", "key", Uint128::new(50 * 10u128.pow(8))); + + // Add rewards; foo should get 50 tkn and bar 25 + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked("treasury".to_string()), + from: Default::default(), + amount: Uint128::new(75 * 10u128.pow(8)), + msg: Some(to_binary(&ReceiveType::Reward).unwrap()), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Query user stake + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("foo".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + .. + } => { + assert_eq!(tokens, Uint128::new(100 * 10u128.pow(8))); + assert_eq!(shares, Uint256::from(100 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::new(50 * 10u128.pow(8))); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, None); + } + _ => panic!("Unexpected result from query"), + }; + + // Query user stake + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("bar".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + .. + } => { + assert_eq!(tokens, Uint128::new(50 * 10u128.pow(8))); + assert_eq!(shares, Uint256::from(50 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::new(25 * 10u128.pow(8))); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, None); + } + _ => panic!("Unexpected result from query"), + }; + + // Total tokens should be total staked plus the rewards + check_staked_state( + &deps, + Uint128::new(225 * 10u128.pow(8)), + Uint256::from(150 * 10u128.pow(18)), + ); + + // Unbond more than allowed + let handle_msg = ExecuteMsg::Unbond { + amount: Uint128::new(50 * 10u128.pow(8)), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("foo".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + .. + } => { + assert_eq!(tokens, Uint128::new(50 * 10u128.pow(8))); + assert!(shares < Uint256::from(50 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::zero()); + assert_eq!(unbonding, Uint128::new(50 * 10u128.pow(8))); + assert_eq!(unbonded, None); + } + _ => panic!("Unexpected result from query"), + }; + } + + #[test] + fn test_handle_set_distributors_status() { + let (init_result, mut deps) = init_helper_staking(); + new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); + + let handle_msg = ExecuteMsg::SetDistributorsStatus { + enabled: false, + padding: None, + }; + + let handle_result = execute(&mut deps, mock_env("other", &[]), handle_msg.clone()); + assert!(handle_result.is_err()); + + let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + } + + #[test] + fn test_handle_add_distributors() { + let (init_result, mut deps) = init_helper_staking(); + + let query_msg = QueryMsg::Distributors {}; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Distributors { distributors } => { + assert_eq!(distributors.unwrap().len(), 1); + } + _ => panic!("Unexpected result from query"), + }; + + let handle_msg = ExecuteMsg::AddDistributors { + distributors: vec![Addr::unchecked("new_distrib".to_string())], + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("not_admin", &[]), handle_msg.clone()); + assert!(handle_result.is_err()); + + let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + let query_msg = QueryMsg::Distributors {}; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Distributors { distributors } => { + let distrib = distributors.unwrap(); + assert_eq!(distrib.len(), 2); + assert_eq!(distrib[1], Addr::unchecked("new_distrib".to_string())); + } + _ => panic!("Unexpected result from query"), + }; + } + + #[test] + fn test_handle_set_distributors() { + let (init_result, mut deps) = init_helper_staking(); + + let handle_msg = ExecuteMsg::SetDistributors { + distributors: vec![Addr::unchecked("new_distrib".to_string())], + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("not_admin", &[]), handle_msg.clone()); + assert!(handle_result.is_err()); + + let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + let query_msg = QueryMsg::Distributors {}; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Distributors { distributors } => { + let distrib = distributors.unwrap(); + assert_eq!(distrib.len(), 1); + assert_eq!(distrib[0], Addr::unchecked("new_distrib".to_string())); + } + _ => panic!("Unexpected result from query"), + }; + } + + #[test] + fn test_send_with_distributors() { + let (init_result, mut deps) = init_helper_staking(); + new_staked_account( + &mut deps, + "sender", + "key", + Uint128::new(100 * 10u128.pow(8)), + ); + new_staked_account( + &mut deps, + "distrib", + "key", + Uint128::new(100 * 10u128.pow(8)), + ); + new_staked_account( + &mut deps, + "not_distrib", + "key", + Uint128::new(100 * 10u128.pow(8)), + ); + + let handle_msg = ExecuteMsg::SetDistributors { + distributors: vec![Addr::unchecked("distrib".to_string())], + padding: None, + }; + + let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Distrib is sender + let handle_msg = ExecuteMsg::Send { + recipient: Addr::unchecked("someone".to_string()), + recipient_code_hash: None, + amount: Uint128::new(10 * 10u128.pow(8)), + msg: None, + memo: None, + padding: None, + }; + + let handle_result = execute(&mut deps, mock_env("not_distrib", &[]), handle_msg.clone()); + assert!(handle_result.is_err()); + + let handle_result = execute(&mut deps, mock_env("distrib", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Send to distrib + let handle_msg = ExecuteMsg::Send { + recipient: Addr::unchecked("distrib".to_string()), + recipient_code_hash: None, + amount: Uint128::new(10 * 10u128.pow(8)), + msg: None, + memo: None, + padding: None, + }; + + let handle_result = execute(&mut deps, mock_env("sender", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + let handle_msg = ExecuteMsg::Send { + recipient: Addr::unchecked("not_distrib".to_string()), + recipient_code_hash: None, + amount: Uint128::new(10 * 10u128.pow(8)), + msg: None, + memo: None, + padding: None, + }; + + let handle_result = execute(&mut deps, mock_env("sender", &[]), handle_msg.clone()); + assert!(handle_result.is_err()); + } + + #[test] + fn test_handle_send_with_rewards() { + let (init_result, mut deps) = init_helper_staking(); + new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); + + let handle_msg = ExecuteMsg::SetDistributorsStatus { + enabled: false, + padding: None, + }; + + let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Add rewards + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked("treasury".to_string()), + from: Default::default(), + amount: Uint128::new(50 * 10u128.pow(8)), + msg: Some(to_binary(&ReceiveType::Reward).unwrap()), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Check account to confirm it works + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("foo".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + .. + } => { + assert_eq!(tokens, Uint128::new(100 * 10u128.pow(8))); + assert_eq!(shares, Uint256::from(100 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::new(50 * 10u128.pow(8))); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, None); + } + _ => panic!("Unexpected result from query"), + }; + + // Send msg + let handle_msg = ExecuteMsg::Send { + recipient: Addr::unchecked("other".to_string()), + recipient_code_hash: None, + amount: Uint128::new(10 * 10u128.pow(8)), + msg: None, + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Check that it was autoclaimed + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("foo".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + .. + } => { + assert_eq!(tokens, Uint128::new(90 * 10u128.pow(8))); + assert!(shares < Uint256::from(90 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::zero()); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, None); + } + _ => panic!("Unexpected result from query"), + }; + } + + #[test] + fn test_handle_send_cooldown() { + let (init_result, mut deps) = init_helper_staking(); + new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); + new_staked_account(&mut deps, "bar", "key", Uint128::new(100 * 10u128.pow(8))); + + let handle_msg = ExecuteMsg::SetDistributorsStatus { + enabled: false, + padding: None, + }; + + let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Send msg + let handle_msg = ExecuteMsg::Send { + recipient: Addr::unchecked("bar".to_string()), + recipient_code_hash: None, + amount: Uint128::new(10 * 10u128.pow(8)), + msg: None, + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Check that it was autoclaimed + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("bar".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + cooldown, + .. + } => { + assert_eq!(tokens, Uint128::new(110 * 10u128.pow(8))); + assert_eq!(shares, Uint256::from(110 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::zero()); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, None); + assert_eq!(cooldown.0.len(), 1); + assert_eq!(cooldown.0[0].amount, Uint128::new(10 * 10u128.pow(8))); + } + _ => panic!("Unexpected result from query"), + }; + + // Send msg + let handle_msg = ExecuteMsg::Send { + recipient: Addr::unchecked("foo".to_string()), + recipient_code_hash: None, + amount: Uint128::new(100 * 10u128.pow(8)), + msg: None, + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bar", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Check that it was autoclaimed + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("bar".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + cooldown, + .. + } => { + assert_eq!(tokens, Uint128::new(10 * 10u128.pow(8))); + assert_eq!(shares, Uint256::from(10 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::zero()); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, None); + assert_eq!(cooldown.0.len(), 1); + assert_eq!(cooldown.0[0].amount, Uint128::new(10 * 10u128.pow(8))); + } + _ => panic!("Unexpected result from query"), + }; + + // Send msg + let handle_msg = ExecuteMsg::Send { + recipient: Addr::unchecked("foo".to_string()), + recipient_code_hash: None, + amount: Uint128::new(10 * 10u128.pow(8)), + msg: None, + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bar", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Check that it was autoclaimed + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("bar".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + cooldown, + .. + } => { + assert_eq!(tokens, Uint128::zero()); + assert_eq!(shares, Uint256::zero()); + assert_eq!(pending_rewards, Uint128::zero()); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, None); + assert_eq!(cooldown.0.len(), 0); + } + _ => panic!("Unexpected result from query"), + }; + } + + #[test] + fn test_handle_unbond_cooldown() { + let (init_result, mut deps) = init_helper_staking(); + new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); + new_staked_account(&mut deps, "bar", "key", Uint128::new(100 * 10u128.pow(8))); + + let handle_msg = ExecuteMsg::SetDistributorsStatus { + enabled: false, + padding: None, + }; + + let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Send msg + let handle_msg = ExecuteMsg::Send { + recipient: Addr::unchecked("bar".to_string()), + recipient_code_hash: None, + amount: Uint128::new(10 * 10u128.pow(8)), + msg: None, + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Check that it was autoclaimed + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("bar".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + cooldown, + .. + } => { + assert_eq!(tokens, Uint128::new(110 * 10u128.pow(8))); + assert_eq!(shares, Uint256::from(110 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::zero()); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, None); + assert_eq!(cooldown.0.len(), 1); + assert_eq!(cooldown.0[0].amount, Uint128::new(10 * 10u128.pow(8))); + } + _ => panic!("Unexpected result from query"), + }; + + // Unbond + let handle_msg = ExecuteMsg::Unbond { + amount: Uint128::new(100 * 10u128.pow(8)), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bar", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Check that it was autoclaimed + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("bar".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + cooldown, + .. + } => { + assert_eq!(tokens, Uint128::new(10 * 10u128.pow(8))); + assert_eq!(shares, Uint256::from(10 * 10u128.pow(18))); + assert_eq!(pending_rewards, Uint128::zero()); + assert_eq!(unbonding, Uint128::new(100 * 10u128.pow(8))); + assert_eq!(unbonded, None); + assert_eq!(cooldown.0.len(), 1); + assert_eq!(cooldown.0[0].amount, Uint128::new(10 * 10u128.pow(8))); + } + _ => panic!("Unexpected result from query"), + }; + + // Unbond + let handle_msg = ExecuteMsg::Unbond { + amount: Uint128::new(10 * 10u128.pow(8)), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bar", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + // Check that it was autoclaimed + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("bar".to_string()), + key: "key".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + cooldown, + .. + } => { + assert_eq!(tokens, Uint128::zero()); + assert_eq!(shares, Uint256::zero()); + assert_eq!(pending_rewards, Uint128::zero()); + assert_eq!(unbonding, Uint128::new(110 * 10u128.pow(8))); + assert_eq!(unbonded, None); + assert_eq!(cooldown.0.len(), 0); + } + _ => panic!("Unexpected result from query"), + }; + } + + #[test] + fn test_handle_stop_bonding() { + let (init_result, mut deps) = init_helper_staking(); + new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); + + let handle_msg = ExecuteMsg::SetDistributorsStatus { + enabled: false, + padding: None, + }; + + let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + let pause_msg = ExecuteMsg::SetContractStatus { + level: ContractStatusLevel::StopBonding, + padding: None, + }; + + let handle_result = execute(&mut deps, mock_env("admin", &[]), pause_msg); + assert!(handle_result.is_ok()); + + let send_msg = ExecuteMsg::Transfer { + recipient: Addr::unchecked("account".to_string()), + amount: Uint128::new(123), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("foo", &[]), send_msg); + assert!(handle_result.is_ok()); + + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked("foo".to_string()), + from: Default::default(), + amount: Uint128::new(100 * 10u128.pow(8)), + msg: Some(to_binary(&ReceiveType::Bond { use_from: None }).unwrap()), + memo: None, + padding: None, + }; + // Bond tokens + let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); + assert!(handle_result.is_err()); + + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked("foo".to_string()), + from: Default::default(), + amount: Uint128::new(100 * 10u128.pow(8)), + msg: Some(to_binary(&ReceiveType::Reward).unwrap()), + memo: None, + padding: None, + }; + // Bond tokens + let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); + assert!(handle_result.is_err()); + + let handle_msg = ExecuteMsg::Unbond { + amount: Uint128::new(10 * 10u128.pow(8)), + padding: None, + }; + // Bond tokens + let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + } + + #[test] + fn test_handle_stop_all_but_unbond() { + let (init_result, mut deps) = init_helper_staking(); + new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); + + let handle_msg = ExecuteMsg::SetDistributorsStatus { + enabled: false, + padding: None, + }; + + let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + + let pause_msg = ExecuteMsg::SetContractStatus { + level: ContractStatusLevel::StopAllButUnbond, + padding: None, + }; + + let handle_result = execute(&mut deps, mock_env("admin", &[]), pause_msg); + assert!(handle_result.is_ok()); + + let send_msg = ExecuteMsg::Transfer { + recipient: Addr::unchecked("account".to_string()), + amount: Uint128::new(123), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("foo", &[]), send_msg); + assert!(handle_result.is_err()); + + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked("foo".to_string()), + from: Default::default(), + amount: Uint128::new(100 * 10u128.pow(8)), + msg: Some(to_binary(&ReceiveType::Bond { use_from: None }).unwrap()), + memo: None, + padding: None, + }; + // Bond tokens + let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); + assert!(handle_result.is_err()); + + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked("foo".to_string()), + from: Default::default(), + amount: Uint128::new(100 * 10u128.pow(8)), + msg: Some(to_binary(&ReceiveType::Reward).unwrap()), + memo: None, + padding: None, + }; + // Bond tokens + let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); + assert!(handle_result.is_err()); + + let handle_msg = ExecuteMsg::Unbond { + amount: Uint128::new(10 * 10u128.pow(8)), + padding: None, + }; + // Bond tokens + let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + } +} + +#[cfg(test)] +mod snip20_tests { + use super::*; + use crate::msg::{InitConfig, ResponseStatus}; + use shade_protocol::c_std::{ + from_binary, + testing::*, + BlockInfo, + Coin, + ContractInfo, + MessageInfo, + Binary, + WasmMsg, + }; + use shade_protocol::{ + contract_interfaces::staking::snip20_staking::ReceiveType, + utils::asset::Contract, + }; + use std::any::Any; + + // Helper functions + #[derive(Clone)] + struct InitBalance { + pub acc: &'static str, + pub pwd: &'static str, + pub stake: Uint128, + } + + fn new_staked_account( + deps: &mut Extern, + acc: &str, + pwd: &str, + stake: Uint128, + ) { + let handle_msg = ExecuteMsg::Receive { + sender: Addr(acc.to_string()), + from: Default::default(), + amount: stake, + msg: Some(to_binary(&ReceiveType::Bond { use_from: None }).unwrap()), + memo: None, + padding: None, + }; + // Bond tokens + let handle_result = execute(deps, mock_env("token", &[]), handle_msg.clone()); + assert!(handle_result.is_ok()); + let handle_msg = ExecuteMsg::SetViewingKey { + key: pwd.to_string(), + padding: None, + }; + let handle_result = execute(deps, mock_env(acc, &[]), handle_msg.clone()); + assert!(handle_result.is_ok()) + } + + fn init_helper( + initial_balances: Vec, + ) -> ( + StdResult, + Extern, + ) { + let mut deps = mock_dependencies(20, &[]); + let env = mock_env("instantiator", &[]); + + let init_msg = InstantiateMsg { + name: "sec-sec".to_string(), + admin: Some(Addr::unchecked("admin".to_string())), + symbol: "SECSEC".to_string(), + decimals: Some(8), + share_decimals: 18, + prng_seed: Binary::from("lolz fun yay".as_bytes()), + config: None, + unbond_time: 10, + staked_token: Contract { + address: Addr::unchecked("token".to_string()), + code_hash: "hash".to_string(), + }, + treasury: Some(Addr::unchecked("treasury".to_string())), + treasury_code_hash: None, + limit_transfer: false, + distributors: None, + }; + + let instantiate = instantiate(&mut deps, env, info, init_msg); + + for account in initial_balances.iter() { + new_staked_account(&mut deps, account.acc, account.pwd, account.stake); + } + + (instantiate, deps) + } + + fn init_helper_with_config( + initial_balances: Vec, + enable_deposit: bool, + enable_redeem: bool, + enable_mint: bool, + enable_burn: bool, + contract_bal: u128, + ) -> ( + StdResult, + Extern, + ) { + let mut deps = mock_dependencies(20, &[Coin { + denom: "uscrt".to_string(), + amount: Uint128::new(contract_bal).into(), + }]); + + let env = mock_env("instantiator", &[]); + let init_config: InitConfig = from_binary(&Binary::from( + format!( + "{{\"public_total_supply\":false, + \"enable_deposit\":{}, + \"enable_redeem\":{}, + \"enable_mint\":{}, + \"enable_burn\":{}}}", + enable_deposit, enable_redeem, enable_mint, enable_burn + ) + .as_bytes(), + )) + .unwrap(); + let init_msg = InstantiateMsg { + name: "sec-sec".to_string(), + admin: Some(Addr::unchecked("admin".to_string())), + symbol: "SECSEC".to_string(), + decimals: Some(8), + share_decimals: 18, + prng_seed: Binary::from("lolz fun yay".as_bytes()), + config: Some(init_config), + unbond_time: 10, + staked_token: Contract { + address: Addr::unchecked("token".to_string()), + code_hash: "hash".to_string(), + }, + treasury: Some(Addr::unchecked("treasury".to_string())), + treasury_code_hash: None, + limit_transfer: false, + distributors: None, + }; + + let instantiate = instantiate(&mut deps, env, info, init_msg); + + for account in initial_balances.iter() { + new_staked_account(&mut deps, account.acc, account.pwd, account.stake); + } + + (instantiate, deps) + } + + /// Will return a ViewingKey only for the first account in `initial_balances` + fn _auth_query_helper( + initial_balances: Vec, + ) -> (ViewingKey, Extern) { + let (init_result, mut deps) = init_helper(initial_balances.clone()); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let account = initial_balances[0].acc; + let create_vk_msg = ExecuteMsg::CreateViewingKey { + entropy: "42".to_string(), + padding: None, + }; + let handle_response = execute(&mut deps, mock_env(account, &[]), create_vk_msg).unwrap(); + let vk = match from_binary(&handle_response.data.unwrap()).unwrap() { + HandleAnswer::CreateViewingKey { key } => key, + _ => panic!("Unexpected result from execute"), + }; + + (vk, deps) + } + + fn extract_error_msg(error: StdResult) -> String { + match error { + Ok(response) => { + let bin_err = (&response as &dyn Any) + .downcast_ref::() + .expect("An error was expected, but no error could be extracted"); + match from_binary(bin_err).unwrap() { + QueryAnswer::ViewingKeyError { msg } => msg, + _ => panic!("Unexpected query answer"), + } + } + Err(err) => match err { + StdError::GenericErr { msg, .. } => msg, + _ => panic!("Unexpected result from instantiate"), + }, + } + } + + fn ensure_success(handle_result: Response) -> bool { + let handle_result: HandleAnswer = from_binary(&handle_result.data.unwrap()).unwrap(); + + match handle_result { + HandleAnswer::UpdateStakeConfig { status } + | HandleAnswer::Receive { status } + | HandleAnswer::Unbond { status } + | HandleAnswer::ClaimUnbond { status } + | HandleAnswer::ClaimRewards { status } + | HandleAnswer::StakeRewards { status } + | HandleAnswer::ExposeBalance { status } + | HandleAnswer::AddDistributors { status } + | HandleAnswer::SetDistributors { status } + | HandleAnswer::Transfer { status } + | HandleAnswer::Send { status } + | HandleAnswer::RegisterReceive { status } + | HandleAnswer::SetViewingKey { status } + | HandleAnswer::TransferFrom { status } + | HandleAnswer::SendFrom { status } + | HandleAnswer::ChangeAdmin { status } + | HandleAnswer::SetContractStatus { status } => { + matches!(status, ResponseStatus::Success { .. }) + } + _ => panic!( + "HandleAnswer not supported for success extraction: {:?}", + handle_result + ), + } + } + + // Init tests + + #[test] + fn test_init_sanity() { + let (init_result, deps) = init_helper(vec![InitBalance { + acc: "lebron", + pwd: "pwd", + stake: Uint128::new(5000), + }]); + + let config = ReadonlyConfig::from_storage(deps.storage); + let constants = config.constants().unwrap(); + assert_eq!(config.total_supply(), 5000); + assert_eq!(config.contract_status(), ContractStatusLevel::NormalRun); + assert_eq!(constants.name, "sec-sec".to_string()); + assert_eq!(constants.admin, Addr::unchecked("admin".to_string())); + assert_eq!(constants.symbol, "STKD-SECSEC".to_string()); + assert_eq!(constants.decimals, 8); + assert_eq!( + constants.prng_seed, + sha_256("lolz fun yay".to_owned().as_bytes()) + ); + assert_eq!(constants.total_supply_is_public, false); + } + + #[test] + fn test_init_with_config_sanity() { + let (init_result, deps) = init_helper_with_config( + vec![InitBalance { + acc: "lebron", + pwd: "pwd", + stake: Uint128::new(5000), + }], + true, + true, + true, + true, + 0, + ); + + let config = ReadonlyConfig::from_storage(deps.storage); + let constants = config.constants().unwrap(); + assert_eq!(config.total_supply(), 5000); + assert_eq!(config.contract_status(), ContractStatusLevel::NormalRun); + assert_eq!(constants.name, "sec-sec".to_string()); + assert_eq!(constants.admin, Addr::unchecked("admin".to_string())); + assert_eq!(constants.symbol, "STKD-SECSEC".to_string()); + assert_eq!(constants.decimals, 8); + assert_eq!( + constants.prng_seed, + sha_256("lolz fun yay".to_owned().as_bytes()) + ); + assert_eq!(constants.total_supply_is_public, false); + } + + #[test] + fn test_total_supply_overflow() { + let (init_result, mut deps) = init_helper(vec![InitBalance { + acc: "lebron", + pwd: "pwd", + stake: Uint128::new(u128::MAX), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let (init_result, _deps) = init_helper(vec![InitBalance { + acc: "lebron", + pwd: "pwd", + stake: Uint128::new(u128::MAX), + }]); + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked("giannis".to_string()), + from: Default::default(), + amount: Uint128::new(1), + msg: Some(to_binary(&ReceiveType::Bond { use_from: None }).unwrap()), + memo: None, + padding: None, + }; + // Bond tokens + let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); + assert!(handle_result.is_err()); + } + + #[test] + fn test_handle_transfer() { + let (init_result, mut deps) = init_helper(vec![InitBalance { + acc: "bob", + pwd: "pwd", + stake: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("bob".to_string()), + key: "pwd".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + .. + } => { + assert_eq!(tokens, Uint128::new(5000)); + assert_eq!(shares, Uint256::from(50000000000000u128)); + assert_eq!(pending_rewards, Uint128::zero()); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, None); + } + _ => panic!("Unexpected result from query"), + }; + + let query_balance_msg = QueryMsg::TotalStaked {}; + + let query_response = query(&deps, query_balance_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::TotalStaked { shares, tokens } => { + assert_eq!(tokens, Uint128::new(5000)); + assert_eq!(shares, Uint256::from(50000000000000u128)) + } + _ => panic!("Unexpected result from query"), + }; + + let handle_msg = ExecuteMsg::Transfer { + recipient: Addr::unchecked("alice".to_string()), + amount: Uint128::new(1000), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + let bob_canonical = deps + .api + .canonical_address(&Addr::unchecked("bob".to_string())) + .unwrap(); + let alice_canonical = deps + .api + .canonical_address(&Addr::unchecked("alice".to_string())) + .unwrap(); + let balances = ReadonlyBalances::from_storage(deps.storage); + assert_eq!(5000 - 1000, balances.account_amount(&bob_canonical)); + assert_eq!(1000, balances.account_amount(&alice_canonical)); + + let handle_msg = ExecuteMsg::Transfer { + recipient: Addr::unchecked("alice".to_string()), + amount: Uint128::new(10000), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient funds")); + + let query_msg = QueryMsg::Staked { + address: Addr::unchecked("bob".to_string()), + key: "pwd".to_string(), + time: None, + }; + + let query_response = query(&deps, query_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::Staked { + tokens, + shares, + pending_rewards, + unbonding, + unbonded, + .. + } => { + assert_eq!(tokens, Uint128::new(4000)); + assert_eq!(shares, Uint256::from(40000000000000u128)); + assert_eq!(pending_rewards, Uint128::zero()); + assert_eq!(unbonding, Uint128::zero()); + assert_eq!(unbonded, None); + } + _ => panic!("Unexpected result from query"), + }; + + let query_balance_msg = QueryMsg::TotalStaked {}; + + let query_response = query(&deps, query_balance_msg).unwrap(); + match from_binary(&query_response).unwrap() { + QueryAnswer::TotalStaked { shares, tokens } => { + assert_eq!(tokens, Uint128::new(5000)); + assert_eq!(shares, Uint256::from(50000000000000u128)) + } + _ => panic!("Unexpected result from query"), + }; + } + + #[test] + fn test_handle_send() { + let (init_result, mut deps) = init_helper(vec![InitBalance { + acc: "bob", + pwd: "pwd", + stake: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::RegisterReceive { + code_hash: "this_is_a_hash_of_a_code".to_string(), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("contract", &[]), handle_msg); + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + let handle_msg = ExecuteMsg::Send { + recipient: Addr::unchecked("contract".to_string()), + recipient_code_hash: None, + amount: Uint128::new(100), + memo: Some("my memo".to_string()), + padding: None, + msg: Some(to_binary("hey hey you you").unwrap()), + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + let result = handle_result.unwrap(); + assert!(ensure_success(result.clone())); + assert!( + result.messages.contains(&CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: Addr::unchecked("contract".to_string()), + callback_code_hash: "this_is_a_hash_of_a_code".to_string(), + msg: Snip20ReceiveMsg::new( + Addr::unchecked("bob".to_string()), + Addr::unchecked("bob".to_string()), + Uint128::new(100), + Some("my memo".to_string()), + Some(to_binary("hey hey you you").unwrap()) + ) + .into_binary() + .unwrap(), + send: vec![] + })) + ); + } + + #[test] + fn test_handle_register_receive() { + let (init_result, mut deps) = init_helper(vec![InitBalance { + acc: "bob", + pwd: "pwd", + stake: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::RegisterReceive { + code_hash: "this_is_a_hash_of_a_code".to_string(), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("contract", &[]), handle_msg); + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + let hash = get_receiver_hash(deps.storage, &Addr::unchecked("contract".to_string())) + .unwrap() + .unwrap(); + assert_eq!(hash, "this_is_a_hash_of_a_code".to_string()); + } + + #[test] + fn test_handle_create_viewing_key() { + let (init_result, mut deps) = init_helper(vec![InitBalance { + acc: "bob", + pwd: "pwd", + stake: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::CreateViewingKey { + entropy: "".to_string(), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + assert!( + handle_result.is_ok(), + "execute() failed: {}", + handle_result.err().unwrap() + ); + let answer: HandleAnswer = from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); + + let key = match answer { + HandleAnswer::CreateViewingKey { key } => key, + _ => panic!("NOPE"), + }; + let bob_canonical = deps + .api + .canonical_address(&Addr::unchecked("bob".to_string())) + .unwrap(); + let saved_vk = read_viewing_key(deps.storage, &bob_canonical).unwrap(); + assert!(key.check_viewing_key(saved_vk.as_slice())); + } + + #[test] + fn test_handle_set_viewing_key() { + let (init_result, mut deps) = init_helper(vec![InitBalance { + acc: "bob", + pwd: "pwd", + stake: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + // Set VK + let handle_msg = ExecuteMsg::SetViewingKey { + key: "hi lol".to_string(), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + let unwrapped_result: HandleAnswer = + from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); + assert_eq!( + to_binary(&unwrapped_result).unwrap(), + to_binary(&HandleAnswer::SetViewingKey { + status: ResponseStatus::Success + }) + .unwrap(), + ); + + // Set valid VK + let actual_vk = ViewingKey("x".to_string().repeat(VIEWING_KEY_SIZE)); + let handle_msg = ExecuteMsg::SetViewingKey { + key: actual_vk.0.clone(), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + let unwrapped_result: HandleAnswer = + from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); + assert_eq!( + to_binary(&unwrapped_result).unwrap(), + to_binary(&HandleAnswer::SetViewingKey { status: Success }).unwrap(), + ); + let bob_canonical = deps + .api + .canonical_address(&Addr::unchecked("bob".to_string())) + .unwrap(); + let saved_vk = read_viewing_key(deps.storage, &bob_canonical).unwrap(); + assert!(actual_vk.check_viewing_key(&saved_vk)); + } + + #[test] + fn test_handle_transfer_from() { + let (init_result, mut deps) = init_helper(vec![InitBalance { + acc: "bob", + pwd: "pwd", + stake: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + // Transfer before allowance + let handle_msg = ExecuteMsg::TransferFrom { + owner: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("alice".to_string()), + amount: Uint128::new(2500), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("alice", &[]), handle_msg); + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + + // Transfer more than allowance + let handle_msg = ExecuteMsg::IncreaseAllowance { + spender: Addr::unchecked("alice".to_string()), + amount: Uint128::new(2000), + padding: None, + expiration: Some(1_571_797_420), + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + assert!( + handle_result.is_ok(), + "execute() failed: {}", + handle_result.err().unwrap() + ); + let handle_msg = ExecuteMsg::TransferFrom { + owner: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("alice".to_string()), + amount: Uint128::new(2500), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("alice", &[]), handle_msg); + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + + // Transfer after allowance expired + let handle_msg = ExecuteMsg::TransferFrom { + owner: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("alice".to_string()), + amount: Uint128::new(2000), + memo: None, + padding: None, + }; + let handle_result = execute( + &mut deps, + Env { + block: BlockInfo { + height: 12_345, + time: 1_571_797_420, + chain_id: "cosmos-testnet-14002".to_string(), + }, + message: MessageInfo { + sender: Addr::unchecked("bob".to_string()), + sent_funds: vec![], + }, + contract: ContractInfo { + address: Addr::unchecked(MOCK_CONTRACT_ADDR), + }, + contract_key: Some("".to_string()), + contract_code_hash: "".to_string(), + }, + handle_msg, + ); + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + + // Sanity check + let handle_msg = ExecuteMsg::TransferFrom { + owner: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("alice".to_string()), + amount: Uint128::new(2000), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("alice", &[]), handle_msg); + assert!( + handle_result.is_ok(), + "execute() failed: {}", + handle_result.err().unwrap() + ); + let bob_canonical = deps + .api + .canonical_address(&Addr::unchecked("bob".to_string())) + .unwrap(); + let alice_canonical = deps + .api + .canonical_address(&Addr::unchecked("alice".to_string())) + .unwrap(); + let bob_balance = crate::state::ReadonlyBalances::from_storage(deps.storage) + .account_amount(&bob_canonical); + let alice_balance = crate::state::ReadonlyBalances::from_storage(deps.storage) + .account_amount(&alice_canonical); + assert_eq!(bob_balance, 5000 - 2000); + assert_eq!(alice_balance, 2000); + let total_supply = ReadonlyConfig::from_storage(deps.storage).total_supply(); + assert_eq!(total_supply, 5000); + + // Second send more than allowance + let handle_msg = ExecuteMsg::TransferFrom { + owner: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("alice".to_string()), + amount: Uint128::new(1), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("alice", &[]), handle_msg); + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + } + + #[test] + fn test_handle_send_from() { + let (init_result, mut deps) = init_helper(vec![InitBalance { + acc: "bob", + pwd: "pwd", + stake: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + // Send before allowance + let handle_msg = ExecuteMsg::SendFrom { + owner: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("alice".to_string()), + recipient_code_hash: None, + amount: Uint128::new(2500), + memo: None, + msg: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("alice", &[]), handle_msg); + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + + // Send more than allowance + let handle_msg = ExecuteMsg::IncreaseAllowance { + spender: Addr::unchecked("alice".to_string()), + amount: Uint128::new(2000), + padding: None, + expiration: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + assert!( + handle_result.is_ok(), + "execute() failed: {}", + handle_result.err().unwrap() + ); + let handle_msg = ExecuteMsg::SendFrom { + owner: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("alice".to_string()), + recipient_code_hash: None, + amount: Uint128::new(2500), + memo: None, + msg: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("alice", &[]), handle_msg); + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + + // Sanity check + let handle_msg = ExecuteMsg::RegisterReceive { + code_hash: "lolz".to_string(), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("contract", &[]), handle_msg); + assert!( + handle_result.is_ok(), + "execute() failed: {}", + handle_result.err().unwrap() + ); + let send_msg = Binary::from(r#"{ "some_msg": { "some_key": "some_val" } }"#.as_bytes()); + let snip20_msg = Snip20ReceiveMsg::new( + Addr::unchecked("alice".to_string()), + Addr::unchecked("bob".to_string()), + Uint128::new(2000), + Some("my memo".to_string()), + Some(send_msg.clone()), + ); + let handle_msg = ExecuteMsg::SendFrom { + owner: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("contract".to_string()), + recipient_code_hash: None, + amount: Uint128::new(2000), + memo: Some("my memo".to_string()), + msg: Some(send_msg), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("alice", &[]), handle_msg); + assert!( + handle_result.is_ok(), + "execute() failed: {}", + handle_result.err().unwrap() + ); + assert!( + handle_result.unwrap().messages.contains( + &snip20_msg + .into_cosmos_msg("lolz".to_string(), Addr::unchecked("contract".to_string())) + .unwrap() + ) + ); + let bob_canonical = deps + .api + .canonical_address(&Addr::unchecked("bob".to_string())) + .unwrap(); + let contract_canonical = deps + .api + .canonical_address(&Addr::unchecked("contract".to_string())) + .unwrap(); + let bob_balance = crate::state::ReadonlyBalances::from_storage(deps.storage) + .account_amount(&bob_canonical); + let contract_balance = crate::state::ReadonlyBalances::from_storage(deps.storage) + .account_amount(&contract_canonical); + assert_eq!(bob_balance, 5000 - 2000); + assert_eq!(contract_balance, 2000); + let total_supply = ReadonlyConfig::from_storage(deps.storage).total_supply(); + assert_eq!(total_supply, 5000); + + // Second send more than allowance + let handle_msg = ExecuteMsg::SendFrom { + owner: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("alice".to_string()), + recipient_code_hash: None, + amount: Uint128::new(1), + memo: None, + msg: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("alice", &[]), handle_msg); + let error = extract_error_msg(handle_result); + assert!(error.contains("insufficient allowance")); + } + + #[test] + fn test_handle_decrease_allowance() { + let (init_result, mut deps) = init_helper(vec![InitBalance { + acc: "bob", + pwd: "pwd", + stake: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::DecreaseAllowance { + spender: Addr::unchecked("alice".to_string()), + amount: Uint128::new(2000), + padding: None, + expiration: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + assert!( + handle_result.is_ok(), + "execute() failed: {}", + handle_result.err().unwrap() + ); + + let bob_canonical = deps + .api + .canonical_address(&Addr::unchecked("bob".to_string())) + .unwrap(); + let alice_canonical = deps + .api + .canonical_address(&Addr::unchecked("alice".to_string())) + .unwrap(); + + let allowance = read_allowance(deps.storage, &bob_canonical, &alice_canonical).unwrap(); + assert_eq!(allowance, crate::state::Allowance { + amount: 0, + expiration: None + }); + + let handle_msg = ExecuteMsg::IncreaseAllowance { + spender: Addr::unchecked("alice".to_string()), + amount: Uint128::new(2000), + padding: None, + expiration: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + assert!( + handle_result.is_ok(), + "execute() failed: {}", + handle_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::DecreaseAllowance { + spender: Addr::unchecked("alice".to_string()), + amount: Uint128::new(50), + padding: None, + expiration: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + assert!( + handle_result.is_ok(), + "execute() failed: {}", + handle_result.err().unwrap() + ); + + let allowance = read_allowance(deps.storage, &bob_canonical, &alice_canonical).unwrap(); + assert_eq!(allowance, crate::state::Allowance { + amount: 1950, + expiration: None + }); + } + + #[test] + fn test_handle_increase_allowance() { + let (init_result, mut deps) = init_helper(vec![InitBalance { + acc: "bob", + pwd: "pwd", + stake: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::IncreaseAllowance { + spender: Addr::unchecked("alice".to_string()), + amount: Uint128::new(2000), + padding: None, + expiration: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + assert!( + handle_result.is_ok(), + "execute() failed: {}", + handle_result.err().unwrap() + ); + + let bob_canonical = deps + .api + .canonical_address(&Addr::unchecked("bob".to_string())) + .unwrap(); + let alice_canonical = deps + .api + .canonical_address(&Addr::unchecked("alice".to_string())) + .unwrap(); + + let allowance = read_allowance(deps.storage, &bob_canonical, &alice_canonical).unwrap(); + assert_eq!(allowance, crate::state::Allowance { + amount: 2000, + expiration: None + }); + + let handle_msg = ExecuteMsg::IncreaseAllowance { + spender: Addr::unchecked("alice".to_string()), + amount: Uint128::new(2000), + padding: None, + expiration: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + assert!( + handle_result.is_ok(), + "execute() failed: {}", + handle_result.err().unwrap() + ); + + let allowance = read_allowance(deps.storage, &bob_canonical, &alice_canonical).unwrap(); + assert_eq!(allowance, crate::state::Allowance { + amount: 4000, + expiration: None + }); + } + + #[test] + fn test_handle_change_admin() { + let (init_result, mut deps) = init_helper(vec![InitBalance { + acc: "bob", + pwd: "pwd", + stake: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::ChangeAdmin { + address: Addr::unchecked("bob".to_string()), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg); + assert!( + handle_result.is_ok(), + "execute() failed: {}", + handle_result.err().unwrap() + ); + + let admin = ReadonlyConfig::from_storage(deps.storage) + .constants() + .unwrap() + .admin; + assert_eq!(admin, Addr::unchecked("bob".to_string())); + } + + #[test] + fn test_handle_set_contract_status() { + let (init_result, mut deps) = init_helper(vec![InitBalance { + acc: "admin", + pwd: "pwd", + stake: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::SetContractStatus { + level: ContractStatusLevel::StopAll, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg); + assert!( + handle_result.is_ok(), + "execute() failed: {}", + handle_result.err().unwrap() + ); + + let contract_status = ReadonlyConfig::from_storage(deps.storage).contract_status(); + assert!(matches!( + contract_status, + ContractStatusLevel::StopAll { .. } + )); + } + + #[test] + fn test_handle_admin_commands() { + let admin_err = "Admin commands can only be run from admin address".to_string(); + let (init_result, mut deps) = init_helper_with_config( + vec![InitBalance { + acc: "lebron", + pwd: "pwd", + stake: Uint128::new(5000), + }], + false, + false, + true, + false, + 0, + ); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let pause_msg = ExecuteMsg::SetContractStatus { + level: ContractStatusLevel::StopAll, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("not_admin", &[]), pause_msg); + let error = extract_error_msg(handle_result); + assert!(error.contains(&admin_err.clone())); + + let change_admin_msg = ExecuteMsg::ChangeAdmin { + address: Addr::unchecked("not_admin".to_string()), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("not_admin", &[]), change_admin_msg); + let error = extract_error_msg(handle_result); + assert!(error.contains(&admin_err.clone())); + } + + #[test] + fn test_handle_pause_all() { + let (init_result, mut deps) = init_helper(vec![InitBalance { + acc: "lebron", + pwd: "pwd", + stake: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let pause_msg = ExecuteMsg::SetContractStatus { + level: ContractStatusLevel::StopAll, + padding: None, + }; + + let handle_result = execute(&mut deps, mock_env("admin", &[]), pause_msg); + assert!( + handle_result.is_ok(), + "Pause execute failed: {}", + handle_result.err().unwrap() + ); + + let send_msg = ExecuteMsg::Transfer { + recipient: Addr::unchecked("account".to_string()), + amount: Uint128::new(123), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("admin", &[]), send_msg); + let error = extract_error_msg(handle_result); + assert_eq!( + error, + "This contract is stopped and this action is not allowed".to_string() + ); + } + + // Query tests + + #[test] + fn test_authenticated_queries() { + let (init_result, mut deps) = init_helper(vec![InitBalance { + acc: "giannis", + pwd: "pwd", + stake: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let no_vk_yet_query_msg = QueryMsg::Balance { + address: Addr::unchecked("giannis".to_string()), + key: "no_vk_yet".to_string(), + }; + let query_result = query(&deps, no_vk_yet_query_msg); + let error = extract_error_msg(query_result); + assert_eq!( + error, + "Wrong viewing key for this address or viewing key not set".to_string() + ); + + let create_vk_msg = ExecuteMsg::CreateViewingKey { + entropy: "34".to_string(), + padding: None, + }; + let handle_response = execute(&mut deps, mock_env("giannis", &[]), create_vk_msg).unwrap(); + let vk = match from_binary(&handle_response.data.unwrap()).unwrap() { + HandleAnswer::CreateViewingKey { key } => key, + _ => panic!("Unexpected result from execute"), + }; + + let query_balance_msg = QueryMsg::Balance { + address: Addr::unchecked("giannis".to_string()), + key: vk.0, + }; + + let query_response = query(&deps, query_balance_msg).unwrap(); + let balance = match from_binary(&query_response).unwrap() { + QueryAnswer::Balance { amount } => amount, + _ => panic!("Unexpected result from query"), + }; + assert_eq!(balance, Uint128::new(5000)); + + let wrong_vk_query_msg = QueryMsg::Balance { + address: Addr::unchecked("giannis".to_string()), + key: "wrong_vk".to_string(), + }; + let query_result = query(&deps, wrong_vk_query_msg); + let error = extract_error_msg(query_result); + assert_eq!( + error, + "Wrong viewing key for this address or viewing key not set".to_string() + ); + } + + #[test] + fn test_query_token_info() { + let init_name = "sec-sec".to_string(); + let init_admin = Addr::unchecked("admin".to_string()); + let init_symbol = "SECSEC".to_string(); + let init_decimals = 8; + let init_config: InitConfig = from_binary(&Binary::from( + r#"{ "public_total_supply": true }"#.as_bytes(), + )) + .unwrap(); + let init_supply = Uint128::new(5000); + + let mut deps = mock_dependencies(20, &[]); + let env = mock_env("instantiator", &[]); + let init_msg = InstantiateMsg { + name: init_name.clone(), + admin: Some(init_admin.clone()), + symbol: init_symbol.clone(), + decimals: Some(init_decimals.clone()), + share_decimals: 18, + prng_seed: Binary::from("lolz fun yay".as_bytes()), + config: Some(init_config), + unbond_time: 10, + staked_token: Contract { + address: Addr::unchecked("token".to_string()), + code_hash: "hash".to_string(), + }, + treasury: Some(Addr::unchecked("treasury".to_string())), + treasury_code_hash: None, + limit_transfer: true, + distributors: None, + }; + let init_result = instantiate(&mut deps, env, info, init_msg); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + new_staked_account(&mut deps, "giannis", "pwd", init_supply); + + let query_msg = QueryMsg::TokenInfo {}; + let query_result = query(&deps, query_msg); + assert!( + query_result.is_ok(), + "Init failed: {}", + query_result.err().unwrap() + ); + let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap(); + match query_answer { + QueryAnswer::TokenInfo { + name, + symbol, + decimals, + total_supply, + } => { + assert_eq!(name, init_name); + assert_eq!(symbol, "STKD-".to_string() + &init_symbol); + assert_eq!(decimals, init_decimals); + assert_eq!(total_supply, Some(Uint128::new(5000))); + } + _ => panic!("unexpected"), + } + } + + #[test] + fn test_query_token_config() { + let init_name = "sec-sec".to_string(); + let init_admin = Addr::unchecked("admin".to_string()); + let init_symbol = "SECSEC".to_string(); + let init_decimals = 8; + let init_config: InitConfig = from_binary(&Binary::from( + format!( + "{{\"public_total_supply\":{}, + \"enable_mint\":{}, + \"enable_burn\":{}}}", + true, true, false + ) + .as_bytes(), + )) + .unwrap(); + + let init_supply = Uint128::new(5000); + + let mut deps = mock_dependencies(20, &[]); + let env = mock_env("instantiator", &[]); + let init_msg = InstantiateMsg { + name: init_name.clone(), + admin: Some(init_admin.clone()), + symbol: init_symbol.clone(), + decimals: Some(init_decimals.clone()), + share_decimals: 18, + prng_seed: Binary::from("lolz fun yay".as_bytes()), + config: Some(init_config), + unbond_time: 10, + staked_token: Contract { + address: Addr::unchecked("token".to_string()), + code_hash: "hash".to_string(), + }, + treasury: Some(Addr::unchecked("treasury".to_string())), + treasury_code_hash: None, + limit_transfer: true, + distributors: None, + }; + let init_result = instantiate(&mut deps, env, info, init_msg); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + new_staked_account(&mut deps, "giannis", "pwd", init_supply); + + let query_msg = QueryMsg::TokenConfig {}; + let query_result = query(&deps, query_msg); + assert!( + query_result.is_ok(), + "Init failed: {}", + query_result.err().unwrap() + ); + let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap(); + match query_answer { + QueryAnswer::TokenConfig { + public_total_supply, + } => { + assert_eq!(public_total_supply, true); + } + _ => panic!("unexpected"), + } + } + + #[test] + fn test_query_allowance() { + let (init_result, mut deps) = init_helper(vec![InitBalance { + acc: "giannis", + pwd: "pwd", + stake: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::IncreaseAllowance { + spender: Addr::unchecked("lebron".to_string()), + amount: Uint128::new(2000), + padding: None, + expiration: None, + }; + let handle_result = execute(&mut deps, mock_env("giannis", &[]), handle_msg); + assert!( + handle_result.is_ok(), + "execute() failed: {}", + handle_result.err().unwrap() + ); + + let vk1 = ViewingKey("key1".to_string()); + let vk2 = ViewingKey("key2".to_string()); + + let query_msg = QueryMsg::Allowance { + owner: Addr::unchecked("giannis".to_string()), + spender: Addr::unchecked("lebron".to_string()), + key: vk1.0.clone(), + }; + let query_result = query(&deps, query_msg); + assert!( + query_result.is_ok(), + "Query failed: {}", + query_result.err().unwrap() + ); + let error = extract_error_msg(query_result); + assert!(error.contains("Wrong viewing key")); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: vk1.0.clone(), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("lebron", &[]), handle_msg); + let unwrapped_result: HandleAnswer = + from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); + assert_eq!( + to_binary(&unwrapped_result).unwrap(), + to_binary(&HandleAnswer::SetViewingKey { + status: ResponseStatus::Success + }) + .unwrap(), + ); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: vk2.0.clone(), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("giannis", &[]), handle_msg); + let unwrapped_result: HandleAnswer = + from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); + assert_eq!( + to_binary(&unwrapped_result).unwrap(), + to_binary(&HandleAnswer::SetViewingKey { + status: ResponseStatus::Success + }) + .unwrap(), + ); + + let query_msg = QueryMsg::Allowance { + owner: Addr::unchecked("giannis".to_string()), + spender: Addr::unchecked("lebron".to_string()), + key: vk1.0.clone(), + }; + let query_result = query(&deps, query_msg); + let allowance = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::Allowance { allowance, .. } => allowance, + _ => panic!("Unexpected"), + }; + assert_eq!(allowance, Uint128::new(2000)); + + let query_msg = QueryMsg::Allowance { + owner: Addr::unchecked("giannis".to_string()), + spender: Addr::unchecked("lebron".to_string()), + key: vk2.0.clone(), + }; + let query_result = query(&deps, query_msg); + let allowance = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::Allowance { allowance, .. } => allowance, + _ => panic!("Unexpected"), + }; + assert_eq!(allowance, Uint128::new(2000)); + + let query_msg = QueryMsg::Allowance { + owner: Addr::unchecked("lebron".to_string()), + spender: Addr::unchecked("giannis".to_string()), + key: vk2.0.clone(), + }; + let query_result = query(&deps, query_msg); + let allowance = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::Allowance { allowance, .. } => allowance, + _ => panic!("Unexpected"), + }; + assert_eq!(allowance, Uint128::new(0)); + } + + #[test] + fn test_query_balance() { + let (init_result, mut deps) = init_helper(vec![InitBalance { + acc: "bob", + pwd: "pwd", + stake: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: "key".to_string(), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + let unwrapped_result: HandleAnswer = + from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); + assert_eq!( + to_binary(&unwrapped_result).unwrap(), + to_binary(&HandleAnswer::SetViewingKey { + status: ResponseStatus::Success + }) + .unwrap(), + ); + + let query_msg = QueryMsg::Balance { + address: Addr::unchecked("bob".to_string()), + key: "wrong_key".to_string(), + }; + let query_result = query(&deps, query_msg); + let error = extract_error_msg(query_result); + assert!(error.contains("Wrong viewing key")); + + let query_msg = QueryMsg::Balance { + address: Addr::unchecked("bob".to_string()), + key: "key".to_string(), + }; + let query_result = query(&deps, query_msg); + let balance = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::Balance { amount } => amount, + _ => panic!("Unexpected"), + }; + assert_eq!(balance, Uint128::new(5000)); + } + + #[test] + fn test_query_transfer_history() { + let (init_result, mut deps) = init_helper(vec![InitBalance { + acc: "bob", + pwd: "pwd", + stake: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: "key".to_string(), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::Transfer { + recipient: Addr::unchecked("alice".to_string()), + amount: Uint128::new(1000), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + let handle_msg = ExecuteMsg::Transfer { + recipient: Addr::unchecked("banana".to_string()), + amount: Uint128::new(500), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + let handle_msg = ExecuteMsg::Transfer { + recipient: Addr::unchecked("mango".to_string()), + amount: Uint128::new(2500), + memo: None, + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + let query_msg = QueryMsg::TransferHistory { + address: Addr::unchecked("bob".to_string()), + key: "key".to_string(), + page: None, + page_size: 0, + }; + let query_result = query(&deps, query_msg); + // let a: QueryAnswer = from_binary(&query_result.unwrap()).unwrap(); + // println!("{:?}", a); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransferHistory { txs, .. } => txs, + _ => panic!("Unexpected"), + }; + assert!(transfers.is_empty()); + + let query_msg = QueryMsg::TransferHistory { + address: Addr::unchecked("bob".to_string()), + key: "key".to_string(), + page: None, + page_size: 10, + }; + let query_result = query(&deps, query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransferHistory { txs, .. } => txs, + _ => panic!("Unexpected"), + }; + assert_eq!(transfers.len(), 3); + + let query_msg = QueryMsg::TransferHistory { + address: Addr::unchecked("bob".to_string()), + key: "key".to_string(), + page: None, + page_size: 2, + }; + let query_result = query(&deps, query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransferHistory { txs, .. } => txs, + _ => panic!("Unexpected"), + }; + assert_eq!(transfers.len(), 2); + + let query_msg = QueryMsg::TransferHistory { + address: Addr::unchecked("bob".to_string()), + key: "key".to_string(), + page: Some(1), + page_size: 2, + }; + let query_result = query(&deps, query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransferHistory { txs, .. } => txs, + _ => panic!("Unexpected"), + }; + assert_eq!(transfers.len(), 1); + } + + #[test] + fn test_query_transaction_history() { + let (init_result, mut deps) = init_helper_with_config( + vec![InitBalance { + acc: "bob", + pwd: "pwd", + stake: Uint128::new(10000), + }], + true, + true, + false, + false, + 0, + ); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::SetViewingKey { + key: "key".to_string(), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + assert!(ensure_success(handle_result.unwrap())); + + let handle_msg = ExecuteMsg::Transfer { + recipient: Addr::unchecked("alice".to_string()), + amount: Uint128::new(1000), + memo: Some("my transfer message #1".to_string()), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + let handle_msg = ExecuteMsg::Transfer { + recipient: Addr::unchecked("banana".to_string()), + amount: Uint128::new(500), + memo: Some("my transfer message #2".to_string()), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + let handle_msg = ExecuteMsg::Transfer { + recipient: Addr::unchecked("mango".to_string()), + amount: Uint128::new(2500), + memo: Some("my transfer message #3".to_string()), + padding: None, + }; + let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + let query_msg = QueryMsg::TransferHistory { + address: Addr::unchecked("bob".to_string()), + key: "key".to_string(), + page: None, + page_size: 10, + }; + let query_result = query(&deps, query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransferHistory { txs, .. } => txs, + _ => panic!("Unexpected"), + }; + assert_eq!(transfers.len(), 3); + + let query_msg = QueryMsg::TransactionHistory { + address: Addr::unchecked("bob".to_string()), + key: "key".to_string(), + page: None, + page_size: 10, + }; + let query_result = query(&deps, query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransactionHistory { txs, .. } => txs, + other => panic!("Unexpected: {:?}", other), + }; + + use crate::transaction_history::{RichTx, TxAction}; + let expected_transfers = [ + RichTx { + id: 4, + action: TxAction::Transfer { + from: Addr::unchecked("bob".to_string()), + sender: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("mango".to_string()), + }, + coins: Coin { + denom: "STKD-SECSEC".to_string(), + amount: Uint128::new(2500).into(), + }, + memo: Some("my transfer message #3".to_string()), + block_time: 1571797419, + block_height: 12345, + }, + RichTx { + id: 3, + action: TxAction::Transfer { + from: Addr::unchecked("bob".to_string()), + sender: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("banana".to_string()), + }, + coins: Coin { + denom: "STKD-SECSEC".to_string(), + amount: Uint128::new(500).into(), + }, + memo: Some("my transfer message #2".to_string()), + block_time: 1571797419, + block_height: 12345, + }, + RichTx { + id: 2, + action: TxAction::Transfer { + from: Addr::unchecked("bob".to_string()), + sender: Addr::unchecked("bob".to_string()), + recipient: Addr::unchecked("alice".to_string()), + }, + coins: Coin { + denom: "STKD-SECSEC".to_string(), + amount: Uint128::new(1000).into(), + }, + memo: Some("my transfer message #1".to_string()), + block_time: 1571797419, + block_height: 12345, + }, + RichTx { + id: 1, + action: TxAction::Stake { + staker: Addr::unchecked("bob".to_string()), + }, + coins: Coin { + denom: "STKD-SECSEC".to_string(), + amount: Uint128::new(10000).into(), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, + ]; + + assert_eq!(transfers, expected_transfers); + } +} diff --git a/archived-contracts/snip20_staking/src/distributors.rs b/archived-contracts/snip20_staking/src/distributors.rs new file mode 100644 index 0000000..98859fc --- /dev/null +++ b/archived-contracts/snip20_staking/src/distributors.rs @@ -0,0 +1,91 @@ +use crate::{ + contract::check_if_admin, + msg::{HandleAnswer, QueryAnswer, ResponseStatus::Success}, + state::Config, + state_staking::{Distributors, DistributorsEnabled}, +}; +use cosmwasm_std::{Deps, MessageInfo}; +use shade_protocol::c_std::{ + to_binary, + Api, + Binary, + Env, + DepsMut, + Response, + Addr, + Querier, + StdResult, + Storage, +}; +use shade_protocol::utils::storage::default::SingletonStorage; + +pub fn get_distributor( + deps: Deps, +) -> StdResult>> { + Ok(match DistributorsEnabled::load(deps.storage)?.0 { + true => Some(Distributors::load(deps.storage)?.0), + false => None, + }) +} + +pub fn try_set_distributors_status( + deps: DepsMut, + env: Env, + info: MessageInfo, + enabled: bool, +) -> StdResult { + let config = Config::from_storage(deps.storage); + + check_if_admin(&config, &info.sender)?; + + DistributorsEnabled(enabled).save(deps.storage)?; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::SetDistributorsStatus { + status: Success, + })?)) +} + +pub fn try_add_distributors( + deps: DepsMut, + env: Env, + info: MessageInfo, + new_distributors: Vec, +) -> StdResult { + let config = Config::from_storage(deps.storage); + + check_if_admin(&config, &info.sender)?; + + let mut distributors = Distributors::load(deps.storage)?; + distributors.0.extend(new_distributors); + distributors.save(deps.storage)?; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::AddDistributors { + status: Success, + })?)) +} + +pub fn try_set_distributors( + deps: DepsMut, + env: Env, + info: MessageInfo, + distributors: Vec, +) -> StdResult { + let config = Config::from_storage(deps.storage); + + check_if_admin(&config, &info.sender)?; + + Distributors(distributors).save(deps.storage)?; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::SetDistributors { + status: Success, + })?)) +} + +pub fn distributors(deps: Deps) -> StdResult { + to_binary(&QueryAnswer::Distributors { + distributors: match DistributorsEnabled::load(deps.storage)?.0 { + true => Some(Distributors::load(deps.storage)?.0), + false => None, + }, + }) +} diff --git a/archived-contracts/snip20_staking/src/expose_balance.rs b/archived-contracts/snip20_staking/src/expose_balance.rs new file mode 100644 index 0000000..79c1821 --- /dev/null +++ b/archived-contracts/snip20_staking/src/expose_balance.rs @@ -0,0 +1,150 @@ + +use shade_protocol::cosmwasm_schema::cw_serde; + +use crate::{ + msg::{HandleAnswer, ResponseStatus::Success}, + state::{get_receiver_hash, Balances}, + state_staking::UserCooldown, +}; +use shade_protocol::c_std::Uint128; +use shade_protocol::c_std::{ + to_binary, + Api, + Binary, + CosmosMsg, + Env, + DepsMut, + Response, + Addr, + Querier, + StdError, + StdResult, + Storage, +}; +use shade_protocol::{ + contract_interfaces::staking::snip20_staking::stake::VecQueue, + utils::storage::default::BucketStorage, +}; + +pub fn try_expose_balance( + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: Addr, + code_hash: Option, + msg: Option, + memo: Option, +) -> StdResult { + // Get balance to expose + let balance = Balances::from_storage(deps.storage) + .balance(&deps.api.canonical_address(&info.sender)?); + + let receiver_hash: String; + if let Some(code_hash) = code_hash { + receiver_hash = code_hash; + } else if let Some(code_hash) = get_receiver_hash(deps.storage, &recipient) { + receiver_hash = code_hash?; + } else { + return Err(StdError::generic_err("No code hash received")); + } + + let messages = vec![ + Snip20BalanceReceiverMsg::new(info.sender, Uint128::new(balance), memo, msg) + .to_cosmos_msg(receiver_hash, recipient)?, + ]; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::ExposeBalance { status: Success })?)) +} + +pub fn try_expose_balance_with_cooldown( + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: Addr, + code_hash: Option, + msg: Option, + memo: Option, +) -> StdResult { + // Get balance to expose + let balance = Balances::from_storage(deps.storage) + .balance(&deps.api.canonical_address(&info.sender)?); + + let receiver_hash: String; + if let Some(code_hash) = code_hash { + receiver_hash = code_hash; + } else if let Some(code_hash) = get_receiver_hash(deps.storage, &recipient) { + receiver_hash = code_hash?; + } else { + return Err(StdError::generic_err("No code hash received")); + } + + let mut cooldown = + UserCooldown::may_load(deps.storage, info.sender.to_string().as_bytes())? + .unwrap_or(UserCooldown { + total: Uint128::zero(), + queue: VecQueue(vec![]), + }); + cooldown.update(env.block.time.seconds()); + cooldown.save(deps.storage, info.sender.to_string().as_bytes())?; + + let messages = vec![ + Snip20BalanceReceiverMsg::new( + info.sender, + Uint128::new(balance).checked_sub(cooldown.total)?, + memo, + msg, + ) + .to_cosmos_msg_cooldown(receiver_hash, recipient)?, + ]; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::ExposeBalance { status: Success })?)) +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct Snip20BalanceReceiverMsg { + pub sender: Addr, + pub balance: Uint128, + pub memo: Option, + pub msg: Option, +} + +impl Snip20BalanceReceiverMsg { + pub fn new( + sender: Addr, + balance: Uint128, + memo: Option, + msg: Option, + ) -> Self { + Self { + sender, + balance, + memo, + msg, + } + } + + pub fn to_cosmos_msg(self, code_hash: String, address: Addr) -> StdResult { + BalanceReceiverHandleMsg::ReceiveBalance(self).to_cosmos_msg(code_hash, address, None) + } + + pub fn to_cosmos_msg_cooldown( + self, + code_hash: String, + address: Addr, + ) -> StdResult { + BalanceReceiverHandleMsg::ReceiveBalanceWithCooldown(self) + .to_cosmos_msg(code_hash, address, None) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum BalanceReceiverHandleMsg { + ReceiveBalance(Snip20BalanceReceiverMsg), + ReceiveBalanceWithCooldown(Snip20BalanceReceiverMsg), +} + +impl ExecuteCallback for BalanceReceiverHandleMsg { + const BLOCK_SIZE: usize = 256; +} diff --git a/archived-contracts/snip20_staking/src/lib.rs b/archived-contracts/snip20_staking/src/lib.rs new file mode 100644 index 0000000..d788e34 --- /dev/null +++ b/archived-contracts/snip20_staking/src/lib.rs @@ -0,0 +1,56 @@ +mod batch; +pub mod contract; +mod distributors; +mod expose_balance; +pub mod msg; +mod rand; +pub mod receiver; +mod stake; +mod stake_queries; +pub mod state; +mod state_staking; +mod transaction_history; +mod utils; +mod viewing_key; + +#[cfg(target_arch = "wasm32")] +mod wasm { + use super::contract; + use shade_protocol::c_std::{ + do_handle, + do_init, + do_query, + ExternalApi, + ExternalQuerier, + ExternalStorage, + }; + + #[no_mangle] + extern "C" fn instantiate(env_ptr: u32, msg_ptr: u32) -> u32 { + do_init( + &contract::instantiate::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn execute(env_ptr: u32, msg_ptr: u32) -> u32 { + do_handle( + &contract::execute::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn query(msg_ptr: u32) -> u32 { + do_query( + &contract::query::, + msg_ptr, + ) + } + + // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available + // automatically because we `use cosmwasm_std`. +} diff --git a/archived-contracts/snip20_staking/src/msg.rs b/archived-contracts/snip20_staking/src/msg.rs new file mode 100644 index 0000000..3b759ff --- /dev/null +++ b/archived-contracts/snip20_staking/src/msg.rs @@ -0,0 +1,556 @@ +#![allow(clippy::field_reassign_with_default)] // This is triggered in `#[derive(JsonSchema)]` + + +use shade_protocol::cosmwasm_schema::cw_serde; + +use crate::{ + batch, + transaction_history::{RichTx, Tx}, + viewing_key::ViewingKey, +}; +use shade_protocol::c_std::{Uint128, Uint256}; +use shade_protocol::c_std::{Binary, Addr, StdError, StdResult}; +use shade_protocol::secret_toolkit::permit::Permit; +use shade_protocol::{ + contract_interfaces::staking::snip20_staking::stake::{QueueItem, StakeConfig, VecQueue}, + utils::asset::Contract, +}; + +#[derive(Serialize, Deserialize)] +pub struct InstantiateMsg { + pub name: String, + pub admin: Option, + pub symbol: String, + // Will default to staked token decimals if not set + pub decimals: Option, + pub share_decimals: u8, + pub prng_seed: Binary, + pub config: Option, + + // Stake + pub unbond_time: u64, + pub staked_token: Contract, + pub treasury: Option, + pub treasury_code_hash: Option, + + // Distributors + pub limit_transfer: bool, + pub distributors: Option>, +} + +impl InstantiateMsg { + pub fn config(&self) -> InitConfig { + self.config.clone().unwrap_or_default() + } +} + +/// This type represents optional configuration values which can be overridden. +/// All values are optional and have defaults which are more private by default, +/// but can be overridden if necessary +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +#[serde(rename_all = "snake_case")] +pub struct InitConfig { + /// Indicates whether the total supply is public or should be kept secret. + /// default: False + pub public_total_supply: Option, +} + +impl InitConfig { + pub fn public_total_supply(&self) -> bool { + self.public_total_supply.unwrap_or(false) + } +} + +#[cw_serde] +pub enum ExecuteMsg { + // Staking + UpdateStakeConfig { + unbond_time: Option, + disable_treasury: bool, + treasury: Option, + padding: Option, + }, + Receive { + sender: Addr, + from: Addr, + amount: Uint128, + msg: Option, + memo: Option, + padding: Option, + }, + Unbond { + amount: Uint128, + padding: Option, + }, + ClaimUnbond { + padding: Option, + }, + ClaimRewards { + padding: Option, + }, + StakeRewards { + padding: Option, + }, + + // Balance + ExposeBalance { + recipient: Addr, + code_hash: Option, + msg: Option, + memo: Option, + padding: Option, + }, + ExposeBalanceWithCooldown { + recipient: Addr, + code_hash: Option, + msg: Option, + memo: Option, + padding: Option, + }, + + // Distributors + SetDistributorsStatus { + enabled: bool, + padding: Option, + }, + AddDistributors { + distributors: Vec, + padding: Option, + }, + SetDistributors { + distributors: Vec, + padding: Option, + }, + + // Base ERC-20 stuff + Transfer { + recipient: Addr, + amount: Uint128, + memo: Option, + padding: Option, + }, + Send { + recipient: Addr, + recipient_code_hash: Option, + amount: Uint128, + msg: Option, + memo: Option, + padding: Option, + }, + BatchTransfer { + actions: Vec, + padding: Option, + }, + BatchSend { + actions: Vec, + padding: Option, + }, + RegisterReceive { + code_hash: String, + padding: Option, + }, + CreateViewingKey { + entropy: String, + padding: Option, + }, + SetViewingKey { + key: String, + padding: Option, + }, + + // Allowance + IncreaseAllowance { + spender: Addr, + amount: Uint128, + expiration: Option, + padding: Option, + }, + DecreaseAllowance { + spender: Addr, + amount: Uint128, + expiration: Option, + padding: Option, + }, + TransferFrom { + owner: Addr, + recipient: Addr, + amount: Uint128, + memo: Option, + padding: Option, + }, + SendFrom { + owner: Addr, + recipient: Addr, + recipient_code_hash: Option, + amount: Uint128, + msg: Option, + memo: Option, + padding: Option, + }, + BatchTransferFrom { + actions: Vec, + padding: Option, + }, + BatchSendFrom { + actions: Vec, + padding: Option, + }, + + // Admin + ChangeAdmin { + address: Addr, + padding: Option, + }, + SetContractStatus { + level: ContractStatusLevel, + padding: Option, + }, + + // Permit + RevokePermit { + permit_name: String, + padding: Option, + }, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "snake_case")] +pub enum HandleAnswer { + UpdateStakeConfig { + status: ResponseStatus, + }, + Receive { + status: ResponseStatus, + }, + Unbond { + status: ResponseStatus, + }, + ClaimUnbond { + status: ResponseStatus, + }, + ClaimRewards { + status: ResponseStatus, + }, + StakeRewards { + status: ResponseStatus, + }, + ExposeBalance { + status: ResponseStatus, + }, + SetDistributorsStatus { + status: ResponseStatus, + }, + AddDistributors { + status: ResponseStatus, + }, + SetDistributors { + status: ResponseStatus, + }, + + // Base + Transfer { + status: ResponseStatus, + }, + Send { + status: ResponseStatus, + }, + BatchTransfer { + status: ResponseStatus, + }, + BatchSend { + status: ResponseStatus, + }, + RegisterReceive { + status: ResponseStatus, + }, + CreateViewingKey { + key: ViewingKey, + }, + SetViewingKey { + status: ResponseStatus, + }, + + // Allowance + IncreaseAllowance { + spender: Addr, + owner: Addr, + allowance: Uint128, + }, + DecreaseAllowance { + spender: Addr, + owner: Addr, + allowance: Uint128, + }, + TransferFrom { + status: ResponseStatus, + }, + SendFrom { + status: ResponseStatus, + }, + BatchTransferFrom { + status: ResponseStatus, + }, + BatchSendFrom { + status: ResponseStatus, + }, + + // Other + ChangeAdmin { + status: ResponseStatus, + }, + SetContractStatus { + status: ResponseStatus, + }, + + // Permit + RevokePermit { + status: ResponseStatus, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + // Staking + StakeConfig {}, + TotalStaked {}, + // Total token shares per token + StakeRate {}, + Unbonding {}, + Unfunded { + start: u64, + total: u64, + }, + Staked { + address: Addr, + key: String, + time: Option, + }, + + // Distributors + Distributors {}, + + // Snip20 stuff + TokenInfo {}, + TokenConfig {}, + ContractStatus {}, + Allowance { + owner: Addr, + spender: Addr, + key: String, + }, + Balance { + address: Addr, + key: String, + }, + TransferHistory { + address: Addr, + key: String, + page: Option, + page_size: u32, + }, + TransactionHistory { + address: Addr, + key: String, + page: Option, + page_size: u32, + }, + WithPermit { + permit: Permit, + query: QueryWithPermit, + }, +} + +impl QueryMsg { + pub fn get_validation_params(&self) -> (Vec<&Addr>, ViewingKey) { + match self { + Self::Staked { address, key, .. } => (vec![address], ViewingKey(key.clone())), + Self::Balance { address, key } => (vec![address], ViewingKey(key.clone())), + Self::TransferHistory { address, key, .. } => (vec![address], ViewingKey(key.clone())), + Self::TransactionHistory { address, key, .. } => { + (vec![address], ViewingKey(key.clone())) + } + Self::Allowance { + owner, + spender, + key, + .. + } => (vec![owner, spender], ViewingKey(key.clone())), + _ => panic!("This query type does not require authentication"), + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum QueryWithPermit { + Staked { + time: Option, + }, + + // Snip20 stuff + Allowance { + owner: Addr, + spender: Addr, + }, + Balance {}, + TransferHistory { + page: Option, + page_size: u32, + }, + TransactionHistory { + page: Option, + page_size: u32, + }, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "snake_case")] +pub enum QueryAnswer { + // Stake + StakedConfig { + config: StakeConfig, + }, + TotalStaked { + tokens: Uint128, + shares: Uint256, + }, + // Shares per token + StakeRate { + shares: Uint256, + }, + Staked { + tokens: Uint128, + shares: Uint256, + pending_rewards: Uint128, + unbonding: Uint128, + unbonded: Option, + cooldown: VecQueue, + }, + Unbonding { + total: Uint128, + }, + Unfunded { + total: Uint128, + }, + + // Distributors + Distributors { + distributors: Option>, + }, + + // Snip20 stuff + TokenInfo { + name: String, + symbol: String, + decimals: u8, + total_supply: Option, + }, + TokenConfig { + public_total_supply: bool, + }, + ContractStatus { + status: ContractStatusLevel, + }, + ExchangeRate { + rate: Uint128, + denom: String, + }, + Allowance { + spender: Addr, + owner: Addr, + allowance: Uint128, + expiration: Option, + }, + Balance { + amount: Uint128, + }, + TransferHistory { + txs: Vec, + total: Option, + }, + TransactionHistory { + txs: Vec, + total: Option, + }, + ViewingKeyError { + msg: String, + }, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq)] +pub struct CreateViewingKeyResponse { + pub key: String, +} + +#[cw_serde] +pub enum ResponseStatus { + Success, + Failure, +} + +#[cw_serde] +pub enum ContractStatusLevel { + NormalRun, + StopBonding, + StopAllButUnbond, //Can set time to 0 for instant unbond + StopAll, +} + +pub fn status_level_to_u8(status_level: ContractStatusLevel) -> u8 { + match status_level { + ContractStatusLevel::NormalRun => 0, + ContractStatusLevel::StopBonding => 1, + ContractStatusLevel::StopAllButUnbond => 2, + ContractStatusLevel::StopAll => 3, + } +} + +pub fn u8_to_status_level(status_level: u8) -> StdResult { + match status_level { + 0 => Ok(ContractStatusLevel::NormalRun), + 1 => Ok(ContractStatusLevel::StopBonding), + 2 => Ok(ContractStatusLevel::StopAllButUnbond), + 3 => Ok(ContractStatusLevel::StopAll), + _ => Err(StdError::generic_err("Invalid state level")), + } +} + +// 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 +} + +#[cfg(test)] +mod tests { + use super::*; + use shade_protocol::c_std::{from_slice, StdResult}; + + #[derive(Serialize, Deserialize, Debug, PartialEq)] + #[serde(rename_all = "snake_case")] + pub enum Something { + Var { padding: Option }, + } + + #[test] + fn test_deserialization_of_missing_option_fields() -> StdResult<()> { + let input = b"{ \"var\": {} }"; + let obj: Something = from_slice(input)?; + assert_eq!( + obj, + Something::Var { padding: None }, + "unexpected value: {:?}", + obj + ); + Ok(()) + } +} diff --git a/archived-contracts/snip20_staking/src/rand.rs b/archived-contracts/snip20_staking/src/rand.rs new file mode 100644 index 0000000..41c3944 --- /dev/null +++ b/archived-contracts/snip20_staking/src/rand.rs @@ -0,0 +1,75 @@ +use rand_chacha::ChaChaRng; +use rand_core::{RngCore, SeedableRng}; + +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/archived-contracts/snip20_staking/src/receiver.rs b/archived-contracts/snip20_staking/src/receiver.rs new file mode 100644 index 0000000..80378a8 --- /dev/null +++ b/archived-contracts/snip20_staking/src/receiver.rs @@ -0,0 +1,68 @@ +#![allow(clippy::field_reassign_with_default)] // This is triggered in `#[derive(JsonSchema)]` + + +use shade_protocol::cosmwasm_schema::cw_serde; + +use shade_protocol::c_std::Uint128; +use shade_protocol::c_std::{to_binary, Binary, CosmosMsg, Addr, StdResult, WasmMsg}; + +use crate::{contract::RESPONSE_BLOCK_SIZE, msg::space_pad}; + +/// Snip20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg +#[cw_serde] +pub struct Snip20ReceiveMsg { + pub sender: Addr, + pub from: Addr, + pub amount: Uint128, + #[serde(skip_serializing_if = "Option::is_none")] + pub memo: Option, + pub msg: Option, +} + +impl Snip20ReceiveMsg { + pub fn new( + sender: Addr, + from: Addr, + amount: Uint128, + memo: Option, + msg: Option, + ) -> Self { + Self { + sender, + from, + amount, + memo, + msg, + } + } + + /// serializes the message, and pads it to 256 bytes + pub fn into_binary(self) -> StdResult { + let msg = ReceiverHandleMsg::Receive(self); + let mut data = to_binary(&msg)?; + space_pad(RESPONSE_BLOCK_SIZE, &mut data.0); + Ok(data) + } + + /// creates a cosmos_msg sending this struct to the named contract + pub fn into_cosmos_msg( + self, + callback_code_hash: String, + contract_addr: Addr, + ) -> StdResult { + let msg = self.into_binary()?; + let execute = WasmMsg::Execute { + msg, + code_hash: callback_code_hash, + contract_addr, + funds: vec![], + }; + Ok(execute.into()) + } +} + +// This is just a helper to properly serialize the above message +#[cw_serde] +enum ReceiverHandleMsg { + Receive(Snip20ReceiveMsg), +} diff --git a/archived-contracts/snip20_staking/src/stake.rs b/archived-contracts/snip20_staking/src/stake.rs new file mode 100644 index 0000000..3deee5f --- /dev/null +++ b/archived-contracts/snip20_staking/src/stake.rs @@ -0,0 +1,1068 @@ +use crate::{ + contract::check_if_admin, + msg::{HandleAnswer, ResponseStatus::Success}, + state::{Balances, Config, ReadonlyConfig}, + state_staking::{ + DailyUnbondingQueue, + TotalShares, + TotalTokens, + TotalUnbonding, + UnbondingQueue, + UnsentStakedTokens, + UserCooldown, + UserShares, + }, + transaction_history::{ + store_add_reward, + store_claim_reward, + store_claim_unbond, + store_fund_unbond, + store_stake, + store_unbond, + }, +}; +use shade_protocol::c_std::{Uint128, Uint256}; +use shade_protocol::c_std::{ + from_binary, + to_binary, + Api, + Binary, + CanonicalAddr, + Env, + DepsMut, + Response, + Addr, + Querier, + StdError, + StdResult, + Storage, +}; +use shade_protocol::snip20::helpers::send_msg; +use shade_protocol::{ + contract_interfaces::staking::snip20_staking::{ + stake::{DailyUnbonding, StakeConfig, Unbonding, VecQueue}, + ReceiveType, + }, + utils::storage::default::{BucketStorage, SingletonStorage}, +}; +use std::convert::TryInto; + +//TODO: set errors + +pub fn try_update_stake_config( + deps: DepsMut, + env: Env, + info: MessageInfo, + unbond_time: Option, + disable_treasury: bool, + treasury: Option, +) -> StdResult { + let config = Config::from_storage(deps.storage); + + check_if_admin(&config, &info.sender)?; + + let mut stake_config = StakeConfig::load(deps.storage)?; + + if let Some(unbond_time) = unbond_time { + stake_config.unbond_time = unbond_time; + } + + let mut messages = vec![]; + + if disable_treasury { + stake_config.treasury = None; + } else if let Some(treasury) = treasury { + stake_config.treasury = Some(treasury.clone()); + + let unsent_tokens = UnsentStakedTokens::load(deps.storage)?.0; + if unsent_tokens != Uint128::zero() { + messages.push(send_msg( + treasury, + unsent_tokens.into(), + None, + None, + None, + 258, + stake_config.staked_token.code_hash.clone(), + stake_config.staked_token.address.clone(), + )?); + UnsentStakedTokens(Uint128::zero()).save(deps.storage)?; + } + } + + stake_config.save(deps.storage)?; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::UpdateStakeConfig { + status: Success, + })?)) +} + +const DAY: u64 = 86400; //60 * 60 * 24 + +/// +/// Rounds down a date to the nearest day +/// +fn round_date(date: u64) -> u64 { + date - (date % DAY) +} + +/// +/// Updates total states to reflect balance changes +/// +fn add_balance( + storage: &mut dyn Storage, + stake_config: &StakeConfig, + sender: &Addr, + sender_canon: &CanonicalAddr, + amount: Uint128, +) -> StdResult<()> { + // Check if user account exists + let mut user_shares = UserShares::may_load(storage, sender.as_str().as_bytes())? + .unwrap_or(UserShares(Uint256::zero())); + + // Update user staked tokens + let mut balances = Balances::from_storage(storage); + let mut account_balance = balances.balance(sender_canon); + if let Some(new_balance) = account_balance.checked_add(amount.u128()) { + account_balance = new_balance; + } else { + return Err(StdError::generic_err( + "This mint attempt would increase the account's balance above the supported maximum", + )); + } + balances.set_account_balance(sender_canon, account_balance); + + // Get total supplied tokens + let mut total_shares = TotalShares::load(storage)?; + let total_tokens = TotalTokens::load(storage)?; + + // Update total staked + // We do this before reaching shares to get overflows out of the way + match total_tokens.0.checked_add(amount) { + Ok(total_staked) => TotalTokens(total_staked).save(storage)?, + Err(_) => return Err(StdError::generic_err("Total staked tokens overflow")), + }; + + let supply = ReadonlyConfig::from_storage(storage).total_supply(); + Config::from_storage(storage).set_total_supply(supply + amount.u128()); + + // Calculate shares per token supplied + let shares = shares_per_token(stake_config, &amount, &total_tokens.0, &total_shares.0)?; + + // Update total shares + match total_shares.0.checked_add(shares) { + Ok(total_added_shares) => total_shares = TotalShares(total_added_shares), + Err(_) => return Err(StdError::generic_err("Shares overflow")), + }; + + total_shares.save(storage)?; + + // Update user's shares - this will not break as total_shares >= user_shares + user_shares.0 += shares; + user_shares.save(storage, sender.as_str().as_bytes())?; + + Ok(()) +} + +/// +/// Removed items from internal supply +/// +fn subtract_internal_supply( + storage: &mut dyn Storage, + total_shares: &mut TotalShares, + shares: Uint256, + total_tokens: &mut TotalTokens, + tokens: Uint128, + remove_supply: bool, +) -> StdResult<()> { + // Update total shares + match total_shares.0.checked_sub(shares) { + Ok(total) => TotalShares(total).save(storage)?, + Err(_) => return Err(StdError::generic_err("Insufficient shares")), + }; + + // Update total staked + match total_tokens.0.checked_sub(tokens) { + Ok(total) => TotalTokens(total).save(storage)?, + Err(_) => return Err(StdError::generic_err("Insufficient tokens")), + }; + + if remove_supply { + let supply = ReadonlyConfig::from_storage(storage).total_supply(); + if let Some(total) = supply.checked_sub(tokens.u128()) { + Config::from_storage(storage).set_total_supply(total); + } else { + return Err(StdError::generic_err("Insufficient shares")); + } + } + + Ok(()) +} + +/// +/// Updates total states to reflect balance changes +/// +fn remove_balance( + storage: &mut dyn Storage, + stake_config: &StakeConfig, + account: &Addr, + account_cannon: &CanonicalAddr, + amount: Uint128, + time: u64, +) -> StdResult<()> { + // Return insufficient funds + let user_shares = + UserShares::may_load(storage, account.as_str().as_bytes())?.expect("No funds"); + + // Get total supplied tokens + let mut total_shares = TotalShares::load(storage)?; + let mut total_tokens = TotalTokens::load(storage)?; + + // Calculate shares per token supplied + let shares = shares_per_token(stake_config, &amount, &total_tokens.0, &total_shares.0)?; + + // Update user's shares + match user_shares.0.checked_sub(shares) { + Ok(user_shares) => UserShares(user_shares).save(storage, account.as_str().as_bytes())?, + Err(_) => return Err(StdError::generic_err("Insufficient shares")), + } + + subtract_internal_supply( + storage, + &mut total_shares, + shares, + &mut total_tokens, + amount, + true, + )?; + + // Load balance + let mut balances = Balances::from_storage(storage); + let mut account_balance = balances.balance(account_cannon); + let account_tokens = account_balance; + + if let Some(new_balance) = account_balance.checked_sub(amount.u128()) { + account_balance = new_balance; + } else { + return Err(StdError::generic_err( + "This burn attempt would decrease the account's balance to a negative", + )); + } + balances.set_account_balance(account_cannon, account_balance); + remove_from_cooldown(storage, account, Uint128::new(account_tokens), amount, time)?; + Ok(()) +} + +pub fn claim_rewards( + storage: &mut dyn Storage, + stake_config: &StakeConfig, + sender: &Addr, + sender_canon: &CanonicalAddr, +) -> StdResult { + let user_shares = UserShares::may_load(storage, sender.as_str().as_bytes())?.expect("No funds"); + + let user_balance = Balances::from_storage(storage).balance(sender_canon); + + // Get total supplied tokens + let mut total_shares = TotalShares::load(storage)?; + let mut total_tokens = TotalTokens::load(storage)?; + + let (reward_token, reward_shares) = calculate_rewards( + stake_config, + Uint128::new(user_balance), + user_shares.0, + total_tokens.0, + total_shares.0, + )?; + + // Do nothing if no rewards are gonna be claimed + if reward_token.is_zero() { + return Ok(reward_token); + } + + match user_shares.0.checked_sub(reward_shares) { + Ok(user_shares) => UserShares(user_shares).save(storage, sender.as_str().as_bytes())?, + Err(_) => return Err(StdError::generic_err("Insufficient shares")), + }; + + subtract_internal_supply( + storage, + &mut total_shares, + reward_shares, + &mut total_tokens, + reward_token, + false, + )?; + + Ok(reward_token) +} + +pub fn shares_per_token( + config: &StakeConfig, + token_amount: &Uint128, + total_tokens: &Uint128, + total_shares: &Uint256, +) -> StdResult { + let t_tokens = Uint256::from(*total_tokens); + let t_shares = *total_shares; + let tokens = Uint256::from(*token_amount); + + if total_tokens.is_zero() && total_shares.is_zero() { + // Used to normalize the staked token to the stake token + let token_multiplier = + Uint256::from(10u128).checked_pow(config.decimal_difference.into())?; + + return match tokens.checked_mul(token_multiplier) { + Ok(shares) => Ok(shares), + Err(_) => Err(StdError::generic_err("Share calculation overflow")), + }; + } + + return match tokens.checked_mul(t_shares) { + Ok(shares) => Ok(shares.checked_div(t_tokens)?), + Err(_) => Err(StdError::generic_err("Share calculation overflow")), + }; +} + +pub fn tokens_per_share( + config: &StakeConfig, + shares_amount: &Uint256, + total_tokens: &Uint128, + total_shares: &Uint256, +) -> StdResult { + let t_tokens = Uint256::from(*total_tokens); + let t_shares = *total_shares; + let shares = *shares_amount; + + if total_tokens.is_zero() && total_shares.is_zero() { + // Used to normalize the staked token to the stake tokes + let token_multiplier = + Uint256::from(10u128).checked_pow(config.decimal_difference.try_into().unwrap())?; + + return match shares.checked_div(token_multiplier) { + Ok(tokens) => Ok(tokens.try_into()?), + Err(_) => Err(StdError::generic_err("Token calculation overflow")), + }; + } + + return match shares.checked_mul(t_tokens) { + Ok(tokens) => Ok(tokens.checked_div(t_shares)?.try_into()?), + Err(_) => Err(StdError::generic_err("Token calculation overflow")), + }; +} + +/// +/// Returns rewards in tokens, and shares +/// +pub fn calculate_rewards( + config: &StakeConfig, + tokens: Uint128, + shares: Uint256, + total_tokens: Uint128, + total_shares: Uint256, +) -> StdResult<(Uint128, Uint256)> { + let token_reward = tokens_per_share(config, &shares, &total_tokens, &total_shares)? + .checked_sub(tokens.into())?; + Ok(( + token_reward, + shares_per_token(config, &token_reward, &total_tokens, &total_shares)?, + )) +} + +pub fn try_receive( + deps: DepsMut, + env: Env, + info: MessageInfo, + sender: Addr, + from: Addr, + amount: Uint128, + msg: Option, + memo: Option, +) -> StdResult { + let sender_canon = deps.api.canonical_address(&sender)?; + + let stake_config = StakeConfig::load(deps.storage)?; + + if info.sender != stake_config.staked_token.address { + return Err(StdError::generic_err("Not the stake token")); + } + + let receive_type: ReceiveType; + if let Some(msg) = msg { + receive_type = from_binary(&msg)?; + } else { + return Err(StdError::generic_err("No receive type supplied in message")); + } + + let symbol = ReadonlyConfig::from_storage(deps.storage) + .constants()? + .symbol; + let mut messages = vec![]; + match receive_type { + ReceiveType::Bond { use_from } => { + let mut target = sender; + let mut target_canon = sender_canon; + if let Some(use_from) = use_from { + if use_from { + target_canon = deps.api.canonical_address(&from)?; + target = from; + } + } + + // Update user stake + add_balance( + deps.storage, + &stake_config, + &target, + &target_canon, + amount, + )?; + + // Store data + store_stake( + deps.storage, + &target_canon, + amount, + symbol, + memo, + &env.block, + )?; + + // Send tokens + if let Some(treasury) = stake_config.treasury { + messages.push(send_msg( + treasury, + amount.into(), + None, + None, + None, + 256, + stake_config.staked_token.code_hash, + stake_config.staked_token.address, + )?); + } else { + let mut stored_tokens = UnsentStakedTokens::load(deps.storage)?; + stored_tokens.0 += amount; + stored_tokens.save(deps.storage)?; + } + } + + ReceiveType::Reward => { + let mut total_tokens = TotalTokens::load(deps.storage)?; + total_tokens.0 += amount; + total_tokens.save(deps.storage)?; + + // Store data + store_add_reward( + deps.storage, + &sender_canon, + amount, + symbol, + memo, + &env.block, + )?; + } + + ReceiveType::Unbond => { + let mut remaining_amount = amount; + + let mut daily_unbond_queue = DailyUnbondingQueue::load(deps.storage)?; + + while !daily_unbond_queue.0.0.is_empty() { + remaining_amount = daily_unbond_queue.0.0[0].fund(remaining_amount); + if daily_unbond_queue.0.0[0].is_funded() { + daily_unbond_queue.0.0.pop(); + } + if remaining_amount == Uint128::zero() { + break; + } + } + + daily_unbond_queue.save(deps.storage)?; + + // Send back if overfunded + if remaining_amount > Uint128::zero() { + messages.push(send_msg( + sender, + remaining_amount.into(), + None, + None, + None, + 256, + stake_config.staked_token.code_hash, + stake_config.staked_token.address, + )?); + } + + store_fund_unbond( + deps.storage, + &sender_canon, + amount.checked_sub(remaining_amount)?, + symbol, + None, + &env.block, + )?; + } + }; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::Receive { status: Success })?)) +} + +pub fn remove_from_cooldown( + store: &mut dyn Storage, + user: &Addr, + user_tokens: Uint128, + remove_amount: Uint128, + time: u64, +) -> StdResult<()> { + let mut cooldown = + UserCooldown::may_load(store, user.as_str().as_bytes())?.unwrap_or(UserCooldown { + total: Uint128::zero(), + queue: VecQueue(vec![]), + }); + + cooldown.update(time); + + let unlocked_tokens = user_tokens.checked_sub(cooldown.total)?; + if remove_amount > unlocked_tokens { + cooldown.remove_cooldown(remove_amount.checked_sub(unlocked_tokens)?); + } + cooldown.save(store, user.as_str().as_bytes())?; + + Ok(()) +} + +pub fn try_unbond( + deps: DepsMut, + env: Env, + info: MessageInfo, + amount: Uint128, +) -> StdResult { + let sender = info.sender; + let sender_canon = deps.api.canonical_address(&sender)?; + + let stake_config = StakeConfig::load(deps.storage)?; + + // Try to claim before unbonding + let claim = claim_rewards(deps.storage, &stake_config, &sender, &sender_canon)?; + + // Subtract tokens from user balance + remove_balance( + deps.storage, + &stake_config, + &sender, + &sender_canon, + amount, + env.block.time.seconds(), + )?; + + let mut total_unbonding = TotalUnbonding::load(deps.storage)?; + total_unbonding.0 += amount; + total_unbonding.save(deps.storage)?; + + // Round to that day's public unbonding queue, initialize one if empty + let mut daily_unbond_queue = DailyUnbondingQueue::load(deps.storage)?; + // Will add or merge a new unbonding date + daily_unbond_queue.0.push(&DailyUnbonding { + unbonding: amount, + funded: Default::default(), + release: round_date(env.block.time.seconds() + stake_config.unbond_time), + }); + + daily_unbond_queue.save(deps.storage)?; + + // Check if user has an existing queue, if not, instantiate one + let mut unbond_queue = UnbondingQueue::may_load(deps.storage, sender.as_str().as_bytes())? + .unwrap_or(UnbondingQueue(VecQueue::new(vec![]))); + + // Add unbonding to user queue + unbond_queue.0.push(&Unbonding { + amount, + release: env.block.time.seconds() + stake_config.unbond_time, + }); + + unbond_queue.save(deps.storage, sender.as_str().as_bytes())?; + + // Store the tx + let symbol = ReadonlyConfig::from_storage(deps.storage) + .constants()? + .symbol; + let mut messages = vec![]; + if !claim.is_zero() { + messages.push(send_msg( + sender.clone(), + claim.into(), + None, + None, + None, + 256, + stake_config.staked_token.code_hash, + stake_config.staked_token.address, + )?); + + store_claim_reward( + deps.storage, + &sender_canon, + claim, + symbol.clone(), + None, + &env.block, + )?; + } + store_unbond( + deps.storage, + &deps.api.canonical_address(&sender)?, + amount, + symbol, + None, + &env.block, + )?; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::Unbond { status: Success })?)) +} + +pub fn try_claim_unbond( + deps: DepsMut, + env: Env, + info: MessageInfo, +) -> StdResult { + let sender = &info.sender; + let sender_canon = &deps.api.canonical_address(sender)?; + + let stake_config = StakeConfig::load(deps.storage)?; + + let mut total_unbonding = TotalUnbonding::load(deps.storage)?; + + // Instead of iterating over it we just look at its smallest value (first in queue) + let daily_unbond_queue = DailyUnbondingQueue::load(deps.storage)?.0; + + // Check if user has an existing queue, if not, instantiate one + let mut unbond_queue = UnbondingQueue::may_load(deps.storage, sender.as_str().as_bytes())? + .expect("No unbonding queue found"); + + let mut total = Uint128::zero(); + // Iterate over the sorted queue + while !unbond_queue.0.0.is_empty() { + // Since the queue is sorted, the moment we find a date above the current then we assume + // that no other item in the queue is eligible + if unbond_queue.0.0[0].release <= env.block.time.seconds() { + // Daily unbond queue is also sorted, therefore as long as its next item is greater + // than the unbond then we assume its funded + if daily_unbond_queue.0.is_empty() + || round_date(unbond_queue.0.0[0].release) < daily_unbond_queue.0[0].release + { + total += unbond_queue.0.0[0].amount; + unbond_queue.0.pop(); + } else { + break; + } + } else { + break; + } + } + + if total == Uint128::zero() { + return Err(StdError::generic_err("Nothing to claim")); + } + + unbond_queue.save(deps.storage, sender.as_str().as_bytes())?; + total_unbonding.0 = total_unbonding.0.checked_sub(total)?; + total_unbonding.save(deps.storage)?; + + let symbol = ReadonlyConfig::from_storage(deps.storage) + .constants()? + .symbol; + store_claim_unbond( + deps.storage, + sender_canon, + total, + symbol, + None, + &env.block, + )?; + + let messages = vec![send_msg( + sender.clone(), + total.into(), + None, + None, + None, + 256, + stake_config.staked_token.code_hash, + stake_config.staked_token.address, + )?]; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::ClaimUnbond { status: Success })?)) +} + +pub fn try_claim_rewards( + deps: DepsMut, + env: Env, + info: MessageInfo, +) -> StdResult { + let stake_config = StakeConfig::load(deps.storage)?; + + let sender = &info.sender; + let sender_canon = &deps.api.canonical_address(sender)?; + + let claim = claim_rewards(deps.storage, &stake_config, sender, sender_canon)?; + + if claim.is_zero() { + return Err(StdError::generic_err("Nothing to claim")); + } + + let messages = vec![send_msg( + sender.clone(), + claim.into(), + None, + None, + None, + 256, + stake_config.staked_token.code_hash, + stake_config.staked_token.address, + )?]; + + let symbol = ReadonlyConfig::from_storage(deps.storage) + .constants()? + .symbol; + store_claim_reward( + deps.storage, + sender_canon, + claim, + symbol, + None, + &env.block, + )?; + + Ok(Response::new().set_data(to_binary(&HandleAnswer::ClaimRewards { status: Success })?)) +} + +pub fn try_stake_rewards( + deps: DepsMut, + env: Env, + info: MessageInfo, +) -> StdResult { + // Clam rewards + let symbol = ReadonlyConfig::from_storage(deps.storage) + .constants()? + .symbol; + let stake_config = StakeConfig::load(deps.storage)?; + + let sender = &info.sender; + let sender_canon = &deps.api.canonical_address(sender)?; + + let claim = claim_rewards(deps.storage, &stake_config, sender, sender_canon)?; + + store_claim_reward( + deps.storage, + sender_canon, + claim, + symbol.clone(), + None, + &env.block, + )?; + + // Stake rewards + // Update user stake + add_balance( + deps.storage, + &stake_config, + sender, + sender_canon, + claim, + )?; + + // Store data + // Store data + store_stake( + deps.storage, + sender_canon, + claim, + symbol, + None, + &env.block, + )?; + + let mut messages = vec![]; + + // Send tokens + if let Some(treasury) = stake_config.treasury { + messages.push(send_msg( + treasury, + claim.into(), + None, + None, + None, + 256, + stake_config.staked_token.code_hash, + stake_config.staked_token.address, + )?); + } else { + let mut stored_tokens = UnsentStakedTokens::load(deps.storage)?; + stored_tokens.0 += claim; + stored_tokens.save(deps.storage)?; + } + + Ok(Response::new().set_data(to_binary(&HandleAnswer::StakeRewards { status: Success })?)) +} + +#[cfg(test)] +mod tests { + use crate::stake::{calculate_rewards, round_date, shares_per_token, tokens_per_share}; + use shade_protocol::{ + contract_interfaces::staking::snip20_staking::stake::StakeConfig, + utils::asset::Contract, + }; + + fn init_config(token_decimals: u8, shares_decimals: u8) -> StakeConfig { + StakeConfig { + unbond_time: 0, + staked_token: Contract { + address: Default::default(), + code_hash: "".to_string(), + }, + decimal_difference: shares_decimals - token_decimals, + treasury: None, + } + } + + #[test] + fn tokens_per_share_test() { + let token_decimals = 8; + let shares_decimals = 18; + let config = init_config(token_decimals, shares_decimals); + + let token_1 = Uint128::new(10000000 * 10u128.pow(token_decimals.into())); + let share_1 = Uint256::from(10000000 * 10u128.pow(shares_decimals.into())); + + // Check for proper instantiate + assert_eq!( + tokens_per_share(&config, &share_1, &Uint128::zero(), &Uint256::zero()).unwrap(), + token_1 + ); + + // Check for stability + assert_eq!( + tokens_per_share(&config, &share_1, &token_1, &share_1).unwrap(), + token_1 + ); + assert_eq!( + tokens_per_share( + &config, + &share_1, + &(token_1 * Uint128::new(2)), + &(share_1 * Uint256::from(2u32)) + ) + .unwrap(), + token_1 + ); + + // check that shares increase when tokens decrease + assert!( + tokens_per_share(&config, &share_1, &(token_1 * Uint128::new(2)), &share_1).unwrap() + > token_1 + ); + + // check that shares decrease when tokens increase + assert!( + tokens_per_share( + &config, + &share_1, + &token_1, + &(share_1 * Uint256::from(2u32)) + ) + .unwrap() + < token_1 + ); + } + + #[test] + fn shares_per_token_test() { + let token_decimals = 8; + let shares_decimals = 18; + let config = init_config(token_decimals, shares_decimals); + + let token_1 = Uint128::new(100 * 10u128.pow(token_decimals.into())); + let share_1 = Uint256::from(100 * 10u128.pow(shares_decimals.into())); + + // Check for proper instantiate + assert_eq!( + shares_per_token(&config, &token_1, &Uint128::zero(), &Uint256::zero()).unwrap(), + share_1 + ); + + // Check for stability + assert_eq!( + shares_per_token(&config, &token_1, &token_1, &share_1).unwrap(), + share_1 + ); + assert_eq!( + shares_per_token( + &config, + &token_1, + &(token_1 * Uint128::new(2)), + &(share_1 * Uint256::from(2u32)) + ) + .unwrap(), + share_1 + ); + + // check that shares increase when tokens decrease + assert!( + shares_per_token(&config, &token_1, &(token_1 * Uint128::new(2)), &share_1).unwrap() + < share_1 + ); + + // check that shares decrease when tokens increase + assert!( + shares_per_token( + &config, + &token_1, + &token_1, + &(share_1 * Uint256::from(2u32)) + ) + .unwrap() + > share_1 + ); + } + + #[test] + fn round_date_test() { + assert_eq!(round_date(1645740448), 1645660800) + } + + #[test] + fn calculate_rewards_test() { + let token_decimals = 8; + let shares_decimals = 18; + let config = init_config(token_decimals, shares_decimals); + + // Tester has 100 tokens + // Other user has 50 + + let u_t = Uint128::new(100 * 10u128.pow(token_decimals.into())); + let mut u_s = Uint256::from(100 * 10u128.pow(shares_decimals.into())); + let mut t_t = Uint128::new(150 * 10u128.pow(token_decimals.into())); + let mut t_s = Uint256::from(150 * 10u128.pow(shares_decimals.into())); + + // No rewards + let (tokens, shares) = calculate_rewards(&config, u_t, u_s, t_t, t_s).unwrap(); + + assert_eq!(tokens, Uint128::zero()); + assert_eq!(shares, Uint256::zero()); + + // Some rewards + // We add 300 tokens, tester should get 200 tokens + let reward = 300 * 10u128.pow(token_decimals.into()); + t_t += Uint128::new(reward); + let (tokens, shares) = calculate_rewards(&config, u_t, u_s, t_t, t_s).unwrap(); + + assert_eq!(tokens.u128(), reward * 2 / 3); + t_t = t_t - tokens; + // We should receive 2/3 of current shares + assert_eq!(shares, u_s * Uint256::from(2u32) / Uint256::from(3u32)); + u_s = u_s - shares; + t_s = t_s - shares; + + // After claiming + let (tokens, shares) = calculate_rewards(&config, u_t, u_s, t_t, t_s).unwrap(); + + assert_eq!(tokens, Uint128::zero()); + assert_eq!(shares, Uint256::zero()); + } + + #[test] + fn simulate_claim_rewards() { + let token_decimals = 8; + let shares_decimals = 18; + let config = init_config(token_decimals, shares_decimals); + let mut user_shares = Uint256::from(50000000000000u128); + + let user_balance = Uint128::new(5000); + + // Get total supplied tokens + let mut total_shares = Uint256::from(50000000000000u128); + let mut total_tokens = Uint128::new(5000); + + let (reward_token, reward_shares) = calculate_rewards( + &config, + user_balance, + user_shares, + total_tokens, + total_shares, + ) + .unwrap(); + + assert_eq!(reward_token, Uint128::zero()); + } + + use shade_protocol::c_std::{Uint128, Uint256}; + use rand::Rng; + + #[test] + fn staking_simulation() { + let token_decimals = 8; + let shares_decimals = 18; + let config = init_config(token_decimals, shares_decimals); + + let mut t_t = Uint128::zero(); + let mut t_s = Uint256::zero(); + let mut rand = rand::thread_rng(); + + let mut stakers = vec![]; + + for _ in 0..10 { + // Generate stakers in this round + for _ in 0..rand.gen_range(1..=4) { + let tokens = + Uint128::new(rand.gen_range(1..100 * 10u128.pow(token_decimals.into()))); + + let shares = shares_per_token(&config, &tokens, &t_t, &t_s).unwrap(); + + stakers.push((tokens, shares)); + + t_t += tokens; + t_s += shares; + } + + // Add random rewards + t_t += Uint128::new(rand.gen_range(1u128..t_t.u128() / 2u128)); + + // Claim and unstake + for _ in 0..rand.gen_range(0..=stakers.len() / 2) { + let (mut tokens, mut shares) = stakers.remove(rand.gen_range(0..stakers.len())); + let (r_tokens, r_shares) = + calculate_rewards(&config, tokens, shares, t_t, t_s).unwrap(); + + t_t -= r_tokens; + t_s -= r_shares; + shares -= r_shares; + + let (r_tokens, r_shares) = + calculate_rewards(&config, tokens, shares, t_t, t_s).unwrap(); + assert_eq!(r_tokens, Uint128::zero()); + assert_eq!(r_shares, Uint256::zero()); + + // Unstake + t_t -= tokens; + t_s -= shares; + } + + // Claim the rest + while !stakers.is_empty() { + let (mut tokens, mut shares) = stakers.pop().unwrap(); + let (r_tokens, r_shares) = + calculate_rewards(&config, tokens, shares, t_t, t_s).unwrap(); + + t_t -= r_tokens; + t_s -= r_shares; + shares -= r_shares; + + let (r_tokens, r_shares) = + calculate_rewards(&config, tokens, shares, t_t, t_s).unwrap(); + assert_eq!(r_tokens, Uint128::zero()); + assert_eq!(r_shares, Uint256::zero()); + } + } + } +} diff --git a/archived-contracts/snip20_staking/src/stake_queries.rs b/archived-contracts/snip20_staking/src/stake_queries.rs new file mode 100644 index 0000000..5d71bcd --- /dev/null +++ b/archived-contracts/snip20_staking/src/stake_queries.rs @@ -0,0 +1,126 @@ +use crate::{ + msg::QueryAnswer, + stake::{calculate_rewards, shares_per_token}, + state::ReadonlyBalances, + state_staking::{ + DailyUnbondingQueue, + TotalShares, + TotalTokens, + TotalUnbonding, + UnbondingQueue, + UserCooldown, + UserShares, + }, +}; +use shade_protocol::c_std::Uint128; +use shade_protocol::c_std::{to_binary, Api, Binary, DepsMut, Addr, Querier, StdResult, Storage}; +use shade_protocol::{ + contract_interfaces::staking::snip20_staking::stake::{StakeConfig, VecQueue}, + utils::storage::default::{BucketStorage, SingletonStorage}, +}; + +pub fn stake_config(deps: Deps) -> StdResult { + to_binary(&QueryAnswer::StakedConfig { + config: StakeConfig::load(deps.storage)?, + }) +} + +pub fn total_staked(deps: Deps) -> StdResult { + to_binary(&QueryAnswer::TotalStaked { + tokens: TotalTokens::load(deps.storage)?.0, + shares: TotalShares::load(deps.storage)?.0, + }) +} + +pub fn stake_rate(deps: Deps) -> StdResult { + to_binary(&QueryAnswer::StakeRate { + shares: shares_per_token( + &StakeConfig::load(deps.storage)?, + &Uint128::new(1), + &TotalTokens::load(deps.storage)?.0, + &TotalShares::load(deps.storage)?.0, + )?, + }) +} + +pub fn unfunded( + deps: Deps, + start: u64, + total: u64, +) -> StdResult { + let mut total_bonded = Uint128::zero(); + + let queue = DailyUnbondingQueue::load(deps.storage)?.0; + + let mut count = 0; + for item in queue.0.iter() { + if item.release >= start { + if count >= total { + break; + } + total_bonded += item.unbonding.checked_sub(item.funded)?; + count += 1; + } + } + + to_binary(&QueryAnswer::Unfunded { + total: total_bonded, + }) +} + +pub fn unbonding(deps: Deps) -> StdResult { + to_binary(&QueryAnswer::Unbonding { + total: TotalUnbonding::load(deps.storage)?.0, + }) +} + +pub fn staked( + deps: Deps, + account: Addr, + time: Option, +) -> StdResult { + let tokens = ReadonlyBalances::from_storage(deps.storage) + .account_amount(&deps.api.canonical_address(&account)?); + + let shares = UserShares::load(deps.storage, account.as_str().as_bytes())?.0; + + let (rewards, _) = calculate_rewards( + &StakeConfig::load(deps.storage)?, + Uint128::new(tokens), + shares, + TotalTokens::load(deps.storage)?.0, + TotalShares::load(deps.storage)?.0, + )?; + + let queue = UnbondingQueue::may_load(deps.storage, account.as_str().as_bytes())? + .unwrap_or_else(|| UnbondingQueue(VecQueue::new(vec![]))); + + let mut unbonding = Uint128::zero(); + let mut unbonded = Uint128::zero(); + + for item in queue.0.0.iter() { + if let Some(time) = time { + if item.release <= time { + unbonded += item.amount; + } else { + unbonding += item.amount; + } + } else { + unbonding += item.amount; + } + } + + to_binary(&QueryAnswer::Staked { + tokens: Uint128::new(tokens), + shares, + pending_rewards: rewards, + unbonding, + unbonded: time.map(|_| unbonded), + cooldown: UserCooldown::may_load(deps.storage, account.as_str().as_bytes())? + .unwrap_or(UserCooldown { + total: Default::default(), + queue: VecQueue(vec![]), + }) + .queue, + }) +} diff --git a/archived-contracts/snip20_staking/src/state.rs b/archived-contracts/snip20_staking/src/state.rs new file mode 100644 index 0000000..168a2c3 --- /dev/null +++ b/archived-contracts/snip20_staking/src/state.rs @@ -0,0 +1,389 @@ +use std::{any::type_name, convert::TryFrom}; + +use shade_protocol::c_std::{CanonicalAddr, Addr, ReadonlyStorage, StdError, StdResult, Storage}; +use shade_protocol::storage::{PrefixedStorage, ReadonlyPrefixedStorage}; + +use shade_protocol::secret_toolkit::storage::{TypedStore, TypedStoreMut}; + + +use shade_protocol::cosmwasm_schema::cw_serde; + +use crate::{ + msg::{status_level_to_u8, u8_to_status_level, ContractStatusLevel}, + viewing_key::ViewingKey, +}; +use shade_protocol::serde::de::DeserializeOwned; + +// Snip20 +pub static CONFIG_KEY: &[u8] = b"config"; +pub const PREFIX_TXS: &[u8] = b"transfers"; + +pub const KEY_CONSTANTS: &[u8] = b"constants"; +pub const KEY_TOTAL_SUPPLY: &[u8] = b"total_supply"; +pub const KEY_CONTRACT_STATUS: &[u8] = b"contract_status"; +pub const KEY_MINTERS: &[u8] = b"minters"; +pub const KEY_TX_COUNT: &[u8] = b"tx-count"; + +pub const PREFIX_CONFIG: &[u8] = b"config"; +pub const PREFIX_BALANCES: &[u8] = b"balances"; +pub const PREFIX_ALLOWANCES: &[u8] = b"allowances"; +pub const PREFIX_VIEW_KEY: &[u8] = b"viewingkey"; +pub const PREFIX_RECEIVERS: &[u8] = b"receivers"; + +// Config + +#[derive(Serialize, Debug, Deserialize, Clone, PartialEq)] +pub struct Constants { + pub name: String, + pub admin: Addr, + pub symbol: String, + pub decimals: u8, + pub prng_seed: Vec, + // privacy configuration + pub total_supply_is_public: bool, + // the address of this contract, used to validate query permits + pub contract_address: Addr, +} + +pub struct ReadonlyConfig<'a> { + storage: ReadonlyPrefixedStorage<'a>, +} + +impl<'a, S: Storage> ReadonlyConfig<'a> { + pub fn from_storage(storage: &'a mut dyn Storage) -> Self { + Self { + storage: ReadonlyPrefixedStorage::new(PREFIX_CONFIG, storage), + } + } + + fn as_readonly(&self) -> ReadonlyConfigImpl { + ReadonlyConfigImpl(self.storage) + } + + pub fn constants(&self) -> StdResult { + self.as_readonly().constants() + } + + pub fn total_supply(&self) -> u128 { + self.as_readonly().total_supply() + } + + pub fn contract_status(&self) -> ContractStatusLevel { + self.as_readonly().contract_status() + } + + pub fn minters(&self) -> Vec { + self.as_readonly().minters() + } + + pub fn tx_count(&self) -> u64 { + self.as_readonly().tx_count() + } +} + +fn ser_bin_data(obj: &T) -> StdResult> { + bincode2::serialize(&obj).map_err(|e| StdError::serialize_err(type_name::(), e)) +} + +fn deser_bin_data(data: &[u8]) -> StdResult { + bincode2::deserialize::(data).map_err(|e| StdError::serialize_err(type_name::(), e)) +} + +fn set_bin_data(storage: &mut dyn Storage, key: &[u8], data: &T) -> StdResult<()> { + let bin_data = ser_bin_data(data)?; + + storage.set(key, &bin_data); + Ok(()) +} + +fn get_bin_data(storage: &dyn Storage, key: &[u8]) -> StdResult { + let bin_data = storage.get(key); + + match bin_data { + None => Err(StdError::not_found("Key not found in storage")), + Some(bin_data) => Ok(deser_bin_data(&bin_data)?), + } +} + +pub struct Config<'a> { + storage: PrefixedStorage<'a>, +} + +impl<'a> Config<'a> { + pub fn from_storage(storage: &mut dyn Storage) -> Self { + Self { + storage: PrefixedStorage::new(PREFIX_CONFIG, storage), + } + } + + fn as_readonly(&self) -> ReadonlyConfigImpl> { + ReadonlyConfigImpl(&self.storage) + } + + pub fn constants(&self) -> StdResult { + self.as_readonly().constants() + } + + pub fn set_constants(&mut self, constants: &Constants) -> StdResult<()> { + set_bin_data(&mut self.storage, KEY_CONSTANTS, constants) + } + + pub fn total_supply(&self) -> u128 { + self.as_readonly().total_supply() + } + + pub fn set_total_supply(&mut self, supply: u128) { + self.storage.set(KEY_TOTAL_SUPPLY, &supply.to_be_bytes()); + } + + pub fn contract_status(&self) -> ContractStatusLevel { + self.as_readonly().contract_status() + } + + pub fn set_contract_status(&mut self, status: ContractStatusLevel) { + let status_u8 = status_level_to_u8(status); + self.storage + .set(KEY_CONTRACT_STATUS, &status_u8.to_be_bytes()); + } + + pub fn set_minters(&mut self, minters_to_set: Vec) -> StdResult<()> { + set_bin_data(&mut self.storage, KEY_MINTERS, &minters_to_set) + } + + pub fn add_minters(&mut self, minters_to_add: Vec) -> StdResult<()> { + let mut minters = self.minters(); + minters.extend(minters_to_add); + + self.set_minters(minters) + } + + pub fn remove_minters(&mut self, minters_to_remove: Vec) -> StdResult<()> { + let mut minters = self.minters(); + + for minter in minters_to_remove { + minters.retain(|x| x != &minter); + } + + self.set_minters(minters) + } + + pub fn minters(&mut self) -> Vec { + self.as_readonly().minters() + } + + pub fn tx_count(&self) -> u64 { + self.as_readonly().tx_count() + } + + pub fn set_tx_count(&mut self, count: u64) -> StdResult<()> { + set_bin_data(&mut self.storage, KEY_TX_COUNT, &count) + } +} + +/// This struct refactors out the readonly methods that we need for `Config` and `ReadonlyConfig` +/// in a way that is generic over their mutability. +/// +/// This was the only way to prevent code duplication of these methods because of the way +/// that `ReadonlyPrefixedStorage` and `PrefixedStorage` are implemented in `cosmwasm-std` +struct ReadonlyConfigImpl<'a, S: ReadonlyStorage>(&'a mut dyn Storage); + +impl<'a, S: ReadonlyStorage> ReadonlyConfigImpl<'a> { + fn constants(&self) -> StdResult { + let consts_bytes = self + .0 + .get(KEY_CONSTANTS) + .ok_or_else(|| StdError::generic_err("no constants stored in configuration"))?; + bincode2::deserialize::(&consts_bytes) + .map_err(|e| StdError::serialize_err(type_name::(), e)) + } + + fn total_supply(&self) -> u128 { + let supply_bytes = self + .0 + .get(KEY_TOTAL_SUPPLY) + .expect("no total supply stored in config"); + // This unwrap is ok because we know we stored things correctly + slice_to_u128(&supply_bytes).unwrap() + } + + fn contract_status(&self) -> ContractStatusLevel { + let supply_bytes = self + .0 + .get(KEY_CONTRACT_STATUS) + .expect("no contract status stored in config"); + + // These unwraps are ok because we know we stored things correctly + let status = slice_to_u8(&supply_bytes).unwrap(); + u8_to_status_level(status).unwrap() + } + + fn minters(&self) -> Vec { + get_bin_data(self.0, KEY_MINTERS).unwrap() + } + + pub fn tx_count(&self) -> u64 { + get_bin_data(self.0, KEY_TX_COUNT).unwrap_or_default() + } +} + +// Balances + +pub struct ReadonlyBalances<'a, S: ReadonlyStorage> { + storage: ReadonlyPrefixedStorage<'a>, +} + +impl<'a, S: ReadonlyStorage> ReadonlyBalances<'a> { + pub fn from_storage(storage: &'a mut dyn Storage) -> Self { + Self { + storage: ReadonlyPrefixedStorage::new(PREFIX_BALANCES, storage), + } + } + + fn as_readonly(&self) -> ReadonlyBalancesImpl> { + ReadonlyBalancesImpl(&self.storage) + } + + pub fn account_amount(&self, account: &CanonicalAddr) -> u128 { + self.as_readonly().account_amount(account) + } +} + +pub struct Balances<'a> { + storage: PrefixedStorage<'a>, +} + +impl<'a> Balances<'a> { + pub fn from_storage(storage: &mut dyn Storage) -> Self { + Self { + storage: PrefixedStorage::new(PREFIX_BALANCES, storage), + } + } + + fn as_readonly(&self) -> ReadonlyBalancesImpl> { + ReadonlyBalancesImpl(&self.storage) + } + + pub fn balance(&self, account: &CanonicalAddr) -> u128 { + self.as_readonly().account_amount(account) + } + + pub fn set_account_balance(&mut self, account: &CanonicalAddr, amount: u128) { + self.storage.set(account.as_slice(), &amount.to_be_bytes()) + } +} + +/// This struct refactors out the readonly methods that we need for `Balances` and `ReadonlyBalances` +/// in a way that is generic over their mutability. +/// +/// This was the only way to prevent code duplication of these methods because of the way +/// that `ReadonlyPrefixedStorage` and `PrefixedStorage` are implemented in `cosmwasm-std` +struct ReadonlyBalancesImpl<'a, S: ReadonlyStorage>(&'a mut dyn Storage); + +impl<'a, S: ReadonlyStorage> ReadonlyBalancesImpl<'a> { + pub fn account_amount(&self, account: &CanonicalAddr) -> u128 { + let account_bytes = account.as_slice(); + let result = self.0.get(account_bytes); + match result { + // This unwrap is ok because we know we stored things correctly + Some(balance_bytes) => slice_to_u128(&balance_bytes).unwrap(), + None => 0, + } + } +} + +// Allowances + +#[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Default)] +pub struct Allowance { + pub amount: u128, + pub expiration: Option, +} + +impl Allowance { + pub fn is_expired_at(&self, block: &shade_protocol::c_std::BlockInfo) -> bool { + match self.expiration { + Some(time) => block.time >= time, + None => false, // allowance has no expiration + } + } +} + +pub fn read_allowance( + store: &dyn Storage, + owner: &CanonicalAddr, + spender: &CanonicalAddr, +) -> StdResult { + let owner_store = + ReadonlyPrefixedStorage::multilevel(&[PREFIX_ALLOWANCES, owner.as_slice()], store); + let owner_store = TypedStore::attach(&owner_store); + let allowance = owner_store.may_load(spender.as_slice()); + allowance.map(Option::unwrap_or_default) +} + +pub fn write_allowance( + store: &mut dyn Storage, + owner: &CanonicalAddr, + spender: &CanonicalAddr, + allowance: Allowance, +) -> StdResult<()> { + let mut owner_store = + PrefixedStorage::multilevel(&[PREFIX_ALLOWANCES, owner.as_slice()], store); + let mut owner_store = TypedStoreMut::attach(&mut owner_store); + + owner_store.store(spender.as_slice(), &allowance) +} + +// Viewing Keys + +pub fn write_viewing_key(store: &mut dyn Storage, owner: &CanonicalAddr, key: &ViewingKey) { + let mut balance_store = PrefixedStorage::new(PREFIX_VIEW_KEY, store); + balance_store.set(owner.as_slice(), &key.to_hashed()); +} + +pub fn read_viewing_key(store: &dyn Storage, owner: &CanonicalAddr) -> Option> { + let balance_store = ReadonlyPrefixedStorage::new(PREFIX_VIEW_KEY, store); + balance_store.get(owner.as_slice()) +} + +// Receiver Interface + +pub fn get_receiver_hash( + store: &dyn Storage, + account: &Addr, +) -> Option> { + let store = ReadonlyPrefixedStorage::new(PREFIX_RECEIVERS, store); + store.get(account.as_str().as_bytes()).map(|data| { + String::from_utf8(data) + .map_err(|_err| StdError::invalid_utf8("stored code hash was not a valid String")) + }) +} + +pub fn set_receiver_hash(store: &mut dyn Storage, account: &Addr, code_hash: String) { + let mut store = PrefixedStorage::new(PREFIX_RECEIVERS, store); + store.set(account.as_str().as_bytes(), code_hash.as_bytes()); +} + +// Helpers + +/// Converts 16 bytes value into u128 +/// Errors if data found that is not 16 bytes +fn slice_to_u128(data: &[u8]) -> StdResult { + match <[u8; 16]>::try_from(data) { + Ok(bytes) => Ok(u128::from_be_bytes(bytes)), + Err(_) => Err(StdError::generic_err( + "Corrupted data found. 16 byte expected.", + )), + } +} + +/// Converts 1 byte value into u8 +/// Errors if data found that is not 1 byte +fn slice_to_u8(data: &[u8]) -> StdResult { + if data.len() == 1 { + Ok(data[0]) + } else { + Err(StdError::generic_err( + "Corrupted data found. 1 byte expected.", + )) + } +} diff --git a/archived-contracts/snip20_staking/src/state_staking.rs b/archived-contracts/snip20_staking/src/state_staking.rs new file mode 100644 index 0000000..409a6f6 --- /dev/null +++ b/archived-contracts/snip20_staking/src/state_staking.rs @@ -0,0 +1,135 @@ +use shade_protocol::c_std::{Uint128, Uint256}; +use shade_protocol::c_std::Addr; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use shade_protocol::{ + contract_interfaces::staking::snip20_staking::stake::{ + Cooldown, + DailyUnbonding, + Unbonding, + VecQueue, + }, + utils::storage::default::{BucketStorage, SingletonStorage}, +}; + +// used to determine what each token is worth to calculate rewards +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct TotalShares(pub Uint256); + +impl SingletonStorage for TotalShares { + const NAMESPACE: &'static [u8] = b"total_shares"; +} + +// used to separate tokens minted from total tokens (includes rewards) +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct TotalTokens(pub Uint128); + +impl SingletonStorage for TotalTokens { + const NAMESPACE: &'static [u8] = b"total_tokens"; +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct UserShares(pub Uint256); + +impl BucketStorage for UserShares { + const NAMESPACE: &'static [u8] = b"user_shares"; +} + +// stores received token info if no treasury is set +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct UnsentStakedTokens(pub Uint128); + +impl SingletonStorage for UnsentStakedTokens { + const NAMESPACE: &'static [u8] = b"unsent_staked_tokens"; +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct TotalUnbonding(pub Uint128); + +impl SingletonStorage for TotalUnbonding { + const NAMESPACE: &'static [u8] = b"total_unbonding"; +} + +// Distributors wrappers + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct Distributors(pub Vec); + +impl SingletonStorage for Distributors { + const NAMESPACE: &'static [u8] = b"distributors"; +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct DistributorsEnabled(pub bool); + +impl SingletonStorage for DistributorsEnabled { + const NAMESPACE: &'static [u8] = b"distributors_transfer"; +} + +// Unbonding Queues + +#[cw_serde] +pub struct UnbondingQueue(pub VecQueue); + +impl BucketStorage for UnbondingQueue { + const NAMESPACE: &'static [u8] = b"unbonding_queue"; +} + +#[cw_serde] +pub struct DailyUnbondingQueue(pub VecQueue); + +impl SingletonStorage for DailyUnbondingQueue { + const NAMESPACE: &'static [u8] = b"daily_unbonding_queue"; +} + +// Used for vote cooldown after send +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct UserCooldown { + pub total: Uint128, + pub queue: VecQueue, +} + +impl BucketStorage for UserCooldown { + const NAMESPACE: &'static [u8] = b"user_cooldown"; +} + +impl UserCooldown { + pub fn add_cooldown(&mut self, cooldown: Cooldown) { + self.total += cooldown.amount; + self.queue.push(&cooldown); + } + + pub fn remove_cooldown(&mut self, amount: Uint128) { + let mut remaining = amount; + while remaining != Uint128::zero() { + let index = self.queue.0.len() - 1; + if self.queue.0[index].amount <= remaining { + let item = self.queue.0.remove(index); + remaining = remaining.checked_sub(item.amount).unwrap(); + } else { + self.queue.0[index].amount = + self.queue.0[index].amount.checked_sub(remaining).unwrap(); + break; + } + } + } + + pub fn update(&mut self, time: u64) { + while !self.queue.0.is_empty() { + if self.queue.0[0].release <= time { + let i = self.queue.pop().unwrap(); + self.total = self.total.checked_sub(i.amount).unwrap(); + } else { + break; + } + } + } +} diff --git a/archived-contracts/snip20_staking/src/transaction_history.rs b/archived-contracts/snip20_staking/src/transaction_history.rs new file mode 100644 index 0000000..4c54b03 --- /dev/null +++ b/archived-contracts/snip20_staking/src/transaction_history.rs @@ -0,0 +1,723 @@ + +use shade_protocol::cosmwasm_schema::cw_serde; + +use shade_protocol::c_std::{ + Api, + CanonicalAddr, + Coin, + Addr, + ReadonlyStorage, + StdError, + StdResult, + Storage, +}; +use shade_protocol::storage::{PrefixedStorage, ReadonlyPrefixedStorage}; + +use shade_protocol::c_std::Uint128; +use shade_protocol::secret_toolkit::storage::{AppendStore, AppendStoreMut}; + +use crate::state::Config; + +const PREFIX_TXS: &[u8] = b"transactions"; +const PREFIX_TRANSFERS: &[u8] = b"transfers"; + +// Note that id is a globally incrementing counter. +// Since it's 64 bits long, even at 50 tx/s it would take +// over 11 billion years for it to rollback. I'm pretty sure +// we'll have bigger issues by then. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Tx { + pub id: u64, + pub from: Addr, + pub sender: Addr, + pub receiver: Addr, + pub coins: Coin, + #[serde(skip_serializing_if = "Option::is_none")] + pub memo: Option, + // The block time and block height are optional so that the JSON schema + // reflects that some SNIP-20 contracts may not include this info. + pub block_time: Option, + pub block_height: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum TxAction { + Transfer { + from: Addr, + sender: Addr, + recipient: Addr, + }, + Mint { + minter: Addr, + recipient: Addr, + }, + Burn { + burner: Addr, + owner: Addr, + }, + Deposit {}, + Redeem {}, + Stake { + staker: Addr, + }, + AddReward { + funder: Addr, + }, + FundUnbond { + funder: Addr, + }, + Unbond { + staker: Addr, + }, + ClaimUnbond { + staker: Addr, + }, + ClaimReward { + staker: Addr, + }, +} + +// Note that id is a globally incrementing counter. +// Since it's 64 bits long, even at 50 tx/s it would take +// over 11 billion years for it to rollback. I'm pretty sure +// we'll have bigger issues by then. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct RichTx { + pub id: u64, + pub action: TxAction, + pub coins: Coin, + #[serde(skip_serializing_if = "Option::is_none")] + pub memo: Option, + pub block_time: u64, + pub block_height: u64, +} + +// Stored types: + +/// This type is the stored version of the legacy transfers +#[cw_serde] +struct StoredLegacyTransfer { + id: u64, + from: CanonicalAddr, + sender: CanonicalAddr, + receiver: CanonicalAddr, + coins: Coin, + memo: Option, + block_time: u64, + block_height: u64, +} + +impl StoredLegacyTransfer { + pub fn into_humanized(self, api: &dyn Api) -> StdResult { + let tx = Tx { + id: self.id, + from: api.human_address(&self.from)?, + sender: api.human_address(&self.sender)?, + receiver: api.human_address(&self.receiver)?, + coins: self.coins, + memo: self.memo, + block_time: Some(self.block_time), + block_height: Some(self.block_height), + }; + Ok(tx) + } +} + +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +enum TxCode { + Transfer = 0, + Mint = 1, + Burn = 2, + Deposit = 3, + Redeem = 4, + Stake = 5, + AddReward = 6, + FundUnbond = 7, + Unbond = 8, + ClaimUnbond = 9, + ClaimReward = 10, +} + +impl TxCode { + fn to_u8(self) -> u8 { + self as u8 + } + + fn from_u8(n: u8) -> StdResult { + use TxCode::*; + match n { + 0 => Ok(Transfer), + 1 => Ok(Mint), + 2 => Ok(Burn), + 3 => Ok(Deposit), + 4 => Ok(Redeem), + 5 => Ok(Stake), + 6 => Ok(AddReward), + 7 => Ok(FundUnbond), + 8 => Ok(Unbond), + 9 => Ok(ClaimUnbond), + 10 => Ok(ClaimReward), + other => Err(StdError::generic_err(format!( + "Unexpected Tx code in transaction history: {} Storage is corrupted.", + other + ))), + } + } +} + +#[cw_serde] +struct StoredTxAction { + tx_type: u8, + address1: Option, + address2: Option, + address3: Option, +} + +impl StoredTxAction { + fn transfer(from: CanonicalAddr, sender: CanonicalAddr, recipient: CanonicalAddr) -> Self { + Self { + tx_type: TxCode::Transfer.to_u8(), + address1: Some(from), + address2: Some(sender), + address3: Some(recipient), + } + } + + fn mint(minter: CanonicalAddr, recipient: CanonicalAddr) -> Self { + Self { + tx_type: TxCode::Mint.to_u8(), + address1: Some(minter), + address2: Some(recipient), + address3: None, + } + } + + fn burn(owner: CanonicalAddr, burner: CanonicalAddr) -> Self { + Self { + tx_type: TxCode::Burn.to_u8(), + address1: Some(burner), + address2: Some(owner), + address3: None, + } + } + + fn deposit() -> Self { + Self { + tx_type: TxCode::Deposit.to_u8(), + address1: None, + address2: None, + address3: None, + } + } + + fn stake(staker: CanonicalAddr) -> Self { + Self { + tx_type: TxCode::Stake.to_u8(), + address1: Some(staker), + address2: None, + address3: None, + } + } + + fn add_reward(funder: CanonicalAddr) -> Self { + Self { + tx_type: TxCode::AddReward.to_u8(), + address1: Some(funder), + address2: None, + address3: None, + } + } + + fn fund_unbond(funder: CanonicalAddr) -> Self { + Self { + tx_type: TxCode::FundUnbond.to_u8(), + address1: Some(funder), + address2: None, + address3: None, + } + } + + fn unbond(staker: CanonicalAddr) -> Self { + Self { + tx_type: TxCode::Unbond.to_u8(), + address1: Some(staker), + address2: None, + address3: None, + } + } + + fn claim_unbond(staker: CanonicalAddr) -> Self { + Self { + tx_type: TxCode::ClaimUnbond.to_u8(), + address1: Some(staker), + address2: None, + address3: None, + } + } + + fn claim_reward(staker: CanonicalAddr) -> Self { + Self { + tx_type: TxCode::ClaimReward.to_u8(), + address1: Some(staker), + address2: None, + address3: None, + } + } + + fn into_humanized(self, api: &dyn Api) -> StdResult { + let transfer_addr_err = || { + StdError::generic_err( + "Missing address in stored Transfer transaction. Storage is corrupt", + ) + }; + let mint_addr_err = || { + StdError::generic_err("Missing address in stored Mint transaction. Storage is corrupt") + }; + let burn_addr_err = || { + StdError::generic_err("Missing address in stored Burn transaction. Storage is corrupt") + }; + let staker_addr_err = || { + StdError::generic_err("Missing address in stored Stake transaction. Storage is corrupt") + }; + + // In all of these, we ignore fields that we don't expect to find populated + let action = match TxCode::from_u8(self.tx_type)? { + TxCode::Transfer => { + let from = self.address1.ok_or_else(transfer_addr_err)?; + let sender = self.address2.ok_or_else(transfer_addr_err)?; + let recipient = self.address3.ok_or_else(transfer_addr_err)?; + let from = api.human_address(&from)?; + let sender = api.human_address(&sender)?; + let recipient = api.human_address(&recipient)?; + TxAction::Transfer { + from, + sender, + recipient, + } + } + TxCode::Mint => { + let minter = self.address1.ok_or_else(mint_addr_err)?; + let recipient = self.address2.ok_or_else(mint_addr_err)?; + let minter = api.human_address(&minter)?; + let recipient = api.human_address(&recipient)?; + TxAction::Mint { minter, recipient } + } + TxCode::Burn => { + let burner = self.address1.ok_or_else(burn_addr_err)?; + let owner = self.address2.ok_or_else(burn_addr_err)?; + let burner = api.human_address(&burner)?; + let owner = api.human_address(&owner)?; + TxAction::Burn { burner, owner } + } + TxCode::Deposit => TxAction::Deposit {}, + TxCode::Redeem => TxAction::Redeem {}, + TxCode::Stake => { + let staker = self.address1.ok_or_else(staker_addr_err)?; + let staker = api.human_address(&staker)?; + TxAction::Stake { staker } + } + TxCode::AddReward => { + let funder = self.address1.ok_or_else(staker_addr_err)?; + let funder = api.human_address(&funder)?; + TxAction::AddReward { funder } + } + TxCode::FundUnbond => { + let funder = self.address1.ok_or_else(staker_addr_err)?; + let funder = api.human_address(&funder)?; + TxAction::FundUnbond { funder } + } + TxCode::Unbond => { + let staker = self.address1.ok_or_else(staker_addr_err)?; + let staker = api.human_address(&staker)?; + TxAction::Unbond { staker } + } + TxCode::ClaimUnbond => { + let staker = self.address1.ok_or_else(staker_addr_err)?; + let staker = api.human_address(&staker)?; + TxAction::ClaimUnbond { staker } + } + TxCode::ClaimReward => { + let staker = self.address1.ok_or_else(staker_addr_err)?; + let staker = api.human_address(&staker)?; + TxAction::ClaimReward { staker } + } + }; + + Ok(action) + } +} + +#[cw_serde] +struct StoredRichTx { + id: u64, + action: StoredTxAction, + coins: Coin, + memo: Option, + block_time: u64, + block_height: u64, +} + +impl StoredRichTx { + fn new( + id: u64, + action: StoredTxAction, + coins: Coin, + memo: Option, + block: &shade_protocol::c_std::BlockInfo, + ) -> Self { + Self { + id, + action, + coins, + memo, + block_time: block.time, + block_height: block.height, + } + } + + fn into_humanized(self, api: &dyn Api) -> StdResult { + Ok(RichTx { + id: self.id, + action: self.action.into_humanized(api)?, + coins: self.coins, + memo: self.memo, + block_time: self.block_time, + block_height: self.block_height, + }) + } + + fn from_stored_legacy_transfer(transfer: StoredLegacyTransfer) -> Self { + let action = StoredTxAction::transfer(transfer.from, transfer.sender, transfer.receiver); + Self { + id: transfer.id, + action, + coins: transfer.coins, + memo: transfer.memo, + block_time: transfer.block_time, + block_height: transfer.block_height, + } + } +} + +// Storage functions: + +fn increment_tx_count(store: &mut dyn Storage) -> StdResult { + let mut config = Config::from_storage(store); + let id = config.tx_count() + 1; + config.set_tx_count(id)?; + Ok(id) +} + +#[allow(clippy::too_many_arguments)] // We just need them +pub fn store_transfer( + store: &mut dyn Storage, + owner: &CanonicalAddr, + sender: &CanonicalAddr, + receiver: &CanonicalAddr, + amount: Uint128, + denom: String, + memo: Option, + block: &shade_protocol::c_std::BlockInfo, +) -> StdResult<()> { + let id = increment_tx_count(store)?; + let coins = Coin { + denom, + amount: amount.into(), + }; + let transfer = StoredLegacyTransfer { + id, + from: owner.clone(), + sender: sender.clone(), + receiver: receiver.clone(), + coins, + memo, + block_time: block.time, + block_height: block.height, + }; + let tx = StoredRichTx::from_stored_legacy_transfer(transfer.clone()); + + // Write to the owners history if it's different from the other two addresses + if owner != sender && owner != receiver { + // shade_protocol::c_std::debug_print("saving transaction history for owner"); + append_tx(store, &tx, owner)?; + append_transfer(store, &transfer, owner)?; + } + // Write to the sender's history if it's different from the receiver + if sender != receiver { + // shade_protocol::c_std::debug_print("saving transaction history for sender"); + append_tx(store, &tx, sender)?; + append_transfer(store, &transfer, sender)?; + } + // Always write to the recipient's history + // shade_protocol::c_std::debug_print("saving transaction history for receiver"); + append_tx(store, &tx, receiver)?; + append_transfer(store, &transfer, receiver)?; + + Ok(()) +} + +pub fn store_mint( + store: &mut dyn Storage, + minter: &CanonicalAddr, + recipient: &CanonicalAddr, + amount: Uint128, + denom: String, + memo: Option, + block: &shade_protocol::c_std::BlockInfo, +) -> StdResult<()> { + let id = increment_tx_count(store)?; + let coins = Coin { + denom, + amount: amount.into(), + }; + let action = StoredTxAction::mint(minter.clone(), recipient.clone()); + let tx = StoredRichTx::new(id, action, coins, memo, block); + + if minter != recipient { + append_tx(store, &tx, recipient)?; + } + append_tx(store, &tx, minter)?; + + Ok(()) +} + +pub fn store_burn( + store: &mut dyn Storage, + owner: &CanonicalAddr, + burner: &CanonicalAddr, + amount: Uint128, + denom: String, + memo: Option, + block: &shade_protocol::c_std::BlockInfo, +) -> StdResult<()> { + let id = increment_tx_count(store)?; + let coins = Coin { + denom, + amount: amount.into(), + }; + let action = StoredTxAction::burn(owner.clone(), burner.clone()); + let tx = StoredRichTx::new(id, action, coins, memo, block); + + if burner != owner { + append_tx(store, &tx, owner)?; + } + append_tx(store, &tx, burner)?; + + Ok(()) +} + +pub fn store_stake( + store: &mut dyn Storage, + staker: &CanonicalAddr, + amount: Uint128, + denom: String, + memo: Option, + block: &shade_protocol::c_std::BlockInfo, +) -> StdResult<()> { + let id = increment_tx_count(store)?; + let coins = Coin { + denom, + amount: amount.into(), + }; + let action = StoredTxAction::stake(staker.clone()); + let tx = StoredRichTx::new(id, action, coins, memo, block); + + append_tx(store, &tx, staker)?; + + Ok(()) +} + +pub fn store_add_reward( + store: &mut dyn Storage, + staker: &CanonicalAddr, + amount: Uint128, + denom: String, + memo: Option, + block: &shade_protocol::c_std::BlockInfo, +) -> StdResult<()> { + let id = increment_tx_count(store)?; + let coins = Coin { + denom, + amount: amount.into(), + }; + let action = StoredTxAction::add_reward(staker.clone()); + let tx = StoredRichTx::new(id, action, coins, memo, block); + + append_tx(store, &tx, staker)?; + + Ok(()) +} + +pub fn store_fund_unbond( + store: &mut dyn Storage, + staker: &CanonicalAddr, + amount: Uint128, + denom: String, + memo: Option, + block: &shade_protocol::c_std::BlockInfo, +) -> StdResult<()> { + let id = increment_tx_count(store)?; + let coins = Coin { + denom, + amount: amount.into(), + }; + let action = StoredTxAction::fund_unbond(staker.clone()); + let tx = StoredRichTx::new(id, action, coins, memo, block); + + append_tx(store, &tx, staker)?; + + Ok(()) +} + +pub fn store_unbond( + store: &mut dyn Storage, + staker: &CanonicalAddr, + amount: Uint128, + denom: String, + memo: Option, + block: &shade_protocol::c_std::BlockInfo, +) -> StdResult<()> { + let id = increment_tx_count(store)?; + let coins = Coin { + denom, + amount: amount.into(), + }; + let action = StoredTxAction::unbond(staker.clone()); + let tx = StoredRichTx::new(id, action, coins, memo, block); + + append_tx(store, &tx, staker)?; + + Ok(()) +} + +pub fn store_claim_unbond( + store: &mut dyn Storage, + staker: &CanonicalAddr, + amount: Uint128, + denom: String, + memo: Option, + block: &shade_protocol::c_std::BlockInfo, +) -> StdResult<()> { + let id = increment_tx_count(store)?; + let coins = Coin { + denom, + amount: amount.into(), + }; + let action = StoredTxAction::claim_unbond(staker.clone()); + let tx = StoredRichTx::new(id, action, coins, memo, block); + + append_tx(store, &tx, staker)?; + + Ok(()) +} + +pub fn store_claim_reward( + store: &mut dyn Storage, + staker: &CanonicalAddr, + amount: Uint128, + denom: String, + memo: Option, + block: &shade_protocol::c_std::BlockInfo, +) -> StdResult<()> { + let id = increment_tx_count(store)?; + let coins = Coin { + denom, + amount: amount.into(), + }; + let action = StoredTxAction::claim_reward(staker.clone()); + let tx = StoredRichTx::new(id, action, coins, memo, block); + + append_tx(store, &tx, staker)?; + + Ok(()) +} + +fn append_tx( + store: &mut dyn Storage, + tx: &StoredRichTx, + for_address: &CanonicalAddr, +) -> StdResult<()> { + let mut store = PrefixedStorage::multilevel(&[PREFIX_TXS, for_address.as_slice()], store); + let mut store = AppendStoreMut::attach_or_create(&mut store)?; + store.push(tx) +} + +fn append_transfer( + store: &mut dyn Storage, + tx: &StoredLegacyTransfer, + for_address: &CanonicalAddr, +) -> StdResult<()> { + let mut store = PrefixedStorage::multilevel(&[PREFIX_TRANSFERS, for_address.as_slice()], store); + let mut store = AppendStoreMut::attach_or_create(&mut store)?; + store.push(tx) +} + +pub fn get_txs( + api: &dyn Api, + storage: &dyn Storage, + for_address: &CanonicalAddr, + page: u32, + page_size: u32, +) -> StdResult<(Vec, u64)> { + let store = ReadonlyPrefixedStorage::multilevel(&[PREFIX_TXS, for_address.as_slice()], storage); + + // Try to access the storage of txs for the account. + // If it doesn't exist yet, return an empty list of transfers. + let store = AppendStore::::attach(&store); + let store = if let Some(result) = store { + result? + } else { + return Ok((vec![], 0)); + }; + + // Take `page_size` txs starting from the latest tx, potentially skipping `page * page_size` + // txs from the start. + let tx_iter = store + .iter() + .rev() + .skip((page * page_size) as _) + .take(page_size as _); + + // The `and_then` here flattens the `StdResult>` to an `StdResult` + let txs: StdResult> = tx_iter + .map(|tx| tx.map(|tx| tx.into_humanized(api)).and_then(|x| x)) + .collect(); + txs.map(|txs| (txs, store.len() as u64)) +} + +pub fn get_transfers( + api: &dyn Api, + storage: &dyn Storage, + for_address: &CanonicalAddr, + page: u32, + page_size: u32, +) -> StdResult<(Vec, u64)> { + let store = + ReadonlyPrefixedStorage::multilevel(&[PREFIX_TRANSFERS, for_address.as_slice()], storage); + + // Try to access the storage of transfers for the account. + // If it doesn't exist yet, return an empty list of transfers. + let store = AppendStore::::attach(&store); + let store = if let Some(result) = store { + result? + } else { + return Ok((vec![], 0)); + }; + + // Take `page_size` txs starting from the latest tx, potentially skipping `page * page_size` + // txs from the start. + let transfer_iter = store + .iter() + .rev() + .skip((page * page_size) as _) + .take(page_size as _); + + // The `and_then` here flattens the `StdResult>` to an `StdResult` + let transfers: StdResult> = transfer_iter + .map(|tx| tx.map(|tx| tx.into_humanized(api)).and_then(|x| x)) + .collect(); + transfers.map(|txs| (txs, store.len() as u64)) +} diff --git a/archived-contracts/snip20_staking/src/utils.rs b/archived-contracts/snip20_staking/src/utils.rs new file mode 100644 index 0000000..ec153e6 --- /dev/null +++ b/archived-contracts/snip20_staking/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/archived-contracts/snip20_staking/src/viewing_key.rs b/archived-contracts/snip20_staking/src/viewing_key.rs new file mode 100644 index 0000000..e0dff0b --- /dev/null +++ b/archived-contracts/snip20_staking/src/viewing_key.rs @@ -0,0 +1,57 @@ +use std::fmt; + + +use shade_protocol::cosmwasm_schema::cw_serde; + +use shade_protocol::c_std::Env; + +use crate::{ + rand::{sha_256, 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, Clone, Debug)] +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, seed: &[u8], entropy: &[u8]) -> Self { + // 16 here represents the lengths in bytes of the block height and time. + let entropy_len = 16 + info.sender.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.0.as_bytes()); + rng_entropy.extend_from_slice(entropy); + + let mut rng = Prng::new(seed, &rng_entropy); + + let rand_slice = rng.rand_bytes(); + + let key = sha_256(&rand_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/archived_packages/network_integration/src/testnet_airdrop.rs b/archived_packages/network_integration/src/testnet_airdrop.rs new file mode 100644 index 0000000..e69de29 diff --git a/contractlib/contractlib.py b/contractlib/contractlib.py new file mode 100644 index 0000000..c050cb5 --- /dev/null +++ b/contractlib/contractlib.py @@ -0,0 +1,99 @@ +from .secretlib import secretlib + + + +import json + + +class PreInstantiatedContract: + def __init__(self, address, code_hash, code_id): + self.address = address + self.code_hash = code_hash + self.code_id = code_id + + def execute(self, msg, sender, amount=None, compute=True): + """ + Execute said msg + :param msg: Execute msg + :param sender: Who will be sending the message, defaults to contract admin + :param amount: Optional string amount to send along with transaction + :return: Result + """ + return secretlib.execute_contract(self.address, msg, sender, 'test', amount, compute) + + def query(self, msg): + """ + Query said msg + :param msg: Query msg + :return: Query + """ + return secretlib.query_contract(self.address, msg) + + def as_dict(self): + return {'address': self.address, 'code_hash': self.code_hash } + + +class Contract: + def __init__(self, contract, initMsg, label, admin='a', uploader='a', backend='test', + instantiated_contract=None, code_id=None): + self.label = label + self.admin = admin + self.uploader = uploader + self.backend = backend + + if code_id: + self.code_id = code_id + else: + self.code_id = secretlib.store_contract(contract, uploader, backend) + + if instantiated_contract: + self.code_id = instantiated_contract.code_id + self.address = instantiated_contract.address + self.code_hash = instantiated_contract.code_hash + else: + Response = secretlib.instantiate_contract(str(self.code_id), initMsg, label, admin, backend) + + try: + for log in Response['logs']: + for event in log['events']: + for attribute in event["attributes"]: + if attribute["key"] == "contract_address": + self.address = attribute["value"] + break + self.code_hash = secretlib.contract_hash(self.address) + except Exception as e: + print(Response) + raise e + + def execute(self, msg, sender=None, amount=None, compute=True): + """ + Execute said msg + :param msg: Execute msg + :param sender: Who will be sending the message, defaults to contract admin + :param amount: Optional string amount to send along with transaction + :return: Result + """ + signer = sender if sender is not None else self.admin + return secretlib.execute_contract(self.address, msg, signer, self.backend, amount, compute) + + def query(self, msg): + """ + Query said msg + :param msg: Query msg + :return: Query + """ + return secretlib.query_contract(self.address, msg) + + def print(self): + """ + Prints the contract info + :return: + """ + print(f"Label: {self.label}\n" + f"Address: {self.address}\n" + f"Id: {self.code_id}\n" + f"Hash: {self.code_hash}") + + def as_dict(self): + return {'address': self.address, 'code_hash': self.code_hash } + diff --git a/contractlib/initializerlib.py b/contractlib/initializerlib.py new file mode 100644 index 0000000..78b4bc2 --- /dev/null +++ b/contractlib/initializerlib.py @@ -0,0 +1,35 @@ +from .contractlib import Contract +import json + + +class Initializer(Contract): + def __init__(self, label, snip20_id, snip20_code_hash, silk_label, silk_seed, silk_initial_balances, + shade_label, shade_seed, shade_initial_balances, contract='initializer.wasm.gz', + admin='a', uploader='a', + backend='test', instantiated_contract=None, code_id=None): + init_msg = { + "snip20_id": int(snip20_id), + "snip20_code_hash": snip20_code_hash, + "shade": { + "label": shade_label, + "prng_seed": shade_seed, + "initial_balances": shade_initial_balances + }, + "silk": { + "label": silk_label, + "prng_seed": silk_seed, + "initial_balances": silk_initial_balances + }, + } + + init_msg = json.dumps(init_msg) + + super().__init__(contract, init_msg, label, admin, uploader, backend, + instantiated_contract=instantiated_contract, code_id=code_id) + + def get_contracts(self): + msg = json.dumps({ + "contracts": {} + }) + + return self.query(msg) \ No newline at end of file diff --git a/contractlib/mintlib.py b/contractlib/mintlib.py new file mode 100644 index 0000000..16ddbe8 --- /dev/null +++ b/contractlib/mintlib.py @@ -0,0 +1,108 @@ +import copy + +from .contractlib import Contract +from .secretlib import secretlib +import json + + +class Mint(Contract): + def __init__(self, label, native_asset, oracle, treasury=None, + asset_peg=None, + contract='mint.wasm.gz', + admin='a', uploader='a', + backend='test', instantiated_contract=None, code_id=None): + init_msg = { + "native_asset": { + "address": native_asset.address, + "code_hash": native_asset.code_hash, + }, + "oracle": { + "address": oracle.address, + "code_hash": oracle.code_hash + }, + } + if treasury: + init_msg['treasury'] = treasury.address + + if asset_peg: + init_msg['peg'] = asset_peg + + # print(json.dumps(init_msg, indent=2)) + init_msg = json.dumps(init_msg) + + super().__init__(contract, init_msg, label, admin, uploader, backend, + instantiated_contract=instantiated_contract, code_id=code_id) + + def update_config(self, owner=None, native_asset=None, oracle=None): + """ + Updates the minting contract's config + :param owner: New admin + :param native_asset: Snip20 to Mint + :param oracle: Oracle contract + :return: Result + """ + raw_msg = {"update_config": {}} + if owner is not None: + raw_msg["update_config"]["owner"] = owner + + if native_asset is not None: + contract = { + "address": native_asset.address, + "code_hash": native_asset.code_hash + } + raw_msg["update_config"]["native_asset"] = contract + + if oracle is not None: + contract = { + "address": oracle.address, + "code_hash": oracle.code_hash + } + raw_msg["update_config"]["oracle"] = contract + + msg = json.dumps(raw_msg) + return self.execute(msg) + + def register_asset(self, snip20, capture=None): + """ + Registers a SNIP20 asset + :param snip20: SNIP20 object to add + :param capture: Comission for the SNIP20 + :return: Result + """ + msg = {"register_asset": {"contract": {"address": snip20.address, "code_hash": snip20.code_hash}}} + + if capture: + msg['register_asset']['capture'] = str(capture) + + return self.execute(json.dumps(msg)) + + def get_supported_assets(self): + """ + Get all supported asset addressed + :return: Supported assets info + """ + msg = json.dumps( + {"get_supported_assets": {}}) + + return self.query(msg) + + def get_config(self): + """ + Get the contracts config information + :return: Contract config info + """ + msg = json.dumps( + {"get_config": {}}) + + return self.query(msg) + + def get_asset(self, snip20): + """ + Returns that assets info + :param snip20: SNIP20 object to query + :return: Asset info + """ + msg = json.dumps( + {"get_asset": {"contract": snip20.address}}) + + return self.query(msg) diff --git a/contractlib/oraclelib.py b/contractlib/oraclelib.py new file mode 100644 index 0000000..819ec81 --- /dev/null +++ b/contractlib/oraclelib.py @@ -0,0 +1,53 @@ +import copy + +from .contractlib import Contract +from .secretlib import secretlib +import json + + +class Oracle(Contract): + def __init__(self, label, band_contract, sscrt, contract='oracle.wasm.gz', admin='a', uploader='a', backend='test', + instantiated_contract=None, code_id=None): + + init_msg = json.dumps({ + 'band': { + 'address': band_contract.address, + 'code_hash': band_contract.code_hash, + }, + 'sscrt': { + 'address': sscrt.address, + 'code_hash': sscrt.code_hash, + } + }) + + super().__init__(contract, init_msg, label, admin, uploader, backend, + instantiated_contract=instantiated_contract, code_id=code_id) + + def price(self, symbol): + """ + Get current coin price + :param symbol: Coin ticker + :return: + """ + msg = json.dumps({'price': {'symbol': symbol}}) + + return self.query(msg) + + def register_sswap_pair(self, pair): + msg = json.dumps({'pair': { + 'address': pair.address, + 'code_hash': pair.code_hash, + }}) + + return self.execute(msg) + + def register_index(self, symbol, basket: list): + msg = json.dumps({ + 'register_index': { + 'symbol': symbol, + 'basket': basket, + } + }) + print(msg) + + return self.execute(msg) diff --git a/contractlib/secretlib/secretlib.py b/contractlib/secretlib/secretlib.py new file mode 100644 index 0000000..0c6479b --- /dev/null +++ b/contractlib/secretlib/secretlib.py @@ -0,0 +1,182 @@ +from subprocess import Popen, PIPE +import json +import time + +# Presetup some commands +query_list_code = ['secretcli', 'query', 'compute', 'list-code'] +MAX_TRIES = 10 + +GAS_METRICS = [] +STORE_GAS = '4000000' +GAS = '4000000' + + +def run_command(command): + """ + Will run any cli command and return its output after waiting a set amount + :param command: Array of command to run + :param wait: Time to wait for command + :return: Output string + """ + #print(' '.join(command)) + p = Popen(command, stdout=PIPE, stderr=PIPE, text=True) + output, err = p.communicate() + status = p.wait() + if err and not output: + return err + return output + + +def store_contract(contract, user='a', backend='test'): + """ + Store contract and return its ID + :param contract: Contract name + :param user: User to upload with + :param gas: Gas to use + :param backend: Keyring backend + :return: Contract ID + """ + + command = ['secretcli', 'tx', 'compute', 'store', f'./compiled/{contract}', + '--from', user, '--gas', STORE_GAS, '-y'] + + if backend is not None: + command += ['--keyring-backend', backend] + + output = run_command_query_hash(command) + try: + for attribute in output['logs'][0]['events'][0]['attributes']: + if attribute["key"] == "code_id": + return attribute['value'] + except: + # print(output) + return output + + +def instantiate_contract(contract, msg, label, user='a', backend='test'): + """ + Instantiates a contract + :param contract: Contract name + :param msg: Init msg + :param label: Name to give to the contract + :param user: User to instantiate with + :param backend: Keyring backend + :return: + """ + + command = ['secretcli', 'tx', 'compute', 'instantiate', contract, msg, '--from', + user, '--label', label, '-y', '--gas', '500000'] + + if backend is not None: + command += ['--keyring-backend', backend] + + return run_command_query_hash(command) + + +def list_code(): + command = ['secretcli', 'query', 'compute', 'list-code'] + + return json.loads(run_command(command)) + + +def list_contract_by_code(code): + command = ['secretcli', 'query', 'compute', 'list-contract-by-code', code] + + return json.loads(run_command(command)) + +def contract_hash(address): + command = ['secretcli', 'query', 'compute', 'contract-hash', address] + + return run_command(command) + + +def execute_contract(contract, msg, user='a', backend='test', amount=None, compute=True): + command = ['secretcli', 'tx', 'compute', 'execute', contract, json.dumps(msg), '--from', user, '--gas', GAS, '-y'] + + if backend is not None: + command += ['--keyring-backend', backend] + + if amount is not None: + command.append("--amount") + command.append(amount) + + if compute: + return run_command_compute_hash(command) + return run_command_query_hash(command) + + +def query_hash(hash): + return run_command(['secretcli', 'q', 'tx', hash]) + + +def compute_hash(hash): + print(hash) + return run_command(['secretcli', 'q', 'compute', 'tx', hash]) + + +def query_contract(contract, msg): + command = ['secretcli', 'query', 'compute', 'query', contract, json.dumps(msg)] + out = run_command(command) + try: + return json.loads(out) + except json.JSONDecodeError as e: + print(out) + raise e + + +def run_command_compute_hash(command): + out = run_command(command) + + try: + txhash = json.loads(out)["txhash"] + #print(txhash) + + except Exception as e: + # print(out) + raise e + + for _ in range(MAX_TRIES): + try: + out = compute_hash(txhash) + out = json.loads(out) + # print(out) + # querying hash once the hash is computed so we can check gas usage + tx_data = json.loads(query_hash(txhash)) + # print(json.dumps(tx_data)) + # print('gas:', tx_data['gas_used'], '\t/', tx_data['gas_wanted']) + GAS_METRICS.append({ + 'want': tx_data['gas_wanted'], + 'used': tx_data['gas_used'], + 'cmd': ' '.join(command) + }) + return out + except json.JSONDecodeError as e: + time.sleep(1) + print(out) + print(' '.join(command), f'exceeded max tries ({MAX_TRIES})') + + +def run_command_query_hash(command): + out = run_command(command) + try: + txhash = json.loads(out)["txhash"] + except json.JSONDecodeError as e: + print(out) + raise e + + for _ in range(MAX_TRIES): + try: + # TODO: Read the gas used and store somewhere for metrics + out = query_hash(txhash) + out = json.loads(out) + # print('gas:', out['gas_used'], '\t/', out['gas_wanted']) + GAS_METRICS.append({ + 'want': out['gas_wanted'], + 'used': out['gas_used'], + 'cmd': ' '.join(command) + }) + return out + except json.JSONDecodeError as e: + time.sleep(1) + print(out) + print(' '.join(command), f'exceeded max tries ({MAX_TRIES})') diff --git a/contractlib/snip20lib.py b/contractlib/snip20lib.py new file mode 100644 index 0000000..6199aec --- /dev/null +++ b/contractlib/snip20lib.py @@ -0,0 +1,124 @@ +from .contractlib import Contract +from .secretlib import secretlib +from base64 import b64encode +import json + + +class SNIP20(Contract): + def __init__(self, label, name="token", symbol="TKN", decimals=3, seed="cGFzc3dvcmQ=", public_total_supply=False, + enable_deposit=False, enable_redeem=False, enable_mint=False, enable_burn=False, initial_balances=[], + contract='snip20.wasm.gz', admin='a', uploader='a', backend='test', + instantiated_contract=None, code_id=None): + self.view_key = "" + self.name = name + self.symbol = symbol + self.decimals = decimals + + initMsg = json.dumps( + { + "name": name, + "symbol": symbol, + "decimals": decimals, + "prng_seed": seed, + "initial_balances": initial_balances, + "config": { + "public_total_supply": public_total_supply, + "enable_deposit": enable_deposit, + "enable_redeem": enable_redeem, + "enable_mint": enable_mint, + "enable_burn": enable_burn, + } + }) + super().__init__(contract, initMsg, label, admin, uploader, backend, + instantiated_contract=instantiated_contract, code_id=code_id) + + def set_minters(self, accounts): + """ + Sets minters + :param accounts: Accounts list + :return: Response + """ + msg = json.dumps( + {"set_minters": {"minters": accounts}}) + + return self.execute(msg) + + def deposit(self, account, amount): + """ + Deposit a specified amount to contract + :param account: User which will deposit + :param amount: uSCRT + :return: Response + """ + msg = json.dumps( + {"deposit": {}}) + + return self.execute(msg, account, amount) + + def mint(self, recipient, amount): + """ + Mint an amount into the recipients wallet + :param recipient: Address to be minted in + :param amount: Amount to mint + :return: Response + """ + msg = json.dumps( + {"mint": {"recipient": recipient, "amount": str(amount)}}) + + return self.execute(msg) + + def send(self, account, recipient, amount, message=None): + """ + Send amount from an account to a recipient + :param account: User to generate the key for + :param recipient: Address to be minted in + :param amount: Amount to mint + :param message: Base64 encoded message + :return: Response + """ + + raw_msg = {"send": {"recipient": recipient, "amount": str(amount)}} + + if message is not None: + raw_msg["send"]["msg"] = b64encode(json.dumps(message).encode('utf-8')).decode('utf-8') + + msg = json.dumps(raw_msg) + + return self.execute(msg, account) + + def set_view_key(self, account, entropy): + """ + Generate view key to query balance + :param account: User to generate the key for + :param entropy: Password generation entropy + :return: Password + """ + msg = json.dumps( + {"set_viewing_key": {"key": entropy}}) + + resp = self.execute(msg, account) + #print('RESP', json.dumps(resp, indent=2)) + return resp + #return json.loads(resp["output_data_as_string"])["create_viewing_key"]["key"] + + def get_balance(self, address, password): + """ + Gets amount of coins in wallet + :param address: Account to access + :param password: View key + :return: Response + """ + msg = json.dumps( + {"balance": {"key": password, "address": address}}) + + msg = {"balance": {"key": password, "address": address}} + res = self.query(msg) + + return res["balance"]["amount"] + + def get_token_info(self): + + msg = json.dumps( + {"token_info": {}}) + return self.query(msg) + diff --git a/contractlib/treasurylib.py b/contractlib/treasurylib.py new file mode 100644 index 0000000..2b8475d --- /dev/null +++ b/contractlib/treasurylib.py @@ -0,0 +1,72 @@ +import copy + +from .contractlib import Contract +from .secretlib import secretlib +import json + + +class Treasury(Contract): + def __init__(self, label, viewing_key='password', contract='treasury.wasm.gz', admin='drpresident', uploader='drpresident', + backend='test', instantiated_contract=None, code_id=None): + init_msg = json.dumps({ + 'viewing_key': viewing_key, + }) + + super().__init__(contract, init_msg, label, admin, uploader, backend, + instantiated_contract=instantiated_contract, code_id=code_id) + + def get_balance(self, contract): + return self.query(json.dumps({ + 'get_balance': { + 'contract': contract.address, + } + }))['balance']['amount'] + + + def update_config(self, owner=None, native_asset=None, oracle=None): + """ + Updates the minting contract's config + :param owner: New admin + :param native_asset: Snip20 to Mint + :param oracle: Oracle contract + :return: Result + """ + raw_msg = {"update_config": {}} + if owner is not None: + raw_msg["update_config"]["owner"] = owner + if native_asset is not None: + contract = { + "address": native_asset.address, + "code_hash": native_asset.code_hash + } + raw_msg["update_config"]["native_asset"] = contract + if oracle is not None: + contract = { + "address": oracle.address, + "code_hash": oracle.code_hash + } + raw_msg["update_config"]["oracle"] = contract + + msg = json.dumps(raw_msg) + return self.execute(msg) + + def register_asset(self, snip20): + """ + Registers a SNIP20 asset + :param snip20: SNIP20 object to add + :return: Result + """ + msg = json.dumps( + {"register_asset": {"contract": {"address": snip20.address, "code_hash": snip20.code_hash}}}) + + return self.execute(msg) + + def get_config(self): + """ + Get the contracts config information + :return: Contract config info + """ + msg = json.dumps( + {"get_config": {}}) + + return self.query(msg) diff --git a/contractlib/utils.py b/contractlib/utils.py new file mode 100644 index 0000000..83f8723 --- /dev/null +++ b/contractlib/utils.py @@ -0,0 +1,15 @@ +import string +import random +import base64 +import json + + +def gen_label(length): + # With combination of lower and upper case + return ''.join(random.choice(string.ascii_letters) for i in range(length)) + + +def to_base64(dict): + dict_str = json.dumps(dict).encode('ascii') + encoded_str = base64.b64encode(dict_str) + return encoded_str.decode() diff --git a/contracts/Staking Deep Dive b/contracts/Staking Deep Dive new file mode 100644 index 0000000..fc4c9c1 --- /dev/null +++ b/contracts/Staking Deep Dive @@ -0,0 +1 @@ +UzV2zq1wL0osyPDNT0nNUTV2VTV2LsrPL4GwciucU3NyVI0MMlNUjV1UjYwMgFjVyA2HrCFY1qAgsSg1rwSLBiADYTaQg2Y1AA== \ No newline at end of file diff --git a/contracts/Staking Overview b/contracts/Staking Overview new file mode 100644 index 0000000..45f9123 --- /dev/null +++ b/contracts/Staking Overview @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contracts/admin/.cargo/config b/contracts/admin/.cargo/config new file mode 100644 index 0000000..c1e7c50 --- /dev/null +++ b/contracts/admin/.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" \ No newline at end of file diff --git a/contracts/admin/.circleci/config.yml b/contracts/admin/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/contracts/admin/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/admin/Cargo.toml b/contracts/admin/Cargo.toml new file mode 100644 index 0000000..7f7c619 --- /dev/null +++ b/contracts/admin/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "admin" +version = "0.2.0" +authors = ["sbeem ", "scrtreddev "] +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +# Needs to be in contract dependency in order for build to work +shade-protocol = { path = "../../packages/shade_protocol", default-features = false, features = ["admin_impl"] } +cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" } + +[dev-dependencies] +rstest = "0.15" +shade-protocol = { path = "../../packages/shade_protocol", features = ["multi-test"] } +shade-multi-test = { version = "0.1.0", path = "../../packages/multi_test", features = [ "admin" ] } diff --git a/contracts/admin/Makefile b/contracts/admin/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/contracts/admin/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/admin/README.md b/contracts/admin/README.md new file mode 100644 index 0000000..5bba3b6 --- /dev/null +++ b/contracts/admin/README.md @@ -0,0 +1,127 @@ +# Admin Auth Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Init](#Init) + * [Admin](#Admin) + * Messages + * [AddContract](#AddContract) + * [RemoveContract](#RemoveContract) + * [AddAuthorization](#AddAuthorization) + * [RemoveAuthorization](#RemoveAuthorization) + * [AddSuper](#AddSuper) + * [RemoveSuper](#RemoveSuper) + * [User](#User) + * Queries + * [GetSuperAdmins](#GetSuperAdmins) + * [GetContracts](#GetContracts) + * [GetAuthorizedUsers](#GetAuthorizedUsers) + * [ValidateAdminPermission](#ValidateAdminPermission) +# Introduction +This contract is used to centrally authorize the owners of a contracts. A contract can query the Shade Admin Contract to confirm whether the original caller has the relevant permissions against the calling contract. + +# Sections + +## Admin + +### Messages +#### AddContract +##### Request +Add a contract. +| Name | Type | Description | optional | +|------------------|--------|---------------------------------|----------| +| contract | String | Address of contract to be added | no | + +#### RemoveContract +##### Request +Remove a contract. +| Name | Type | Description | optional | +|------------------|--------|-----------------------------------|----------| +| contract | String | Address of contract to be removed | no | + +#### AddAuthorization +##### Request +Authorize a user with admin perms for the inputted contract. +| Name | Type | Description | optional | +|------------------|--------|----------------------------------------------|----------| +| contract | String | Address of contract | no | +| admin | String | Address of user to be given admin privileges | no | + +#### RemoveAuthorization +##### Request +Deauthorize a user for the inputted contract. +| Name | Type | Description | optional | +|------------------|--------|------------------------------------------|----------| +| contract | String | Address of contract | no | +| admin | String | Address of user to lose admin privileges | no | + +#### AddSuper +##### Request +Authorize a user to be given super-admin perms. +| Name | Type | Description | optional | +|---------------|--------|----------------------------------------------------|----------| +| super_address | String | Address of user to be given super-admin privileges | no | + +#### RemoveSuper +##### Request +Deauthorize a user from super-admin perms. +| Name | Type | Description | optional | +|---------------|--------|------------------------------------------------|----------| +| super_address | String | Address of user to lose super-admin privileges | no | + + +## User + +### Queries + +#### GetSuperAdmins +Gets a list of the super-admin addresses. +##### Response +```json +{ + "SuperAdminResponse": { + "super_admins": "Vector of strings of the super-admin addresses" + } +} +``` + +#### GetContracts +Gets a list of all of the contracts and the users' that have perms over them. +##### Response +```json +{ + "ContractsResponse": { + "contracts": "Vector containing tuples of the contract addresses and a vector of strings of the authorized users" + } +} +``` + +#### GetAuthorizedUsers +Gets a vector of strings of the users' that have perms for the inputted contract address. +##### Request +| Name | Type | Description | optional | +|------------------|--------|---------------------|----------| +| contract | String | Address of contract | no | +##### Response +```json +{ + "AuthorizedUsersResponse": { + "authorized_users": "Vector of strings of the authorized users", + } +} +``` + +#### ValidateAdminPermission +Determines if inputted admin has admin perms over contract. +##### Request +| Name | Type | Description | optional | +|------------------|--------|---------------------------------|----------| +| contract | String | Address of contract | no | +| admin | String | Address of user to be validated | no | +##### Response +```json +{ + "ValidateAdminPermissionResponse": { + "error_msg": "Option determining if user has perms", + } +} +``` \ No newline at end of file diff --git a/contracts/admin/src/contract.rs b/contracts/admin/src/contract.rs new file mode 100644 index 0000000..666d521 --- /dev/null +++ b/contracts/admin/src/contract.rs @@ -0,0 +1,107 @@ +use shade_protocol::{ + admin::{ + errors::unauthorized_super, AdminAuthStatus, AdminsResponse, ConfigResponse, ExecuteMsg, + InstantiateMsg, PermissionsResponse, QueryMsg, + }, + c_std::{ + shd_entry_point, to_binary, Addr, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, + StdResult, Storage, + }, + utils::pad_handle_result, +}; + +use crate::{ + execute::{ + try_self_destruct, try_toggle_status, try_transfer_super, try_update_registry, + try_update_registry_bulk, + }, + query::query_validate_permission, + shared::{ADMINS, PERMISSIONS, STATUS, SUPER}, +}; + +pub const RESPONSE_BLOCK_SIZE: usize = 256; + +#[cfg_attr(not(feature = "library"), shd_entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let super_admin = msg.super_admin.unwrap_or_else(|| info.sender.to_string()); + let super_admin_addr = deps.api.addr_validate(super_admin.as_str())?; + SUPER.save(deps.storage, &super_admin_addr)?; + + ADMINS.save(deps.storage, &Vec::new())?; + STATUS.save(deps.storage, &AdminAuthStatus::Active)?; + + let res = Response::new() + .add_attribute("action", "initialized") + .add_attribute("superadmin", &info.sender); + Ok(res) +} + +#[cfg_attr(not(feature = "library"), shd_entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> StdResult { + // Only the super user can execute anything on this contract. + is_super(deps.storage, &info.sender)?; + // Super user is assumed to have been verified by this point. + pad_handle_result( + match msg { + ExecuteMsg::UpdateRegistry { action } => { + try_update_registry(deps.storage, deps.api, action) + } + ExecuteMsg::UpdateRegistryBulk { actions } => try_update_registry_bulk(deps, actions), + ExecuteMsg::TransferSuper { new_super } => try_transfer_super(deps, new_super), + ExecuteMsg::SelfDestruct {} => try_self_destruct(deps), + ExecuteMsg::ToggleStatus { new_status } => try_toggle_status(deps, new_status), + }, + RESPONSE_BLOCK_SIZE, + ) +} + +fn is_super(storage: &dyn Storage, address: &Addr) -> StdResult<()> { + let super_admin = SUPER.load(storage)?; + if super_admin == *address { + Ok(()) + } else { + Err(unauthorized_super(address.as_str())) + } +} + +#[cfg_attr(not(feature = "library"), shd_entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + Ok(match msg { + QueryMsg::GetConfig {} => to_binary(&ConfigResponse { + super_admin: SUPER.load(deps.storage)?, + status: STATUS.load(deps.storage)?, + }), + QueryMsg::ValidateAdminPermission { permission, user } => { + to_binary(&query_validate_permission(deps, permission, user)?) + } + QueryMsg::GetAdmins {} => { + STATUS + .load(deps.storage)? + .not_shutdown()? + .not_under_maintenance()?; + to_binary(&AdminsResponse { + admins: ADMINS.load(deps.storage)?, + }) + } + QueryMsg::GetPermissions { user } => { + STATUS + .load(deps.storage)? + .not_shutdown()? + .not_under_maintenance()?; + let validated_user = deps.api.addr_validate(user.as_str())?; + to_binary(&PermissionsResponse { + permissions: PERMISSIONS.load(deps.storage, &validated_user)?, + }) + } + }?) +} diff --git a/contracts/admin/src/execute.rs b/contracts/admin/src/execute.rs new file mode 100644 index 0000000..f23d22d --- /dev/null +++ b/contracts/admin/src/execute.rs @@ -0,0 +1,167 @@ +use crate::shared::{validate_permissions, ADMINS, PERMISSIONS, STATUS, SUPER}; +use shade_protocol::admin::errors::{no_permission, unregistered_admin}; +use shade_protocol::admin::{AdminAuthStatus, RegistryAction}; +use shade_protocol::c_std::{Addr, Api, DepsMut, Response, StdResult, Storage}; + +/// Performs one registry update. Cannot be run during a shutdown. +pub fn try_update_registry( + store: &mut dyn Storage, + api: &dyn Api, + action: RegistryAction, +) -> StdResult { + STATUS.load(store)?.not_shutdown()?; + let mut admins = ADMINS.load(store)?; + resolve_registry_action(store, &mut admins, api, action)?; + ADMINS.save(store, &admins)?; + Ok(Response::default()) +} + +/// Performs bulk registry updates. Cannot be run during a shutdown. +pub fn try_update_registry_bulk( + deps: DepsMut, + actions: Vec, +) -> StdResult { + STATUS.load(deps.storage)?.not_shutdown()?; + let mut admins = ADMINS.load(deps.storage)?; + for action in actions { + resolve_registry_action(deps.storage, &mut admins, deps.api, action)?; + } + ADMINS.save(deps.storage, &admins)?; + Ok(Response::default()) +} + +pub fn try_transfer_super(deps: DepsMut, new_super: String) -> StdResult { + let valid_super = deps.api.addr_validate(new_super.as_str())?; + // If you're trying to transfer the super permissions to someone who hasn't been registered as an admin, + // it won't work. This is a safeguard. + let mut admins = ADMINS.load(deps.storage)?; + if !admins.contains(&valid_super) { + return Err(unregistered_admin(valid_super.as_str())); + } else { + // Update the super and remove them from the admin list. + SUPER.save(deps.storage, &valid_super)?; + delete_admin(deps.storage, &mut admins, deps.api, new_super)?; + ADMINS.save(deps.storage, &admins)?; + } + Ok(Response::default()) +} + +pub fn try_self_destruct(deps: DepsMut) -> StdResult { + // Clear permissions + let admins = ADMINS.load(deps.storage)?; + admins + .iter() + .for_each(|admin| PERMISSIONS.remove(deps.storage, admin)); + // Clear admins + ADMINS.save(deps.storage, &vec![])?; + // Disable contract + STATUS.save(deps.storage, &AdminAuthStatus::Shutdown)?; + Ok(Response::default()) +} + +pub fn try_toggle_status(deps: DepsMut, new_status: AdminAuthStatus) -> StdResult { + STATUS.update(deps.storage, |_| -> StdResult<_> { Ok(new_status) })?; + Ok(Response::default()) +} + +fn resolve_registry_action( + store: &mut dyn Storage, + admins: &mut Vec, + api: &dyn Api, + action: RegistryAction, +) -> StdResult<()> { + match action { + RegistryAction::RegisterAdmin { user } => register_admin(store, admins, api, user), + RegistryAction::GrantAccess { permissions, user } => { + grant_access(store, api, admins, permissions, user) + } + RegistryAction::RevokeAccess { permissions, user } => { + revoke_access(store, api, admins, permissions, user) + } + RegistryAction::DeleteAdmin { user } => delete_admin(store, admins, api, user), + }?; + Ok(()) +} + +fn register_admin( + store: &mut dyn Storage, + admins: &mut Vec, + api: &dyn Api, + user: String, +) -> StdResult<()> { + let user_addr = api.addr_validate(user.as_str())?; + if !admins.contains(&user_addr) { + // Create an empty permissions for them and add their address to the registered array. + admins.push(user_addr.clone()); + PERMISSIONS.save(store, &user_addr, &vec![])?; + }; + Ok(()) +} + +fn delete_admin( + store: &mut dyn Storage, + admins: &mut Vec, + api: &dyn Api, + user: String, +) -> StdResult<()> { + let user_addr = api.addr_validate(user.as_str())?; + if admins.contains(&user_addr) { + // Delete admin from list. + admins.retain(|x| x.ne(&user_addr)); + // Delete their permissions. + PERMISSIONS.remove(store, &user_addr); + }; + Ok(()) +} + +fn grant_access( + store: &mut dyn Storage, + api: &dyn Api, + admins: &[Addr], + mut permissions: Vec, + user: String, +) -> StdResult<()> { + let user = api.addr_validate(user.as_str())?; + validate_permissions(permissions.as_slice())?; + verify_registered(admins, &user)?; + PERMISSIONS.update(store, &user, |old_perms| -> StdResult<_> { + match old_perms { + Some(mut old_perms) => { + permissions.retain(|c| !old_perms.contains(c)); + old_perms.append(&mut permissions); + Ok(old_perms) + } + None => Err(no_permission(user.as_str())), + } + })?; + Ok(()) +} + +fn revoke_access( + store: &mut dyn Storage, + api: &dyn Api, + admins: &[Addr], + permissions: Vec, + user: String, +) -> StdResult<()> { + let user = api.addr_validate(user.as_str())?; + validate_permissions(permissions.as_slice())?; + verify_registered(admins, &user)?; + PERMISSIONS.update(store, &user, |old_perms| -> StdResult<_> { + match old_perms { + Some(mut old_perms) => { + old_perms.retain(|c| !permissions.contains(c)); + Ok(old_perms) + } + None => Err(no_permission(user.as_str())), + } + })?; + Ok(()) +} + +fn verify_registered(admins: &[Addr], user: &Addr) -> StdResult<()> { + if !admins.contains(user) { + return Err(no_permission(user.as_str())); + } + Ok(()) +} diff --git a/contracts/admin/src/lib.rs b/contracts/admin/src/lib.rs new file mode 100644 index 0000000..6258ab5 --- /dev/null +++ b/contracts/admin/src/lib.rs @@ -0,0 +1,6 @@ +pub mod contract; +pub mod execute; +pub mod query; +pub mod shared; +#[cfg(test)] +mod test; diff --git a/contracts/admin/src/query.rs b/contracts/admin/src/query.rs new file mode 100644 index 0000000..0d7b144 --- /dev/null +++ b/contracts/admin/src/query.rs @@ -0,0 +1,40 @@ +use crate::shared::{is_valid_permission, PERMISSIONS, STATUS, SUPER}; +use shade_protocol::{ + admin::{errors::unregistered_admin, ValidateAdminPermissionResponse}, + c_std::{Deps, StdResult}, +}; + +/// Checks if the user has the requested permission. Permissions are case sensitive. +pub fn query_validate_permission( + deps: Deps, + permission: String, + user: String, +) -> StdResult { + STATUS + .load(deps.storage)? + .not_shutdown()? + .not_under_maintenance()?; + is_valid_permission(permission.as_str())?; + let valid_user = deps.api.addr_validate(user.as_str())?; + let super_admin = SUPER.load(deps.storage)?; + + let has_permission: bool; + + // Super admin has all permissions. The permissions don't need to have been created and assigned to the super admin beforehand. We do this because we assume that the super admin is secure (like a multi-sig or the main governance contract) so it would be a hassle to whitelist every permission we want them to have. + if valid_user == super_admin { + has_permission = true; + } else { + let permissions = PERMISSIONS.may_load(deps.storage, &valid_user)?; + match permissions { + Some(permissions) => { + if permissions.iter().any(|perm| permission.eq(perm)) { + has_permission = true; + } else { + has_permission = false; + } + } + None => return Err(unregistered_admin(valid_user.as_str())), + } + } + Ok(ValidateAdminPermissionResponse { has_permission }) +} diff --git a/contracts/admin/src/shared.rs b/contracts/admin/src/shared.rs new file mode 100644 index 0000000..cb73a4b --- /dev/null +++ b/contracts/admin/src/shared.rs @@ -0,0 +1,35 @@ +use shade_protocol::c_std::Addr; +use shade_protocol::utils::storage::plus::{Item, Map}; +use shade_protocol::{ + admin::{errors::invalid_permission_format, AdminAuthStatus}, + c_std::StdResult, +}; + +/// Maps user to permissions for which they have user. +pub const PERMISSIONS: Map<&Addr, Vec> = Map::new("permissions"); +/// List of all admins. +pub const ADMINS: Item> = Item::new("admins"); +/// Super user. +pub const SUPER: Item = Item::new("super"); +/// Whether or not this contract can be consumed. +pub const STATUS: Item = Item::new("is_active"); + +pub fn validate_permissions(permissions: &[String]) -> StdResult<()> { + for permission in permissions { + is_valid_permission(permission.as_str())?; + } + Ok(()) +} + +pub fn is_valid_permission(permission: &str) -> StdResult<()> { + if permission.len() <= 10 { + return Err(invalid_permission_format(permission)); + } + let valid_chars = permission.bytes().all(|byte| { + (b'A'..=b'Z').contains(&byte) || (b'0'..=b'9').contains(&byte) || b'_'.eq(&byte) + }); + if !valid_chars { + return Err(invalid_permission_format(permission)); + } + Ok(()) +} diff --git a/contracts/admin/src/test.rs b/contracts/admin/src/test.rs new file mode 100644 index 0000000..fa6eb5c --- /dev/null +++ b/contracts/admin/src/test.rs @@ -0,0 +1,351 @@ +use crate::shared::is_valid_permission; +use rstest::*; +use shade_multi_test::multi::admin::Admin; +use shade_protocol::{ + admin::{ + AdminAuthStatus, AdminsResponse, ConfigResponse, ExecuteMsg, InstantiateMsg, + PermissionsResponse, QueryMsg, RegistryAction, ValidateAdminPermissionResponse, + }, + c_std::Addr, + multi_test::App, + utils::{ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +#[rstest] +#[case("VAULT", false)] +#[case("test", false)] +#[case("VAULT_", false)] +#[case("VAULT_TARGET", true)] +#[case("VAULT_TARG3T_2", true)] +#[case("", false)] +#[case("*@#$*!*#!#!#****", false)] +#[case("VAULT_TARGET_addr", false)] +fn test_is_valid_permission(#[case] permission: String, #[case] is_valid: bool) { + let resp = is_valid_permission(permission.as_str()); + if is_valid { + assert!(resp.is_ok()); + } else { + assert!(resp.is_err()); + } +} + +#[rstest] +#[case(AdminAuthStatus::Active, vec![true, true, true, false, true, true, true])] +#[case(AdminAuthStatus::Maintenance, vec![true, true, true, false, true, true, true])] +#[case(AdminAuthStatus::Shutdown, vec![false, false, false, false, false, false, true])] +fn test_status(#[case] status: AdminAuthStatus, #[case] expect_success: Vec) { + //init + let mut chain: App = App::default(); + let contract = InstantiateMsg { super_admin: None } + .test_init( + Admin::default(), + &mut chain, + Addr::unchecked("admin"), + "admin_contract", + &[], + ) + .unwrap(); + //set state + ExecuteMsg::ToggleStatus { new_status: status } + .test_exec(&contract, &mut chain, Addr::unchecked("admin"), &[]) + .unwrap(); + + //register 'super' as admin + let action = RegistryAction::RegisterAdmin { + user: "super".to_string(), + }; + let result = ExecuteMsg::UpdateRegistry { + action: action.clone(), + } + .test_exec(&contract, &mut chain, Addr::unchecked("admin"), &[]); + assert_eq!(&result.is_ok(), expect_success.get(0).unwrap()); + + //test bulk update + let actions = vec![action.clone()]; + let result = ExecuteMsg::UpdateRegistryBulk { actions }.test_exec( + &contract, + &mut chain, + Addr::unchecked("admin"), + &[], + ); + assert_eq!(&result.is_ok(), expect_success.get(1).unwrap()); + + //set super admin to 'super' + let result = ExecuteMsg::TransferSuper { + new_super: "super".to_string(), + } + .test_exec(&contract, &mut chain, Addr::unchecked("admin"), &[]); + assert_eq!(&result.is_ok(), expect_success.get(2).unwrap()); + + //register 'admin' as admin without being the super user + let action = RegistryAction::RegisterAdmin { + user: "admin".to_string(), + }; + let result = ExecuteMsg::UpdateRegistry { + action: action.clone(), + } + .test_exec(&contract, &mut chain, Addr::unchecked("admin"), &[]); + assert_eq!(&result.is_ok(), expect_success.get(3).unwrap()); + + //register admin as admin with correct permissions + let action = RegistryAction::RegisterAdmin { + user: "admin".to_string(), + }; + let result = ExecuteMsg::UpdateRegistry { + action: action.clone(), + } + .test_exec(&contract, &mut chain, Addr::unchecked("super"), &[]); + assert_eq!(&result.is_ok(), expect_success.get(4).unwrap()); + + //set super admin to 'admin' + let result = ExecuteMsg::TransferSuper { + new_super: "admin".to_string(), + } + .test_exec(&contract, &mut chain, Addr::unchecked("super"), &[]); + assert_eq!(&result.is_ok(), expect_success.get(5).unwrap()); + + //self destruct + let result = + ExecuteMsg::SelfDestruct {}.test_exec(&contract, &mut chain, Addr::unchecked("admin"), &[]); + assert_eq!(&result.is_ok(), expect_success.get(6).unwrap()); +} + +#[rstest] +#[case(vec!["test", "blah"], vec!["test", "blah"], vec![false, false])] +#[case(vec!["test", "blah", "aaaa", "bbbb", "cccc"], vec!["test", "bbbb"], vec![false, true, true, false, true])] +fn test_admin( + #[case] admins_to_add: Vec<&str>, + #[case] admins_to_remove: Vec<&str>, + #[case] expected_in_final_admins: Vec, +) { + //init + let mut chain = App::default(); + + let admin_contract = InstantiateMsg { super_admin: None } + .test_init( + Admin::default(), + &mut chain, + Addr::unchecked("admin"), + "admin_contract", + &[], + ) + .unwrap(); + + //check config + let config: ConfigResponse = QueryMsg::GetConfig {} + .test_query(&admin_contract, &chain) + .unwrap(); + assert_eq!(config.super_admin.as_str(), "admin"); + assert_eq!(config.status, AdminAuthStatus::Active); + + //read admins + let response: AdminsResponse = QueryMsg::GetAdmins {} + .test_query(&admin_contract, &chain) + .unwrap(); + assert!(response.admins.is_empty()); + + //add admins + for admin in admins_to_add.iter() { + ExecuteMsg::UpdateRegistry { + action: RegistryAction::RegisterAdmin { + user: admin.to_string(), + }, + } + .test_exec(&admin_contract, &mut chain, Addr::unchecked("admin"), &[]) + .unwrap(); + } + + //read admins + let response: AdminsResponse = QueryMsg::GetAdmins {} + .test_query(&admin_contract, &chain) + .unwrap(); + let admin_list = response.admins; + let admin_list_str: Vec = admin_list.into_iter().map(|x| x.to_string()).collect(); + for admin in admins_to_add.iter() { + assert!(admin_list_str.contains(&admin.to_string())); + } + + //remove some admins + for admin in admins_to_remove.iter() { + ExecuteMsg::UpdateRegistry { + action: RegistryAction::DeleteAdmin { + user: admin.to_string(), + }, + } + .test_exec(&admin_contract, &mut chain, Addr::unchecked("admin"), &[]) + .unwrap(); + } + + //read admins + let response: AdminsResponse = QueryMsg::GetAdmins {} + .test_query(&admin_contract, &chain) + .unwrap(); + let admin_list = response.admins; + let admin_list_str: Vec = admin_list.into_iter().map(|x| x.to_string()).collect(); + for (i, admin) in admins_to_add.iter().enumerate() { + assert_eq!( + &admin_list_str.contains(&admin.to_string()), + expected_in_final_admins.get(i).unwrap() + ); + } + + //remove all admins with batch + let mut actions = vec![]; + for admin in &admins_to_add { + actions.push(RegistryAction::DeleteAdmin { + user: admin.to_string(), + }); + } + + ExecuteMsg::UpdateRegistryBulk { actions } + .test_exec(&admin_contract, &mut chain, Addr::unchecked("admin"), &[]) + .unwrap(); + + //read admins + let response: AdminsResponse = QueryMsg::GetAdmins {} + .test_query(&admin_contract, &chain) + .unwrap(); + let admin_list = response.admins; + let admin_list_str: Vec = admin_list.into_iter().map(|x| x.to_string()).collect(); + for admin in &admins_to_add { + assert_eq!(&admin_list_str.contains(&admin.to_string()), &false); + } +} + +#[rstest] +#[case( +vec![ +("user", vec!["SOME_TARGET"]), +("places", vec!["PLACE_SAN_JUAN", "PLACE_NEW_YORK", "PLACE_CAPRI_ISLAND"]), +("not_admin", vec!["SOME_TARGET_ONE", "SOME_TARGET_TWO", "TARGET_THREE"]) +], +vec![ +("places", vec!["PLACE_NEW_YORK"]), +("not_admin", vec!["SOME_TARGET_ONE", "TARGET_THREE"]), +("user", vec!["SOME_TARGET"]), +] +)] +fn test_permissions( + #[case] permissions: Vec<(&str, Vec<&str>)>, + #[case] revoke_permissions: Vec<(&str, Vec<&str>)>, +) { + let mut chain = App::default(); + + let admin = InstantiateMsg { super_admin: None } + .test_init( + Admin::default(), + &mut chain, + Addr::unchecked("admin"), + "admin_contract", + &[], + ) + .unwrap(); + + let mut actions = vec![]; + for permission in permissions.iter() { + actions.append(&mut vec![ + RegistryAction::RegisterAdmin { + user: permission.0.to_string(), + }, + RegistryAction::GrantAccess { + permissions: permission.1.iter().map(|&i| i.to_string()).collect(), + user: permission.0.to_string(), + }, + ]) + } + + // Check that only super admin chan do this + assert!(ExecuteMsg::UpdateRegistryBulk { + actions: actions.clone() + } + .test_exec(&admin, &mut chain, Addr::unchecked("user"), &[]) + .is_err()); + + assert!(ExecuteMsg::UpdateRegistryBulk { actions } + .test_exec(&admin, &mut chain, Addr::unchecked("admin"), &[]) + .is_ok()); + + // Confirm that all permissions are set + for permission in permissions.iter() { + // Check that the permissions are correctly returned + let stored_permissions: PermissionsResponse = QueryMsg::GetPermissions { + user: permission.0.to_string(), + } + .test_query(&admin, &chain) + .unwrap(); + + assert_eq!(stored_permissions.permissions.len(), permission.1.len()); + for perm in permission.1.iter() { + assert!(stored_permissions.permissions.contains(&perm.to_string())); + + // Check that no other permission is "accepted" + let res: ValidateAdminPermissionResponse = QueryMsg::ValidateAdminPermission { + permission: perm.to_string(), + user: permission.0.to_string(), + } + .test_query(&admin, &chain) + .unwrap(); + assert!(res.has_permission); + } + } + + // Remove permissions + let revoke_actions: Vec = revoke_permissions + .iter() + .map(|permission| RegistryAction::RevokeAccess { + permissions: permission.1.iter().map(|&item| item.to_string()).collect(), + user: permission.0.to_string(), + }) + .collect(); + + assert!(ExecuteMsg::UpdateRegistryBulk { + actions: revoke_actions.clone() + } + .test_exec(&admin, &mut chain, Addr::unchecked("user"), &[]) + .is_err()); + + assert!(ExecuteMsg::UpdateRegistryBulk { + actions: revoke_actions + } + .test_exec(&admin, &mut chain, Addr::unchecked("admin"), &[]) + .is_ok()); + + for permission in permissions.iter() { + // Check that the permissions are correctly returned + let stored_permissions: PermissionsResponse = QueryMsg::GetPermissions { + user: permission.0.to_string(), + } + .test_query(&admin, &chain) + .unwrap(); + + for perm in permission.1.iter() { + let mut assertion: Option = None; + for p in revoke_permissions.iter() { + if p.0 == permission.0 { + assertion = Some(!p.1.contains(perm)); + + assert_eq!( + stored_permissions.permissions.len(), + permission.1.len().wrapping_sub(p.1.len()) + ); + break; + } + } + assert!(assertion.is_some(), "Never found the required item"); + + assert_eq!( + stored_permissions.permissions.contains(&perm.to_string()), + assertion.unwrap() + ); + + // Check that no other permission is "accepted" + let res: ValidateAdminPermissionResponse = QueryMsg::ValidateAdminPermission { + permission: perm.to_string(), + user: permission.0.to_string(), + } + .test_query(&admin, &chain) + .unwrap(); + assert_eq!(res.has_permission, assertion.unwrap()); + } + } +} diff --git a/contracts/airdrop/.cargo/config b/contracts/airdrop/.cargo/config new file mode 100644 index 0000000..c1e7c50 --- /dev/null +++ b/contracts/airdrop/.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" \ No newline at end of file diff --git a/contracts/airdrop/.circleci/config.yml b/contracts/airdrop/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/contracts/airdrop/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/airdrop/Cargo.toml b/contracts/airdrop/Cargo.toml new file mode 100644 index 0000000..29518b2 --- /dev/null +++ b/contracts/airdrop/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "airdrop" +version = "0.1.0" +authors = ["Guy Garcia "] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +ethereum-verify = { version = "0.1.0", path = "../../packages/ethereum_veri"} +hex = "0.4" +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ + "airdrop" +] } +rs_merkle = { git = "https://github.com/FloppyDisck/rs-merkle", branch = "node_export" } \ No newline at end of file diff --git a/contracts/airdrop/Makefile b/contracts/airdrop/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/contracts/airdrop/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/airdrop/README.md b/contracts/airdrop/README.md new file mode 100644 index 0000000..ed33134 --- /dev/null +++ b/contracts/airdrop/README.md @@ -0,0 +1,348 @@ + +# Airdrop Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Init](#Init) + * [Admin](#Admin) + * Messages + * [UpdateConfig](#UpdateConfig) + * [AddTasks](#AddTasks) + * [ClaimDecay](#ClaimDecay) + * [Task_Admin](#Task_Admin) + * Messages + * [CompleteTask](#CompleteTask) + * [User](#User) + * Messages + * [Account](#Account) + * [DisablePermitKey](#DisablePermitKey) + * [SetViewingKey](#SetViewingKey) + * [Claim](#Claim) + * Queries + * [Config](#Config) + * [Dates](#Dates) + * [TotalClaimed](#TotalClaimed) + * [Account](#Account) + * [AccountWithKey](#AccountWithKey) + +# Introduction +Contract responsible to handle snip20 airdrop + +# Sections + +## Init +##### Request +| Name | Type | Description | optional | +|----------------|---------------|----------------------------------------------------------------------------|----------| +| admin | String | New contract owner; SHOULD be a valid bech32 address | yes | +| dump_address | String | Where the decay amount will be sent | yes | +| airdrop_token | Contract | The token that will be airdropped | no | +| airdrop_amount | String | Total airdrop amount to be claimed | no | +| start_date | u64 | When the airdrop starts in UNIX time | yes | +| end_date | u64 | When the airdrop ends in UNIX time | yes | +| decay_start | u64 | When the airdrop decay starts in UNIX time | yes | +| merkle_root | String | Base 64 encoded merkle root of the airdrop data tree | no | +| total_accounts | u32 | Total accounts in airdrop (needed for merkle proof) | no | +| max_amount | String | Used to limit the user permit amounts (lowers exploit possibility) | no | +| default_claim | String | The default amount to be gifted regardless of tasks | no | +| task_claim | RequiredTasks | The amounts per tasks to gift | no | +| query_rounding | string | To prevent leaking information, total claimed is rounded off to this value | no | + +##Admin + +### Messages + +#### UpdateConfig +Updates the given values +##### Request +| Name | Type | Description | optional | +|----------------|--------|------------------------------------------------------|----------| +| admin | string | New contract admin; SHOULD be a valid bech32 address | yes | +| dump_address | string | Sets the dump address if there isnt any | yes | +| query_rounding | String | To prevent leaking information | yes | +| start_date | u64 | When the airdrop starts in UNIX time | yes | +| end_date | u64 | When the airdrop ends in UNIX time | yes | +| decay_start | u64 | When the airdrop decay starts in UNIX time | yes | +| padding | string | Allows for enforcing constant length messages | yes | + +#### AddTasks +Adds another task that can unlock the users claim percentage, total task percentage cannot exceed 100% +##### Task +| Name | Type | Description | optional | +|---------|--------|--------------------------------------------------|----------| +| address | String | The address that will grant the task to accounts | no | +| percent | string | The percent to be unlocked when completed | no | + +##### Request +| Name | Type | Description | optional | +|---------|--------|-----------------------------------------------|----------| +| tasks | Tasks | The new tasks to be added | no | +| padding | string | Allows for enforcing constant length messages | yes | + +##### Response +```json +{ + "add_tasks": { + "status": "success" + } +} +``` + +#### ClaimDecay +Drains the decayed amount of airdrop into the specified dump_address + +##### Response +```json +{ + "claim_decay": { + "status": "success" + } +} +``` + +##Task Admin + +### Messages + +#### CompleteTask +Complete that address' tasks for a given user +##### Request +| Name | Type | Description | optional | +|---------|--------|-----------------------------------------------|----------| +| address | String | The address that completed the task | no | +| padding | string | Allows for enforcing constant length messages | yes | + +##### Response +```json +{ + "complete_task": { + "status": "success" + } +} +``` + +##User + +### Messages + +### Account +(Creates / Updates) an account from which the user will claim all of his given addresses' rewards +##### Request +| Name | Type | Description | optional | +|--------------|----------------------------------------------------|-----------------------------------------------------------|----------| +| addresses | Array of [AddressProofPermit](#AddressProofPermit) | Proof that the user owns those addresses | no | +| partial_tree | Array of string | An array of nodes that serve as a proof for the addresses | no | +| padding | string | Allows for enforcing constant length messages | yes | + +##### Response +```json +{ + "account": { + "status": "success", + "total": "Total airdrop amount", + "claimed": "Claimed amount", + "finished_tasks": "All of the finished tasks", + "addresses": ["claimed addresses"] + } +} +``` + +### DisablePermitKey +Disables that permit's key. Any permit that has that key for that address will be declined. +##### Request +| Name | Type | Description | optional | +|---------|--------|-----------------------------------------------|----------| +| key | string | Permit key | no | +| padding | string | Allows for enforcing constant length messages | yes | + +##### Response +```json +{ + "disable_permit_key": { + "status": "success" + } +} +``` + +### SetViewingKey +Sets a viewing key for the account, useful for when the network is congested because of permits. +##### Request +| Name | Type | Description | optional | +|---------|--------|-----------------------------------------------|----------| +| key | string | Viewing key | no | +| padding | string | Allows for enforcing constant length messages | yes | + +##### Response +```json +{ + "set_viewing_key": { + "status": "success" + } +} +``` + +#### Claim +Claim the user's available claimable amount + +##### Response +```json +{ + "claim": { + "status": "success", + "total": "Total airdrop amount", + "claimed": "Claimed amount", + "finished_tasks": "All of the finished tasks", + "addresses": ["claimed addresses"] + } +} +``` + +### Queries + +#### GetConfig +Gets the contract's config +#### Response +```json +{ + "config": { + "config": "Contract's config" + } +} +``` + +## Dates +Get the contracts airdrop timeframe, can calculate the decay factor if a time is given +##### Request +| Name | Type | Description | optional | +|--------------|------|---------------------------------|----------| +| current_date | u64 | The current time in UNIX format | yes | +```json +{ + "dates": { + "start": "Airdrop start", + "end": "Airdrop end", + "decay_start": "Airdrop start of decay", + "decay_factor": "Decay percentage" + } +} +``` + +## TotalClaimed +Shows the total amount of the token that has been claimed. If airdrop hasn't ended then it'll just show an estimation. +##### Request +```json +{ + "total_claimed": { + "claimed": "Claimed amount" + } +} +``` + +## Account +Get the account's information +##### Request +| Name | Type | Description | optional | +|--------------|----------------------------------------|-----------------------------|----------| +| permit | [AccountProofPermit](#AccountProofMsg) | Address's permit | no | +| current_date | u64 | Current time in UNIT format | yes | +```json +{ + "account": { + "total": "Total airdrop amount", + "claimed": "Claimed amount", + "unclaimed": "Amount available to claim", + "finished_tasks": "All of the finished tasks", + "addresses": ["claimed addresses"] + } +} +``` + +## AccountWithKey +Get the account's information using a viewing key +##### Request +| Name | Type | Description | optional | +|--------------|--------|-----------------------------|----------| +| account | String | Accounts address | yes | +| key | String | Address's viewing key | no | +| current_date | u64 | Current time in UNIT format | yes | +```json +{ + "account_with_key": { + "total": "Total airdrop amount", + "claimed": "Claimed amount", + "unclaimed": "Amount available to claim", + "finished_tasks": "All of the finished tasks", + "addresses": ["claimed addresses"] + } +} +``` + +## AddressProofPermit +This is a structure used to prove that the user has permission to query that address's information (when querying account info). +This is also used to prove that the user owns that address (when creating/updating accounts) and the given amount is in the airdrop. +This permit is written differently from the rest since its made taking into consideration many of Terra's limitations compared to Keplr's flexibility. + +NOTE: The parameters must be in order + +[How to sign](https://github.com/securesecrets/shade/blob/77abdc70bc645d97aee7de5eb9a2347d22da425f/packages/shade_protocol/src/signature/mod.rs#L100) +#### Structure +| Name | Type | Description | optional | +|------------|-----------------|--------------------------------------------------------|----------| +| params | FillerMsg | Filler params accounting for Terra Ledgers limitations | no | +| memo | String | Base64Encoded AddressProofMsg | no | +| chain_id | String | Chain ID of the network this proof will be used in | no | +| signature | PermitSignature | Signature of the permit | no | + +## FillerMsg + +```json +{ + "coins": [], + "contract": "", + "execute_msg": "", + "sender": "" +} +``` + +## AddressProofMsg +The information inside permits that validate the airdrop eligibility and validate the account holder's key. + +NOTE: The parameters must be in order +### Structure +| Name | Type | Description | optional | +|----------|---------|---------------------------------------------------------|----------| +| address | String | Address of the signer (might be redundant) | no | +| amount | String | Airdrop amount | no | +| contract | String | Airdrop contract | no | +| index | Integer | Index of airdrop data in reference to the original tree | no | +| key | String | Some permit key | no | + +## AccountProofMsg +The information inside permits that validate account ownership + +NOTE: The parameters must be in order +### Structure +| Name | Type | Description | optional | +|----------|---------|---------------------------------------------------------|----------| +| contract | String | Airdrop contract | no | +| key | String | Some permit key | no | + + +## PermitSignature +The signature that proves the validity of the data + +NOTE: The parameters must be in order +### Structure +| Name | Type | Description | optional | +|-----------|--------|---------------------------|----------| +| pub_key | pubkey | Signer's public key | no | +| signature | String | Base 64 encoded signature | no | + +## Pubkey +Public key + +NOTE: The parameters must be in order +### Structure +| Name | Type | Description | optional | +|-------|--------|------------------------------------|----------| +| type | String | Must be tendermint/PubKeySecp256k1 | no | +| value | String | The base 64 key | no | \ No newline at end of file diff --git a/contracts/airdrop/src/contract.rs b/contracts/airdrop/src/contract.rs new file mode 100644 index 0000000..12fdef8 --- /dev/null +++ b/contracts/airdrop/src/contract.rs @@ -0,0 +1,201 @@ +use crate::{ + handle::{ + try_account, + try_add_tasks, + try_claim, + try_claim_decay, + try_complete_task, + try_disable_permit_key, + try_set_viewing_key, + try_update_config, + }, + query, + state::{config_w, decay_claimed_w, total_claimed_w}, +}; +use shade_protocol::{ + airdrop::{ + claim_info::RequiredTask, + errors::{invalid_dates, invalid_task_percentage}, + Config, + ExecuteMsg, + InstantiateMsg, + QueryMsg, + }, + c_std::{ + shd_entry_point, + to_binary, + Binary, + Deps, + DepsMut, + Env, + MessageInfo, + Response, + StdError, + StdResult, + Uint128, + }, + utils::{pad_handle_result, pad_query_result}, +}; + +// Used to pad up responses for better privacy. +pub const RESPONSE_BLOCK_SIZE: usize = 256; + +#[shd_entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + // Setup task claim + // let mut task_claim = vec![RequiredTask { + // address: env.contract.address.clone(), + // percent: msg.default_claim, + // }]; + // let mut claim = msg.task_claim; + // task_claim.append(&mut claim); + + // // Validate claim percentage + // let mut count = Uint128::zero(); + // for claim in task_claim.iter() { + // count += claim.percent; + // } + + // if count > Uint128::new(100u128) { + // return Err(invalid_task_percentage(count.to_string().as_str())); + // } + + let start_date = match msg.start_date { + None => env.block.time.seconds(), + Some(date) => date, + }; + + if let Some(end_date) = msg.end_date { + if end_date < start_date { + return Err(invalid_dates( + "EndDate", + end_date.to_string().as_str(), + "before", + "StartDate", + start_date.to_string().as_str(), + )); + } + } + + // Avoid decay collisions + if let Some(start_decay) = msg.decay_start { + if start_decay < start_date { + return Err(invalid_dates( + "Decay", + start_decay.to_string().as_str(), + "before", + "StartDate", + start_date.to_string().as_str(), + )); + } + if let Some(end_date) = msg.end_date { + if start_decay > end_date { + return Err(invalid_dates( + "EndDate", + end_date.to_string().as_str(), + "before", + "Decay", + start_decay.to_string().as_str(), + )); + } + } else { + return Err(StdError::generic_err("Decay must have an end date")); + } + } + + let config = Config { + admin: msg.admin.unwrap_or(info.sender), + contract: env.contract.address, + dump_address: msg.dump_address, + airdrop_snip20: msg.airdrop_token.clone(), + airdrop_snip20_optional: msg.airdrop_token_optional.clone(), + airdrop_amount: msg.airdrop_amount, + // task_claim, + start_date, + end_date: msg.end_date, + // decay_start: msg.decay_start, + merkle_root: msg.merkle_root, + total_accounts: msg.total_accounts, + max_amount: msg.max_amount, + query_rounding: msg.query_rounding, + claim_msg_plaintext: msg.claim_msg_plaintext, + }; + + config_w(deps.storage).save(&config)?; + + // Initialize claim amount + total_claimed_w(deps.storage).save(&Uint128::zero())?; + + decay_claimed_w(deps.storage).save(&false)?; + + Ok(Response::new()) +} + +#[shd_entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + pad_handle_result( + match msg { + ExecuteMsg::UpdateConfig { + admin, + dump_address, + query_rounding: redeem_step_size, + start_date, + end_date, + decay_start: start_decay, + .. + } => try_update_config( + deps, + env, + &info, + admin, + dump_address, + redeem_step_size, + start_date, + end_date, + start_decay, + ), + // ExecuteMsg::AddTasks { tasks, .. } => try_add_tasks(deps, &env, &info, tasks), + // ExecuteMsg::CompleteTask { address, .. } => { + // try_complete_task(deps, &env, &info, address) + // } + // ExecuteMsg::Account { + // addresses, + // partial_tree, + // .. + // } => try_account(deps, &env, &info, addresses, partial_tree), + ExecuteMsg::DisablePermitKey { key, .. } => { + try_disable_permit_key(deps, &env, &info, key) + } + ExecuteMsg::SetViewingKey { key, .. } => try_set_viewing_key(deps, &env, &info, key), + ExecuteMsg::Claim { amount, eth_pubkey, eth_sig, proof, .. } => try_claim(deps, &env, &info, amount, eth_pubkey, eth_sig, proof), + // ExecuteMsg::ClaimDecay { .. } => try_claim_decay(deps, &env, &info), + }, + RESPONSE_BLOCK_SIZE, + ) +} + +#[shd_entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + pad_query_result( + match msg { + QueryMsg::Config {} => to_binary(&query::config(deps)?), + QueryMsg::Dates { current_date } => to_binary(&query::dates(deps, current_date)?), + QueryMsg::TotalClaimed {} => to_binary(&query::total_claimed(deps)?), + // QueryMsg::Account { + // permit, + // current_date, + // } => to_binary(&query::account(deps, permit, current_date)?), + QueryMsg::AccountWithKey { + account, + key, + current_date, + } => to_binary(&query::account_with_key(deps, account, key, current_date)?), + }, + RESPONSE_BLOCK_SIZE, + ) +} \ No newline at end of file diff --git a/contracts/airdrop/src/handle.rs b/contracts/airdrop/src/handle.rs new file mode 100644 index 0000000..b1c5a2c --- /dev/null +++ b/contracts/airdrop/src/handle.rs @@ -0,0 +1,777 @@ +use crate::state::{ + account_r, + account_total_claimed_r, + account_total_claimed_w, + account_viewkey_w, + account_w, + address_in_account_w, + claim_status_r, + claim_status_w, + config_r, + config_w, + decay_claimed_w, + revoke_permit, + total_claimed_r, + total_claimed_w, + validate_address_permit, +}; +use rs_merkle::{algorithms::Sha256, Hasher, MerkleProof}; +use shade_protocol::{ + airdrop::{ + account::{Account, AccountKey, AddressProofMsg, AddressProofPermit}, + claim_info::RequiredTask, + errors::{ + account_does_not_exist, + address_already_in_account, + airdrop_ended, + airdrop_not_started, + claim_too_high, + decay_claimed, + decay_not_set, + expected_memo, + invalid_dates, + invalid_partial_tree, + invalid_task_percentage, + not_admin, + nothing_to_claim, + unexpected_error, + }, + Config, + ExecuteAnswer, + }, + c_std::{ + from_binary, + to_binary, + Addr, + Api, + Binary, + Decimal, + DepsMut, + Env, + MessageInfo, + Response, + StdResult, + Storage, + Uint128, + }, + query_authentication::viewing_keys::ViewingKey, + snip20::helpers::send_msg, + utils::generic_response::{ResponseStatus, ResponseStatus::Success}, +}; + +#[allow(clippy::too_many_arguments)] +pub fn try_update_config( + deps: DepsMut, + _env: Env, + info: &MessageInfo, + admin: Option, + dump_address: Option, + query_rounding: Option, + start_date: Option, + end_date: Option, + decay_start: Option, +) -> StdResult { + let config = config_r(deps.storage).load()?; + // Check if admin + if info.sender != config.admin { + return Err(not_admin(config.admin.as_str())); + } + + // Save new info + let mut config = config_w(deps.storage); + config.update(|mut state| { + if let Some(admin) = admin { + state.admin = admin; + } + if let Some(dump_address) = dump_address { + state.dump_address = Some(dump_address); + } + if let Some(query_rounding) = query_rounding { + state.query_rounding = query_rounding; + } + if let Some(start_date) = start_date { + // Avoid date collisions + if let Some(end_date) = end_date { + if start_date > end_date { + return Err(invalid_dates( + "EndDate", + end_date.to_string().as_str(), + "before", + "StartDate", + start_date.to_string().as_str(), + )); + } + } else if let Some(end_date) = state.end_date { + if start_date > end_date { + return Err(invalid_dates( + "EndDate", + end_date.to_string().as_str(), + "before", + "StartDate", + start_date.to_string().as_str(), + )); + } + } + if let Some(start_decay) = decay_start { + if start_date > start_decay { + return Err(invalid_dates( + "Decay", + start_decay.to_string().as_str(), + "before", + "StartDate", + start_date.to_string().as_str(), + )); + } + } else if let Some(start_decay) = state.decay_start { + if start_date > start_decay { + return Err(invalid_dates( + "Decay", + start_decay.to_string().as_str(), + "before", + "StartDate", + start_date.to_string().as_str(), + )); + } + } + + state.start_date = start_date; + } + if let Some(end_date) = end_date { + // Avoid date collisions + if let Some(decay_start) = decay_start { + if decay_start > end_date { + return Err(invalid_dates( + "EndDate", + end_date.to_string().as_str(), + "before", + "Decay", + decay_start.to_string().as_str(), + )); + } + } else if let Some(decay_start) = state.decay_start { + if decay_start > end_date { + return Err(invalid_dates( + "EndDate", + end_date.to_string().as_str(), + "before", + "Decay", + decay_start.to_string().as_str(), + )); + } + } + + state.end_date = Some(end_date); + } + if decay_start.is_some() { + state.decay_start = decay_start + } + + Ok(state) + })?; + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { status: Success })?)) +} + +// pub fn try_add_tasks( +// deps: DepsMut, +// _env: &Env, +// info: &MessageInfo, +// tasks: Vec, +// ) -> StdResult { +// let config = config_r(deps.storage).load()?; +// // Check if admin +// if info.sender != config.admin { +// return Err(not_admin(config.admin.as_str())); +// } + +// config_w(deps.storage).update(|mut config| { +// let mut task_list = tasks; +// config.task_claim.append(&mut task_list); + +// //Validate that they do not exceed 100 +// let mut count = Uint128::zero(); +// for task in config.task_claim.iter() { +// count += task.percent; +// } + +// if count > Uint128::new(100u128) { +// return Err(invalid_task_percentage(count.to_string().as_str())); +// } + +// Ok(config) +// })?; + +// Ok(Response::new().set_data(to_binary(&ExecuteAnswer::AddTask { status: Success })?)) +// } + +// pub fn try_account( +// deps: DepsMut, +// env: &Env, +// info: &MessageInfo, +// addresses: Vec, +// partial_tree: Vec, +// ) -> StdResult { +// // Check if airdrop active +// let config = config_r(deps.storage).load()?; + +// // Check that airdrop hasn't ended +// available(&config, env)?; + +// // Setup account +// let sender = info.sender.to_string(); + +// // These variables are setup to facilitate updating +// let updating_account: bool; +// let old_claim_amount: Uint128; + +// let mut account = match account_r(deps.storage).may_load(sender.as_bytes())? { +// None => { +// updating_account = false; +// old_claim_amount = Uint128::zero(); +// let mut account = Account::default(); + +// // Validate permits +// try_add_account_addresses( +// deps.storage, +// deps.api, +// &config, +// &info.sender, +// &mut account, +// addresses.clone(), +// partial_tree.clone(), +// )?; + +// // Add default claim at index 0 +// account_total_claimed_w(deps.storage).save(sender.as_bytes(), &Uint128::zero())?; +// claim_status_w(deps.storage, 0).save(sender.as_bytes(), &false)?; + +// account +// } +// Some(acc) => { +// updating_account = true; +// old_claim_amount = acc.total_claimable; +// acc +// } +// }; + +// // Claim airdrop +// let mut messages = vec![]; + +// // let (completed_percentage, unclaimed_percentage) = +// // update_tasks(deps.storage, &config, sender.clone())?; + +// let + +// redeem_amount = claim_tokens( +// deps.storage, +// env, +// info, +// &config, +// &account, +// amount +// )?; + + +// // Update account after claim to calculate difference +// if updating_account { +// // Validate permits +// try_add_account_addresses( +// deps.storage, +// deps.api, +// &config, +// &info.sender, +// &mut account, +// addresses.clone(), +// partial_tree.clone(), +// )?; +// } + +// if updating_account && completed_percentage > Uint128::zero() { +// // Calculate the total new address amount +// let added_address_total = account.total_claimable.checked_sub(old_claim_amount)?; +// account_total_claimed_w(deps.storage).update(sender.as_bytes(), |claimed| { +// if let Some(claimed) = claimed { +// let new_redeem: Uint128; +// if completed_percentage == Uint128::new(100u128) { +// new_redeem = +// added_address_total * decay_factor(env.block.time.seconds(), &config); +// } else { +// new_redeem = completed_percentage +// .multiply_ratio(added_address_total, Uint128::new(100u128)) +// * decay_factor(env.block.time.seconds(), &config); +// } + +// redeem_amount += new_redeem; +// Ok(claimed + new_redeem) +// } else { +// Err(unexpected_error()) +// } +// })?; +// } + +// if redeem_amount > Uint128::zero() { +// total_claimed_w(deps.storage) +// .update(|claimed| -> StdResult { Ok(claimed + redeem_amount) })?; + +// messages.push(send_msg( +// info.sender.clone(), +// redeem_amount.into(), +// None, +// None, +// None, +// &config.airdrop_snip20, +// )?); +// } + +// // Save account +// account_w(deps.storage).save(sender.as_bytes(), &account)?; + +// Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Account { +// status: ResponseStatus::Success, +// total: account.total_claimable, +// claimed: account_total_claimed_r(deps.storage).load(sender.to_string().as_bytes())?, +// // Will always be 0 since rewards are automatically claimed here +// finished_tasks: finished_tasks(deps.storage, sender.clone())?, +// addresses: account.addresses, +// })?)) +// } + +pub fn try_disable_permit_key( + deps: DepsMut, + _env: &Env, + info: &MessageInfo, + key: String, +) -> StdResult { + revoke_permit(deps.storage, info.sender.to_string(), key); + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::DisablePermitKey { + status: Success, + })?), + ) +} + +pub fn try_set_viewing_key( + deps: DepsMut, + _env: &Env, + info: &MessageInfo, + key: String, +) -> StdResult { + account_viewkey_w(deps.storage) + .save(&info.sender.to_string().as_bytes(), &AccountKey(key).hash())?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::SetViewingKey { + status: Success, + })?), + ) +} + +// pub fn try_complete_task( +// deps: DepsMut, +// _env: &Env, +// info: &MessageInfo, +// account: Addr, +// ) -> StdResult { +// let config = config_r(deps.storage).load()?; + +// for (i, task) in config.task_claim.iter().enumerate() { +// if task.address == info.sender { +// claim_status_w(deps.storage, i).update( +// account.to_string().as_bytes(), +// |status| -> StdResult { +// // If there was a state then ignore +// if let Some(status) = status { +// Ok(status) +// } else { +// Ok(false) +// } +// }, +// )?; +// } +// } + +// Ok(Response::new().set_data(to_binary(&ExecuteAnswer::CompleteTask { status: Success })?)) +// } + +pub fn try_claim( + deps: DepsMut, + env: &Env, + info: &MessageInfo, + amount: Uint128, + eth_pubkey: String, + eth_sig: String, + proof: Vec, +) -> StdResult { + let config = config_r(deps.storage).load()?; + + // Check that airdrop hasn't ended + available(&config, env)?; + + // Validate eth_signature + validation::validate_claim( &deps, + info.clone(), + eth_pubkey.clone(), + eth_sig, + config.clone(), + )?; + + // Get account + let sender = info.sender.clone(); + // let account = account_r(deps.storage).load(sender.to_string().as_bytes())?; + + + // Calculate airdrop + // let (completed_percentage, unclaimed_percentage) = + // update_tasks(deps.storage, &config, sender.to_string())?; + + // if unclaimed_percentage == Uint128::zero() { + // return Err(nothing_to_claim()); + // } + + let redeem_amount = claim_tokens( + deps.storage, + env, + info, + &config, + &sender, + &amount, + // completed_percentage, + // unclaimed_percentage, + )?; + + // let redeem_amount = amount.clone(); + + // If we want to have 100% allocation claim in 1tx, redeem_amount = amount.clone(); + total_claimed_w(deps.storage) + .update(|claimed| -> StdResult { Ok(claimed + redeem_amount) })?; + + Ok(Response::new() + .set_data(to_binary(&ExecuteAnswer::Claim { + status: ResponseStatus::Success, + total: account.total_claimable, + claimed: account_total_claimed_r(deps.storage).load(sender.to_string().as_bytes())?, + // finished_tasks: finished_tasks(deps.storage, sender.to_string())?, + addresses: account.addresses, + })?) + .add_message(send_msg( + sender.clone(), + redeem_amount.into(), + None, + None, + None, + &config.airdrop_snip20, + )?) + .add_message(send_msg( + sender.clone(), + redeem_amount.into(), + None, + None, + None, + &config.airdrop_snip20, + )?)) +} + +// pub fn try_claim_decay(deps: DepsMut, env: &Env, _info: &MessageInfo) -> StdResult { +// let config = config_r(deps.storage).load()?; + +// // Check if airdrop ended +// if let Some(end_date) = config.end_date { +// if let Some(dump_address) = config.dump_address { +// if env.block.time.seconds() > end_date { +// decay_claimed_w(deps.storage).update(|claimed| { +// if claimed { +// Err(decay_claimed()) +// } else { +// Ok(true) +// } +// })?; + +// let total_claimed = total_claimed_r(deps.storage).load()?; +// let send_total = config.airdrop_amount.checked_sub(total_claimed)?; +// let messages = vec![send_msg( +// dump_address.clone(), +// send_total.into(), +// None, +// None, +// None, +// &config.airdrop_snip20, +// )?]; + +// return Ok(Response::new() +// .set_data(to_binary(&ExecuteAnswer::ClaimDecay { status: Success })?)); +// } +// } +// } + +// Err(decay_not_set()) +// } + +// pub fn finished_tasks(storage: &dyn Storage, account: String) -> StdResult> { +// let mut finished_tasks = vec![]; +// let config = config_r(storage).load()?; + +// for (index, _task) in config.task_claim.iter().enumerate() { +// match claim_status_r(storage, index).may_load(account.as_bytes())? { +// None => {} +// Some(_) => { +// finished_tasks.push(config.task_claim[index].clone()); +// } +// } +// } + +// Ok(finished_tasks) +// } + +// /// Gets task information and sets them +// pub fn update_tasks( +// storage: &mut dyn Storage, +// config: &Config, +// sender: String, +// ) -> StdResult<(Uint128, Uint128)> { +// // Calculate eligible tasks +// let mut completed_percentage = Uint128::zero(); +// let mut unclaimed_percentage = Uint128::zero(); +// for (index, task) in config.task_claim.iter().enumerate() { +// // Check if task has been completed +// let state = claim_status_r(storage, index).may_load(sender.as_bytes())?; + +// match state { +// // Ignore if none +// None => {} +// Some(claimed) => { +// completed_percentage += task.percent; +// if !claimed { +// // Set claim status to true since we're going to claim it now +// claim_status_w(storage, index).save(sender.as_bytes(), &true)?; + +// unclaimed_percentage += task.percent; +// } +// } +// } +// } + +// Ok((completed_percentage, unclaimed_percentage)) +// } + +pub fn claim_tokens( + storage: &mut dyn Storage, + env: &Env, + info: &MessageInfo, + config: &Config, + sender: &Addr, + amount: Uint128, +) -> StdResult { + // send_amount + let sender = info.sender.to_string(); + + // Amount to be redeemed + let mut redeem_amount = amount.to; + + // Update total claimed and calculate claimable + // account_total_claimed_w(storage).update(sender.as_bytes(), |claimed| { + // if let Some(claimed) = claimed { + // // This solves possible uToken inaccuracies + // if completed_percentage == Uint128::new(100u128) { + // redeem_amount = account.total_claimable.checked_sub(claimed)?; + // } else { + // redeem_amount = unclaimed_percentage + // .multiply_ratio(account.total_claimable, Uint128::new(100u128)); + // } + + // // Update redeem amount with the decay multiplier + // redeem_amount = redeem_amount * decay_factor(env.block.time.seconds(), config); + + // Ok(claimed + redeem_amount) + // } else { + // Err(account_does_not_exist()) + // } + // })?; + + Ok(redeem_amount) +} + +// /// Validates all of the information and updates relevant states +// pub fn try_add_account_addresses( +// storage: &mut dyn Storage, +// api: &dyn Api, +// config: &Config, +// sender: &Addr, +// account: &mut Account, +// addresses: Vec, +// partial_tree: Vec, +// ) -> StdResult<()> { +// // Setup the items to validate +// let mut leaves_to_validate: Vec<(usize, [u8; 32])> = vec![]; + +// // Iterate addresses +// for permit in addresses.iter() { +// if let Some(memo) = permit.memo.clone() { +// let params: AddressProofMsg = from_binary(&Binary::from_base64(&memo)?)?; + +// // Avoid verifying sender +// if ¶ms.address != sender { +// // Check permit legitimacy +// validate_address_permit(storage, api, permit, ¶ms, config.contract.clone())?; +// } + +// // Check that airdrop amount does not exceed maximum +// if params.amount > config.max_amount { +// return Err(claim_too_high( +// params.amount.to_string().as_str(), +// config.max_amount.to_string().as_str(), +// )); +// } + +// // Update address if its not in an account +// address_in_account_w(storage).update( +// params.address.to_string().as_bytes(), +// |state| -> StdResult { +// if state.is_some() { +// return Err(address_already_in_account(params.address.as_str())); +// } + +// Ok(true) +// }, +// )?; + +// // Add account as a leaf +// let leaf_hash = +// Sha256::hash((params.address.to_string() + ¶ms.amount.to_string()).as_bytes()); +// leaves_to_validate.push((params.index as usize, leaf_hash)); + +// // If valid then add to account array and sum total amount +// account.addresses.push(params.address); +// account.total_claimable += params.amount; +// } else { +// return Err(expected_memo()); +// } +// } + +// // Need to sort by index in order for the proof to work +// leaves_to_validate.sort_by_key(|item| item.0); + +// let mut indices: Vec = vec![]; +// let mut leaves: Vec<[u8; 32]> = vec![]; + +// for leaf in leaves_to_validate.iter() { +// indices.push(leaf.0); +// leaves.push(leaf.1); +// } + +// // Convert partial tree from base64 to binary +// let mut partial_tree_binary: Vec<[u8; 32]> = vec![]; +// for node in partial_tree.iter() { +// let mut arr: [u8; 32] = Default::default(); +// arr.clone_from_slice(node.as_slice()); +// partial_tree_binary.push(arr); +// } + +// // Prove that user is in airdrop +// let proof = MerkleProof::::new(partial_tree_binary); +// // Convert to a fixed length array without messing up the contract +// let mut root: [u8; 32] = Default::default(); +// root.clone_from_slice(config.merkle_root.as_slice()); +// if !proof.verify(root, &indices, &leaves, config.total_accounts as usize) { +// return Err(invalid_partial_tree()); +// } + +// Ok(()) +// } + +pub fn available(config: &Config, env: &Env) -> StdResult<()> { + let current_time = env.block.time.seconds(); + + // Check if airdrop started + if current_time < config.start_date { + return Err(airdrop_not_started( + config.start_date.to_string().as_str(), + current_time.to_string().as_str(), + )); + } + if let Some(end_date) = config.end_date { + if current_time > end_date { + return Err(airdrop_ended( + end_date.to_string().as_str(), + current_time.to_string().as_str(), + )); + } + } + + Ok(()) +} + +/// Get the multiplier for decay, will return 1 when decay isnt in effect. +pub fn decay_factor(current_time: u64, config: &Config) -> Decimal { + // Calculate redeem amount after applying decay + if let Some(decay_start) = config.decay_start { + if current_time >= decay_start { + return inverse_normalizer(decay_start, current_time, config.end_date.unwrap()); + } + } + Decimal::one() +} + +/// Get the inverse normalized value [0,1] of x between [min, max] +pub fn inverse_normalizer(min: u64, x: u64, max: u64) -> Decimal { + Decimal::from_ratio(max - x, max - min) +} + +// src: https://github.com/public-awesome/launchpad/blob/main/contracts/sg-eth-airdrop/src/claim_airdrop.rs#L85 +mod validation { + use super::*; + use ethereum_verify::verify_ethereum_text; + use shade_protocol::c_std::StdError; + + + pub fn compute_plaintext_msg(config: &Config, info: MessageInfo) -> String { + str::replace( + &config.claim_msg_plaintext, + "{wallet}", + info.sender.as_ref(), + ) + } + + pub fn validate_claim( + deps: &DepsMut, + info: MessageInfo, + eth_pubkey: String, + eth_sig: String, + config: Config, + ) -> Result<(), StdError> { + validate_eth_sig(deps, info, eth_pubkey.clone(), eth_sig, config)?; + Ok(()) + } + + fn validate_eth_sig( + deps: &DepsMut, + info: MessageInfo, + eth_pubkey: String, + eth_sig: String, + config: Config, + ) -> Result<(), StdError> { + let valid_eth_sig = + validate_ethereum_text(deps, info, &config, eth_sig, eth_pubkey.clone())?; + match valid_eth_sig { + true => Ok(()), + false => Err(StdError::generic_err("cannot validate eth_sig")), + } + } + + pub fn validate_ethereum_text( + deps: &DepsMut, + info: MessageInfo, + config: &Config, + eth_sig: String, + eth_pubkey: String, + ) -> StdResult { + let plaintext_msg = compute_plaintext_msg(config, info); + match hex::decode(eth_sig.clone()) { + Ok(eth_sig_hex) => { + verify_ethereum_text(deps.as_ref(), &plaintext_msg, ð_sig_hex, ð_pubkey) + } + Err(_) => Err(StdError::InvalidHex { + msg: format!("Could not decode {eth_sig}"), + }), + } + } +} diff --git a/contracts/airdrop/src/lib.rs b/contracts/airdrop/src/lib.rs new file mode 100644 index 0000000..4fab968 --- /dev/null +++ b/contracts/airdrop/src/lib.rs @@ -0,0 +1,7 @@ +pub mod contract; +pub mod handle; +pub mod query; +pub mod state; + +#[cfg(test)] +mod test; \ No newline at end of file diff --git a/contracts/airdrop/src/query.rs b/contracts/airdrop/src/query.rs new file mode 100644 index 0000000..6780f15 --- /dev/null +++ b/contracts/airdrop/src/query.rs @@ -0,0 +1,134 @@ +use crate::{ + handle::decay_factor, + state::{ + account_r, + account_total_claimed_r, + account_viewkey_r, + claim_status_r, + config_r, + decay_claimed_r, + total_claimed_r, + validate_account_permit, + }, +}; +use shade_protocol::{ + airdrop::{ + account::{AccountKey, AccountPermit}, + claim_info::RequiredTask, + errors::invalid_viewing_key, + QueryAnswer, + }, + c_std::{Addr, Deps, StdResult, Uint128}, + query_authentication::viewing_keys::ViewingKey, +}; + +pub fn config(deps: Deps) -> StdResult { + Ok(QueryAnswer::Config { + config: config_r(deps.storage).load()?, + }) +} + +pub fn dates(deps: Deps, current_date: Option) -> StdResult { + let config = config_r(deps.storage).load()?; + Ok(QueryAnswer::Dates { + start: config.start_date, + end: config.end_date, + decay_start: config.decay_start, + decay_factor: current_date.map(|date| Uint128::new(100u128) * decay_factor(date, &config)), + }) +} + +pub fn total_claimed(deps: Deps) -> StdResult { + let claimed: Uint128; + let total_claimed = total_claimed_r(deps.storage).load()?; + if decay_claimed_r(deps.storage).load()? { + claimed = total_claimed; + } else { + let config = config_r(deps.storage).load()?; + claimed = total_claimed.checked_div(config.query_rounding)? * config.query_rounding; + } + Ok(QueryAnswer::TotalClaimed { claimed }) +} + +fn account_information( + deps: Deps, + account_address: Addr, + current_date: Option, +) -> StdResult { + let account = account_r(deps.storage).load(account_address.to_string().as_bytes())?; + + // Calculate eligible tasks + let config = config_r(deps.storage).load()?; + let mut finished_tasks: Vec = vec![]; + let mut completed_percentage = Uint128::zero(); + let mut unclaimed_percentage = Uint128::zero(); + for (index, task) in config.task_claim.iter().enumerate() { + // Check if task has been completed + let state = + claim_status_r(deps.storage, index).may_load(account_address.to_string().as_bytes())?; + + match state { + // Ignore if none + None => {} + Some(claimed) => { + finished_tasks.push(task.clone()); + if !claimed { + unclaimed_percentage += task.percent; + } else { + completed_percentage += task.percent; + } + } + } + } + + let mut unclaimed: Uint128; + + if unclaimed_percentage == Uint128::new(100u128) { + unclaimed = account.total_claimable; + } else { + unclaimed = + unclaimed_percentage.multiply_ratio(account.total_claimable, Uint128::new(100u128)); + } + + if let Some(time) = current_date { + unclaimed = unclaimed * decay_factor(time, &config); + } + + Ok(QueryAnswer::Account { + total: account.total_claimable, + claimed: account_total_claimed_r(deps.storage) + .load(account_address.to_string().as_bytes())?, + unclaimed, + finished_tasks, + addresses: account.addresses, + }) +} + +pub fn account( + deps: Deps, + permit: AccountPermit, + current_date: Option, +) -> StdResult { + let config = config_r(deps.storage).load()?; + account_information( + deps, + validate_account_permit(deps, &permit, config.contract)?, + current_date, + ) +} + +pub fn account_with_key( + deps: Deps, + account: Addr, + key: String, + current_date: Option, +) -> StdResult { + // Validate address + let stored_hash = account_viewkey_r(deps.storage).load(account.to_string().as_bytes())?; + + if !AccountKey(key).compare(&stored_hash) { + return Err(invalid_viewing_key()); + } + + account_information(deps, account, current_date) +} diff --git a/contracts/airdrop/src/state.rs b/contracts/airdrop/src/state.rs new file mode 100644 index 0000000..acf98dc --- /dev/null +++ b/contracts/airdrop/src/state.rs @@ -0,0 +1,196 @@ +use shade_protocol::c_std::{Deps, Uint128}; +use shade_protocol::c_std::{ + Api, + Addr, + StdResult, + Storage, +}; +use shade_protocol::storage::{ + bucket, + bucket_read, + singleton, + singleton_read, + Bucket, + ReadonlyBucket, + ReadonlySingleton, + Singleton, +}; +use shade_protocol::contract_interfaces::airdrop::{ + account::{ + authenticate_ownership, + Account, + AccountPermit, + AddressProofMsg, + AddressProofPermit, + }, + errors::{permit_contract_mismatch, permit_key_revoked}, + Config, +}; + +pub static CONFIG_KEY: &[u8] = b"config"; +pub static DECAY_CLAIMED_KEY: &[u8] = b"decay_claimed"; +pub static CLAIM_STATUS_KEY: &[u8] = b"claim_status_"; +pub static REWARD_IN_ACCOUNT_KEY: &[u8] = b"reward_in_account"; +pub static ACCOUNTS_KEY: &[u8] = b"accounts"; +pub static TOTAL_CLAIMED_KEY: &[u8] = b"total_claimed"; +pub static USER_TOTAL_CLAIMED_KEY: &[u8] = b"user_total_claimed"; +pub static ACCOUNT_PERMIT_KEY: &str = "account_permit_key"; +pub static ACCOUNT_VIEWING_KEY: &[u8] = b"account_viewing_key"; + +pub fn config_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, CONFIG_KEY) +} + +pub fn config_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, CONFIG_KEY) +} + +pub fn decay_claimed_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, DECAY_CLAIMED_KEY) +} + +pub fn decay_claimed_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, DECAY_CLAIMED_KEY) +} + +// Is address added to an account +pub fn address_in_account_r(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, REWARD_IN_ACCOUNT_KEY) +} + +pub fn address_in_account_w(storage: &mut dyn Storage) -> Bucket { + bucket(storage, REWARD_IN_ACCOUNT_KEY) +} + +// airdrop account +pub fn account_r(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, ACCOUNTS_KEY) +} + +pub fn account_w(storage: &mut dyn Storage) -> Bucket { + bucket(storage, ACCOUNTS_KEY) +} + +// If not found then its unrewarded; if true then claimed +pub fn claim_status_r(storage: &dyn Storage, index: usize) -> ReadonlyBucket { + let mut key = CLAIM_STATUS_KEY.to_vec(); + key.push(index as u8); + bucket_read(storage, &key) +} + +pub fn claim_status_w(storage: &mut dyn Storage, index: usize) -> Bucket { + let mut key = CLAIM_STATUS_KEY.to_vec(); + key.push(index as u8); + bucket(storage, &key) +} + +// Total claimed +pub fn total_claimed_r(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, TOTAL_CLAIMED_KEY) +} + +pub fn total_claimed_w(storage: &mut dyn Storage) -> Singleton { + singleton(storage, TOTAL_CLAIMED_KEY) +} + +// Total account claimed +pub fn account_total_claimed_r(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, USER_TOTAL_CLAIMED_KEY) +} + +pub fn account_total_claimed_w(storage: &mut dyn Storage) -> Bucket { + bucket(storage, USER_TOTAL_CLAIMED_KEY) +} + +// Account viewing key +pub fn account_viewkey_r(storage: &dyn Storage) -> ReadonlyBucket<[u8; 32]> { + bucket_read(storage, ACCOUNT_VIEWING_KEY) +} + +pub fn account_viewkey_w(storage: &mut dyn Storage) -> Bucket<[u8; 32]> { + bucket(storage, ACCOUNT_VIEWING_KEY) +} + +// Account permit key +pub fn account_permit_key_r(storage: &dyn Storage, account: String) -> ReadonlyBucket { + let key = ACCOUNT_PERMIT_KEY.to_string() + &account; + bucket_read(storage, key.as_bytes()) +} + +pub fn account_permit_key_w(storage: &mut dyn Storage, account: String) -> Bucket { + let key = ACCOUNT_PERMIT_KEY.to_string() + &account; + bucket(storage, key.as_bytes()) +} + +pub fn revoke_permit(storage: &mut dyn Storage, account: String, permit_key: String) { + account_permit_key_w(storage, account) + .save(permit_key.as_bytes(), &false) + .unwrap(); +} + +pub fn is_permit_revoked( + storage: &dyn Storage, + account: String, + permit_key: String, +) -> StdResult { + if account_permit_key_r(storage, account) + .may_load(permit_key.as_bytes())? + .is_some() + { + Ok(true) + } else { + Ok(false) + } +} + +pub fn validate_address_permit( + storage: &dyn Storage, + api: &dyn Api, + permit: &AddressProofPermit, + params: &AddressProofMsg, + contract: Addr, +) -> StdResult<()> { + // Check that contract matches + if params.contract != contract { + return Err(permit_contract_mismatch( + params.contract.as_str(), + contract.as_str(), + )); + } + + // Check that permit is not revoked + if is_permit_revoked(storage, params.address.to_string(), params.key.clone())? { + return Err(permit_key_revoked(params.key.as_str())); + } + + // Authenticate permit + authenticate_ownership(api, permit, params.address.as_str()) +} + +pub fn validate_account_permit( + deps: Deps, + permit: &AccountPermit, + contract: Addr, +) -> StdResult { + // Check that contract matches + if permit.params.contract != contract { + return Err(permit_contract_mismatch( + permit.params.contract.as_str(), + contract.as_str(), + )); + } + + // Authenticate permit + let address = permit.validate(deps.api, None)?.as_addr(None)?; + + // Check that permit is not revoked + if is_permit_revoked( + deps.storage, + address.to_string(), + permit.params.key.clone(), + )? { + return Err(permit_key_revoked(permit.params.key.as_str())); + } + + return Ok(address); +} diff --git a/contracts/airdrop/src/test.rs b/contracts/airdrop/src/test.rs new file mode 100644 index 0000000..af4835c --- /dev/null +++ b/contracts/airdrop/src/test.rs @@ -0,0 +1,346 @@ +#[cfg(test)] +pub mod tests { + use crate::handle::inverse_normalizer; + use shade_protocol::{ + airdrop::account::{AddressProofMsg, AddressProofPermit, FillerMsg}, + c_std::{from_binary, testing::mock_dependencies, Addr, Binary, Uint128}, + query_authentication::{ + permit::bech32_to_canonical, + transaction::{PermitSignature, PubKey}, + }, + }; + + #[test] + fn decay_factor() { + assert_eq!( + Uint128::new(50u128), + Uint128::new(100u128) * inverse_normalizer(100, 200, 300) + ); + + assert_eq!( + Uint128::new(25u128), + Uint128::new(100u128) * inverse_normalizer(0, 75, 100) + ); + } + + const MSGTYPE: &str = "wasm/MsgExecuteContract"; + + #[test] + fn terra_station_ledger() { + let mut permit = AddressProofPermit { + params: FillerMsg::default(), + chain_id: Some("columbus-5".to_string()), + sequence: None, + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64( + "Ar7aIv8k6Rm7ugLBAHShtRWmZ/CDgvwXYOc8Ffycwggc").unwrap()), + signature: Binary::from_base64( + "MM1UOheGCYX0Cb3r8zVhyZyWk/qIY61yqiDP53//31cjkd7G5FfEki+JC91kBRYCnt9NlI7gjnY8ZcJauDH3FA==").unwrap(), + }, + account_number: Some(Uint128::new(3441602u128).into()), + memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) + }; + + let deps = mock_dependencies(); + let addr = permit + .validate(&deps.api, Some(MSGTYPE.to_string())) + .expect("Signature validation failed"); + assert_eq!( + addr.as_canonical(), + bech32_to_canonical("terra17dhvxnwzazszgtuc498qsudh7zq945qh29gj4e") + ); + assert_ne!( + addr.as_canonical(), + bech32_to_canonical("terra19m2zgdyuq0crpww00jc2a9k70ut944dum53p7x") + ); + + permit.memo = Some("OtherMemo".to_string()); + + // NOTE: New SN broke unit testing + // assert!( + // permit + // .validate(&deps.api, Some("wasm/MsgExecuteContract".to_string())) + // .is_err() + // ) + } + + #[test] + fn terra_station_non_ledger() { + let mut permit = AddressProofPermit { + params: FillerMsg::default(), + chain_id: Some("columbus-5".to_string()), + sequence: None, + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64( + "A8r22cTiywZYSoWR5DnmAeP1jPDF3CLVKJe1QGorv9cM").unwrap()), + signature: Binary::from_base64( + "xhU2JkJDWO/eZEeJVp8vo1rNAK7H7G2uDucZAjAhfVRjLHHX7C+16dwQzr0Jmd2DdZHAJZNkGhGb5nucicN1TA==").unwrap(), + }, + account_number: None, + memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) + }; + + let deps = mock_dependencies(); + let addr = permit + .validate(&deps.api, Some(MSGTYPE.to_string())) + .expect("Signature validation failed"); + assert_eq!( + addr.as_canonical(), + bech32_to_canonical("terra1j8wupj3kpclp98dgg4j5am44kjykx6uztjttyr") + ); + assert_ne!( + addr.as_canonical(), + bech32_to_canonical("terra1ns69jhkjg5wmcgf8w8ecewnpca7sezyhvg0a29") + ); + + permit.memo = Some("OtherMemo".to_string()); + + // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) + } + + #[test] + fn keplr_terra_non_ledger() { + let mut permit = AddressProofPermit { + params: FillerMsg::default(), + chain_id: Some("columbus-5".to_string()), + sequence: None, + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64( + "AyGKvc3OCs/pg7unFCJgKjtqiLYRACeR4ZU0f8UVDFbM").unwrap()), + signature: Binary::from_base64( + "fbgFeYUsAjI2CB2dwaqttolFE1wx/3MXbNWYKicJj20mV3marS4zz+k5aCKsYlv4HSd9NYxl4deuhasMKndB2w==").unwrap(), + }, + account_number: None, + memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) + }; + + let deps = mock_dependencies(); + let addr = permit + .validate(&deps.api, Some(MSGTYPE.to_string())) + .expect("Signature validation failed"); + assert_eq!( + addr.as_canonical(), + bech32_to_canonical("terra18xg6g5yfzflnt8v45r2yndnydhg2vndvzsv3rn") + ); + assert_ne!( + addr.as_canonical(), + bech32_to_canonical("terra1ns69jhkjg5wmcgf8w8ecewnpca7sezyhvg0a29") + ); + + permit.memo = Some("OtherMemo".to_string()); + + // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) + } + + #[test] + fn keplr_terra_ledger() { + let mut permit = AddressProofPermit { + params: FillerMsg::default(), + chain_id: Some("columbus-5".to_string()), + sequence: None, + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64( + "AqjDFVbY+znM1F5XCDuaca0JT0uAdd3QyuHt04j9k0DB").unwrap()), + signature: Binary::from_base64( + "1kwDnlsltqgj8fqbohs0MMgEWiRMUmznM98ofOranBAe5f8Ja1tZCKmh5miPkgC6KoUdOam7BvBjuFhM1q0rBA==").unwrap(), + }, + account_number: None, + memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) + }; + + let deps = mock_dependencies(); + let addr = permit + .validate(&deps.api, Some(MSGTYPE.to_string())) + .expect("Signature validation failed"); + assert_eq!( + addr.as_canonical(), + bech32_to_canonical("terra1ns69jhkjg5wmcgf8w8ecewnpca7sezyhvg0a29") + ); + assert_ne!( + addr.as_canonical(), + bech32_to_canonical("terra18xg6g5yfzflnt8v45r2yndnydhg2vndvzsv3rn") + ); + + permit.memo = Some("OtherMemo".to_string()); + + // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) + } + + #[test] + fn keplr_sn_non_ledger() { + let mut permit = AddressProofPermit { + params: FillerMsg::default(), + chain_id: Some("secret-4".to_string()), + sequence: None, + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64( + "A2uZZ02iy/QhPZ0s6WO8HTEfNZEnt5o5PsQ34WHmQFPK").unwrap()), + signature: Binary::from_base64( + "s80mH5OuZCudS20d0k73evWx5xGrC2l3uubQjIkukT4L5mcgsepDIq9d1YpAJwiUEitaHFOGy42MfHZJVY1LdA==").unwrap(), + }, + account_number: None, + memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) + }; + + let deps = mock_dependencies(); + let addr = permit + .validate(&deps.api, Some(MSGTYPE.to_string())) + .expect("Signature validation failed"); + assert_eq!( + addr.as_canonical(), + bech32_to_canonical("secret19q7h2zy8mgesy3r39el5fcm986nxqjd7cgylrz") + ); + assert_ne!( + addr.as_canonical(), + bech32_to_canonical("secret1ns69jhkjg5wmcgf8w8ecewnpca7sezyhgfp54e") + ); + + permit.memo = Some("OtherMemo".to_string()); + + // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) + } + + #[test] + fn keplr_sn_ledger() { + let mut permit = AddressProofPermit { + params: FillerMsg::default(), + chain_id: Some("secret-4".to_string()), + sequence: None, + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64( + "AqjDFVbY+znM1F5XCDuaca0JT0uAdd3QyuHt04j9k0DB").unwrap()), + signature: Binary::from_base64( + "snd8k5nWAAVoUytxKZt1FCUNQNXLAQpBlF7h4YGbTmx3S5+rqaZnM2bKq1ifCvErz/pdeE7B/s+WsGLdQRpzoA==").unwrap(), + }, + account_number: None, + memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) + }; + + let deps = mock_dependencies(); + let addr = permit + .validate(&deps.api, Some(MSGTYPE.to_string())) + .expect("Signature validation failed"); + assert_eq!( + addr.as_canonical(), + bech32_to_canonical("secret1ns69jhkjg5wmcgf8w8ecewnpca7sezyhgfp54e") + ); + assert_ne!( + addr.as_canonical(), + bech32_to_canonical("secret19q7h2zy8mgesy3r39el5fcm986nxqjd7cgylrz") + ); + + permit.memo = Some("OtherMemo".to_string()); + + // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) + } + + #[test] + fn keplr_cosmos_non_ledger() { + let mut permit = AddressProofPermit { + params: FillerMsg::default(), + chain_id: Some("cosmoshub-4".to_string()), + sequence: None, + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64( + "AqcyBLqPn7QnOctkK9i9KhnhD0aHA03+LppvNTCdZ1wK").unwrap()), + signature: Binary::from_base64( + "KLwotev7wnbj2VGBxbyTfIrRn/1vQY3x3I7BAUhu4FIC6OHVXqxIl/lclgdBWksnr32ULVfz8u78OqEbaePRZQ==").unwrap(), + }, + account_number: None, + memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) + }; + + let deps = mock_dependencies(); + let addr = permit + .validate(&deps.api, Some(MSGTYPE.to_string())) + .expect("Signature validation failed"); + assert_eq!( + addr.as_canonical(), + bech32_to_canonical("cosmos1lj5vh5y8yp4a97jmfwpd98lsg0tf5lsqgnnhq3") + ); + assert_ne!( + addr.as_canonical(), + bech32_to_canonical("cosmos1ns69jhkjg5wmcgf8w8ecewnpca7sezyh2v4ag9") + ); + + permit.memo = Some("OtherMemo".to_string()); + + // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) + } + + #[test] + fn keplr_cosmos_ledger() { + let mut permit = AddressProofPermit { + params: FillerMsg::default(), + chain_id: Some("cosmoshub-4".to_string()), + sequence: None, + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64( + "AqjDFVbY+znM1F5XCDuaca0JT0uAdd3QyuHt04j9k0DB").unwrap()), + signature: Binary::from_base64( + "h/RpG1eKzN03oId0GvN7TSxoHOUibjmqPEQ1E+ZWh+BvghPL99lBj4L3BKpjjsaRtXX3lexO7ztafLKBVtq4xA==").unwrap(), + }, + account_number: None, + memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) + }; + + let deps = mock_dependencies(); + let addr = permit + .validate(&deps.api, Some(MSGTYPE.to_string())) + .expect("Signature validation failed"); + assert_eq!( + addr.as_canonical(), + bech32_to_canonical("cosmos1ns69jhkjg5wmcgf8w8ecewnpca7sezyh2v4ag9") + ); + assert_ne!( + addr.as_canonical(), + bech32_to_canonical("cosmos1lj5vh5y8yp4a97jmfwpd98lsg0tf5lsqgnnhq3") + ); + + permit.memo = Some("OtherMemo".to_string()); + + // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) + } + + #[test] + fn memo_deserialization() { + let expected_memo = AddressProofMsg { + address: Addr::unchecked("secret19q7h2zy8mgesy3r39el5fcm986nxqjd7cgylrz".to_string()), + amount: Uint128::new(1000000u128), + contract: Addr::unchecked("secret1sr62lehajgwhdzpmnl65u35rugjrgznh2572mv".to_string()), + index: 10, + key: "account-creation-permit".to_string(), + }; + + let deserialized_memo: AddressProofMsg = from_binary( + &Binary::from_base64( + &"eyJhZGRyZXNzIjoic2VjcmV0MTlxN2gyenk4bWdlc3kzcjM5ZWw1ZmNtOTg2bnhxamQ3Y2d5bHJ6IiwiYW1vdW50IjoiMTAwMDAwMCIsImNvbnRyYWN0Ijoic2VjcmV0MXNyNjJsZWhhamd3aGR6cG1ubDY1dTM1cnVnanJnem5oMjU3Mm12IiwiaW5kZXgiOjEwLCJrZXkiOiJhY2NvdW50LWNyZWF0aW9uLXBlcm1pdCJ9" + .to_string()).unwrap()).unwrap(); + + assert_eq!(deserialized_memo, expected_memo) + } + + #[test] + fn claim_query() { + assert_eq!( + Uint128::new(300u128), + (Uint128::new(345u128) / Uint128::new(100u128)) * Uint128::new(100u128) + ) + } + + #[test] + fn claim_query_odd_multiple() { + assert_eq!( + Uint128::new(13475u128), + (Uint128::new(13480u128) / Uint128::new(7u128)) * Uint128::new(7u128) + ) + } + + #[test] + fn claim_query_under_step() { + assert_eq!( + Uint128::zero(), + (Uint128::new(200u128) / Uint128::new(1000u128)) * Uint128::new(1000u128) + ) + } +} diff --git a/contracts/basic_staking/.cargo/config b/contracts/basic_staking/.cargo/config new file mode 100644 index 0000000..882fe08 --- /dev/null +++ b/contracts/basic_staking/.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/basic_staking/.circleci/config.yml b/contracts/basic_staking/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/contracts/basic_staking/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/basic_staking/Cargo.toml b/contracts/basic_staking/Cargo.toml new file mode 100644 index 0000000..4c830ee --- /dev/null +++ b/contracts/basic_staking/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "basic_staking" +version = "0.1.0" +authors = ["Jack Swenson "] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ + "basic_staking", + "math", + "storage_plus", + "admin", + "airdrop", +] } +cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" } + +[dev-dependencies] +shade-multi-test = { path = "../../packages/multi_test", features = [ + "basic_staking", + "snip20", + "query_auth", + "admin" +] } diff --git a/contracts/basic_staking/Makefile b/contracts/basic_staking/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/contracts/basic_staking/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/basic_staking/README.md b/contracts/basic_staking/README.md new file mode 100644 index 0000000..d41a979 --- /dev/null +++ b/contracts/basic_staking/README.md @@ -0,0 +1,83 @@ +# Snip20 Staking +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Init](#Init) + * [Interface](#Interface) + * Messages + * [Receive](#Receive) + * [UpdateConfig](#UpdateConfig) + * [RegisterRewards](#RegisterRewards) + * [Unbond](#Unbond) + * [Withdraw](#Withdraw) + * [Claim](#Claim) + * [Compound](#Compound) + * [CancelRewardPool](#CancelRewardPool) + * [TransferStake](#TransferStake) + * Queries + * [Config](#Config) + * [StakeToken](#StakeToken) + * [StakingInfo](#StakingInfo) + * [TotalStaked](#TotalStaked) + * [RewardTokens](#RewardTokens) + * [RewardPools](#RewardPools) + * [Balance](#Balance) + * [Staked](#Staked) + * [Rewards](#Rewards) + * [Unbonding](#Unbonding) + +# Introduction +This contract allows users to lock up their 'stake_token', with a configurable unbonding period. Staking users will earn rewards from all active reward pools based on their stake amount / total staked. +Rewards will be initialized by sending in an amount of tokens to be emitted, with start/end timestamps for the rewards period. +Reward pools can be initialized with any registered reward token (admin-only registration). Admins can always init a reward pool (known as 'official'), there is also a configurable 'max_user_pools' that determines how many pools are allowed at 1 time that can be initialized permissionlessly (by any user) + +# Sections + +## Init +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +| admin_auth | Contract | shade admin authentication contract +| query_auth | Contract | shade query authentication contract +| stake_token | Contract | token that will be deposited for staking +| unbond_period | Uint128 | How long it takes to unbond funds in seconds +| max_user_pools | Uint128 | How many permissionless pools are allowed +| reward_cancel_threshold | Uint128 | Percentage of rewards that must be claimed for a reward pool to be cancelled without 'force' +| viewing_key | String | Contract viewing key for snip20's + +## Interface + +### Messages +#### UpdateConfig +Updates the given values +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +| admin_auth | Contract | shade admin authentication contract +| query_auth | Contract | shade query authentication contract +| unbond_period | Uint128 | How long it takes to unbond funds in seconds +| max_user_pools | Uint128 | How many permissionless pools are allowed +| reward_cancel_threshold | Uint128 | Percentage of rewards that must be claimed for a reward pool to be cancelled without 'force' + +##### Response +```json +{ + "update_config": { + "status": "success" + } +} +``` + +### Queries + +#### Config +Gets the contract's configuration variables +##### Response +```json +{ + "config": { + "config": { + "owner": "Owner address", + } + } +} +``` diff --git a/contracts/basic_staking/src/contract.rs b/contracts/basic_staking/src/contract.rs new file mode 100644 index 0000000..5649695 --- /dev/null +++ b/contracts/basic_staking/src/contract.rs @@ -0,0 +1,212 @@ +use shade_protocol::{ + basic_staking::{Auth, AuthPermit, Config, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg}, + c_std::{ + shd_entry_point, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, + StdError, StdResult, Uint128, + }, + query_auth::helpers::{authenticate_permit, authenticate_vk, PermitAuthentication}, + snip20::helpers::{register_receive, set_viewing_key_msg}, + utils::{asset::Contract, pad_handle_result}, +}; + +use crate::{execute, query, storage::*}; + +pub const RESPONSE_BLOCK_SIZE: usize = 256; + +#[shd_entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + CONFIG.save( + deps.storage, + &Config { + admin_auth: msg.admin_auth.into_valid(deps.api)?, + query_auth: msg.query_auth.into_valid(deps.api)?, + airdrop: match msg.airdrop { + Some(airdrop) => Some(airdrop.into_valid(deps.api)?), + None => None, + }, + unbond_period: msg.unbond_period, + max_user_pools: msg.max_user_pools, + }, + )?; + + let stake_token = msg.stake_token.into_valid(deps.api)?; + + STAKE_TOKEN.save(deps.storage, &stake_token)?; + VIEWING_KEY.save(deps.storage, &msg.viewing_key)?; + + REWARD_TOKENS.save(deps.storage, &vec![stake_token.clone()])?; + REWARD_POOLS.save(deps.storage, &vec![])?; + MAX_POOL_ID.save(deps.storage, &Uint128::zero())?; + + TRANSFER_WL.save(deps.storage, &vec![])?; + + TOTAL_STAKED.save(deps.storage, &Uint128::zero())?; + + let resp = Response::new().add_messages(vec![ + set_viewing_key_msg(msg.viewing_key, None, &stake_token)?, + register_receive(env.contract.code_hash, None, &stake_token)?, + ]); + + Ok(resp) +} + +#[shd_entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + pad_handle_result( + match msg { + ExecuteMsg::UpdateConfig { + admin_auth, + query_auth, + airdrop, + unbond_period, + max_user_pools, + padding, + } => execute::update_config( + deps, + env, + info, + admin_auth, + query_auth, + airdrop, + unbond_period, + max_user_pools, + ), + ExecuteMsg::RegisterRewards { token, padding } => { + let api = deps.api; + execute::register_reward(deps, env, info, token.into_valid(api)?) + } + ExecuteMsg::AddTransferWhitelist { user, padding } => { + let api = deps.api; + execute::add_transfer_whitelist(deps, env, info, api.addr_validate(&user)?) + } + ExecuteMsg::RemoveTransferWhitelist { user, padding } => { + let api = deps.api; + execute::rm_transfer_whitelist(deps, env, info, api.addr_validate(&user)?) + } + ExecuteMsg::Receive { + sender, + from, + amount, + msg, + .. + } => execute::receive(deps, env, info, sender, from, amount, msg), + ExecuteMsg::Claim { padding } => execute::claim(deps, env, info), + ExecuteMsg::Unbond { + amount, + compound, + padding, + } => execute::unbond(deps, env, info, amount, compound.unwrap_or(false)), + ExecuteMsg::Withdraw { ids, padding } => { + execute::withdraw(deps, env, info.clone(), ids) + } + ExecuteMsg::Compound { padding } => execute::compound(deps, env, info), + ExecuteMsg::EndRewardPool { id, force, padding } => { + execute::end_reward_pool(deps, env, info, id, force.unwrap_or(false)) + } + ExecuteMsg::TransferStake { + amount, + recipient, + compound, + padding, + } => { + let api = deps.api; + execute::transfer_stake( + deps, + env, + info, + amount, + api.addr_validate(&recipient)?, + compound.unwrap_or(false), + ) + } + }, + RESPONSE_BLOCK_SIZE, + ) +} + +pub fn authenticate(deps: Deps, auth: Auth, query_auth: Contract) -> StdResult { + match auth { + Auth::ViewingKey { key, address } => { + let address = deps.api.addr_validate(&address)?; + if !authenticate_vk(address.clone(), key, &deps.querier, &query_auth)? { + return Err(StdError::generic_err("Invalid Viewing Key")); + } + Ok(address) + } + Auth::Permit(permit) => { + let res: PermitAuthentication = + authenticate_permit(permit, &deps.querier, query_auth)?; + if res.revoked { + return Err(StdError::generic_err("Permit Revoked")); + } + Ok(res.sender) + } + } +} + +#[shd_entry_point] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => to_binary(&query::config(deps)?), + QueryMsg::StakeToken {} => to_binary(&query::stake_token(deps)?), + QueryMsg::StakingInfo {} => to_binary(&query::staking_info(deps)?), + QueryMsg::TotalStaked {} => to_binary(&query::total_staked(deps)?), + QueryMsg::RewardTokens {} => to_binary(&query::reward_tokens(deps)?), + QueryMsg::RewardPools {} => to_binary(&query::reward_pools(deps)?), + QueryMsg::Balance { + auth, + unbonding_ids, + } => { + let config = CONFIG.load(deps.storage)?; + let user = authenticate(deps, auth, config.query_auth)?; + let unbonding_ids = match unbonding_ids { + Some(ids) => ids, + None => { + if let Some(ids) = USER_UNBONDING_IDS.may_load(deps.storage, user.clone())? { + ids + } else { + vec![] + } + } + }; + to_binary(&query::user_balance( + deps, + env, + user.clone(), + unbonding_ids, + )?) + } + QueryMsg::Staked { auth } => { + let config = CONFIG.load(deps.storage)?; + to_binary(&query::user_staked( + deps, + authenticate(deps, auth, config.query_auth)?, + )?) + } + QueryMsg::Rewards { auth } => { + let config = CONFIG.load(deps.storage)?; + to_binary(&query::user_rewards( + deps, + env, + authenticate(deps, auth, config.query_auth)?, + )?) + } + QueryMsg::Unbonding { auth, ids } => { + let config = CONFIG.load(deps.storage)?; + let user = authenticate(deps, auth, config.query_auth)?; + to_binary(&query::user_unbondings( + deps, + user.clone(), + ids.unwrap_or(USER_UNBONDING_IDS.load(deps.storage, user)?), + )?) + } + QueryMsg::TransferWhitelist {} => to_binary(&QueryAnswer::TransferWhitelist { + whitelist: TRANSFER_WL.load(deps.storage)?, + }), + } +} diff --git a/contracts/basic_staking/src/execute.rs b/contracts/basic_staking/src/execute.rs new file mode 100644 index 0000000..e637533 --- /dev/null +++ b/contracts/basic_staking/src/execute.rs @@ -0,0 +1,938 @@ +use shade_protocol::{ + admin::helpers::{admin_is_valid, validate_admin, AdminPermissions}, + basic_staking::{Action, ExecuteAnswer, RewardPoolInternal, Unbonding}, + c_std::{ + from_binary, to_binary, Addr, Binary, DepsMut, Env, MessageInfo, Response, StdError, + StdResult, Storage, Uint128, + }, + contract_interfaces::airdrop::ExecuteMsg::CompleteTask, + snip20::helpers::{register_receive, send_msg, set_viewing_key_msg}, + utils::{ + asset::{Contract, RawContract}, + generic_response::ResponseStatus, + ExecuteCallback, + }, +}; + +use crate::storage::*; +use std::cmp::{max, min}; + +pub fn update_config( + deps: DepsMut, + _env: Env, + info: MessageInfo, + admin_auth: Option, + query_auth: Option, + airdrop: Option, + unbond_period: Option, + max_user_pools: Option, +) -> StdResult { + let mut config = CONFIG.load(deps.storage)?; + + validate_admin( + &deps.querier, + AdminPermissions::StakingAdmin, + info.sender.to_string(), + &config.admin_auth, + )?; + + if let Some(admin_auth) = admin_auth { + config.admin_auth = admin_auth.into_valid(deps.api)?; + } + + if let Some(query_auth) = query_auth { + config.query_auth = query_auth.into_valid(deps.api)?; + } + + if let Some(airdrop) = airdrop { + config.airdrop = Some(airdrop.into_valid(deps.api)?); + } + + if let Some(unbond_period) = unbond_period { + config.unbond_period = unbond_period; + } + + if let Some(max_user_pools) = max_user_pools { + config.max_user_pools = max_user_pools; + } + + CONFIG.save(deps.storage, &config)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn register_reward( + deps: DepsMut, + env: Env, + info: MessageInfo, + token: Contract, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + validate_admin( + &deps.querier, + AdminPermissions::StakingAdmin, + info.sender.to_string(), + &config.admin_auth, + )?; + + let mut reward_tokens = REWARD_TOKENS.load(deps.storage)?; + + if reward_tokens.contains(&token) { + return Err(StdError::generic_err("Reward token already registered")); + } + + reward_tokens.push(token.clone()); + REWARD_TOKENS.save(deps.storage, &reward_tokens)?; + + Ok(Response::new() + .add_messages(vec![ + set_viewing_key_msg(VIEWING_KEY.load(deps.storage)?, None, &token)?, + register_receive(env.contract.code_hash, None, &token)?, + ]) + .set_data(to_binary(&ExecuteAnswer::RegisterRewards { + status: ResponseStatus::Success, + })?)) +} + +pub fn receive( + deps: DepsMut, + env: Env, + info: MessageInfo, + _sender: Addr, + from: Addr, + amount: Uint128, + msg: Option, +) -> StdResult { + let now = Uint128::new(env.block.time.seconds() as u128); + + match msg { + Some(m) => match from_binary(&m)? { + Action::Stake { + compound, + airdrop_task, + } => { + let stake_token = STAKE_TOKEN.load(deps.storage)?; + if info.sender != stake_token.address { + return Err(StdError::generic_err(format!( + "Invalid Stake Token: {}", + info.sender + ))); + } + + let compound = compound.unwrap_or(false); + + let total_staked = TOTAL_STAKED.load(deps.storage)?; + + let mut reward_pools = + update_rewards(env.clone(), &REWARD_POOLS.load(deps.storage)?, total_staked); + + let mut response = Response::new(); + + let mut compound_amount = Uint128::zero(); + + let user_staked = USER_STAKED + .may_load(deps.storage, from.clone())? + .unwrap_or(Uint128::zero()); + + if !user_staked.is_zero() { + // Claim Rewards + for reward_pool in reward_pools.iter_mut() { + let reward_claimed = reward_pool_claim( + deps.storage, + from.clone(), + user_staked, + &reward_pool, + )?; + reward_pool.claimed += reward_claimed; + if compound && reward_pool.token == stake_token { + // Compound stake_token rewards + compound_amount += reward_claimed; + } else { + // Claim if not compound or not stake token rewards + response = response + .add_message(send_msg( + info.sender.clone(), + reward_claimed, + None, + None, + None, + &reward_pool.token, + )?) + .add_attribute( + reward_pool.token.address.to_string(), + reward_claimed, + ); + } + } + } else { + for reward_pool in reward_pools.iter() { + // make sure user rewards start now + USER_REWARD_PER_TOKEN_PAID.save( + deps.storage, + user_pool_key(from.clone(), reward_pool.id), + &reward_pool.reward_per_token, + )?; + } + } + + // Send airdrop message + if let Some(true) = airdrop_task { + let config = CONFIG.load(deps.storage)?; + if let Some(airdrop) = config.airdrop { + response = response.add_message( + CompleteTask { + address: from.clone(), + padding: None, + } + .to_cosmos_msg(&airdrop, vec![])?, + ); + } else { + return Err(StdError::generic_err("No airdrop contract configured")); + } + } + + if compound_amount > Uint128::zero() { + response = response.add_attribute("compounded", compound_amount); + } + + USER_STAKED.save( + deps.storage, + from.clone(), + &(user_staked + amount + compound_amount), + )?; + TOTAL_STAKED.save(deps.storage, &(total_staked + amount + compound_amount))?; + + REWARD_POOLS.save(deps.storage, &reward_pools.clone())?; + + Ok(response.set_data(to_binary(&ExecuteAnswer::Stake { + staked: user_staked + amount, + status: ResponseStatus::Success, + })?)) + } + Action::Rewards { start, end } => { + let reward_tokens = REWARD_TOKENS.load(deps.storage)?; + + if let Some(token) = reward_tokens + .iter() + .find(|contract| contract.address == info.sender) + { + // Disallow end before start + if start >= end { + return Err(StdError::generic_err("'start' must be after 'end'")); + } + + // Disallow retro-active emissions (maybe could allow?) + if start < now { + return Err(StdError::generic_err("Cannot start emitting in the past")); + } + + let mut reward_pools = REWARD_POOLS.load(deps.storage)?; + + let config = CONFIG.load(deps.storage)?; + let is_admin = match admin_is_valid( + &deps.querier, + AdminPermissions::StakingAdmin, + from.to_string(), + &config.admin_auth, + ) { + Ok(_) => true, + Err(_) => false, + }; + + // check user_pool limit + if !is_admin { + let user_pools_count = reward_pools + .iter() + .filter(|pool| !pool.official) + .collect::>() + .len(); + if user_pools_count as u128 >= config.max_user_pools.u128() { + return Err(StdError::generic_err("Max user pools exceeded")); + } + } + + let new_id = MAX_POOL_ID.load(deps.storage)? + Uint128::new(1); + MAX_POOL_ID.save(deps.storage, &new_id)?; + + // Tokens per second emitted from this pool + let rate = amount * Uint128::new(10u128.pow(18)) / (end - start); + + reward_pools.push(RewardPoolInternal { + id: new_id, + amount, + start, + end, + token: token.clone(), + rate, + reward_per_token: Uint128::zero(), + claimed: Uint128::zero(), + last_update: now, + creator: from, + official: is_admin, + }); + REWARD_POOLS.save(deps.storage, &reward_pools)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Rewards { + status: ResponseStatus::Success, + })?)) + } else { + return Err(StdError::generic_err(format!( + "Invalid Reward: {}", + info.sender + ))); + } + } + }, + None => { + return Err(StdError::generic_err("No action provided")); + } + } +} + +pub fn reward_per_token(total_staked: Uint128, now: u64, pool: &RewardPoolInternal) -> Uint128 { + if total_staked.is_zero() { + return Uint128::zero(); + } + + let start = max(pool.last_update, pool.start); + let end = min(pool.end, Uint128::new(now as u128)); + + if start > end { + return pool.reward_per_token; + } + + pool.reward_per_token + (((end - start) * pool.rate) / total_staked) +} + +pub fn rewards_earned( + user_staked: Uint128, + reward_per_token: Uint128, + user_reward_per_token_paid: Uint128, +) -> Uint128 { + user_staked * (reward_per_token - user_reward_per_token_paid) / Uint128::new(10u128.pow(18)) +} + +/* + * Returns new reward_per_token + */ +pub fn updated_reward_pool( + reward_pool: &RewardPoolInternal, + total_staked: Uint128, + now: u64, +) -> RewardPoolInternal { + let mut pool = reward_pool.clone(); + pool.reward_per_token = reward_per_token(total_staked, now, &reward_pool); + pool.last_update = min(reward_pool.end, Uint128::new(now as u128)); + pool +} + +pub fn update_rewards( + env: Env, + reward_pools: &Vec, + total_staked: Uint128, +) -> Vec { + reward_pools + .iter() + .map(|pool| updated_reward_pool(pool, total_staked, env.block.time.seconds())) + .collect() +} + +/* returns the earned rewards + * Reward must be sent buy calling code + */ +pub fn reward_pool_claim( + storage: &mut dyn Storage, + user: Addr, + user_staked: Uint128, + reward_pool: &RewardPoolInternal, +) -> StdResult { + let user_reward_per_token_paid = USER_REWARD_PER_TOKEN_PAID + .may_load(storage, user_pool_key(user.clone(), reward_pool.id))? + .unwrap_or(Uint128::zero()); + + let user_reward = rewards_earned( + user_staked, + reward_pool.reward_per_token, + user_reward_per_token_paid, + ); + + USER_REWARD_PER_TOKEN_PAID.save( + storage, + user_pool_key(user.clone(), reward_pool.id), + &reward_pool.reward_per_token, + )?; + + Ok(user_reward) +} + +pub fn claim(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { + let user_staked = USER_STAKED.load(deps.storage, info.sender.clone())?; + + if user_staked.is_zero() { + return Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Claim { + status: ResponseStatus::Success, + })?)); + } + + let total_staked = TOTAL_STAKED.load(deps.storage)?; + + let now = env.block.time.seconds(); + + let mut reward_pools = + update_rewards(env.clone(), &REWARD_POOLS.load(deps.storage)?, total_staked); + + let mut response = Response::new(); + + for reward_pool in reward_pools.iter_mut() { + let reward_claimed = + reward_pool_claim(deps.storage, info.sender.clone(), user_staked, &reward_pool)?; + + reward_pool.claimed += reward_claimed; + + response = response + .add_message(send_msg( + info.sender.clone(), + reward_claimed, + None, + None, + None, + &reward_pool.token, + )?) + .add_attribute(reward_pool.token.address.to_string(), reward_claimed); + } + + REWARD_POOLS.save(deps.storage, &reward_pools)?; + + Ok(response.set_data(to_binary(&ExecuteAnswer::Claim { + //claimed: + status: ResponseStatus::Success, + })?)) +} + +pub fn unbond( + deps: DepsMut, + env: Env, + info: MessageInfo, + amount: Uint128, + compound: bool, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if amount.is_zero() { + return Err(StdError::generic_err("Must unbond non-zero amount")); + } + + if let Some(mut user_staked) = USER_STAKED.may_load(deps.storage, info.sender.clone())? { + // if not compounding, check staked >= unbond amount + if !compound && user_staked < amount { + return Err(StdError::generic_err(format!( + "Cannot unbond {}, only {} staked", + amount, user_staked + ))); + } + + let now = env.block.time.seconds(); + + let mut total_staked = TOTAL_STAKED.load(deps.storage)?; + + let mut reward_pools = + update_rewards(env.clone(), &REWARD_POOLS.load(deps.storage)?, total_staked); + + let stake_token = STAKE_TOKEN.load(deps.storage)?; + let mut compound_amount = Uint128::zero(); + + let mut response = Response::new(); + + // Claim/Compound rewards + for reward_pool in reward_pools.iter_mut() { + let reward_claimed = + reward_pool_claim(deps.storage, info.sender.clone(), user_staked, &reward_pool)?; + reward_pool.claimed += reward_claimed; + + if compound && reward_pool.token == stake_token { + // Compound stake_token rewards + compound_amount += reward_claimed; + } else { + // Claim if not compound or not stake token rewards + response = response + .add_message(send_msg( + info.sender.clone(), + reward_claimed, + None, + None, + None, + &reward_pool.token, + )?) + .add_attribute(reward_pool.token.address.to_string(), reward_claimed); + } + } + + // if compounding, check staked + compounded >= unbond amount + if user_staked + compound_amount < amount { + return Err(StdError::generic_err(format!( + "Cannot unbond {}, only {} staked after compounding", + amount, + user_staked + compound_amount, + ))); + } + if compound_amount > Uint128::zero() { + response = response.add_attribute("compounded", compound_amount); + } + + user_staked = (user_staked + compound_amount) - amount; + total_staked = (total_staked + compound_amount) - amount; + + TOTAL_STAKED.save(deps.storage, &total_staked)?; + USER_STAKED.save(deps.storage, info.sender.clone(), &user_staked)?; + REWARD_POOLS.save(deps.storage, &reward_pools)?; + + let mut user_unbonding_ids = USER_UNBONDING_IDS + .may_load(deps.storage, info.sender.clone())? + .unwrap_or(vec![]); + + let next_id = *user_unbonding_ids.iter().max().unwrap_or(&Uint128::zero()) + Uint128::one(); + + user_unbonding_ids.push(next_id); + USER_UNBONDING_IDS.save(deps.storage, info.sender.clone(), &user_unbonding_ids)?; + + USER_UNBONDING.save( + deps.storage, + user_unbonding_key(info.sender, next_id), + &Unbonding { + id: next_id, + amount, + complete: Uint128::new(now as u128) + config.unbond_period, + }, + )?; + + Ok(response.set_data(to_binary(&ExecuteAnswer::Unbond { + id: next_id, + unbonded: amount, + status: ResponseStatus::Success, + })?)) + } else { + return Err(StdError::generic_err("User is not a staker")); + } +} + +pub fn withdraw( + deps: DepsMut, + env: Env, + info: MessageInfo, + ids: Option>, +) -> StdResult { + let mut user_unbonding_ids = USER_UNBONDING_IDS + .may_load(deps.storage, info.sender.clone())? + .unwrap_or(vec![]); + + // If null ids, use all user unbondings + let ids = ids.unwrap_or(user_unbonding_ids.clone()); + + let now = Uint128::new(env.block.time.seconds() as u128); + + let mut withdrawn_ids = vec![]; + let mut withdrawn_amount = Uint128::zero(); + + for id in ids.into_iter() { + if let Some(unbonding) = + USER_UNBONDING.may_load(deps.storage, user_unbonding_key(info.sender.clone(), id))? + { + if now >= unbonding.complete { + withdrawn_amount += unbonding.amount; + withdrawn_ids.push(id); + } + } else { + return Err(StdError::generic_err(format!("Bad ID {}", id))); + } + } + + if withdrawn_amount.is_zero() { + return Ok(Response::new() + .add_attribute("withdrawn", withdrawn_amount) + .set_data(to_binary(&ExecuteAnswer::Withdraw { + withdrawn: withdrawn_amount, + status: ResponseStatus::Success, + })?)); + } + + // Sort lists so the operation is O(n) + withdrawn_ids.sort(); + user_unbonding_ids.sort(); + + // To store remaining ids + let mut new_unbonding_ids = vec![]; + + // Maps to the withdrawn_ids list + let mut withdrawn_i = 0; + + for i in 0..user_unbonding_ids.len() { + // If all withdrawn handled, or it doesn't collide with withdrawn + if withdrawn_i >= withdrawn_ids.len() || user_unbonding_ids[i] != withdrawn_ids[withdrawn_i] + { + new_unbonding_ids.push(user_unbonding_ids[i]); + } else { + // advance withdrawn index + withdrawn_i += 1; + } + } + + USER_UNBONDING_IDS.save(deps.storage, info.sender.clone(), &new_unbonding_ids)?; + + Ok(Response::new() + .add_message(send_msg( + info.sender, + withdrawn_amount, + None, + None, + None, + &STAKE_TOKEN.load(deps.storage)?, + )?) + .add_attribute("withdrawn", withdrawn_amount) + .set_data(to_binary(&ExecuteAnswer::Withdraw { + withdrawn: withdrawn_amount, + status: ResponseStatus::Success, + })?)) +} + +pub fn compound(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { + let mut response = Response::new(); + + let user_staked = USER_STAKED + .may_load(deps.storage, info.sender.clone())? + .unwrap_or(Uint128::zero()); + + if user_staked.is_zero() { + return Err(StdError::generic_err("User has no stake")); + } + + let total_staked = TOTAL_STAKED.load(deps.storage)?; + let mut reward_pools = + update_rewards(env.clone(), &REWARD_POOLS.load(deps.storage)?, total_staked); + let stake_token = STAKE_TOKEN.load(deps.storage)?; + + let mut compound_amount = Uint128::zero(); + + for reward_pool in reward_pools.iter_mut() { + let reward_claimed = + reward_pool_claim(deps.storage, info.sender.clone(), user_staked, &reward_pool)?; + reward_pool.claimed += reward_claimed; + + if reward_pool.token == stake_token { + // Compound stake_token rewards + compound_amount += reward_claimed; + } else { + // Claim non-stake_token rewards + response = response + .add_message(send_msg( + info.sender.clone(), + reward_claimed, + None, + None, + None, + &reward_pool.token, + )?) + .add_attribute(reward_pool.token.address.to_string(), reward_claimed); + } + } + REWARD_POOLS.save(deps.storage, &reward_pools)?; + + if compound_amount > Uint128::zero() { + response = response.add_attribute("compounded", compound_amount); + } + + USER_STAKED.save( + deps.storage, + info.sender.clone(), + &(user_staked + compound_amount), + )?; + TOTAL_STAKED.save(deps.storage, &(total_staked + compound_amount))?; + + Ok(response.set_data(to_binary(&ExecuteAnswer::Compound { + compounded: compound_amount, + status: ResponseStatus::Success, + })?)) +} + +pub fn end_reward_pool( + deps: DepsMut, + env: Env, + info: MessageInfo, + id: Uint128, + force: bool, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + validate_admin( + &deps.querier, + AdminPermissions::StakingAdmin, + info.sender.to_string(), + &config.admin_auth, + )?; + + let total_staked = TOTAL_STAKED.load(deps.storage)?; + let mut reward_pools = + update_rewards(env.clone(), &REWARD_POOLS.load(deps.storage)?, total_staked); + + // Amount of rewards pulled from contract + let mut extract_amount = Uint128::zero(); + + let now = Uint128::new(env.block.time.seconds() as u128); + + let pool_i = match reward_pools.iter().position(|p| p.id == id) { + Some(i) => i, + None => { + return Err(StdError::generic_err("Could not match id")); + } + }; + + // Remove reward pool, will edit & push it later + let mut reward_pool = reward_pools.remove(pool_i); + + // Delete reward pool if it hasn't started + let deleted = if reward_pool.start > now { + println!("DELETING BEFORE START"); + extract_amount = reward_pool.amount; + true + } + // Reward pool hasn't ended, trim off un-emitted tokens & edit pool to end now + else if reward_pool.end > now { + // remove rewards from now -> end + extract_amount = reward_pool.rate * (reward_pool.end - now) / Uint128::new(10u128.pow(18)); + println!("EXTRACTING {}", extract_amount); + reward_pool.end = now; + reward_pool.amount -= extract_amount; + + if reward_pool.claimed == reward_pool.amount { + true + } else { + reward_pools.push(reward_pool.clone()); + false + } + } + // Delete reward pool if reward pool is fully claimed, or forced + else if reward_pool.claimed == reward_pool.amount || force { + extract_amount += reward_pool.amount - reward_pool.claimed; + true + } else { + return Err(StdError::generic_err( + "Reward pool is complete but claims are still pending", + )); + }; + + REWARD_POOLS.save(deps.storage, &reward_pools)?; + + Ok(Response::new() + .add_message(send_msg( + info.sender, + extract_amount, + None, + None, + None, + &reward_pool.token, + )?) + .set_data(to_binary(&ExecuteAnswer::EndRewardPool { + deleted, + extracted: extract_amount, + status: ResponseStatus::Success, + })?)) +} + +pub fn add_transfer_whitelist( + deps: DepsMut, + _env: Env, + info: MessageInfo, + user: Addr, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + validate_admin( + &deps.querier, + AdminPermissions::StakingAdmin, + info.sender.to_string(), + &config.admin_auth, + )?; + + let mut whitelist = TRANSFER_WL.load(deps.storage)?; + + if whitelist.contains(&user) { + return Err(StdError::generic_err("User already whitelisted")); + } + + whitelist.push(user); + + TRANSFER_WL.save(deps.storage, &whitelist)?; + + Ok( + Response::default().set_data(to_binary(&ExecuteAnswer::AddTransferWhitelist { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn rm_transfer_whitelist( + deps: DepsMut, + _env: Env, + info: MessageInfo, + user: Addr, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + validate_admin( + &deps.querier, + AdminPermissions::StakingAdmin, + info.sender.to_string(), + &config.admin_auth, + )?; + + let mut whitelist = TRANSFER_WL.load(deps.storage)?; + + match whitelist.iter().position(|u| *u == user) { + Some(i) => { + whitelist.remove(i); + } + None => { + return Err(StdError::generic_err("User not in whitelist")); + } + } + + TRANSFER_WL.save(deps.storage, &whitelist)?; + + Ok( + Response::default().set_data(to_binary(&ExecuteAnswer::AddTransferWhitelist { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn transfer_stake( + deps: DepsMut, + env: Env, + info: MessageInfo, + amount: Uint128, + recipient: Addr, + compound: bool, +) -> StdResult { + let whitelist = TRANSFER_WL.load(deps.storage)?; + + if !whitelist.contains(&info.sender) { + return Err(StdError::generic_err(format!( + "Transfer Stake not allowed for {}", + info.sender + ))); + } + + // Claim/Compound for sending user + let total_staked = TOTAL_STAKED.load(deps.storage)?; + + let mut reward_pools = + update_rewards(env.clone(), &REWARD_POOLS.load(deps.storage)?, total_staked); + + let stake_token = STAKE_TOKEN.load(deps.storage)?; + + let mut response = Response::new(); + + let sender_staked = USER_STAKED + .may_load(deps.storage, info.sender.clone())? + .unwrap_or(Uint128::zero()); + + if sender_staked == Uint128::zero() { + return Err(StdError::generic_err("Cannot transfer with 0 staked")); + } + + let mut sender_compound_amount = Uint128::zero(); + + // Claim/Compound rewards for Sender + for reward_pool in reward_pools.iter_mut() { + println!("reward pool claim sender"); + let reward_claimed = reward_pool_claim( + deps.storage, + info.sender.clone(), + sender_staked, + &reward_pool, + )?; + println!("POST reward pool claim sender"); + reward_pool.claimed += reward_claimed; + + if compound && reward_pool.token == stake_token { + // Compound stake_token rewards + sender_compound_amount += reward_claimed; + } else { + // Claim if not compound or not stake token rewards + response = response + .add_message(send_msg( + info.sender.clone(), + reward_claimed, + None, + None, + None, + &reward_pool.token, + )?) + .add_attribute(reward_pool.token.address.to_string(), reward_claimed); + } + } + + if sender_staked + sender_compound_amount < amount { + return Err(StdError::generic_err(format!( + "Cannot transfer {}, only {} available", + amount, + sender_staked + sender_compound_amount + ))); + } + + println!("sender compound amount {}", sender_compound_amount); + + if sender_compound_amount > Uint128::zero() { + response = response.add_attribute("compounded", sender_compound_amount); + } + + // Adjust sender staked + USER_STAKED.save( + deps.storage, + info.sender, + &(sender_staked + sender_compound_amount - amount), + )?; + + // Claim for receiving user + let recipient_staked = USER_STAKED + .may_load(deps.storage, recipient.clone())? + .unwrap_or(Uint128::zero()); + + // Claim rewards for Receiver (no compound) + for reward_pool in reward_pools.iter_mut() { + println!("reward pool claim recipient"); + let reward_claimed = reward_pool_claim( + deps.storage, + recipient.clone(), + recipient_staked, + &reward_pool, + )?; + reward_pool.claimed += reward_claimed; + + // Claim if not compound or not stake token rewards + println!( + "SENDING RECIPIETN REWARD {} {}", + reward_claimed, + recipient.clone() + ); + response = response.add_message(send_msg( + recipient.clone(), + reward_claimed, + None, + None, + None, + &reward_pool.token, + )?); + } + + // Adjust recipient staked + USER_STAKED.save(deps.storage, recipient, &(recipient_staked + amount))?; + + Ok(response.set_data(to_binary(&ExecuteAnswer::TransferStake { + transferred: amount, + status: ResponseStatus::Success, + })?)) +} diff --git a/contracts/basic_staking/src/lib.rs b/contracts/basic_staking/src/lib.rs new file mode 100644 index 0000000..25ee0d9 --- /dev/null +++ b/contracts/basic_staking/src/lib.rs @@ -0,0 +1,4 @@ +pub mod contract; +pub mod execute; +pub mod query; +pub mod storage; diff --git a/contracts/basic_staking/src/query.rs b/contracts/basic_staking/src/query.rs new file mode 100644 index 0000000..4243ed4 --- /dev/null +++ b/contracts/basic_staking/src/query.rs @@ -0,0 +1,215 @@ +use shade_protocol::{ + basic_staking::{QueryAnswer, Reward, RewardPool, RewardPoolInternal, StakingInfo}, + c_std::{Addr, Deps, Env, StdError, StdResult, Uint128}, +}; + +use crate::{ + execute::{reward_per_token, rewards_earned}, + storage::*, +}; + +pub fn config(deps: Deps) -> StdResult { + Ok(QueryAnswer::Config { + config: CONFIG.load(deps.storage)?, + }) +} + +pub fn stake_token(deps: Deps) -> StdResult { + Ok(QueryAnswer::StakeToken { + token: STAKE_TOKEN.load(deps.storage)?.address, + }) +} + +pub fn staking_info(deps: Deps) -> StdResult { + Ok(QueryAnswer::StakingInfo { + info: StakingInfo { + stake_token: STAKE_TOKEN.load(deps.storage)?.address, + total_staked: TOTAL_STAKED.load(deps.storage)?, + unbond_period: CONFIG.load(deps.storage)?.unbond_period, + reward_pools: REWARD_POOLS + .load(deps.storage)? + .into_iter() + .map( + |RewardPoolInternal { + id, + amount, + start, + end, + token, + rate, + official, + .. + }| RewardPool { + id, + amount, + start, + end, + token, + rate, + official, + }, + ) + .collect(), + }, + }) +} + +pub fn total_staked(deps: Deps) -> StdResult { + Ok(QueryAnswer::TotalStaked { + amount: TOTAL_STAKED.load(deps.storage)?, + }) +} + +pub fn reward_tokens(deps: Deps) -> StdResult { + Ok(QueryAnswer::RewardTokens { + tokens: REWARD_TOKENS + .load(deps.storage)? + .iter() + .map(|contract| contract.address.clone()) + .collect(), + }) +} + +pub fn reward_pools(deps: Deps) -> StdResult { + Ok(QueryAnswer::RewardPools { + rewards: REWARD_POOLS + .load(deps.storage)? + .into_iter() + .map( + |RewardPoolInternal { + id, + amount, + start, + end, + token, + rate, + official, + .. + }| RewardPool { + id, + amount, + start, + end, + token, + rate, + official, + }, + ) + .collect(), + }) +} + +pub fn user_balance( + deps: Deps, + env: Env, + user: Addr, + unbonding_ids: Vec, +) -> StdResult { + let mut unbondings = vec![]; + + for unbonding_id in unbonding_ids.iter() { + if let Some(unbonding) = USER_UNBONDING.may_load( + deps.storage, + user_unbonding_key(user.clone(), *unbonding_id), + )? { + unbondings.push(unbonding); + } else { + return Err(StdError::generic_err(format!( + "Bad Unbonding ID {}", + unbonding_id + ))); + } + } + + let mut rewards = vec![]; + + if let Some(user_staked) = USER_STAKED.may_load(deps.storage, user.clone())? { + if user_staked.is_zero() { + return Ok(QueryAnswer::Balance { + staked: user_staked, + rewards, + unbondings, + }); + } + let reward_pools = REWARD_POOLS.load(deps.storage)?; + let total_staked = TOTAL_STAKED.load(deps.storage)?; + let now = env.block.time.seconds(); + + for reward_pool in reward_pools { + let user_reward_per_token_paid = USER_REWARD_PER_TOKEN_PAID + .may_load(deps.storage, user_pool_key(user.clone(), reward_pool.id))? + .unwrap_or(Uint128::zero()); + let reward_per_token = reward_per_token(total_staked, now, &reward_pool); + let rewards_earned = + rewards_earned(user_staked, reward_per_token, user_reward_per_token_paid); + if !rewards_earned.is_zero() { + rewards.push(Reward { + token: reward_pool.token, + amount: rewards_earned, + }); + } + } + + Ok(QueryAnswer::Balance { + staked: user_staked, + rewards, + unbondings, + }) + } else { + Ok(QueryAnswer::Balance { + staked: Uint128::zero(), + rewards, + unbondings, + }) + } +} + +pub fn user_staked(deps: Deps, user: Addr) -> StdResult { + Ok(QueryAnswer::Staked { + amount: USER_STAKED + .may_load(deps.storage, user)? + .unwrap_or(Uint128::zero()), + }) +} + +pub fn user_rewards(deps: Deps, env: Env, user: Addr) -> StdResult { + let mut rewards = vec![]; + + if let Some(user_staked) = USER_STAKED.may_load(deps.storage, user.clone())? { + if user_staked.is_zero() { + return Ok(QueryAnswer::Rewards { rewards }); + } + let reward_pools = REWARD_POOLS.load(deps.storage)?; + let total_staked = TOTAL_STAKED.load(deps.storage)?; + let now = env.block.time.seconds(); + + for reward_pool in reward_pools { + let user_reward_per_token_paid = USER_REWARD_PER_TOKEN_PAID + .may_load(deps.storage, user_pool_key(user.clone(), reward_pool.id))? + .unwrap_or(Uint128::zero()); + let reward_per_token = reward_per_token(total_staked, now, &reward_pool); + rewards.push(Reward { + token: reward_pool.token, + amount: rewards_earned(user_staked, reward_per_token, user_reward_per_token_paid), + }); + } + } + + Ok(QueryAnswer::Rewards { rewards }) +} + +pub fn user_unbondings(deps: Deps, user: Addr, ids: Vec) -> StdResult { + let mut unbondings = vec![]; + + for id in ids.iter() { + if let Some(unbonding) = + USER_UNBONDING.may_load(deps.storage, user_unbonding_key(user.clone(), *id))? + { + unbondings.push(unbonding); + } else { + return Err(StdError::generic_err(format!("Bad ID {}", id))); + } + } + + Ok(QueryAnswer::Unbonding { unbondings }) +} diff --git a/contracts/basic_staking/src/storage.rs b/contracts/basic_staking/src/storage.rs new file mode 100644 index 0000000..23065b4 --- /dev/null +++ b/contracts/basic_staking/src/storage.rs @@ -0,0 +1,34 @@ +use shade_protocol::{ + basic_staking, + c_std::{Addr, Uint128}, + utils::asset::Contract, +}; + +use shade_protocol::secret_storage_plus::{Item, Map}; + +pub const CONFIG: Item = Item::new("config"); +pub const STAKE_TOKEN: Item = Item::new("stake_token"); +pub const VIEWING_KEY: Item = Item::new("viewing_key"); + +// Whitelist for transferring stake +pub const TRANSFER_WL: Item> = Item::new("transfer_whitelist"); + +pub const TOTAL_STAKED: Item = Item::new("total_stake"); + +pub const REWARD_TOKENS: Item> = Item::new("reward_tokens"); +pub const REWARD_POOLS: Item> = Item::new("reward_pools"); + +pub const USER_STAKED: Map = Map::new("user_stake"); + +pub fn user_unbonding_key(user: Addr, unbond_id: Uint128) -> String { + format!("{}-{}", user, unbond_id) +} +pub const USER_UNBONDING_IDS: Map> = Map::new("user_unbonding_ids"); +pub const USER_UNBONDING: Map = Map::new("user_unbonding"); +pub const MAX_POOL_ID: Item = Item::new("max_pool_id"); + +pub fn user_pool_key(user: Addr, pool_id: Uint128) -> String { + format!("{}-{}", user, pool_id) +} + +pub const USER_REWARD_PER_TOKEN_PAID: Map = Map::new("user_reward_per_token_paid"); diff --git a/contracts/basic_staking/tests/bad_stake_token.rs b/contracts/basic_staking/tests/bad_stake_token.rs new file mode 100644 index 0000000..b7aede7 --- /dev/null +++ b/contracts/basic_staking/tests/bad_stake_token.rs @@ -0,0 +1,347 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +// Add other adapters here as they come +fn bad_stake_token( + stake_amount: Uint128, + unbond_period: Uint128, + reward_amount: Uint128, + reward_start: Uint128, + reward_end: Uint128, +) { + let mut app = App::default(); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let staking_user = Addr::unchecked("staker"); + let reward_user = Addr::unchecked("reward_user"); + + let stake_token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![snip20::InitialBalance { + amount: stake_amount, + address: staking_user.to_string(), + }]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + let reward_token = snip20::InstantiateMsg { + name: "reward_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![ + snip20::InitialBalance { + amount: reward_amount, + address: reward_user.to_string(), + }, + snip20::InitialBalance { + amount: stake_amount, + address: staking_user.to_string(), + }, + ]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + // set staking_user viewing keys + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&stake_token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&reward_token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + // set staking user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // set reward user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + airdrop: None, + stake_token: stake_token.clone().into(), + unbond_period, + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + + // Pre-staking user stake amount + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Stake funds + match (snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: basic_staking.code_hash.clone().into(), + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&reward_token, &mut app, staking_user.clone(), &[])) + { + Ok(_) => panic!("Allowed to stake bad tokens"), + Err(e) => {} + } + + // Post-staking user balance + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "Post-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Register Reward Token + basic_staking::ExecuteMsg::RegisterRewards { + token: reward_token.clone().into(), + padding: None, + } + .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) + .unwrap(); + + // Init Rewards + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: reward_amount, + msg: Some( + to_binary(&basic_staking::Action::Rewards { + start: reward_start, + end: reward_end, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&reward_token, &mut app, reward_user.clone(), &[]) + .unwrap(); + // Stake funds + match (snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&reward_token, &mut app, staking_user.clone(), &[])) + { + Ok(_) => panic!("Allowed to stake bad tokens after registering rewards"), + Err(e) => {} + } +} + +macro_rules! bad_stake_token { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + stake_amount, + unbond_period, + reward_amount, + reward_start, + reward_end, + ) = $value; + bad_stake_token( + stake_amount, + unbond_period, + reward_amount, + reward_start, + reward_end, + ) + } + )* + } +} + +bad_stake_token! { + bad_stake_token_0: ( + Uint128::new(1), // stake_amount + Uint128::new(100), // unbond_period + Uint128::new(100), // reward_amount + Uint128::new(0), // reward_start (0-*) + Uint128::new(100), // reward_end + ), + bad_stake_token_1: ( + Uint128::new(100), + Uint128::new(100), + Uint128::new(1000), + Uint128::new(0), + Uint128::new(100), + ), + bad_stake_token_2: ( + Uint128::new(1000), + Uint128::new(100), + Uint128::new(300), + Uint128::new(0), + Uint128::new(100), + ), + bad_stake_token_3: ( + Uint128::new(10), + Uint128::new(100), + Uint128::new(50000), + Uint128::new(0), + Uint128::new(2500000), + ), + bad_stake_token_4: ( + Uint128::new(1234567), + Uint128::new(10000), + Uint128::new(500), + Uint128::new(0), + Uint128::new(10000), + ), + bad_stake_token_5: ( + Uint128::new(99999999999), + Uint128::new(100), + Uint128::new(8192), + Uint128::new(20), + Uint128::new(8000), + ), +} diff --git a/contracts/basic_staking/tests/end_reward_pool_after_end_claimed.rs b/contracts/basic_staking/tests/end_reward_pool_after_end_claimed.rs new file mode 100644 index 0000000..7d6b480 --- /dev/null +++ b/contracts/basic_staking/tests/end_reward_pool_after_end_claimed.rs @@ -0,0 +1,290 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +#[test] +fn end_reward_pool_after_end() { + let mut app = App::default(); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let staking_user = Addr::unchecked("staker"); + let reward_user = Addr::unchecked("reward_user"); + + let stake_amount = Uint128::new(100); + let reward_start = Uint128::new(100); + let reward_end = Uint128::new(200); + let reward_amount = Uint128::new(100); + let unbond_period = Uint128::zero(); + + let token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![ + snip20::InitialBalance { + amount: stake_amount, + address: staking_user.to_string(), + }, + snip20::InitialBalance { + amount: reward_amount, + address: reward_user.to_string(), + }, + ]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + // set staking_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + // set admin_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, admin_user.clone(), &[]) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + // set staking user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // set reward user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + airdrop: None, + stake_token: token.clone().into(), + unbond_period, + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + + // Pre-staking user stake amount + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Stake funds + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Post-staking user balance + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, stake_amount, "Post-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Init Rewards + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: reward_amount, + msg: Some( + to_binary(&basic_staking::Action::Rewards { + start: reward_start, + end: reward_end, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, reward_user.clone(), &[]) + .unwrap(); + + // Check reward pool + let pool_id = match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); + assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); + assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); + rewards[0].id + } + _ => { + panic!("Staking balance query failed"); + } + }; + + let end_period = (reward_end + Uint128::new(100)); + + // move to middle of emissions period + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(end_period.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + // Claim rewards + basic_staking::ExecuteMsg::Claim { padding: None } + .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // End reward pool + basic_staking::ExecuteMsg::EndRewardPool { + id: pool_id, + force: Some(false), + padding: None, + } + .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) + .unwrap(); + + // Check reward pool was removed + match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + assert_eq!(rewards.len(), 0, "Reward pool removed bc fully claimed"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Admin user that cancelled should receive un-emitted funds + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: admin_user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!( + amount, + Uint128::zero(), + "No pending emissions when cancelled", + ); + } + _ => { + panic!("admin snip20 balance query failed"); + } + }; +} diff --git a/contracts/basic_staking/tests/end_reward_pool_after_end_unclaimed.rs b/contracts/basic_staking/tests/end_reward_pool_after_end_unclaimed.rs new file mode 100644 index 0000000..97064af --- /dev/null +++ b/contracts/basic_staking/tests/end_reward_pool_after_end_unclaimed.rs @@ -0,0 +1,288 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +#[test] +fn end_reward_pool_after_end() { + let mut app = App::default(); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let staking_user = Addr::unchecked("staker"); + let reward_user = Addr::unchecked("reward_user"); + + let stake_amount = Uint128::new(100); + let reward_start = Uint128::new(100); + let reward_end = Uint128::new(200); + let reward_amount = Uint128::new(100); + let unbond_period = Uint128::zero(); + + let token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![ + snip20::InitialBalance { + amount: stake_amount, + address: staking_user.to_string(), + }, + snip20::InitialBalance { + amount: reward_amount, + address: reward_user.to_string(), + }, + ]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + // set staking_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + // set admin_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, admin_user.clone(), &[]) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + // set staking user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // set reward user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + airdrop: None, + stake_token: token.clone().into(), + unbond_period, + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + + // Pre-staking user stake amount + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Stake funds + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Post-staking user balance + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, stake_amount, "Post-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Init Rewards + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: reward_amount, + msg: Some( + to_binary(&basic_staking::Action::Rewards { + start: reward_start, + end: reward_end, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, reward_user.clone(), &[]) + .unwrap(); + + // Check reward pool + let pool_id = match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); + assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); + assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); + rewards[0].id + } + _ => { + panic!("Staking balance query failed"); + } + }; + + let end_period = (reward_end + Uint128::new(100)); + + // move to middle of emissions period + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(end_period.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + // End reward pool + match (basic_staking::ExecuteMsg::EndRewardPool { + id: pool_id, + force: Some(false), + padding: None, + }) + .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) + { + Ok(_) => panic!("Reward pool cancelled before fully claimed"), + Err(_) => {} + } + + // Check reward pool was not removed + match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + assert_eq!(rewards.len(), 1, "Unclaimed reward pool was not removed"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Admin user that cancelled should receive un-emitted funds + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: admin_user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!( + amount, + Uint128::zero(), + "No pending emissions when cancelled", + ); + } + _ => { + panic!("admin snip20 balance query failed"); + } + }; +} diff --git a/contracts/basic_staking/tests/end_reward_pool_after_end_unclaimed_force.rs b/contracts/basic_staking/tests/end_reward_pool_after_end_unclaimed_force.rs new file mode 100644 index 0000000..781e273 --- /dev/null +++ b/contracts/basic_staking/tests/end_reward_pool_after_end_unclaimed_force.rs @@ -0,0 +1,386 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +#[test] +fn end_reward_pool_after_end() { + let mut app = App::default(); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let staking_user = Addr::unchecked("staker"); + let reward_user = Addr::unchecked("reward_user"); + + let stake_amount = Uint128::new(100); + let reward_start = Uint128::new(100); + let reward_end = Uint128::new(200); + let reward_amount = Uint128::new(100); + let unbond_period = Uint128::zero(); + + let token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![ + snip20::InitialBalance { + amount: stake_amount, + address: staking_user.to_string(), + }, + snip20::InitialBalance { + amount: reward_amount, + address: reward_user.to_string(), + }, + ]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + let second_token = snip20::InstantiateMsg { + name: "reward_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKNB".into(), + decimals: 6, + initial_balances: Some(vec![snip20::InitialBalance { + amount: reward_amount, + address: reward_user.to_string(), + }]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + // set staking_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + // set admin_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, admin_user.clone(), &[]) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + // set staking user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // set reward user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + airdrop: None, + stake_token: token.clone().into(), + unbond_period, + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + + // Pre-staking user stake amount + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Stake funds + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Post-staking user balance + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, stake_amount, "Post-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Init Rewards + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: reward_amount, + msg: Some( + to_binary(&basic_staking::Action::Rewards { + start: reward_start, + end: reward_end, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, reward_user.clone(), &[]) + .unwrap(); + + // Check reward pool + let pool_id = match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); + assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); + assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); + rewards[0].id + } + _ => { + panic!("Staking balance query failed"); + } + }; + + let end_period = (reward_end + Uint128::new(100)); + + // move to middle of emissions period + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(end_period.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + // End reward pool + basic_staking::ExecuteMsg::EndRewardPool { + id: pool_id, + force: Some(true), + padding: None, + } + .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) + .unwrap(); + + // Check reward pool was removed + match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + assert_eq!(rewards.len(), 0, "Unclaimed reward pool force removed"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Admin user that cancelled should receive un-emitted funds + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: admin_user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!( + amount, reward_amount, + "pending emissions when cancelled sent to admin", + ); + } + _ => { + panic!("admin snip20 balance query failed"); + } + }; + + // Register Reward Token + basic_staking::ExecuteMsg::RegisterRewards { + token: second_token.clone().into(), + padding: None, + } + .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) + .unwrap(); + + let second_reward_start = Uint128::new(10000); + let second_reward_end = Uint128::new(100000); + + // Setup new rewards with new token + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: reward_amount, + msg: Some( + to_binary(&basic_staking::Action::Rewards { + start: second_reward_start, + end: second_reward_end, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&second_token, &mut app, reward_user.clone(), &[]) + .unwrap(); + + // Check reward pool + match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + println!("{:?}", rewards); + assert_eq!(rewards.len(), 1, "Second reward pool exists"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(second_reward_end.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + // Check queries work + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + println!("Rewards {:?}", rewards); + assert_eq!(rewards.len(), 1, "Second rewards pool"); + // panic!("OOF"); + } + _ => { + panic!("Staking balance query failed"); + } + }; +} diff --git a/contracts/basic_staking/tests/end_reward_pool_before_end_claimed.rs b/contracts/basic_staking/tests/end_reward_pool_before_end_claimed.rs new file mode 100644 index 0000000..33ca7bb --- /dev/null +++ b/contracts/basic_staking/tests/end_reward_pool_before_end_claimed.rs @@ -0,0 +1,291 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +#[test] +fn end_reward_pool_before_end_claimed() { + let mut app = App::default(); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let staking_user = Addr::unchecked("staker"); + let reward_user = Addr::unchecked("reward_user"); + + let stake_amount = Uint128::new(100); + let reward_start = Uint128::new(100); + let reward_end = Uint128::new(200); + let reward_amount = Uint128::new(100); + let unbond_period = Uint128::zero(); + + let token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![ + snip20::InitialBalance { + amount: stake_amount, + address: staking_user.to_string(), + }, + snip20::InitialBalance { + amount: reward_amount, + address: reward_user.to_string(), + }, + ]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + // set staking_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + // set admin_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, admin_user.clone(), &[]) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + // set staking user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // set reward user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + airdrop: None, + stake_token: token.clone().into(), + unbond_period, + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + + // Pre-staking user stake amount + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Stake funds + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Post-staking user balance + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, stake_amount, "Post-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Init Rewards + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: reward_amount, + msg: Some( + to_binary(&basic_staking::Action::Rewards { + start: reward_start, + end: reward_end, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, reward_user.clone(), &[]) + .unwrap(); + + // Check reward pool + let pool_id = match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); + assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); + assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); + rewards[0].id + } + _ => { + panic!("Staking balance query failed"); + } + }; + + let mid_period = reward_start + ((reward_end - reward_start) / Uint128::new(2)); + println!("mid period {}", mid_period); + + // move to middle of emissions period + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(mid_period.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + // Claim rewards + basic_staking::ExecuteMsg::Claim { padding: None } + .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // End reward pool + basic_staking::ExecuteMsg::EndRewardPool { + id: pool_id, + force: Some(false), + padding: None, + } + .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) + .unwrap(); + + // Check reward pool was removed + match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + assert_eq!(rewards.len(), 0, "Reward pool removed bc fully claimed"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Admin user that cancelled should receive un-emitted funds + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: admin_user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!( + amount, + reward_amount / Uint128::new(2), + "Cancelled rewards sent to cancelling admin user" + ); + } + _ => { + panic!("admin snip20 balance query failed"); + } + }; +} diff --git a/contracts/basic_staking/tests/end_reward_pool_before_end_unclaimed.rs b/contracts/basic_staking/tests/end_reward_pool_before_end_unclaimed.rs new file mode 100644 index 0000000..46e223b --- /dev/null +++ b/contracts/basic_staking/tests/end_reward_pool_before_end_unclaimed.rs @@ -0,0 +1,291 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +#[test] +fn end_reward_pool_before_end_unclaimed() { + let mut app = App::default(); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let staking_user = Addr::unchecked("staker"); + let reward_user = Addr::unchecked("reward_user"); + + let stake_amount = Uint128::new(100); + let reward_start = Uint128::new(100); + let reward_end = Uint128::new(200); + let reward_amount = Uint128::new(100); + let unbond_period = Uint128::zero(); + + let token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![ + snip20::InitialBalance { + amount: stake_amount, + address: staking_user.to_string(), + }, + snip20::InitialBalance { + amount: reward_amount, + address: reward_user.to_string(), + }, + ]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + // set staking_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + // set admin_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, admin_user.clone(), &[]) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + // set staking user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // set reward user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + airdrop: None, + stake_token: token.clone().into(), + unbond_period, + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + + // Pre-staking user stake amount + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Stake funds + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Post-staking user balance + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, stake_amount, "Post-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Init Rewards + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: reward_amount, + msg: Some( + to_binary(&basic_staking::Action::Rewards { + start: reward_start, + end: reward_end, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, reward_user.clone(), &[]) + .unwrap(); + + // Check reward pool + let pool_id = match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); + assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); + assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); + rewards[0].id + } + _ => { + panic!("Staking balance query failed"); + } + }; + + let mid_period = reward_start + ((reward_end - reward_start) / Uint128::new(2)); + println!("mid period {}", mid_period); + + // move to middle of emissions period + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(mid_period.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + // End reward pool + basic_staking::ExecuteMsg::EndRewardPool { + id: pool_id, + force: Some(false), + padding: None, + } + .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) + .unwrap(); + + // Check reward pool was removed + match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + assert_eq!(rewards.len(), 1, "Reward pool remains until fully claimed"); + assert_eq!( + rewards[0].amount, + reward_amount / Uint128::new(2), + "Reward pool half remaining", + ); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Admin user that cancelled should receive un-emitted funds + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: admin_user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!( + amount, + reward_amount / Uint128::new(2), + "Cancelled rewards sent to cancelling admin user" + ); + } + _ => { + panic!("admin snip20 balance query failed"); + } + }; +} diff --git a/contracts/basic_staking/tests/end_reward_pool_before_start.rs b/contracts/basic_staking/tests/end_reward_pool_before_start.rs new file mode 100644 index 0000000..e15b12b --- /dev/null +++ b/contracts/basic_staking/tests/end_reward_pool_before_start.rs @@ -0,0 +1,273 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +// Add other adapters here as they come +#[test] +fn end_reward_pool_before_start() { + let mut app = App::default(); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let staking_user = Addr::unchecked("staker"); + let reward_user = Addr::unchecked("reward_user"); + + let stake_amount = Uint128::new(100); + let reward_start = Uint128::new(100); + let reward_end = Uint128::new(200); + let reward_amount = Uint128::new(100); + let unbond_period = Uint128::zero(); + + let token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![ + snip20::InitialBalance { + amount: stake_amount, + address: staking_user.to_string(), + }, + snip20::InitialBalance { + amount: reward_amount, + address: reward_user.to_string(), + }, + ]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + // set staking_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + // set admin_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, admin_user.clone(), &[]) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + // set staking user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // set reward user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + airdrop: None, + stake_token: token.clone().into(), + unbond_period, + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + + // Pre-staking user stake amount + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Stake funds + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Post-staking user balance + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, stake_amount, "Post-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Init Rewards + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: reward_amount, + msg: Some( + to_binary(&basic_staking::Action::Rewards { + start: reward_start, + end: reward_end, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, reward_user.clone(), &[]) + .unwrap(); + + // Check reward pool + let pool_id = match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); + assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); + assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); + rewards[0].id + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // End reward pool + basic_staking::ExecuteMsg::EndRewardPool { + id: pool_id, + force: Some(false), + padding: None, + } + .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) + .unwrap(); + + // Check reward pool was removed + match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + assert_eq!(rewards.len(), 0, "No reward pools after cancelling"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Admin user that cancelled should receive un-emitted funds + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: admin_user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, reward_amount, "Rewards cancelled to admin user"); + } + _ => { + panic!("admin snip20 balance query failed"); + } + }; +} diff --git a/contracts/basic_staking/tests/multi_staker.rs b/contracts/basic_staking/tests/multi_staker.rs new file mode 100644 index 0000000..d31f047 --- /dev/null +++ b/contracts/basic_staking/tests/multi_staker.rs @@ -0,0 +1,523 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +// Add other adapters here as they come +fn multi_staker_single_pool( + stake_amounts: Vec, + unbond_period: Uint128, + reward_amount: Uint128, + reward_start: Uint128, + reward_end: Uint128, + expected_rewards: Vec, +) { + let mut app = App::default(); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let reward_user = Addr::unchecked("reward_user"); + let staking_users = stake_amounts + .iter() + .enumerate() + .map(|(i, x)| Addr::unchecked(format!("staker-{}", i))) + .collect::>(); + + let mut initial_balances = staking_users + .iter() + .zip(stake_amounts.clone().into_iter()) + .map(|(user, amount)| snip20::InitialBalance { + amount, + address: user.to_string(), + }) + .collect::>(); + + initial_balances.push(snip20::InitialBalance { + amount: reward_amount, + address: reward_user.to_string(), + }); + + let token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(initial_balances), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + // set staking_users viewing key + for user in staking_users.clone().into_iter() { + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, user.clone(), &[]) + .unwrap(); + + // set staking user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, user.clone(), &[]) + .unwrap(); + } + + // set reward user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + airdrop: None, + stake_token: token.clone().into(), + unbond_period, + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + println!("BASIC STAKING {}", basic_staking.address); + + for (user, stake_amount) in staking_users.iter().zip(stake_amounts.clone().into_iter()) { + // Pre-staking user balance + match (basic_staking::QueryMsg::Staked { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Staked { amount } => { + assert_eq!(amount, Uint128::zero(), "Pre-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Stake funds + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, user.clone(), &[]) + .unwrap(); + + // Post-staking user balance + match (basic_staking::QueryMsg::Staked { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Staked { amount } => { + assert_eq!(amount, stake_amount, "Post-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + } + + // Init Rewards + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: reward_amount, + msg: Some( + to_binary(&basic_staking::Action::Rewards { + start: reward_start, + end: reward_end, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, reward_user.clone(), &[]) + .unwrap(); + + // reward user has no stake + match (basic_staking::QueryMsg::Staked { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: reward_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Staked { amount } => { + assert_eq!(amount, Uint128::zero(), "Reward User Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Check reward pool + match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); + assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); + assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Move forward to reward start + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(reward_start.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + for user in staking_users.iter() { + // No rewards should be pending + match (basic_staking::QueryMsg::Rewards { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Rewards { rewards } => { + assert_eq!(rewards.len(), 1, "Rewards length at beginning"); + assert_eq!( + rewards[0].amount, + Uint128::zero(), + "Rewards claimable at beginning" + ); + } + _ => { + panic!("Staking rewards query failed"); + } + }; + } + + let reward_duration = reward_end - reward_start; + + // Move to middle of reward period + println!("Fast-forward to reward middle"); + app.set_block(BlockInfo { + height: 2, + time: Timestamp::from_seconds((reward_start.u128() + reward_duration.u128() / 2) as u64), + chain_id: "chain_id".to_string(), + }); + + for (user, reward) in staking_users + .iter() + .zip(expected_rewards.clone().into_iter()) + { + // Half-ish rewards should be pending + match (basic_staking::QueryMsg::Rewards { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Rewards { rewards } => { + assert_eq!(rewards.len(), 1, "Rewards length in the middle"); + let amount = rewards[0].amount; + let expected = reward / Uint128::new(2); + assert!( + amount >= expected - Uint128::one() && amount <= expected, + "Rewards claimable in the middle within error of 1 unit token {} != {}", + amount, + expected + ); + } + _ => { + panic!("Staking rewards query failed"); + } + }; + } + + // Move to end of rewards + println!("Fast-forward to reward end"); + app.set_block(BlockInfo { + height: 3, + time: Timestamp::from_seconds(reward_end.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + for ((user, amount), reward) in staking_users + .iter() + .zip(stake_amounts.clone().into_iter()) + .zip(expected_rewards.clone().into_iter()) + { + // All rewards should be pending + match (basic_staking::QueryMsg::Rewards { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Rewards { rewards } => { + assert_eq!(rewards.len(), 1, "Rewards length in the middle"); + let amount = rewards[0].amount; + assert!( + amount >= reward - Uint128::one() && amount <= reward, + "Rewards claimable at the end within error of 1 unit token {} != {}", + amount, + reward, + ); + } + _ => { + panic!("Staking rewards query failed"); + } + }; + + // Claim rewards + basic_staking::ExecuteMsg::Claim { padding: None } + .test_exec(&basic_staking, &mut app, user.clone(), &[]) + .unwrap(); + + // Check rewards were claimed + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert!( + amount >= reward - Uint128::one() && amount <= reward, + "Rewards claimed at the end within error of 1 unit token {} != {}", + amount, + reward, + ); + } + _ => { + panic!("Snip20 balance query failed"); + } + }; + + // Unbond + basic_staking::ExecuteMsg::Unbond { + amount, + compound: None, + padding: None, + } + .test_exec(&basic_staking, &mut app, user.clone(), &[]) + .unwrap(); + + // All funds should be unbonding + match (basic_staking::QueryMsg::Unbonding { + ids: None, + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Unbonding { unbondings } => { + assert_eq!(unbondings.len(), 1, "1 unbonding"); + assert_eq!(unbondings[0].amount, amount, "Unbonding full amount"); + assert_eq!( + unbondings[0].complete, + reward_end + unbond_period, + "Unbonding complete expectedt" + ); + } + _ => { + panic!("Staking unbonding query failed"); + } + }; + } + + println!("Fast-forward to end of unbonding period"); + app.set_block(BlockInfo { + height: 10, + time: Timestamp::from_seconds((reward_end + unbond_period).u128() as u64), + chain_id: "chain_id".to_string(), + }); + + for ((user, stake_amount), reward) in staking_users + .iter() + .zip(stake_amounts.clone().into_iter()) + .zip(expected_rewards.clone().into_iter()) + { + // Withdraw unbonding + basic_staking::ExecuteMsg::Withdraw { + ids: None, + padding: None, + } + .test_exec(&basic_staking, &mut app, user.clone(), &[]) + .unwrap(); + + // Check unbonding withdrawn + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + let expected = stake_amount + reward; + assert!( + amount >= expected - Uint128::one() && amount <= expected, + "Final user balance within error of 1 unit token {} != {}", + amount, + expected, + ); + } + _ => { + panic!("Snip20 balance query failed"); + } + }; + } +} + +macro_rules! multi_staker_single_pool { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + stake_amounts, + unbond_period, + reward_amount, + reward_start, + reward_end, + expected_rewards, + ) = $value; + multi_staker_single_pool( + stake_amounts, + unbond_period, + reward_amount, + reward_start, + reward_end, + expected_rewards, + ) + } + )* + } +} + +multi_staker_single_pool! { + multi_staker_single_pool_0: ( + vec![ // stake_amount + Uint128::new(1), + Uint128::new(10), + ], + Uint128::new(100), // unbond_period + Uint128::new(100), // reward_amount + Uint128::new(0), // reward_start (0-*) + Uint128::new(100), // reward_end + vec![ // expected rewards + Uint128::new(10), + Uint128::new(90), + ], + ), + multi_staker_single_pool_1: ( + vec![ // stake_amount + Uint128::new(33), + Uint128::new(33), + Uint128::new(34), + ], + Uint128::new(100), // unbond_period + Uint128::new(100), // reward_amount + Uint128::new(0), // reward_start (0-*) + Uint128::new(100), // reward_end + vec![ // expected rewards + Uint128::new(33), + Uint128::new(33), + Uint128::new(34), + ], + ), +} diff --git a/contracts/basic_staking/tests/non_admin_access.rs b/contracts/basic_staking/tests/non_admin_access.rs new file mode 100644 index 0000000..b00972a --- /dev/null +++ b/contracts/basic_staking/tests/non_admin_access.rs @@ -0,0 +1,186 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +#[test] +fn non_admin_access() { + let mut app = App::default(); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let non_admin_user = Addr::unchecked("nonadmin"); + let token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + airdrop: None, + stake_token: token.clone().into(), + unbond_period: Uint128::zero(), + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + + match (basic_staking::ExecuteMsg::UpdateConfig { + admin_auth: None, + query_auth: None, + airdrop: None, + unbond_period: None, + max_user_pools: None, + padding: None, + } + .test_exec(&basic_staking, &mut app, non_admin_user.clone(), &[])) + { + Ok(_) => panic!("Register Rewards by non-admin"), + Err(e) => { + assert!( + e.source() + .unwrap() + .to_string() + .contains("unregistered_admin"), + "Not an admin error" + ); + } + } + + match (basic_staking::ExecuteMsg::RegisterRewards { + token: RawContract { + address: "any_token".to_string(), + code_hash: "any_hash".to_string(), + }, + padding: None, + } + .test_exec(&basic_staking, &mut app, non_admin_user.clone(), &[])) + { + Ok(_) => panic!("Register Rewards by non-admin"), + Err(e) => { + assert!( + e.source() + .unwrap() + .to_string() + .contains("unregistered_admin"), + "Not an admin error" + ); + } + } + + match (basic_staking::ExecuteMsg::EndRewardPool { + id: Uint128::zero(), + force: None, + padding: None, + } + .test_exec(&basic_staking, &mut app, non_admin_user.clone(), &[])) + { + Ok(_) => panic!("End reward pool by non-admin"), + Err(e) => { + assert!( + e.source() + .unwrap() + .to_string() + .contains("unregistered_admin"), + "Not an admin error" + ); + } + } + + match (basic_staking::ExecuteMsg::AddTransferWhitelist { + user: "any_user".to_string(), + padding: None, + } + .test_exec(&basic_staking, &mut app, non_admin_user.clone(), &[])) + { + Ok(_) => panic!("End reward pool by non-admin"), + Err(e) => { + assert!( + e.source() + .unwrap() + .to_string() + .contains("unregistered_admin"), + "Not an admin error" + ); + } + } + + match (basic_staking::ExecuteMsg::RemoveTransferWhitelist { + user: "any_user".to_string(), + padding: None, + } + .test_exec(&basic_staking, &mut app, non_admin_user.clone(), &[])) + { + Ok(_) => panic!("End reward pool by non-admin"), + Err(e) => { + assert!( + e.source() + .unwrap() + .to_string() + .contains("unregistered_admin"), + "Not an admin error" + ); + } + } +} diff --git a/contracts/basic_staking/tests/non_stake_rewards.rs b/contracts/basic_staking/tests/non_stake_rewards.rs new file mode 100644 index 0000000..4d0bd03 --- /dev/null +++ b/contracts/basic_staking/tests/non_stake_rewards.rs @@ -0,0 +1,610 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +// Add other adapters here as they come +fn non_stake_rewards( + stake_amount: Uint128, + unbond_period: Uint128, + reward_amount: Uint128, + reward_start: Uint128, + reward_end: Uint128, +) { + let mut app = App::default(); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let staking_user = Addr::unchecked("staker"); + let reward_user = Addr::unchecked("reward_user"); + + let stake_token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![snip20::InitialBalance { + amount: stake_amount, + address: staking_user.to_string(), + }]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + let reward_token = snip20::InstantiateMsg { + name: "reward_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![snip20::InitialBalance { + amount: reward_amount, + address: reward_user.to_string(), + }]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + // set staking_user viewing keys + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&stake_token, &mut app, staking_user.clone(), &[]) + .unwrap(); + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&reward_token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + // set staking user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // set reward user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + airdrop: None, + stake_token: stake_token.clone().into(), + unbond_period, + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + println!("BASIC STAKING {}", basic_staking.address); + + // Pre-staking user stake amount + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Stake funds + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&stake_token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Post-staking user balance + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, stake_amount, "Post-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Register Reward Token + basic_staking::ExecuteMsg::RegisterRewards { + token: reward_token.clone().into(), + padding: None, + } + .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) + .unwrap(); + + // Init Rewards + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: reward_amount, + msg: Some( + to_binary(&basic_staking::Action::Rewards { + start: reward_start, + end: reward_end, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&reward_token, &mut app, reward_user.clone(), &[]) + .unwrap(); + + // reward user has no stake + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: reward_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "Reward User Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Check reward pool + match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); + assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); + assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Move forward to reward start + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(reward_start.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + // No rewards should be pending + match (basic_staking::QueryMsg::Rewards { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Rewards { rewards } => { + assert_eq!(rewards.len(), 1, "Rewards length at beginning"); + assert_eq!( + rewards[0].amount, + Uint128::zero(), + "Rewards claimable at beginning" + ); + } + _ => { + panic!("Staking rewards query failed"); + } + }; + + let reward_duration = reward_end - reward_start; + + // Move to middle of reward period + println!("Fast-forward to reward middle"); + app.set_block(BlockInfo { + height: 2, + time: Timestamp::from_seconds((reward_start.u128() + reward_duration.u128() / 2) as u64), + chain_id: "chain_id".to_string(), + }); + + // Half-ish rewards should be pending + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(rewards.len(), 1, "rewards length in the middle"); + let amount = rewards[0].amount; + let expected = reward_amount / Uint128::new(2); + assert!( + amount >= expected - Uint128::one() && amount <= expected, + "Rewards claimable in the middle within error of 1 unit token {} != {}", + amount, + expected + ); + } + _ => { + panic!("Staking rewards query failed"); + } + }; + + // Move to end of rewards + println!("Fast-forward to reward end"); + app.set_block(BlockInfo { + height: 3, + time: Timestamp::from_seconds(reward_end.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + // All rewards should be pending + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(rewards.len(), 1, "rewards length at end"); + let amount = rewards[0].amount; + assert!( + amount >= reward_amount - Uint128::one() && amount <= reward_amount, + "Rewards claimable at the end within error of 1 unit token {} != {}", + amount, + reward_amount, + ); + } + _ => { + panic!("Staking rewards query failed"); + } + }; + + // Claim rewards + basic_staking::ExecuteMsg::Compound { padding: None } + .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Check rewards were received + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }) + .test_query(&reward_token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert!( + amount >= reward_amount - Uint128::one() && amount <= reward_amount, + "Rewards claimed at the end within error of 1 unit token {} != {}", + amount, + reward_amount, + ); + } + _ => { + panic!("Snip20 balance query failed"); + } + }; + + // Unbond + basic_staking::ExecuteMsg::Unbond { + amount: stake_amount, + compound: None, + padding: None, + } + .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) + .unwrap(); + + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "0 staked after unbonding"); + assert_eq!(rewards, vec![], "0 rewards after unbonding"); + + assert_eq!(unbondings.len(), 1, "1 unbonding"); + assert_eq!(unbondings[0].amount, stake_amount, "Unbonding full amount"); + assert_eq!( + unbondings[0].complete, + reward_end + unbond_period, + "Unbonding complete expectedt" + ); + } + _ => { + panic!("Staking unbonding query failed"); + } + }; + println!("Fast-forward to end of unbonding period"); + app.set_block(BlockInfo { + height: 10, + time: Timestamp::from_seconds((reward_end + unbond_period).u128() as u64), + chain_id: "chain_id".to_string(), + }); + + basic_staking::ExecuteMsg::Withdraw { + ids: None, + padding: None, + } + .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) + .unwrap(); + + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "Final Staked Balance"); + assert_eq!(rewards, vec![], "Final Rewards"); + assert_eq!(unbondings, vec![], "Final Unbondings"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Check user stake token balance + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }) + .test_query(&stake_token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!( + amount, stake_amount, + "Final user balance of stake token {}, expected {}", + amount, stake_amount, + ); + } + _ => { + panic!("Snip20 balance query failed"); + } + }; + + // Check user reward token balance + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }) + .test_query(&reward_token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert!( + amount >= reward_amount - Uint128::one() + && amount <= reward_amount + Uint128::one(), + "Final user balance of reward token {}, expected {}", + amount, + reward_amount, + ); + } + _ => { + panic!("Snip20 balance query failed"); + } + }; +} + +macro_rules! non_stake_rewards { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + stake_amount, + unbond_period, + reward_amount, + reward_start, + reward_end, + ) = $value; + non_stake_rewards( + stake_amount, + unbond_period, + reward_amount, + reward_start, + reward_end, + ) + } + )* + } +} + +non_stake_rewards! { + non_stake_rewards_0: ( + Uint128::new(1), // stake_amount + Uint128::new(100), // unbond_period + Uint128::new(100), // reward_amount + Uint128::new(0), // reward_start (0-*) + Uint128::new(100), // reward_end + ), + non_stake_rewards_1: ( + Uint128::new(100), + Uint128::new(100), + Uint128::new(1000), + Uint128::new(0), + Uint128::new(100), + ), + non_stake_rewards_2: ( + Uint128::new(1000), + Uint128::new(100), + Uint128::new(300), + Uint128::new(0), + Uint128::new(100), + ), + non_stake_rewards_3: ( + Uint128::new(10), + Uint128::new(100), + Uint128::new(50000), + Uint128::new(0), + Uint128::new(2500000), + ), + non_stake_rewards_4: ( + Uint128::new(1234567), + Uint128::new(10000), + Uint128::new(500), + Uint128::new(0), + Uint128::new(10000), + ), + non_stake_rewards_5: ( + Uint128::new(99999999999), + Uint128::new(100), + Uint128::new(8192), + Uint128::new(20), + Uint128::new(8000), + ), +} diff --git a/contracts/basic_staking/tests/single_staker.rs b/contracts/basic_staking/tests/single_staker.rs new file mode 100644 index 0000000..01d6e90 --- /dev/null +++ b/contracts/basic_staking/tests/single_staker.rs @@ -0,0 +1,593 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +// Add other adapters here as they come +fn single_staker_single_pool( + stake_amount: Uint128, + unbond_period: Uint128, + reward_amount: Uint128, + reward_start: Uint128, + reward_end: Uint128, +) { + let mut app = App::default(); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let staking_user = Addr::unchecked("staker"); + let reward_user = Addr::unchecked("reward_user"); + + let token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![ + snip20::InitialBalance { + amount: stake_amount, + address: staking_user.to_string(), + }, + snip20::InitialBalance { + amount: reward_amount, + address: reward_user.to_string(), + }, + ]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + // set staking_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + // set staking user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // set reward user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + airdrop: None, + stake_token: token.clone().into(), + unbond_period, + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + println!("BASIC STAKING {}", basic_staking.address); + + // Pre-staking user stake amount + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Stake funds + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Post-staking user balance + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, stake_amount, "Post-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Post staking total staked + match (basic_staking::QueryMsg::TotalStaked {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::TotalStaked { amount } => { + assert_eq!(amount, stake_amount, "Post staking total staked"); + } + _ => { + panic!("Total Staked query failed"); + } + }; + + // Verify stake token + match (basic_staking::QueryMsg::StakeToken {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::StakeToken { token: stake_token } => { + assert_eq!(stake_token, token.address); + } + _ => { + panic!("Total Staked query failed"); + } + }; + + // Stake token should be registered + match (basic_staking::QueryMsg::RewardTokens {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardTokens { tokens } => { + assert_eq!(tokens.len(), 1, "Only stake token registered"); + assert_eq!(tokens[0], token.address); + } + _ => { + panic!("Total Staked query failed"); + } + }; + + // Init Rewards + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: reward_amount, + msg: Some( + to_binary(&basic_staking::Action::Rewards { + start: reward_start, + end: reward_end, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, reward_user.clone(), &[]) + .unwrap(); + + // reward user has no stake + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: reward_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "Reward User Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Check reward pool + match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); + assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); + assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Move forward to reward start + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(reward_start.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + // No rewards should be pending + match (basic_staking::QueryMsg::Rewards { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Rewards { rewards } => { + assert_eq!(rewards.len(), 1, "Rewards length at beginning"); + assert_eq!( + rewards[0].amount, + Uint128::zero(), + "Rewards claimable at beginning" + ); + } + _ => { + panic!("Staking rewards query failed"); + } + }; + + let reward_duration = reward_end - reward_start; + + // Move to middle of reward period + println!("Fast-forward to reward middle"); + app.set_block(BlockInfo { + height: 2, + time: Timestamp::from_seconds((reward_start.u128() + reward_duration.u128() / 2) as u64), + chain_id: "chain_id".to_string(), + }); + + // Half-ish rewards should be pending + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(rewards.len(), 1, "rewards length in the middle"); + let amount = rewards[0].amount; + let expected = reward_amount / Uint128::new(2); + assert!( + amount >= expected - Uint128::one() && amount <= expected, + "Rewards claimable in the middle within error of 1 unit token {} != {}", + amount, + expected + ); + } + _ => { + panic!("Staking rewards query failed"); + } + }; + + // Move to end of rewards + println!("Fast-forward to reward end"); + app.set_block(BlockInfo { + height: 3, + time: Timestamp::from_seconds(reward_end.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + // All rewards should be pending + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(rewards.len(), 1, "rewards length at end"); + let amount = rewards[0].amount; + assert!( + amount >= reward_amount - Uint128::one() && amount <= reward_amount, + "Rewards claimable at the end within error of 1 unit token {} != {}", + amount, + reward_amount, + ); + } + _ => { + panic!("Staking rewards query failed"); + } + }; + + // Claim rewards + basic_staking::ExecuteMsg::Claim { padding: None } + .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Check rewards were received + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert!( + amount >= reward_amount - Uint128::one() && amount <= reward_amount, + "Rewards claimed at the end within error of 1 unit token {} != {}", + amount, + reward_amount, + ); + } + _ => { + panic!("Snip20 balance query failed"); + } + }; + + // Unbond + basic_staking::ExecuteMsg::Unbond { + amount: stake_amount, + compound: None, + padding: None, + } + .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) + .unwrap(); + + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "0 staked after unbonding"); + assert_eq!(rewards, vec![], "0 rewards after unbonding"); + + assert_eq!(unbondings.len(), 1, "1 unbonding"); + assert_eq!(unbondings[0].amount, stake_amount, "Unbonding full amount"); + assert_eq!( + unbondings[0].complete, + reward_end + unbond_period, + "Unbonding complete expectedt" + ); + } + _ => { + panic!("Staking unbonding query failed"); + } + }; + println!("Fast-forward to end of unbonding period"); + app.set_block(BlockInfo { + height: 10, + time: Timestamp::from_seconds((reward_end + unbond_period).u128() as u64), + chain_id: "chain_id".to_string(), + }); + + basic_staking::ExecuteMsg::Withdraw { + ids: None, + padding: None, + } + .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) + .unwrap(); + + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "Final Staked Balance"); + assert_eq!(rewards, vec![], "Final Rewards"); + assert_eq!(unbondings, vec![], "Final Unbondings"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Check snip20 received by user + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + let expected = stake_amount + reward_amount; + assert!( + amount >= expected - Uint128::one() && amount <= expected, + "Final user balance within error of 1 unit token {} != {}", + amount, + expected, + ); + } + _ => { + panic!("Snip20 balance query failed"); + } + }; +} + +macro_rules! single_staker_single_pool { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + stake_amount, + unbond_period, + reward_amount, + reward_start, + reward_end, + ) = $value; + single_staker_single_pool( + stake_amount, + unbond_period, + reward_amount, + reward_start, + reward_end, + ) + } + )* + } +} + +single_staker_single_pool! { + single_staker_single_pool_0: ( + Uint128::new(1), // stake_amount + Uint128::new(100), // unbond_period + Uint128::new(100), // reward_amount + Uint128::new(0), // reward_start (0-*) + Uint128::new(100), // reward_end + ), + single_staker_single_pool_1: ( + Uint128::new(100), + Uint128::new(100), + Uint128::new(1000), + Uint128::new(0), + Uint128::new(100), + ), + single_staker_single_pool_2: ( + Uint128::new(1000), + Uint128::new(100), + Uint128::new(300), + Uint128::new(0), + Uint128::new(100), + ), + single_staker_single_pool_3: ( + Uint128::new(10), + Uint128::new(100), + Uint128::new(50000), + Uint128::new(0), + Uint128::new(2500000), + ), + single_staker_single_pool_4: ( + Uint128::new(1234567), + Uint128::new(10000), + Uint128::new(500), + Uint128::new(0), + Uint128::new(10000), + ), + single_staker_single_pool_5: ( + Uint128::new(99999999999), + Uint128::new(100), + Uint128::new(8192), + Uint128::new(20), + Uint128::new(8000), + ), +} diff --git a/contracts/basic_staking/tests/single_staker_compounding.rs b/contracts/basic_staking/tests/single_staker_compounding.rs new file mode 100644 index 0000000..2eed6ed --- /dev/null +++ b/contracts/basic_staking/tests/single_staker_compounding.rs @@ -0,0 +1,588 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +// Add other adapters here as they come +fn single_staker_compounding( + stake_amount: Uint128, + unbond_period: Uint128, + reward_amount: Uint128, + reward_start: Uint128, + reward_end: Uint128, +) { + let mut app = App::default(); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let staking_user = Addr::unchecked("staker"); + let reward_user = Addr::unchecked("reward_user"); + + let token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![ + snip20::InitialBalance { + amount: stake_amount, + address: staking_user.to_string(), + }, + snip20::InitialBalance { + amount: reward_amount, + address: reward_user.to_string(), + }, + ]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + // set staking_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + // set staking user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // set reward user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + airdrop: None, + stake_token: token.clone().into(), + unbond_period, + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + println!("BASIC STAKING {}", basic_staking.address); + + // Pre-staking user balance + match (basic_staking::QueryMsg::Staked { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Staked { amount } => { + assert_eq!(amount, Uint128::zero(), "Pre-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Stake half funds (half after some rewards to compound on stake) + let first_amount = stake_amount / Uint128::new(2); + let second_amount = stake_amount - first_amount; + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: first_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Post-staking user balance + match (basic_staking::QueryMsg::Staked { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Staked { amount } => { + assert_eq!(amount, first_amount, "Post First Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Init Rewards + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: reward_amount, + msg: Some( + to_binary(&basic_staking::Action::Rewards { + start: reward_start, + end: reward_end, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, reward_user.clone(), &[]) + .unwrap(); + + // reward user has no stake + match (basic_staking::QueryMsg::Staked { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: reward_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Staked { amount } => { + assert_eq!(amount, Uint128::zero(), "Reward User Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Check reward pool + match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + println!("init rewards {:?}", rewards); + assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); + assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); + assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Move forward to reward start + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(reward_start.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + // No rewards should be pending + match (basic_staking::QueryMsg::Rewards { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Rewards { rewards } => { + assert_eq!(rewards.len(), 1, "Rewards length at beginning"); + assert_eq!( + rewards[0].amount, + Uint128::zero(), + "Rewards claimable at beginning" + ); + } + _ => { + panic!("Staking rewards query failed"); + } + }; + + let reward_duration = reward_end - reward_start; + + // Move to middle of reward period + println!("Fast-forward to reward middle"); + app.set_block(BlockInfo { + height: 2, + time: Timestamp::from_seconds((reward_start.u128() + reward_duration.u128() / 2) as u64), + chain_id: "chain_id".to_string(), + }); + + let expected_mid_rewards = reward_amount / Uint128::new(2); + let mut mid_rewards = Uint128::zero(); + + // Half-ish rewards should be pending + match (basic_staking::QueryMsg::Rewards { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Rewards { rewards } => { + assert_eq!(rewards.len(), 1, "rewards length in the middle"); + let amount = rewards[0].amount; + mid_rewards = amount; + assert!( + amount >= expected_mid_rewards - Uint128::one() && amount <= expected_mid_rewards, + "Rewards claimable in the middle within error of 1 unit token {} != {}", + amount, + expected_mid_rewards + ); + } + _ => { + panic!("Staking rewards query failed"); + } + }; + + // Stake & compound + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: second_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: Some(true), + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Re-query rewards + match (basic_staking::QueryMsg::Rewards { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Rewards { rewards } => { + assert_eq!( + rewards.len(), + 1, + "rewards length in the middle post-compound" + ); + assert_eq!( + rewards[0].amount, + Uint128::zero(), + "Rewards claimable post-compound" + ); + } + _ => { + panic!("Staking rewards query failed"); + } + }; + // Stake balance reflects compounded rewards + match (basic_staking::QueryMsg::Staked { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Staked { amount } => { + println!("PRE COMPOUND STAKED {}", amount); + let amount = amount.u128(); + let expected = (stake_amount + mid_rewards).u128(); + assert!( + amount <= expected && expected <= amount, + "Reward User Stake Balance post-compound" + ); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Move to end of rewards + println!("Fast-forward to reward end"); + app.set_block(BlockInfo { + height: 3, + time: Timestamp::from_seconds(reward_end.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + let current_rewards: Uint128; + // All rewards should be pending + match (basic_staking::QueryMsg::Rewards { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Rewards { rewards } => { + assert_eq!(rewards.len(), 1, "rewards length at end"); + current_rewards = rewards[0].amount; + println!("CUR REWARDS {}", current_rewards); + println!( + "reward amount {} mid rewards {}", + reward_amount, mid_rewards + ); + let expected = reward_amount - mid_rewards; + assert!( + current_rewards >= expected - Uint128::new(2) + && current_rewards <= expected + Uint128::new(2), + "Rewards claimable at the end within error of 2 unit tokens {} != {}", + current_rewards, + expected, + ); + } + _ => { + panic!("Staking rewards query failed"); + } + }; + + // Compound rewards + basic_staking::ExecuteMsg::Compound { padding: None } + .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Check rewards were compounded + match (basic_staking::QueryMsg::Staked { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Staked { amount } => { + assert_eq!( + amount, + stake_amount + current_rewards + mid_rewards, + "Post compound staked {}, {}", + stake_amount, + current_rewards + ); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Unbond + basic_staking::ExecuteMsg::Unbond { + amount: stake_amount + current_rewards + mid_rewards, + compound: None, + padding: None, + } + .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // All rewards should be pending + match (basic_staking::QueryMsg::Unbonding { + ids: None, + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Unbonding { unbondings } => { + assert_eq!(unbondings.len(), 1, "1 unbonding"); + assert_eq!( + unbondings[0].amount, + stake_amount + current_rewards + mid_rewards, + "Unbonding full amount" + ); + assert_eq!( + unbondings[0].complete, + reward_end + unbond_period, + "Unbonding complete expectedt" + ); + } + _ => { + panic!("Staking unbonding query failed"); + } + }; + + println!("Fast-forward to end of unbonding period"); + app.set_block(BlockInfo { + height: 10, + time: Timestamp::from_seconds((reward_end + unbond_period).u128() as u64), + chain_id: "chain_id".to_string(), + }); + + basic_staking::ExecuteMsg::Withdraw { + ids: None, + padding: None, + } + .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Check unbonding withdrawn + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + let expected = stake_amount + reward_amount; + assert!( + amount >= expected - Uint128::new(2) && amount <= expected, + "Final user balance within error of 2 unit tokens {} != {}", + amount, + expected, + ); + } + _ => { + panic!("Snip20 balance query failed"); + } + }; +} + +macro_rules! single_staker_compounding { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + stake_amount, + unbond_period, + reward_amount, + reward_start, + reward_end, + ) = $value; + single_staker_compounding( + stake_amount, + unbond_period, + reward_amount, + reward_start, + reward_end, + ) + } + )* + } +} + +single_staker_compounding! { + single_staker_compounding_0: ( + Uint128::new(2), // stake_amount + Uint128::new(100), // unbond_period + Uint128::new(100), // reward_amount + Uint128::new(0), // reward_start (0-*) + Uint128::new(100), // reward_end + ), + single_staker_compounding_1: ( + Uint128::new(100), + Uint128::new(100), + Uint128::new(1000), + Uint128::new(0), + Uint128::new(100), + ), + single_staker_compounding_2: ( + Uint128::new(1000), + Uint128::new(100), + Uint128::new(300), + Uint128::new(0), + Uint128::new(100), + ), + single_staker_compounding_3: ( + Uint128::new(10), + Uint128::new(100), + Uint128::new(50000), + Uint128::new(0), + Uint128::new(2500000), + ), + single_staker_compounding_4: ( + Uint128::new(1234567), + Uint128::new(10000), + Uint128::new(500), + Uint128::new(0), + Uint128::new(10000), + ), + single_staker_compounding_5: ( + Uint128::new(99999999999), + Uint128::new(100), + Uint128::new(8192), + Uint128::new(20), + Uint128::new(8000), + ), +} diff --git a/contracts/basic_staking/tests/stake_0_total_bug.rs b/contracts/basic_staking/tests/stake_0_total_bug.rs new file mode 100644 index 0000000..538e20f --- /dev/null +++ b/contracts/basic_staking/tests/stake_0_total_bug.rs @@ -0,0 +1,445 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +// Add other adapters here as they come +fn stake_0_total_bug( + stake_amount: Uint128, + unbond_period: Uint128, + reward_amount: Uint128, + reward_start: Uint128, + reward_end: Uint128, +) { + let mut app = App::default(); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let staking_user = Addr::unchecked("staker"); + let reward_user = Addr::unchecked("reward_user"); + + let token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![ + snip20::InitialBalance { + amount: stake_amount, + address: staking_user.to_string(), + }, + snip20::InitialBalance { + amount: reward_amount, + address: reward_user.to_string(), + }, + ]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + // set staking_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + // set staking user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // set reward user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + airdrop: None, + stake_token: token.clone().into(), + unbond_period, + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + println!("BASIC STAKING {}", basic_staking.address); + + // Init Rewards + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: reward_amount, + msg: Some( + to_binary(&basic_staking::Action::Rewards { + start: reward_start, + end: reward_end, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, reward_user.clone(), &[]) + .unwrap(); + + // reward user has no stake + match (basic_staking::QueryMsg::Staked { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: reward_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Staked { amount } => { + assert_eq!(amount, Uint128::zero(), "Reward User Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Check reward pool + match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); + assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); + assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Stake funds + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Post-staking user balance + match (basic_staking::QueryMsg::Staked { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Staked { amount } => { + assert_eq!(amount, stake_amount, "Post-Stake Balance"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Move forward to reward start + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(reward_start.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + // No rewards should be pending + match (basic_staking::QueryMsg::Rewards { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Rewards { rewards } => { + assert_eq!(rewards.len(), 1, "Rewards length at beginning"); + assert_eq!( + rewards[0].amount, + Uint128::zero(), + "Rewards claimable at beginning" + ); + } + _ => { + panic!("Staking rewards query failed"); + } + }; + + let reward_duration = reward_end - reward_start; + + // Move to middle of reward period + println!("Fast-forward to reward middle"); + app.set_block(BlockInfo { + height: 2, + time: Timestamp::from_seconds((reward_start.u128() + reward_duration.u128() / 2) as u64), + chain_id: "chain_id".to_string(), + }); + + // Half-ish rewards should be pending + match (basic_staking::QueryMsg::Rewards { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Rewards { rewards } => { + assert_eq!(rewards.len(), 1, "rewards length in the middle"); + let amount = rewards[0].amount; + let expected = reward_amount / Uint128::new(2); + assert!( + amount >= expected - Uint128::one() && amount <= expected, + "Rewards claimable in the middle within error of 1 unit token {} != {}", + amount, + expected + ); + } + _ => { + panic!("Staking rewards query failed"); + } + }; + + // Move to end of rewards + println!("Fast-forward to reward end"); + app.set_block(BlockInfo { + height: 3, + time: Timestamp::from_seconds(reward_end.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + // All rewards should be pending + match (basic_staking::QueryMsg::Rewards { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Rewards { rewards } => { + assert_eq!(rewards.len(), 1, "rewards length at end"); + let amount = rewards[0].amount; + assert!( + amount >= reward_amount - Uint128::one() && amount <= reward_amount, + "Rewards claimable at the end within error of 1 unit token {} != {}", + amount, + reward_amount, + ); + } + _ => { + panic!("Staking rewards query failed"); + } + }; + + // Claim rewards + basic_staking::ExecuteMsg::Claim { padding: None } + .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Check rewards were claimed + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert!( + amount >= reward_amount - Uint128::one() && amount <= reward_amount, + "Rewards claimed at the end within error of 1 unit token {} != {}", + amount, + reward_amount, + ); + } + _ => { + panic!("Snip20 balance query failed"); + } + }; + + // Unbond + basic_staking::ExecuteMsg::Unbond { + amount: stake_amount, + compound: None, + padding: None, + } + .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // All rewards should be pending + match (basic_staking::QueryMsg::Unbonding { + ids: None, + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Unbonding { unbondings } => { + assert_eq!(unbondings.len(), 1, "1 unbonding"); + assert_eq!(unbondings[0].amount, stake_amount, "Unbonding full amount"); + assert_eq!( + unbondings[0].complete, + reward_end + unbond_period, + "Unbonding complete expectedt" + ); + } + _ => { + panic!("Staking unbonding query failed"); + } + }; + println!("Fast-forward to end of unbonding period"); + app.set_block(BlockInfo { + height: 10, + time: Timestamp::from_seconds((reward_end + unbond_period).u128() as u64), + chain_id: "chain_id".to_string(), + }); + + basic_staking::ExecuteMsg::Withdraw { + ids: None, + padding: None, + } + .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Check unbonding withdrawn + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + let expected = stake_amount + reward_amount; + assert!( + amount >= expected - Uint128::one() && amount <= expected, + "Final user balance within error of 1 unit token {} != {}", + amount, + expected, + ); + } + _ => { + panic!("Snip20 balance query failed"); + } + }; +} + +macro_rules! stake_0_total_bug { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + stake_amount, + unbond_period, + reward_amount, + reward_start, + reward_end, + ) = $value; + stake_0_total_bug( + stake_amount, + unbond_period, + reward_amount, + reward_start, + reward_end, + ) + } + )* + } +} + +stake_0_total_bug! { + stake_0_total_bug_0: ( + Uint128::new(1), // stake_amount + Uint128::new(100), // unbond_period + Uint128::new(100), // reward_amount + Uint128::new(0), // reward_start (0-*) + Uint128::new(100), // reward_end + ), +} diff --git a/contracts/basic_staking/tests/transfer_stake.rs b/contracts/basic_staking/tests/transfer_stake.rs new file mode 100644 index 0000000..1f77069 --- /dev/null +++ b/contracts/basic_staking/tests/transfer_stake.rs @@ -0,0 +1,454 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +// Add other adapters here as they come +fn transfer_stake(stake_amount: Uint128, transfer_amount: Uint128) { + let mut app = App::default(); + + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let staking_user = Addr::unchecked("staker"); + let transfer_user = Addr::unchecked("transfer"); + + let reward_user = Addr::unchecked("reward_user"); + let reward_amount = Uint128::new(100000000); + let reward_start = Uint128::new(100); + let reward_end = Uint128::new(1000); + + let token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![ + snip20::InitialBalance { + amount: stake_amount, + address: staking_user.to_string(), + }, + snip20::InitialBalance { + amount: transfer_amount, + address: transfer_user.to_string(), + }, + snip20::InitialBalance { + amount: reward_amount, + address: reward_user.to_string(), + }, + ]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + // set staking_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // set transfer_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, transfer_user.clone(), &[]) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + // set staking user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) + .unwrap(); + // set transfer user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, transfer_user.clone(), &[]) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + stake_token: token.clone().into(), + airdrop: None, + unbond_period: Uint128::zero(), + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + + // Init Rewards + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: reward_amount, + msg: Some( + to_binary(&basic_staking::Action::Rewards { + start: reward_start, + end: reward_end, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, reward_user.clone(), &[]) + .unwrap(); + + // Stake staking user + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Stake transfer user + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: transfer_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, transfer_user.clone(), &[]) + .unwrap(); + + // Skip to end of rewards + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(reward_end.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + // Transfer should fail, not on whitelist + match (basic_staking::ExecuteMsg::TransferStake { + amount: transfer_amount, + recipient: staking_user.clone().into(), + compound: Some(false), + padding: None, + } + .test_exec(&basic_staking, &mut app, transfer_user.clone(), &[])) + { + Err(_) => {} + Ok(_) => { + panic!("Shouldn't allow transfer if source not on whitelist"); + } + } + + // Add transfer user to whitelist + basic_staking::ExecuteMsg::AddTransferWhitelist { + user: transfer_user.clone().into(), + padding: None, + } + .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) + .unwrap(); + + // Check whitelist + match (basic_staking::QueryMsg::TransferWhitelist {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::TransferWhitelist { whitelist } => { + assert_eq!( + whitelist, + vec![transfer_user.clone()], + "Whitelist contains transfer user" + ); + } + _ => { + panic!("Unexpected transfer whitelist response"); + } + } + + // check balances to verify rewards claim + let stake_user_reward = match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, stake_amount, "Pre-Transfer recipient balance"); + rewards[0].amount + } + _ => { + panic!("Staking balance query failed"); + } + }; + + let transfer_user_reward = match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: transfer_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!( + staked, transfer_amount, + "Pre-Transfer transfer user balance" + ); + rewards[0].amount + } + _ => { + panic!("Staking balance query failed"); + } + }; + + basic_staking::ExecuteMsg::TransferStake { + amount: transfer_amount, + recipient: staking_user.clone().into(), + compound: Some(false), + padding: None, + } + .test_exec(&basic_staking, &mut app, transfer_user.clone(), &[]) + .unwrap(); + + // check staking user balance after transfer + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!( + staked, + stake_amount + transfer_amount, + "Post-Transfer recipient balance" + ); + assert_eq!(rewards.len(), 0, "Recipient rewards claimed"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + // Check recipient rewards claimed + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, stake_user_reward, "Recipient rewards claimed",); + } + _ => { + panic!("Snip20 balance query failed"); + } + }; + + // check transfer user balance after transfer + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: transfer_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "Post-Transfer sender balance"); + assert_eq!(rewards.len(), 0, "Sender rewards claimed"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Check sender rewards claimed + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: transfer_user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, transfer_user_reward, "Sender rewards claimed"); + } + _ => { + panic!("Snip20 balance query failed"); + } + }; + + // Remove from whitelist + basic_staking::ExecuteMsg::RemoveTransferWhitelist { + user: transfer_user.clone().into(), + padding: None, + } + .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) + .unwrap(); + + // Check whitelist + match (basic_staking::QueryMsg::TransferWhitelist {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::TransferWhitelist { whitelist } => { + assert!(whitelist.is_empty(), "Whitelist empty after removal"); + } + _ => { + panic!("Unexpected transfer whitelist response"); + } + } +} + +macro_rules! transfer_stake { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + stake_amount, + transfer_amount, + ) = $value; + transfer_stake( + stake_amount, + transfer_amount, + ) + } + )* + } +} + +transfer_stake! { + transfer_stake_0: ( + Uint128::new(1), // stake_amount + Uint128::new(1), // transfer_amount + ), + transfer_stake_1: ( + Uint128::new(100), + Uint128::new(100), + ), + transfer_stake_2: ( + Uint128::new(1000), + Uint128::new(1000), + ), + transfer_stake_3: ( + Uint128::new(10), + Uint128::new(10), + ), + transfer_stake_4: ( + Uint128::new(1234567), + Uint128::new(1234567), + ), + transfer_stake_5: ( + Uint128::new(99999999999), + Uint128::new(99999999999), + ), +} diff --git a/contracts/basic_staking/tests/transfer_stake_compound.rs b/contracts/basic_staking/tests/transfer_stake_compound.rs new file mode 100644 index 0000000..722a849 --- /dev/null +++ b/contracts/basic_staking/tests/transfer_stake_compound.rs @@ -0,0 +1,454 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +// Add other adapters here as they come +fn transfer_stake(stake_amount: Uint128, transfer_amount: Uint128) { + let mut app = App::default(); + + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let staking_user = Addr::unchecked("staker"); + let transfer_user = Addr::unchecked("transfer"); + + let reward_user = Addr::unchecked("reward_user"); + let reward_amount = Uint128::new(100000000); + let reward_start = Uint128::new(100); + let reward_end = Uint128::new(1000); + + let token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![ + snip20::InitialBalance { + amount: stake_amount, + address: staking_user.to_string(), + }, + snip20::InitialBalance { + amount: transfer_amount, + address: transfer_user.to_string(), + }, + snip20::InitialBalance { + amount: reward_amount, + address: reward_user.to_string(), + }, + ]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + // set staking_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // set transfer_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, transfer_user.clone(), &[]) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + // set staking user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) + .unwrap(); + // set transfer user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, transfer_user.clone(), &[]) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + stake_token: token.clone().into(), + airdrop: None, + unbond_period: Uint128::zero(), + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + + // Init Rewards + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: reward_amount, + msg: Some( + to_binary(&basic_staking::Action::Rewards { + start: reward_start, + end: reward_end, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, reward_user.clone(), &[]) + .unwrap(); + + // Stake staking user + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Stake transfer user + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: transfer_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, transfer_user.clone(), &[]) + .unwrap(); + + // Skip to end of rewards + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(reward_end.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + // Transfer should fail, not on whitelist + match (basic_staking::ExecuteMsg::TransferStake { + amount: transfer_amount, + recipient: staking_user.clone().into(), + compound: Some(true), + padding: None, + } + .test_exec(&basic_staking, &mut app, transfer_user.clone(), &[])) + { + Err(_) => {} + Ok(_) => { + panic!("Shouldn't allow transfer if source not on whitelist"); + } + } + + // Add transfer user to whitelist + basic_staking::ExecuteMsg::AddTransferWhitelist { + user: transfer_user.clone().into(), + padding: None, + } + .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) + .unwrap(); + + // Check whitelist + match (basic_staking::QueryMsg::TransferWhitelist {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::TransferWhitelist { whitelist } => { + assert_eq!( + whitelist, + vec![transfer_user.clone()], + "Whitelist contains transfer user" + ); + } + _ => { + panic!("Unexpected transfer whitelist response"); + } + } + + // check balances to verify rewards claim + let stake_user_reward = match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, stake_amount, "Pre-Transfer recipient balance"); + rewards[0].amount + } + _ => { + panic!("Staking balance query failed"); + } + }; + + let transfer_user_reward = match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: transfer_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!( + staked, transfer_amount, + "Pre-Transfer transfer user balance" + ); + rewards[0].amount + } + _ => { + panic!("Staking balance query failed"); + } + }; + + basic_staking::ExecuteMsg::TransferStake { + amount: transfer_amount, + recipient: staking_user.clone().into(), + compound: Some(true), + padding: None, + } + .test_exec(&basic_staking, &mut app, transfer_user.clone(), &[]) + .unwrap(); + + // check staking user balance after transfer + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!( + staked, + stake_amount + transfer_amount, + "Post-Transfer recipient balance" + ); + assert_eq!(rewards.len(), 0, "Recipient rewards claimed"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + // Check recipient rewards claimed + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, stake_user_reward, "Recipient rewards claimed",); + } + _ => { + panic!("Snip20 balance query failed"); + } + }; + + // check transfer user balance after transfer + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: transfer_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, transfer_user_reward, "Post-Transfer sender balance"); + assert_eq!(rewards.len(), 0, "Sender rewards compounded"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Check sender rewards compounded + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: transfer_user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, Uint128::zero(), "Sender no balance bc compound"); + } + _ => { + panic!("Snip20 balance query failed"); + } + }; + + // Remove from whitelist + basic_staking::ExecuteMsg::RemoveTransferWhitelist { + user: transfer_user.clone().into(), + padding: None, + } + .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) + .unwrap(); + + // Check whitelist + match (basic_staking::QueryMsg::TransferWhitelist {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::TransferWhitelist { whitelist } => { + assert!(whitelist.is_empty(), "Whitelist empty after removal"); + } + _ => { + panic!("Unexpected transfer whitelist response"); + } + } +} + +macro_rules! transfer_stake { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + stake_amount, + transfer_amount, + ) = $value; + transfer_stake( + stake_amount, + transfer_amount, + ) + } + )* + } +} + +transfer_stake! { + transfer_stake_0: ( + Uint128::new(1), // stake_amount + Uint128::new(1), // transfer_amount + ), + transfer_stake_1: ( + Uint128::new(100), + Uint128::new(100), + ), + transfer_stake_2: ( + Uint128::new(1000), + Uint128::new(1000), + ), + transfer_stake_3: ( + Uint128::new(10), + Uint128::new(10), + ), + transfer_stake_4: ( + Uint128::new(1234567), + Uint128::new(1234567), + ), + transfer_stake_5: ( + Uint128::new(99999999999), + Uint128::new(99999999999), + ), +} diff --git a/contracts/basic_staking/tests/transfer_stake_sender_no_stake.rs b/contracts/basic_staking/tests/transfer_stake_sender_no_stake.rs new file mode 100644 index 0000000..3fed621 --- /dev/null +++ b/contracts/basic_staking/tests/transfer_stake_sender_no_stake.rs @@ -0,0 +1,232 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +// Add other adapters here as they come +fn transfer_stake(stake_amount: Uint128, transfer_amount: Uint128) { + let mut app = App::default(); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let receiving_user = Addr::unchecked("staker"); + let sending_user = Addr::unchecked("transfer"); + + let token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![ + snip20::InitialBalance { + amount: stake_amount, + address: receiving_user.to_string(), + }, + snip20::InitialBalance { + amount: transfer_amount, + address: sending_user.to_string(), + }, + ]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + // set receiving_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, receiving_user.clone(), &[]) + .unwrap(); + + // set sending_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, sending_user.clone(), &[]) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + // set staking user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, receiving_user.clone(), &[]) + .unwrap(); + + // set transfer user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, sending_user.clone(), &[]) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + stake_token: token.clone().into(), + airdrop: None, + unbond_period: Uint128::zero(), + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + println!("BASIC STAKING {}", basic_staking.address); + + // Stake staking user + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, receiving_user.clone(), &[]) + .unwrap(); + + // Transfer should fail, not on whitelist + match (basic_staking::ExecuteMsg::TransferStake { + amount: transfer_amount, + recipient: receiving_user.clone().into(), + compound: Some(true), + padding: None, + } + .test_exec(&basic_staking, &mut app, sending_user.clone(), &[])) + { + Err(_) => {} + Ok(_) => { + panic!("Shouldn't allow transfer if source not on whitelist"); + } + } + + // Add transfer user to whitelist + basic_staking::ExecuteMsg::AddTransferWhitelist { + user: sending_user.clone().into(), + padding: None, + } + .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) + .unwrap(); + + match (basic_staking::ExecuteMsg::TransferStake { + amount: transfer_amount, + recipient: receiving_user.clone().into(), + compound: Some(true), + padding: None, + } + .test_exec(&basic_staking, &mut app, sending_user.clone(), &[])) + { + Ok(_) => { + panic!("Transfer success with no balance!"); + } + Err(e) => {} + } +} + +macro_rules! transfer_stake { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + stake_amount, + transfer_amount, + ) = $value; + transfer_stake( + stake_amount, + transfer_amount, + ) + } + )* + } +} + +transfer_stake! { + transfer_stake_0: ( + Uint128::new(1), // stake_amount + Uint128::new(1), // transfer_amount + ), + transfer_stake_1: ( + Uint128::new(100), + Uint128::new(100), + ), + transfer_stake_2: ( + Uint128::new(1000), + Uint128::new(1000), + ), + transfer_stake_3: ( + Uint128::new(10), + Uint128::new(10), + ), + transfer_stake_4: ( + Uint128::new(1234567), + Uint128::new(1234567), + ), + transfer_stake_5: ( + Uint128::new(99999999999), + Uint128::new(99999999999), + ), +} diff --git a/contracts/basic_staking/tests/unbonding_withdrawals.rs b/contracts/basic_staking/tests/unbonding_withdrawals.rs new file mode 100644 index 0000000..d1f962f --- /dev/null +++ b/contracts/basic_staking/tests/unbonding_withdrawals.rs @@ -0,0 +1,316 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +// Add other adapters here as they come +fn unbonding_withdrawals( + stake_amount: Uint128, + unbond_period: Uint128, + unbonding_amounts: Vec, + withdraw_order: Vec, +) { + let mut app = App::default(); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let staking_user = Addr::unchecked("staker"); + let reward_user = Addr::unchecked("reward_user"); + + let token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![snip20::InitialBalance { + amount: stake_amount, + address: staking_user.to_string(), + }]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + // set staking_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + // set staking user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // set reward user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + airdrop: None, + stake_token: token.clone().into(), + unbond_period, + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + println!("BASIC STAKING {}", basic_staking.address); + + // Stake funds + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + let mut now = 50; + let mut unbonded = Uint128::zero(); + + // Setup timestamp + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(now), + chain_id: "chain_id".to_string(), + }); + + // Perform each unbonding + for unbond_amount in unbonding_amounts.iter() { + // Unbond + basic_staking::ExecuteMsg::Unbond { + amount: unbond_amount.clone(), + compound: None, + padding: None, + } + .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) + .unwrap(); + } + + // Check that unbonding list is as expected + let unbonding_ids = match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!( + unbondings + .iter() + .map(|u| u.amount) + .collect::>(), + unbonding_amounts, + "unbondings order as expectedt" + ); + unbondings.iter().map(|u| u.id).collect::>() + } + _ => { + panic!("Balance query failed"); + vec![] + } + }; + + println!("Fast-forward to end of unbonding period"); + app.set_block(BlockInfo { + height: 10, + time: Timestamp::from_seconds(now + unbond_period.u128() as u64), + chain_id: "chain_id".to_string(), + }); + + let mut withdrawn_ids = vec![]; + + for i in withdraw_order.into_iter() { + // Withdraw + basic_staking::ExecuteMsg::Withdraw { + ids: Some(vec![unbonding_ids[i]]), + padding: None, + } + .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) + .unwrap(); + + withdrawn_ids.push(unbonding_ids[i]); + + let expected_unbonding_ids = unbonding_ids + .clone() + .into_iter() + .filter(|id| !withdrawn_ids.contains(id)) + .collect::>(); + + // Check that unbonding list is as expected + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!( + unbondings.iter().map(|u| u.id).collect::>(), + expected_unbonding_ids, + "unbondings order as expected after withdrawing", + ); + } + _ => { + panic!("Balance query failed"); + } + }; + } + + // Check snip20 received by user + match (snip20::QueryMsg::Balance { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, stake_amount, "Final user balance",); + } + _ => { + panic!("Snip20 balance query failed"); + } + }; +} + +macro_rules! unbonding_withdrawals { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + stake_amount, + unbond_period, + unbonding_amounts, + withdraw_order, + ) = $value; + unbonding_withdrawals( + stake_amount, + unbond_period, + unbonding_amounts, + withdraw_order, + ) + } + )* + } +} + +unbonding_withdrawals! { + unbonding_withdrawals_0: ( + Uint128::new(100), // stake_amount + Uint128::new(100), // unbond_period + vec![ + Uint128::new(10), + Uint128::new(50), + Uint128::new(17), + Uint128::new(23), + ], + vec![0, 2, 1, 3], + ), + unbonding_withdrawals_1: ( + Uint128::new(1000), // stake_amount + Uint128::new(100), // unbond_period + vec![ + Uint128::new(10), + Uint128::new(50), + Uint128::new(17), + Uint128::new(23), + Uint128::new(100), + Uint128::new(700), + Uint128::new(50), + Uint128::new(50), + ], + vec![7, 5, 6, 2, 4, 3, 1, 0], + ), +} diff --git a/contracts/basic_staking/tests/unstake_softlock_bug.rs b/contracts/basic_staking/tests/unstake_softlock_bug.rs new file mode 100644 index 0000000..b9c5e98 --- /dev/null +++ b/contracts/basic_staking/tests/unstake_softlock_bug.rs @@ -0,0 +1,340 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +// Add other adapters here as they come +fn unstake_softlock_bug(stake_amount: Uint128, unbond_period: Uint128, reward_amount: Uint128) { + let mut app = App::default(); + + let mut now = 0; + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(now), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + let staking_user = Addr::unchecked("staker"); + let reward_user = Addr::unchecked("reward_user"); + + let extra_amount = Uint128::new(100000000000); + + let token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![ + snip20::InitialBalance { + amount: stake_amount + extra_amount, + address: staking_user.to_string(), + }, + snip20::InitialBalance { + amount: reward_amount, + address: reward_user.to_string(), + }, + ]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + // set staking_user viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + // set staking user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // set reward user VK + query_auth::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) + .unwrap(); + + // Init Staking + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.into(), + query_auth: query_contract.into(), + airdrop: None, + stake_token: token.clone().into(), + unbond_period, + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + println!("BASIC STAKING {}", basic_staking.address); + + now = 10; + app.set_block(BlockInfo { + height: 10, + time: Timestamp::from_seconds(now), + chain_id: "chain_id".to_string(), + }); + + let reward_end = now + 100000000; + + // Init Rewards + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: reward_amount, + msg: Some( + to_binary(&basic_staking::Action::Rewards { + start: Uint128::new(now as u128), + end: Uint128::new(reward_end as u128), + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, reward_user.clone(), &[]) + .unwrap(); + + // Check reward pool + match (basic_staking::QueryMsg::RewardPools {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::RewardPools { rewards } => { + assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Stake funds + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Generate some rewards + now += reward_end / 4; + app.set_block(BlockInfo { + height: 10, + time: Timestamp::from_seconds(now), + chain_id: "chain_id".to_string(), + }); + + // Post-staking user balance + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, stake_amount, "Post-Stake Balance"); + /* + panic!( + "REWARDS {}", + rewards.iter().map(|r| r.amount).sum::() + ); + */ + } + _ => { + panic!("Staking balance query failed"); + } + }; + + // Unbond all funds + basic_staking::ExecuteMsg::Unbond { + amount: stake_amount, + compound: None, + padding: None, + } + .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) + .unwrap(); + + // Check balance reflects unbonding + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, Uint128::zero(), "Post-Unbond stake amount p"); + assert_eq!(unbondings.len(), 1, "Post unbond unbondings"); + assert_eq!( + unbondings[0].amount, stake_amount, + "Post unbond amount unbonding" + ); + } + _ => { + panic!("Staking balance query failed"); + } + }; + + println!("Fast-forward to end of unbonding period"); + + now += unbond_period.u128() as u64; + app.set_block(BlockInfo { + height: 10, + time: Timestamp::from_seconds(now), + chain_id: "chain_id".to_string(), + }); + + // Restake + snip20::ExecuteMsg::Send { + recipient: basic_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: stake_amount, + msg: Some( + to_binary(&basic_staking::Action::Stake { + compound: None, + airdrop_task: None, + }) + .unwrap(), + ), + memo: None, + padding: None, + } + .test_exec(&token, &mut app, staking_user.clone(), &[]) + .unwrap(); + + println!("Checking balance after restake..."); + match (basic_staking::QueryMsg::Balance { + auth: basic_staking::Auth::ViewingKey { + key: viewing_key.clone(), + address: staking_user.clone().into(), + }, + unbonding_ids: None, + }) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Balance { + staked, + rewards, + unbondings, + } => { + assert_eq!(staked, stake_amount, "Re-Staked Balance"); + // assert_eq!(rewards, vec![], "Re-staked Rewards"); + // assert_eq!(unbondings, vec![], "Final Unbondings"); + } + _ => { + panic!("Staking balance query failed"); + } + }; +} + +macro_rules! unstake_softlock_bug { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + stake_amount, + unbond_period, + reward_amount, + ) = $value; + unstake_softlock_bug( + stake_amount, + unbond_period, + reward_amount, + ) + } + )* + } +} + +unstake_softlock_bug! { + unstake_softlock_bug_0: ( + Uint128::new(100), // stake_amount + Uint128::new(100), // unbond_period + Uint128::new(100000000000), // reward_amount + ), +} diff --git a/contracts/basic_staking/tests/update_config.rs b/contracts/basic_staking/tests/update_config.rs new file mode 100644 index 0000000..ffb5e8d --- /dev/null +++ b/contracts/basic_staking/tests/update_config.rs @@ -0,0 +1,127 @@ +use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; + +use shade_protocol::{ + contract_interfaces::{basic_staking, query_auth, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + basic_staking::BasicStaking, + query_auth::QueryAuth, + snip20::Snip20, +}; + +#[test] +fn update_config() { + let mut app = App::default(); + + // init block time for predictable behavior + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(0), + chain_id: "chain_id".to_string(), + }); + + let viewing_key = "unguessable".to_string(); + let admin_user = Addr::unchecked("admin"); + + let token = snip20::InstantiateMsg { + name: "stake_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "STKN".into(), + decimals: 6, + initial_balances: Some(vec![]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let query_contract = query_auth::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + prng_seed: to_binary("").ok().unwrap(), + } + .test_init( + QueryAuth::default(), + &mut app, + admin_user.clone(), + "query_auth", + &[], + ) + .unwrap(); + + let basic_staking = basic_staking::InstantiateMsg { + admin_auth: admin_contract.clone().into(), + query_auth: query_contract.clone().into(), + airdrop: None, + stake_token: token.clone().into(), + unbond_period: Uint128::zero(), + max_user_pools: Uint128::one(), + viewing_key: viewing_key.clone(), + } + .test_init( + BasicStaking::default(), + &mut app, + admin_user.clone(), + "basic_staking", + &[], + ) + .unwrap(); + + let mut config_match = match (basic_staking::QueryMsg::Config {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Config { config } => config, + _ => panic!("Config query failed"), + }; + + config_match.admin_auth = query_contract.clone().into(); + config_match.query_auth = admin_contract.clone().into(); + config_match.airdrop = Some(admin_contract.clone().into()); + config_match.unbond_period = Uint128::new(100); + config_match.max_user_pools = Uint128::new(10); + + // update config fields + basic_staking::ExecuteMsg::UpdateConfig { + admin_auth: Some(config_match.admin_auth.clone().into()), + query_auth: Some(config_match.query_auth.clone().into()), + airdrop: Some(RawContract { + address: admin_contract.address.to_string(), + code_hash: admin_contract.code_hash, + }), + unbond_period: Some(config_match.unbond_period.clone()), + max_user_pools: Some(config_match.max_user_pools.clone()), + padding: None, + } + .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) + .unwrap(); + + match (basic_staking::QueryMsg::Config {}) + .test_query(&basic_staking, &app) + .unwrap() + { + basic_staking::QueryAnswer::Config { config } => { + assert_eq!(config_match.clone(), config); + } + _ => panic!("Config query failed"), + } +} diff --git a/contracts/dao/scrt_staking/.cargo/config b/contracts/dao/scrt_staking/.cargo/config new file mode 100644 index 0000000..882fe08 --- /dev/null +++ b/contracts/dao/scrt_staking/.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/dao/scrt_staking/.circleci/config.yml b/contracts/dao/scrt_staking/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/contracts/dao/scrt_staking/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/dao/scrt_staking/Cargo.toml b/contracts/dao/scrt_staking/Cargo.toml new file mode 100644 index 0000000..0e644e9 --- /dev/null +++ b/contracts/dao/scrt_staking/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "scrt_staking" +version = "0.1.0" +authors = ["Jack Swenson "] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ + "adapter", + "dao", + "scrt_staking", + "treasury", + "math", + "storage_plus", +] } +cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" } + +[dev-dependencies] +shade-multi-test = { path = "../../../packages/multi_test", features = [ + "scrt_staking", + "snip20", + "admin" +] } diff --git a/contracts/dao/scrt_staking/Makefile b/contracts/dao/scrt_staking/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/contracts/dao/scrt_staking/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/dao/scrt_staking/README.md b/contracts/dao/scrt_staking/README.md new file mode 100644 index 0000000..eb0af80 --- /dev/null +++ b/contracts/dao/scrt_staking/README.md @@ -0,0 +1,65 @@ +# sSCRT Staking Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Init](#Init) + * [DAO Adapter](/packages/shade_protocol/src/DAO_ADAPTER.md) + * [Interface](#Interface) + * Messages + * [Receive](#Receive) + * [UpdateConfig](#UpdateConfig) + * Queries + * [Config](#Config) + * [Delegations](#Delegations) + +# Introduction +The sSCRT Staking contract receives sSCRT, redeems it for SCRT, then stakes it with a validator that falls within the criteria it has been configured with. The configured `treasury` will receive all funds from claiming rewards/unbonding. + +# Sections + +## Init +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|admin | Addr | contract owner/admin; a valid bech32 address; +|treasury | Addr | contract designated to receive all outgoing funds +|sscrt | Contract | sSCRT Snip-20 contract to accept for redemption/staking, all other funds will error +|validator_bounds | ValidatorBounds | criteria defining an acceptable validator to stake with +|viewing_key | String | Viewing Key to be set for any relevant SNIP-20 + +## Interface + +### Messages +#### UpdateConfig +Updates the given values +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|owner | Addr | contract owner/admin; a valid bech32 address; +|treasury | Addr | contract designated to receive all outgoing funds +|sscrt | Contract | sSCRT Snip-20 contract to accept for redemption/staking, all other funds will error +|validator_bounds | ValidatorBounds | criteria defining an acceptable validator to stake with + +##### Response +```json +{ + "update_config": { + "status": "success" + } +} +``` + + +### Queries + +#### Config +Gets the contract's configuration variables +##### Response +```json +{ + "config": { + "config": { + "owner": "Owner address", + } + } +} +``` diff --git a/contracts/dao/scrt_staking/src/contract.rs b/contracts/dao/scrt_staking/src/contract.rs new file mode 100644 index 0000000..31af03e --- /dev/null +++ b/contracts/dao/scrt_staking/src/contract.rs @@ -0,0 +1,116 @@ +use shade_protocol::{ + c_std::{ + entry_point, + to_binary, + Binary, + Deps, + DepsMut, + Env, + MessageInfo, + Response, + StdResult, + Uint128, + }, + dao::{ + adapter, + scrt_staking::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}, + }, + snip20::helpers::{register_receive, set_viewing_key_msg}, +}; + +use crate::{ + execute, + query, + storage::{CONFIG, SELF_ADDRESS, UNBONDING, VIEWING_KEY}, +}; + +#[entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let config = Config { + admin_auth: msg.admin_auth.into_valid(deps.api)?, + sscrt: msg.sscrt.into_valid(deps.api)?, + owner: deps.api.addr_validate(msg.owner.as_str())?, + validator_bounds: msg.validator_bounds, + }; + + CONFIG.save(deps.storage, &config)?; + + SELF_ADDRESS.save(deps.storage, &env.contract.address)?; + VIEWING_KEY.save(deps.storage, &msg.viewing_key)?; + UNBONDING.save(deps.storage, &Uint128::zero())?; + + let resp = Response::new().add_messages(vec![ + set_viewing_key_msg(msg.viewing_key, None, &config.sscrt)?, + register_receive(env.contract.code_hash, None, &config.sscrt)?, + ]); + + Ok(resp) +} + +#[entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg { + ExecuteMsg::Receive { + sender, + from, + amount, + msg, + .. + } => { + let sender = deps.api.addr_validate(&sender)?; + let from = deps.api.addr_validate(&from)?; + execute::receive(deps, env, info, sender, from, amount, msg) + } + ExecuteMsg::UpdateConfig { config } => execute::try_update_config(deps, env, info, config), + ExecuteMsg::Adapter(adapter) => match adapter { + adapter::SubExecuteMsg::Unbond { asset, amount } => { + let asset = deps.api.addr_validate(&asset)?; + execute::unbond(deps, env, info, asset, amount) + } + adapter::SubExecuteMsg::Claim { asset } => { + let asset = deps.api.addr_validate(&asset)?; + execute::claim(deps, env, info, asset) + } + adapter::SubExecuteMsg::Update { asset } => { + let asset = deps.api.addr_validate(&asset)?; + execute::update(deps, env, info, asset) + } + }, + } +} + +#[entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => to_binary(&query::config(deps)?), + QueryMsg::Delegations {} => to_binary(&query::delegations(deps)?), + QueryMsg::Rewards {} => to_binary(&query::rewards(deps)?), + QueryMsg::Adapter(adapter) => match adapter { + adapter::SubQueryMsg::Balance { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::balance(deps, asset)?) + } + adapter::SubQueryMsg::Claimable { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::claimable(deps, asset)?) + } + adapter::SubQueryMsg::Unbonding { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::unbonding(deps, asset)?) + } + adapter::SubQueryMsg::Unbondable { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::unbondable(deps, asset)?) + } + adapter::SubQueryMsg::Reserves { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::reserves(deps, asset)?) + } + }, + } +} diff --git a/contracts/dao/scrt_staking/src/execute.rs b/contracts/dao/scrt_staking/src/execute.rs new file mode 100644 index 0000000..1ee3fbc --- /dev/null +++ b/contracts/dao/scrt_staking/src/execute.rs @@ -0,0 +1,424 @@ +use shade_protocol::{ + admin::helpers::{validate_admin, AdminPermissions}, + c_std::{ + to_binary, + Addr, + Binary, + Coin, + CosmosMsg, + Deps, + DepsMut, + DistributionMsg, + Env, + MessageInfo, + Response, + StakingMsg, + StdError, + StdResult, + Uint128, + Validator, + }, +}; + +use shade_protocol::snip20::helpers::redeem_msg; + +use shade_protocol::{ + dao::{ + adapter, + scrt_staking::{Config, ExecuteAnswer, ValidatorBounds}, + }, + utils::{ + asset::{scrt_balance, Contract}, + generic_response::ResponseStatus, + wrap::{unwrap, wrap_and_send}, + }, +}; + +use crate::{ + query, + storage::{CONFIG, SELF_ADDRESS, UNBONDING}, +}; + +pub fn receive( + deps: DepsMut, + env: Env, + info: MessageInfo, + _sender: Addr, + _from: Addr, + amount: Uint128, + _msg: Option, +) -> StdResult { + //panic!("scrt staking Received {}", amount); + + let config = CONFIG.load(deps.storage)?; + + if info.sender != config.sscrt.address { + return Err(StdError::generic_err("Only accepts sSCRT")); + } + + let validator = choose_validator(deps, env.block.time.seconds())?; + + Ok(Response::new() + .add_messages(vec![ + redeem_msg(amount, None, None, &config.sscrt)?, + CosmosMsg::Staking(StakingMsg::Delegate { + validator: validator.address.clone(), + amount: Coin { + amount, + denom: "uscrt".to_string(), + }, + }), + ]) + .set_data(to_binary(&ExecuteAnswer::Receive { + status: ResponseStatus::Success, + validator, + })?)) +} + +pub fn try_update_config( + deps: DepsMut, + _env: Env, + info: MessageInfo, + config: Config, +) -> StdResult { + let cur_config = CONFIG.load(deps.storage)?; + + validate_admin( + &deps.querier, + AdminPermissions::ScrtStakingAdmin, + &info.sender, + &cur_config.admin_auth, + )?; + + // Save new info + CONFIG.save(deps.storage, &config)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { + status: ResponseStatus::Success, + })?), + ) +} + +/* Claim rewards and restake, hold enough for pending unbondings + * Send reserves unbonded funds to treasury + */ +pub fn update(deps: DepsMut, env: Env, _info: MessageInfo, asset: Addr) -> StdResult { + let mut messages = vec![]; + //let asset = deps.api.addr_validate(asset.as_str())?; + + let config = CONFIG.load(deps.storage)?; + + if asset != config.sscrt.address { + return Err(StdError::generic_err("Unrecognized Asset")); + } + + let scrt_balance = scrt_balance(deps.as_ref(), SELF_ADDRESS.load(deps.storage)?)?; + + // Claim Rewards + let rewards = query::rewards(deps.as_ref())?; + if !rewards.is_zero() { + messages.append(&mut withdraw_rewards(deps.as_ref())?); + } + + let mut stake_amount = rewards + scrt_balance; + let unbonding = UNBONDING.load(deps.storage)?; + + // Don't restake funds that unbonded + if unbonding < stake_amount { + stake_amount = stake_amount - unbonding; + } else { + stake_amount = Uint128::zero(); + } + + if stake_amount > Uint128::zero() { + let validator = choose_validator(deps, env.block.time.seconds())?; + messages.push(CosmosMsg::Staking(StakingMsg::Delegate { + validator: validator.address.clone(), + amount: Coin { + amount: stake_amount, + denom: "uscrt".to_string(), + }, + })); + } + + Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Update { + status: ResponseStatus::Success, + }, + )?)) +} + +pub fn unbond( + deps: DepsMut, + _env: Env, + info: MessageInfo, + asset: Addr, + amount: Uint128, +) -> StdResult { + /* Unbonding to the scrt staking contract + * Once scrt is on balance sheet, treasury can claim + * and this contract will take all scrt->sscrt and send + */ + + //let asset = deps.api.addr_validate(asset.as_str())?; + let config = CONFIG.load(deps.storage)?; + + if validate_admin( + &deps.querier, + AdminPermissions::ScrtStakingAdmin, + &info.sender, + &config.admin_auth, + ) + .is_err() + && config.owner != info.sender + { + return Err(StdError::generic_err("Unauthorized")); + } + + if asset != config.sscrt.address { + return Err(StdError::generic_err("Unrecognized Asset")); + } + + let self_address = SELF_ADDRESS.load(deps.storage)?; + let delegations = query::delegations(deps.as_ref())?; + + let delegated = Uint128::new( + delegations + .iter() + .map(|d| d.amount.amount.u128()) + .sum::(), + ); + let scrt_balance = scrt_balance(deps.as_ref(), self_address)?; + let rewards = query::rewards(deps.as_ref())?; + + let mut messages = vec![]; + + if !rewards.is_zero() { + messages.append(&mut withdraw_rewards(deps.as_ref())?); + } + + let mut undelegated = vec![]; + + let prev_unbonding = UNBONDING.load(deps.storage)?; + + let mut total = delegated + scrt_balance + rewards + delegated; + total -= prev_unbonding; + + if total - prev_unbonding < amount { + return Err(StdError::generic_err(format!( + "Total Unbond amount {} greater than delegated {}; rew {}, bal {}", + amount, delegated, rewards, scrt_balance + ))); + } + + let mut unbonding = amount; + let mut total_unbonding = amount + prev_unbonding; + let reserves = scrt_balance + rewards; + + if total_unbonding.is_zero() { + return Ok( + Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Unbond { + status: ResponseStatus::Success, + amount: unbonding, + })?), + ); + } + + // Send full unbonding + if total_unbonding <= reserves { + messages.append(&mut wrap_and_send( + unbonding, + config.owner, + config.sscrt, + None, + )?); + total_unbonding = Uint128::zero(); + } + // Send all reserves + else if !reserves.is_zero() { + messages.append(&mut wrap_and_send( + reserves, + config.owner, + config.sscrt, + None, + )?); + total_unbonding -= reserves; + } + + UNBONDING.save(deps.storage, &total_unbonding)?; + + while !total_unbonding.is_zero() { + // Unbond from largest validator first + let max_delegation = delegations.iter().max_by_key(|d| { + if undelegated.contains(&d.validator) { + Uint128::zero() + } else { + d.amount.amount + } + }); + + // No more delegated funds to unbond + match max_delegation { + None => { + break; + } + Some(delegation) => { + if undelegated.contains(&delegation.validator) + || delegation.amount.amount.clone() == Uint128::zero() + { + break; + } + + // This delegation isn't enough to fully unbond + if delegation.amount.amount.clone() < unbonding + && !delegation.amount.amount.clone().is_zero() + { + messages.push(CosmosMsg::Staking(StakingMsg::Undelegate { + validator: delegation.validator.clone(), + amount: delegation.amount.clone(), + })); + unbonding = unbonding - delegation.amount.amount.clone(); + undelegated.push(delegation.validator.clone()); + } else if !delegation.amount.amount.clone().is_zero() { + messages.push(CosmosMsg::Staking(StakingMsg::Undelegate { + validator: delegation.validator.clone(), + amount: Coin { + denom: delegation.amount.denom.clone(), + amount: unbonding, + }, + })); + unbonding = Uint128::zero(); + undelegated.push(delegation.validator.clone()); + } + } + } + } + + Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Unbond { + status: ResponseStatus::Success, + amount: unbonding, + }, + )?)) +} + +pub fn withdraw_rewards(deps: Deps) -> StdResult> { + let mut messages = vec![]; + let address = SELF_ADDRESS.load(deps.storage)?; + + for delegation in deps.querier.query_all_delegations(address.clone())? { + messages.push(CosmosMsg::Distribution( + DistributionMsg::WithdrawDelegatorReward { + validator: delegation.validator, + }, + )); + } + + Ok(messages) +} + +pub fn unwrap_and_stake( + _deps: DepsMut, + amount: Uint128, + validator: Validator, + token: Contract, +) -> StdResult> { + Ok(vec![ + // unwrap + unwrap(amount, token.clone())?, + // Stake + CosmosMsg::Staking(StakingMsg::Delegate { + validator: validator.address.clone(), + amount: Coin { + amount, + denom: "uscrt".to_string(), + }, + }), + ]) +} + +/* Claims completed unbondings, wraps them, + * and returns them to treasury + */ +pub fn claim(deps: DepsMut, _env: Env, _info: MessageInfo, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + //let asset = deps.api.addr_validate(asset.as_str())?; + if asset != config.sscrt.address { + return Err(StdError::generic_err("Unrecognized Asset")); + } + + let mut messages = vec![]; + + let unbond_amount = UNBONDING.load(deps.storage)?; + let claim_amount; + + let scrt_balance = scrt_balance(deps.as_ref(), SELF_ADDRESS.load(deps.storage)?)?; + + if scrt_balance >= unbond_amount { + claim_amount = unbond_amount; + } else { + // Claim Rewards + let rewards = query::rewards(deps.as_ref())?; + + if !rewards.is_zero() { + //assert!(false, "withdraw rewards"); + messages.append(&mut withdraw_rewards(deps.as_ref())?); + } + + if rewards + scrt_balance >= unbond_amount { + claim_amount = unbond_amount; + } else { + claim_amount = rewards + scrt_balance; + } + } + + if !claim_amount.is_zero() { + messages.append(&mut wrap_and_send( + claim_amount, + config.owner, + config.sscrt, + None, + )?); + + //assert!(false, "u - claim_amount: {} - {}", unbond_amount, claim_amount); + let u = UNBONDING.load(deps.storage)?; + UNBONDING.save(deps.storage, &(u - claim_amount))?; + } + + Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Claim { + status: ResponseStatus::Success, + amount: claim_amount, + }, + )?)) +} + +pub fn choose_validator(deps: DepsMut, seed: u64) -> StdResult { + let mut validators = deps.querier.query_all_validators()?; + + // filter down to viable candidates + if let Some(bounds) = (CONFIG.load(deps.storage)?).validator_bounds { + let mut candidates = vec![]; + + for validator in validators { + if is_validator_inbounds(&validator, &bounds) { + candidates.push(validator); + } + } + + validators = candidates; + } + + if validators.is_empty() { + return Err(StdError::generic_err("No validators within bounds")); + } + + // seed will likely be env.block.time.seconds() + Ok(validators[(seed % validators.len() as u64) as usize].clone()) +} + +pub fn is_validator_inbounds(validator: &Validator, bounds: &ValidatorBounds) -> bool { + validator.commission <= bounds.max_commission && validator.commission >= bounds.min_commission +} diff --git a/contracts/dao/scrt_staking/src/lib.rs b/contracts/dao/scrt_staking/src/lib.rs new file mode 100644 index 0000000..cce0227 --- /dev/null +++ b/contracts/dao/scrt_staking/src/lib.rs @@ -0,0 +1,7 @@ +pub mod contract; +pub mod execute; +pub mod query; +pub mod storage; + +#[cfg(test)] +mod test; diff --git a/contracts/dao/scrt_staking/src/query.rs b/contracts/dao/scrt_staking/src/query.rs new file mode 100644 index 0000000..b9da002 --- /dev/null +++ b/contracts/dao/scrt_staking/src/query.rs @@ -0,0 +1,182 @@ +use shade_protocol::{ + c_std::{Addr, Delegation, Deps, StdError, StdResult, Uint128}, + dao::{adapter, scrt_staking::QueryAnswer}, + utils::asset::scrt_balance, +}; + +use crate::storage::*; + +pub fn config(deps: Deps) -> StdResult { + Ok(QueryAnswer::Config { + config: CONFIG.load(deps.storage)?, + }) +} + +pub fn delegations(deps: Deps) -> StdResult> { + deps.querier + .query_all_delegations(SELF_ADDRESS.load(deps.storage)?) +} + +pub fn rewards(deps: Deps) -> StdResult { + let self_address = SELF_ADDRESS.load(deps.storage)?; + + let mut rewards = Uint128::zero(); + + // TODO change to stargate query + for d in deps.querier.query_all_delegations(self_address.clone())? { + if let Some(delegation) = deps + .querier + .query_delegation(self_address.clone(), d.validator.clone())? + { + for coin in delegation.accumulated_rewards { + if coin.denom != "uscrt" { + // TODO send to treasury + return Err(StdError::generic_err("Non-scrt coin in rewards!")); + } + rewards += coin.amount; + } + } else { + return Err(StdError::generic_err(format!( + "No delegation to {} but it was in storage", + d.validator + ))); + } + } + + Ok(rewards) +} + +pub fn balance(deps: Deps, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if asset != config.sscrt.address { + return Err(StdError::generic_err(format!( + "Unrecognized Asset {}", + asset + ))); + } + + let delegated = Uint128::new( + delegations(deps)? + .into_iter() + .map(|d| d.amount.amount.u128()) + .sum::(), + ); + + let scrt_balance = scrt_balance(deps, SELF_ADDRESS.load(deps.storage)?)?; + + let rewards = rewards(deps)?; + + Ok(adapter::QueryAnswer::Balance { + amount: delegated + rewards + scrt_balance, + }) +} + +pub fn claimable(deps: Deps, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if asset != config.sscrt.address { + return Err(StdError::generic_err(format!( + "Unrecognized Asset {}", + asset + ))); + } + + let scrt_balance = scrt_balance(deps, SELF_ADDRESS.load(deps.storage)?)?; + let rewards = rewards(deps)?; + //assert!(false, "balance {}", scrt_balance); + let unbonding = UNBONDING.load(deps.storage)?; + //assert!(false, "unbonding {}", unbonding); + + let mut amount = scrt_balance + rewards; + if amount > unbonding { + amount = unbonding; + } + + Ok(adapter::QueryAnswer::Claimable { amount }) +} + +pub fn unbonding(deps: Deps, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if asset != config.sscrt.address { + return Err(StdError::generic_err(format!( + "Unrecognized Asset {}", + asset + ))); + } + + let scrt_balance = scrt_balance(deps, SELF_ADDRESS.load(deps.storage)?)?; + + let rewards = rewards(deps)?; + let mut unbonding = UNBONDING.load(deps.storage)?; + + if unbonding >= (scrt_balance + rewards) { + unbonding -= scrt_balance + rewards; + } else { + unbonding = Uint128::zero(); + } + Ok(adapter::QueryAnswer::Unbonding { amount: unbonding }) +} + +pub fn unbondable(deps: Deps, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if asset != config.sscrt.address { + return Err(StdError::generic_err(format!( + "Unrecognized Asset {}", + asset + ))); + } + + /* TODO: issues since we cant query unbondings + * While assets are unbonding they don't reflect anywhere in balance + * Once the unbonding funds are here they will show, making it difficult to present + * unbondable funds that arent being currently unbonded + */ + let delegated = Uint128::new( + delegations(deps)? + .into_iter() + .map(|d| d.amount.amount.u128()) + .sum::(), + ); + + let scrt_balance = scrt_balance(deps, SELF_ADDRESS.load(deps.storage)?)?; + let rewards = rewards(deps)?; + + let unbonding = UNBONDING.load(deps.storage)?; + + let mut unbondable = delegated; + + if unbonding < scrt_balance + rewards { + unbondable += scrt_balance + rewards - unbonding; + } + + /*TODO: Query current unbondings + * u >= 7 = 0 + * u < 7 = unbondable + */ + Ok(adapter::QueryAnswer::Unbondable { amount: unbondable }) +} + +pub fn reserves(deps: Deps, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if asset != config.sscrt.address { + return Err(StdError::generic_err(format!( + "Unrecognized Asset {}", + asset + ))); + } + + let scrt_balance = scrt_balance(deps, SELF_ADDRESS.load(deps.storage)?)?; + + let rewards = rewards(deps)?; + + if !scrt_balance.is_zero() { + assert!(false, "scrt bal {}", scrt_balance); + } + Ok(adapter::QueryAnswer::Reserves { + amount: scrt_balance + rewards, + }) +} diff --git a/contracts/dao/scrt_staking/src/storage.rs b/contracts/dao/scrt_staking/src/storage.rs new file mode 100644 index 0000000..2726ffd --- /dev/null +++ b/contracts/dao/scrt_staking/src/storage.rs @@ -0,0 +1,9 @@ +use shade_protocol::c_std::{Addr, Uint128}; +use shade_protocol::dao::scrt_staking; + +use shade_protocol::secret_storage_plus::Item; + +pub const CONFIG: Item = Item::new("config"); +pub const SELF_ADDRESS: Item = Item::new("self_address"); +pub const VIEWING_KEY: Item = Item::new("viewing_key"); +pub const UNBONDING: Item = Item::new("unbonding"); diff --git a/contracts/dao/scrt_staking/src/test.rs b/contracts/dao/scrt_staking/src/test.rs new file mode 100644 index 0000000..04225b9 --- /dev/null +++ b/contracts/dao/scrt_staking/src/test.rs @@ -0,0 +1,46 @@ +/* +#[cfg(test)] +pub mod tests { + use shade_protocol::c_std::{ + testing::{ + mock_dependencies, mock_env, MockStorage, MockApi, MockQuerier + }, + Addr, + coins, from_binary, StdError, Uint128, + DepsMut, + }; + use shade_protocol::{ + treasury::{ + QueryAnswer, InstantiateMsg, ExecuteMsg, + QueryMsg, + }, + asset::Contract, + }; + + use crate::{ + contract::{ + init, handle, query, + }, + }; + + fn create_contract(address: &str, code_hash: &str) -> Contract { + let env = mock_env(address.to_string(), &[]); + return Contract{ + address: info.sender, + code_hash: code_hash.to_string() + } + } + + fn dummy_init(admin: String, viewing_key: String) -> Extern { + let mut deps = mock_dependencies(20, &[]); + let msg = InstantiateMsg { + admin: Option::from(Addr(admin.clone())), + viewing_key, + }; + let env = mock_env(admin, &coins(1000, "earth")); + let _res = init(&mut deps, env, info, msg).unwrap(); + + return deps + } +} +*/ diff --git a/contracts/dao/scrt_staking/tests/integration.rs b/contracts/dao/scrt_staking/tests/integration.rs new file mode 100644 index 0000000..f9300f9 --- /dev/null +++ b/contracts/dao/scrt_staking/tests/integration.rs @@ -0,0 +1,420 @@ +use shade_multi_test::multi::admin::init_admin_auth; +use shade_protocol::c_std::{to_binary, Addr, Coin, Delegation, Uint128}; + +use shade_protocol::{ + contract_interfaces::{ + dao::{adapter, scrt_staking}, + snip20, + }, + utils::{ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{scrt_staking::ScrtStaking, snip20::Snip20}; +use shade_protocol::multi_test::{App, StakingSudo, SudoMsg}; + +// Add other adapters here as they come +fn basic_scrt_staking_integration( + deposit: Uint128, + rewards: Uint128, + expected_scrt_staking: Uint128, +) { + let mut app = App::default(); + + let viewing_key = "unguessable".to_string(); + let admin = Addr::unchecked("admin"); + let validator = Addr::unchecked("validator"); + let admin_auth = init_admin_auth(&mut app, &admin); + let token = snip20::InstantiateMsg { + name: "secretSCRT".into(), + admin: Some("admin".into()), + symbol: "SSCRT".into(), + decimals: 6, + initial_balances: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + query_auth: None, + } + .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) + .unwrap(); + + let scrt_staking = scrt_staking::InstantiateMsg { + admin_auth: admin_auth.into(), + owner: admin.clone().into(), + sscrt: token.clone().into(), + validator_bounds: None, + viewing_key: viewing_key.clone(), + } + .test_init( + ScrtStaking::default(), + &mut app, + admin.clone(), + "scrt_staking", + &[], + ) + .unwrap(); + println!("SCRT STAKING ADDR {}", scrt_staking.address); + + app.sudo(SudoMsg::Staking(StakingSudo::AddValidator { + validator: validator.to_string().clone(), + })) + .unwrap(); + + // set admin owner key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + if !deposit.is_zero() { + let deposit_coin = Coin { + denom: "uscrt".into(), + amount: deposit, + }; + app.init_modules(|router, _, storage| { + router + .bank + .init_balance(storage, &admin.clone(), vec![deposit_coin.clone()]) + .unwrap(); + }); + + // Wrap L1 into tokens + snip20::ExecuteMsg::Deposit { padding: None } + .test_exec(&token, &mut app, admin.clone(), &vec![deposit_coin]) + .unwrap(); + + // Deposit funds in scrt staking + snip20::ExecuteMsg::Send { + recipient: scrt_staking.address.to_string().clone(), + recipient_code_hash: None, + amount: deposit, + msg: None, + memo: None, + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Delegations + let delegations: Vec = scrt_staking::QueryMsg::Delegations {} + .test_query(&scrt_staking, &app) + .unwrap(); + assert!( + !delegations.is_empty(), + "empty delegations! {}", + delegations.len() + ); + } + + // reserves should be 0 (all staked) + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap() + { + adapter::QueryAnswer::Reserves { amount } => { + assert_eq!(amount, Uint128::zero(), "Reserves Pre-Rewards"); + } + _ => panic!("Query failed"), + }; + + // Balance + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap() + { + adapter::QueryAnswer::Balance { amount } => { + assert_eq!(amount, deposit, "Balance Pre-Rewards"); + } + _ => panic!("Query failed"), + }; + + // Rewards + let cur_rewards: Uint128 = scrt_staking::QueryMsg::Rewards {} + .test_query(&scrt_staking, &app) + .unwrap(); + assert_eq!(cur_rewards, Uint128::zero(), "Rewards Pre-add"); + + //ensemble.add_rewards(rewards); + app.sudo(SudoMsg::Staking(StakingSudo::AddRewards { + amount: Coin { + amount: rewards, + denom: "uscrt".into(), + }, + })) + .unwrap(); + /* + let block = app.block_info(); + app.init_modules(|router, api, storage| { + router.staking.add_rewards( + api, + storage, + router, + &block, + Coin { amount: rewards, denom: "uscrt".into() }, + ).unwrap(); + }); + */ + + // Rewards + let cur_rewards: Uint128 = scrt_staking::QueryMsg::Rewards {} + .test_query(&scrt_staking, &app) + .unwrap(); + + if deposit.is_zero() { + assert_eq!(cur_rewards, Uint128::zero(), "Rewards Post-add"); + } else { + assert_eq!(cur_rewards, rewards, "Rewards Post-add"); + } + + // reserves should be rewards + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap() + { + adapter::QueryAnswer::Reserves { amount } => { + if deposit.is_zero() { + assert_eq!(amount, Uint128::zero(), "Reserves Post-Rewards"); + } else { + assert_eq!(amount, rewards, "Reserves Post-Rewards"); + } + } + _ => panic!("Query failed"), + }; + + // Balance + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap() + { + adapter::QueryAnswer::Balance { amount } => { + if deposit.is_zero() { + assert_eq!(amount, Uint128::zero(), "Balance Post-Rewards"); + } else { + assert_eq!(amount, deposit + rewards, "Balance Post-Rewards"); + } + } + _ => panic!("Query failed"), + }; + + // Update SCRT Staking + adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { + asset: token.address.to_string().clone().to_string(), + }) + .test_exec(&scrt_staking, &mut app, admin.clone(), &[]) + .unwrap(); + + // reserves/rewards should be staked + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap() + { + adapter::QueryAnswer::Reserves { amount } => { + assert_eq!(amount, Uint128::zero(), "Reserves Post-Update"); + } + _ => panic!("Query failed"), + }; + + // Balance + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap() + { + adapter::QueryAnswer::Balance { amount } => { + assert_eq!(amount, expected_scrt_staking, "Balance Post-Update"); + } + _ => panic!("Query failed"), + }; + + // Claimable + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap() + { + adapter::QueryAnswer::Claimable { amount } => { + assert_eq!(amount, Uint128::zero(), "Claimable Pre-Unbond"); + } + _ => panic!("Query failed"), + }; + + // Unbondable + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbondable { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap() + { + adapter::QueryAnswer::Unbondable { amount } => { + assert_eq!(amount, expected_scrt_staking, "Unbondable Pre-Unbond"); + } + _ => panic!("Query failed"), + }; + + println!("SCRT STAKING ADDR {}", scrt_staking.address); + // Unbond all + adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Unbond { + amount: expected_scrt_staking, + asset: token.address.to_string().clone().to_string(), + }) + .test_exec(&scrt_staking, &mut app, admin.clone(), &[]) + .unwrap(); + println!("SCRT STAKING ADDR {}", scrt_staking.address); + + // Unbonding + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbonding { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap() + { + adapter::QueryAnswer::Unbonding { amount } => { + assert_eq!(amount, expected_scrt_staking, "Unbonding Pre fast forward"); + } + _ => panic!("Query failed"), + }; + + // Claimable + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap() + { + adapter::QueryAnswer::Claimable { amount } => { + assert_eq!(amount, Uint128::zero(), "Claimable Pre unbond fast forward"); + } + _ => panic!("Query failed"), + }; + + app.sudo(SudoMsg::Staking(StakingSudo::FastForwardUndelegate {})) + .unwrap(); + + // Claimable + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap() + { + adapter::QueryAnswer::Claimable { amount } => { + if deposit.is_zero() { + assert_eq!(amount, Uint128::zero(), "Claimable post fast forward"); + } else { + assert_eq!(amount, deposit + rewards, "Claimable post fast forward"); + } + } + _ => panic!("Query failed"), + }; + + // Claim + adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Claim { + asset: token.address.to_string().clone().to_string(), + }) + .test_exec(&scrt_staking, &mut app, admin.clone(), &[]) + .unwrap(); + + // Reserves + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap() + { + adapter::QueryAnswer::Reserves { amount } => { + assert_eq!(amount, Uint128::zero(), "Reserves Post Claim"); + } + _ => panic!("Query failed"), + }; + + // Balance + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap() + { + adapter::QueryAnswer::Balance { amount } => { + assert_eq!(amount, Uint128::zero(), "Balance Post Claim"); + } + _ => panic!("Query failed"), + }; + + // ensure wrapped tokens were returned + match (snip20::QueryMsg::Balance { + address: admin.to_string().clone(), + key: viewing_key.clone(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + if deposit.is_zero() { + assert_eq!(amount.u128(), 0u128, "Final User balance"); + } else { + assert_eq!( + amount.u128(), + deposit.u128() + rewards.u128(), + "Final user balance" + ); + } + } + _ => { + panic!("snip20 balance query failed"); + } + }; +} + +macro_rules! basic_scrt_staking_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + deposit, + rewards, + expected_scrt_staking, + ) = $value; + basic_scrt_staking_integration(deposit, rewards, expected_scrt_staking); + } + )* + } +} + +basic_scrt_staking_tests! { + basic_scrt_staking_0: ( + Uint128::new(100), // deposit + Uint128::new(0), // rewards + Uint128::new(100), // balance + ), + basic_scrt_staking_1: ( + Uint128::new(100), // deposit + Uint128::new(50), // rewards + Uint128::new(150), // balance + ), + basic_scrt_staking_no_deposit: ( + Uint128::new(0), // deposit + Uint128::new(1000), // rewards + Uint128::new(0), // balance + ), +} diff --git a/contracts/dao/scrt_staking/tests/unit.rs b/contracts/dao/scrt_staking/tests/unit.rs new file mode 100644 index 0000000..ea3e8b7 --- /dev/null +++ b/contracts/dao/scrt_staking/tests/unit.rs @@ -0,0 +1,36 @@ +/* +use cosmwasm_std::{ + coins, from_binary, to_binary, + Extern, Addr, StdError, + Binary, StdResult, HandleResponse, Env, + InitResponse, Uint128, +}; + +#[test] +fn test_function(param0, param1) { + assert_eq!(param0, param1); +} + +macro_rules! test_function_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (param0, param1) = $value; + test_function(param0, param1); + } + )* + } +} + +test_function_tests! { + test_function_0: ( + Uint128(1), + Uint128(1), + ), + test_function_1: ( + Uint128(1), + Uint128(2), + ), +} +*/ diff --git a/contracts/dao/stkd_scrt/.cargo/config b/contracts/dao/stkd_scrt/.cargo/config new file mode 100644 index 0000000..882fe08 --- /dev/null +++ b/contracts/dao/stkd_scrt/.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/dao/stkd_scrt/.circleci/config.yml b/contracts/dao/stkd_scrt/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/contracts/dao/stkd_scrt/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/dao/stkd_scrt/Cargo.toml b/contracts/dao/stkd_scrt/Cargo.toml new file mode 100644 index 0000000..061fa8b --- /dev/null +++ b/contracts/dao/stkd_scrt/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "stkd_scrt" +version = "0.1.0" +authors = ["Jack Swenson "] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ + "adapter", + "dao", + "stkd_scrt", + "treasury", + "math", + "storage_plus", +] } + +[dev-dependencies] +shade-multi-test = { path = "../../../packages/multi_test", features = [ + "scrt_staking", + "snip20", + "admin" +] } diff --git a/contracts/dao/stkd_scrt/Makefile b/contracts/dao/stkd_scrt/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/contracts/dao/stkd_scrt/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/dao/stkd_scrt/README.md b/contracts/dao/stkd_scrt/README.md new file mode 100644 index 0000000..eb0af80 --- /dev/null +++ b/contracts/dao/stkd_scrt/README.md @@ -0,0 +1,65 @@ +# sSCRT Staking Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Init](#Init) + * [DAO Adapter](/packages/shade_protocol/src/DAO_ADAPTER.md) + * [Interface](#Interface) + * Messages + * [Receive](#Receive) + * [UpdateConfig](#UpdateConfig) + * Queries + * [Config](#Config) + * [Delegations](#Delegations) + +# Introduction +The sSCRT Staking contract receives sSCRT, redeems it for SCRT, then stakes it with a validator that falls within the criteria it has been configured with. The configured `treasury` will receive all funds from claiming rewards/unbonding. + +# Sections + +## Init +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|admin | Addr | contract owner/admin; a valid bech32 address; +|treasury | Addr | contract designated to receive all outgoing funds +|sscrt | Contract | sSCRT Snip-20 contract to accept for redemption/staking, all other funds will error +|validator_bounds | ValidatorBounds | criteria defining an acceptable validator to stake with +|viewing_key | String | Viewing Key to be set for any relevant SNIP-20 + +## Interface + +### Messages +#### UpdateConfig +Updates the given values +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|owner | Addr | contract owner/admin; a valid bech32 address; +|treasury | Addr | contract designated to receive all outgoing funds +|sscrt | Contract | sSCRT Snip-20 contract to accept for redemption/staking, all other funds will error +|validator_bounds | ValidatorBounds | criteria defining an acceptable validator to stake with + +##### Response +```json +{ + "update_config": { + "status": "success" + } +} +``` + + +### Queries + +#### Config +Gets the contract's configuration variables +##### Response +```json +{ + "config": { + "config": { + "owner": "Owner address", + } + } +} +``` diff --git a/contracts/dao/stkd_scrt/src/contract.rs b/contracts/dao/stkd_scrt/src/contract.rs new file mode 100644 index 0000000..392d266 --- /dev/null +++ b/contracts/dao/stkd_scrt/src/contract.rs @@ -0,0 +1,108 @@ +use shade_protocol::{ + c_std::{ + shd_entry_point, + to_binary, + Binary, + Deps, + DepsMut, + Env, + MessageInfo, + Response, + StdResult, + }, + dao::{ + adapter, + stkd_scrt::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}, + }, + snip20::helpers::{register_receive, set_viewing_key_msg}, + utils::generic_response::ResponseStatus, +}; + +use crate::{execute, query, storage::*}; + +#[shd_entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let config = Config { + admin_auth: msg.admin_auth.into_valid(deps.api)?, + sscrt: msg.sscrt.into_valid(deps.api)?, + owner: deps.api.addr_validate(msg.owner.as_str())?, + staking_derivatives: msg.staking_derivatives.into_valid(deps.api)?, + }; + + CONFIG.save(deps.storage, &config)?; + + SELF_ADDRESS.save(deps.storage, &env.contract.address)?; + VIEWING_KEY.save(deps.storage, &msg.viewing_key)?; + + Ok(Response::new().add_messages(vec![ + set_viewing_key_msg(msg.viewing_key, None, &config.sscrt)?, + register_receive(env.contract.code_hash, None, &config.sscrt)?, + ])) +} + +#[shd_entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg { + ExecuteMsg::Receive { + sender, + from, + amount, + msg, + .. + } => { + let sender = deps.api.addr_validate(&sender)?; + let from = deps.api.addr_validate(&from)?; + execute::receive(deps, env, info, sender, from, amount, msg) + } + ExecuteMsg::UpdateConfig { config } => execute::try_update_config(deps, env, info, config), + ExecuteMsg::Adapter(adapter) => match adapter { + adapter::SubExecuteMsg::Unbond { asset, amount } => { + let asset = deps.api.addr_validate(&asset)?; + execute::unbond(deps, env, info, asset, amount) + } + adapter::SubExecuteMsg::Claim { asset } => { + let asset = deps.api.addr_validate(&asset)?; + execute::claim(deps, env, info, asset) + } + adapter::SubExecuteMsg::Update { asset: _ } => Ok(Response::new().set_data(to_binary( + &adapter::ExecuteAnswer::Update { + status: ResponseStatus::Success, + }, + )?)), + }, + } +} + +#[shd_entry_point] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => to_binary(&query::config(deps)?), + QueryMsg::Adapter(adapter) => match adapter { + adapter::SubQueryMsg::Balance { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::balance(deps, env, asset)?) + } + adapter::SubQueryMsg::Claimable { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::claimable(deps, env, asset)?) + } + adapter::SubQueryMsg::Unbonding { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::unbonding(deps, env, asset)?) + } + adapter::SubQueryMsg::Unbondable { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::unbondable(deps, env, asset)?) + } + adapter::SubQueryMsg::Reserves { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::reserves(deps, env, asset)?) + } + }, + } +} diff --git a/contracts/dao/stkd_scrt/src/execute.rs b/contracts/dao/stkd_scrt/src/execute.rs new file mode 100644 index 0000000..0876590 --- /dev/null +++ b/contracts/dao/stkd_scrt/src/execute.rs @@ -0,0 +1,158 @@ +use shade_protocol::{ + admin::helpers::{validate_admin, AdminPermissions}, + c_std::{ + to_binary, + Addr, + Binary, + DepsMut, + Env, + MessageInfo, + Response, + StdError, + StdResult, + Uint128, + }, +}; + +use shade_protocol::{ + dao::{ + adapter, + stkd_scrt::{staking_derivatives, Config, ExecuteAnswer}, + }, + utils::{ + generic_response::ResponseStatus, + wrap::{unwrap, wrap_and_send}, + }, +}; + +use crate::storage::*; + +pub fn receive( + deps: DepsMut, + _env: Env, + info: MessageInfo, + _sender: Addr, + _from: Addr, + amount: Uint128, + _msg: Option, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if info.sender != config.sscrt.address { + return Err(StdError::generic_err("Only accepts sSCRT")); + } + + // Unwrap & stake + Ok(Response::new() + .add_message(unwrap(amount, config.sscrt.clone())?) + .add_message(staking_derivatives::stake_msg( + amount, + &config.staking_derivatives, + )?) + .set_data(to_binary(&ExecuteAnswer::Receive { + status: ResponseStatus::Success, + })?)) +} + +pub fn try_update_config( + deps: DepsMut, + _env: Env, + info: MessageInfo, + config: Config, +) -> StdResult { + let cur_config = CONFIG.load(deps.storage)?; + + validate_admin( + &deps.querier, + AdminPermissions::ScrtStakingAdmin, + &info.sender, + &cur_config.admin_auth, + )?; + + // Save new info + CONFIG.save(deps.storage, &config)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn unbond( + deps: DepsMut, + _env: Env, + info: MessageInfo, + asset: Addr, + amount: Uint128, +) -> StdResult { + /* Unbonding to the scrt staking contract + * Once scrt is on balance sheet, treasury can claim + * and this contract will take all scrt->sscrt and send + */ + let config = CONFIG.load(deps.storage)?; + + if validate_admin( + &deps.querier, + AdminPermissions::ScrtStakingAdmin, + &info.sender, + &config.admin_auth, + ) + .is_err() + && config.owner != info.sender + { + return Err(StdError::generic_err("Unauthorized")); + } + + if asset != config.sscrt.address { + return Err(StdError::generic_err("Unrecognized Asset")); + } + + Ok(Response::new() + .add_message(staking_derivatives::unbond_msg( + amount, + &config.staking_derivatives, + )?) + .set_data(to_binary(&adapter::ExecuteAnswer::Unbond { + status: ResponseStatus::Success, + amount, + })?)) +} + +/* Claims completed unbondings, wraps them, + * and returns them to treasury + */ +pub fn claim(deps: DepsMut, env: Env, _info: MessageInfo, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if asset != config.sscrt.address { + return Err(StdError::generic_err("Unrecognized Asset")); + } + + let claimable = staking_derivatives::holdings_query( + &deps.querier, + env.contract.address, + VIEWING_KEY.load(deps.storage)?, + env.block.time.seconds(), + &config.staking_derivatives, + )? + .claimable_scrt; + + let mut messages = vec![]; + if !claimable.is_zero() { + messages.push(staking_derivatives::claim_msg(&config.staking_derivatives)?); + messages.append(&mut wrap_and_send( + claimable, + config.owner, + config.sscrt, + None, + )?); + } + + Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Claim { + status: ResponseStatus::Success, + amount: claimable, + }, + )?)) +} diff --git a/contracts/dao/stkd_scrt/src/lib.rs b/contracts/dao/stkd_scrt/src/lib.rs new file mode 100644 index 0000000..cce0227 --- /dev/null +++ b/contracts/dao/stkd_scrt/src/lib.rs @@ -0,0 +1,7 @@ +pub mod contract; +pub mod execute; +pub mod query; +pub mod storage; + +#[cfg(test)] +mod test; diff --git a/contracts/dao/stkd_scrt/src/query.rs b/contracts/dao/stkd_scrt/src/query.rs new file mode 100644 index 0000000..a53da5c --- /dev/null +++ b/contracts/dao/stkd_scrt/src/query.rs @@ -0,0 +1,124 @@ +use shade_protocol::{ + c_std::{Addr, Deps, Env, StdError, StdResult, Uint128}, + dao::{ + adapter, + stkd_scrt::{staking_derivatives, QueryAnswer}, + }, +}; + +use crate::storage::*; + +pub fn config(deps: Deps) -> StdResult { + Ok(QueryAnswer::Config { + config: CONFIG.load(deps.storage)?, + }) +} + +pub fn balance(deps: Deps, env: Env, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if asset != config.sscrt.address { + return Err(StdError::generic_err(format!( + "Unrecognized Asset {}", + asset + ))); + } + + let holdings = staking_derivatives::holdings_query( + &deps.querier, + env.contract.address, + VIEWING_KEY.load(deps.storage)?, + env.block.time.seconds(), + &config.staking_derivatives, + )?; + + Ok(adapter::QueryAnswer::Balance { + amount: holdings.claimable_scrt + + holdings.unbonding_scrt + + holdings.token_balance_value_in_scrt, + }) +} + +pub fn claimable(deps: Deps, env: Env, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if asset != config.sscrt.address { + return Err(StdError::generic_err(format!( + "Unrecognized Asset {}", + asset + ))); + } + + let holdings = staking_derivatives::holdings_query( + &deps.querier, + env.contract.address, + VIEWING_KEY.load(deps.storage)?, + env.block.time.seconds(), + &config.staking_derivatives, + )?; + + Ok(adapter::QueryAnswer::Claimable { + amount: holdings.claimable_scrt, + }) +} + +pub fn unbonding(deps: Deps, env: Env, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if asset != config.sscrt.address { + return Err(StdError::generic_err(format!( + "Unrecognized Asset {}", + asset + ))); + } + + let holdings = staking_derivatives::holdings_query( + &deps.querier, + env.contract.address, + VIEWING_KEY.load(deps.storage)?, + env.block.time.seconds(), + &config.staking_derivatives, + )?; + + Ok(adapter::QueryAnswer::Unbonding { + amount: holdings.unbonding_scrt, + }) +} + +pub fn unbondable(deps: Deps, env: Env, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if asset != config.sscrt.address { + return Err(StdError::generic_err(format!( + "Unrecognized Asset {}", + asset + ))); + } + + let holdings = staking_derivatives::holdings_query( + &deps.querier, + env.contract.address, + VIEWING_KEY.load(deps.storage)?, + env.block.time.seconds(), + &config.staking_derivatives, + )?; + + Ok(adapter::QueryAnswer::Unbondable { + amount: holdings.token_balance_value_in_scrt, + }) +} + +pub fn reserves(deps: Deps, _env: Env, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if asset != config.sscrt.address { + return Err(StdError::generic_err(format!( + "Unrecognized Asset {}", + asset + ))); + } + + Ok(adapter::QueryAnswer::Reserves { + amount: Uint128::zero(), + }) +} diff --git a/contracts/dao/stkd_scrt/src/storage.rs b/contracts/dao/stkd_scrt/src/storage.rs new file mode 100644 index 0000000..2795df2 --- /dev/null +++ b/contracts/dao/stkd_scrt/src/storage.rs @@ -0,0 +1,7 @@ +use shade_protocol::{c_std::Addr, dao::stkd_scrt}; + +use shade_protocol::secret_storage_plus::Item; + +pub const CONFIG: Item = Item::new("config"); +pub const SELF_ADDRESS: Item = Item::new("self_address"); +pub const VIEWING_KEY: Item = Item::new("viewing_key"); diff --git a/contracts/dao/stkd_scrt/src/test.rs b/contracts/dao/stkd_scrt/src/test.rs new file mode 100644 index 0000000..04225b9 --- /dev/null +++ b/contracts/dao/stkd_scrt/src/test.rs @@ -0,0 +1,46 @@ +/* +#[cfg(test)] +pub mod tests { + use shade_protocol::c_std::{ + testing::{ + mock_dependencies, mock_env, MockStorage, MockApi, MockQuerier + }, + Addr, + coins, from_binary, StdError, Uint128, + DepsMut, + }; + use shade_protocol::{ + treasury::{ + QueryAnswer, InstantiateMsg, ExecuteMsg, + QueryMsg, + }, + asset::Contract, + }; + + use crate::{ + contract::{ + init, handle, query, + }, + }; + + fn create_contract(address: &str, code_hash: &str) -> Contract { + let env = mock_env(address.to_string(), &[]); + return Contract{ + address: info.sender, + code_hash: code_hash.to_string() + } + } + + fn dummy_init(admin: String, viewing_key: String) -> Extern { + let mut deps = mock_dependencies(20, &[]); + let msg = InstantiateMsg { + admin: Option::from(Addr(admin.clone())), + viewing_key, + }; + let env = mock_env(admin, &coins(1000, "earth")); + let _res = init(&mut deps, env, info, msg).unwrap(); + + return deps + } +} +*/ diff --git a/contracts/dao/stkd_scrt/tests/integration.rs b/contracts/dao/stkd_scrt/tests/integration.rs new file mode 100644 index 0000000..4065456 --- /dev/null +++ b/contracts/dao/stkd_scrt/tests/integration.rs @@ -0,0 +1,331 @@ +/*use shade_multi_test::multi::admin::init_admin_auth; +use shade_protocol::c_std::{to_binary, Addr, Coin, Decimal, Delegation, Uint128, Validator}; + +use shade_protocol::{ + contract_interfaces::{ + dao::{adapter, stkd_scrt}, + snip20, + }, + utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{snip20::Snip20, stkd_scrt::StkdScrt}; +use shade_protocol::multi_test::{App, StakingSudo, SudoMsg}; + +fn bonded_adapter_test(deposit: Uint128, rewards: Uint128, reserves: Uint128, balance: Uint128) { + let mut app = App::default(); + + let viewing_key = "unguessable".to_string(); + let admin = Addr::unchecked("admin"); + let validator = Addr::unchecked("validator"); + let admin_auth = init_admin_auth(&mut app, &admin); + let token = snip20::InstantiateMsg { + name: "secretSCRT".into(), + admin: Some("admin".into()), + symbol: "SSCRT".into(), + decimals: 6, + initial_balances: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + query_auth: None, + } + .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) + .unwrap(); + + let stkd_scrt = stkd_scrt::InstantiateMsg { + admin_auth: admin_auth.into(), + owner: admin.clone().into(), + sscrt: token.clone().into(), + validator_bounds: None, + viewing_key: viewing_key.clone(), + } + .test_init( + ScrtStaking::default(), + &mut app, + admin.clone(), + "stkd_scrt", + &[], + ) + .unwrap(); + + app.sudo(SudoMsg::Staking(StakingSudo::AddValidator { + validator: validator.to_string().clone(), + })) + .unwrap(); + + //TODO deploy staking_derivatives + + // set admin owner key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + let deposit_coin = Coin { + denom: "uscrt".into(), + amount: deposit, + }; + app.init_modules(|router, _, storage| { + router + .bank + .init_balance(storage, &admin.clone(), vec![deposit_coin.clone()]) + .unwrap(); + }); + + // Wrap L1 into tokens + snip20::ExecuteMsg::Deposit { padding: None } + .test_exec(&token, &mut app, admin.clone(), &vec![deposit_coin]) + .unwrap(); + + // Send funds to adapter + snip20::ExecuteMsg::Send { + recipient: stkd_scrt.address.to_string().clone(), + recipient_code_hash: None, + amount: deposit, + msg: None, + memo: None, + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // reserves + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { + asset: token.address.to_string().clone(), + }) + .test_query(&stkd_scrt, &app) + .unwrap() + { + adapter::QueryAnswer::Reserves { amount } => { + assert_eq!(amount, reserves, "Reserves Pre-Rewards"); + } + _ => panic!("Query failed"), + }; + + // Balance + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&stkd_scrt, &app) + .unwrap() + { + adapter::QueryAnswer::Balance { amount } => { + assert_eq!(amount, balance, "Balance Pre-Rewards"); + } + _ => panic!("Query failed"), + }; + + // Rewards + let cur_rewards: Uint128 = stkd_scrt::QueryMsg::Rewards {} + .test_query(&stkd_scrt, &app) + .unwrap(); + assert_eq!(cur_rewards, Uint128::zero(), "Rewards Pre-add"); + + //ensemble.add_rewards(rewards); + app.sudo(SudoMsg::Staking(StakingSudo::AddRewards { + amount: Coin { + amount: rewards, + denom: "uscrt".into(), + }, + })) + .unwrap(); + + // Reserves + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { + asset: token.address.to_string().clone(), + }) + .test_query(&stkd_scrt, &app) + .unwrap() + { + adapter::QueryAnswer::Reserves { amount } => { + assert_eq!(amount, reserves, "Reserves Post-Rewards"); + } + _ => panic!("Query failed"), + }; + + // Balance + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&stkd_scrt, &app) + .unwrap() + { + adapter::QueryAnswer::Balance { amount } => { + assert_eq!(amount, deposit + rewards, "Balance Post-Rewards"); + } + _ => panic!("Query failed"), + }; + + // Claimable + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { + asset: token.address.to_string().clone(), + }) + .test_query(&stkd_scrt, &app) + .unwrap() + { + adapter::QueryAnswer::Claimable { amount } => { + assert_eq!(amount, Uint128::zero(), "Claimable Pre-Unbond"); + } + _ => panic!("Query failed"), + }; + + // Unbondable + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbondable { + asset: token.address.to_string().clone(), + }) + .test_query(&stkd_scrt, &app) + .unwrap() + { + adapter::QueryAnswer::Unbondable { amount } => { + assert_eq!(amount, balance, "Unbondable Pre-Unbond"); + } + _ => panic!("Query failed"), + }; + + // Unbond all + adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Unbond { + amount: balance, + asset: token.address.to_string().clone().to_string(), + }) + .test_exec(&stkd_scrt, &mut app, admin.clone(), &[]) + .unwrap(); + println!("SCRT STAKING ADDR {}", stkd_scrt.address); + + // Unbonding + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbonding { + asset: token.address.to_string().clone(), + }) + .test_query(&stkd_scrt, &app) + .unwrap() + { + adapter::QueryAnswer::Unbonding { amount } => { + assert_eq!(amount, deposit, "Unbonding Pre fast forward"); + } + _ => panic!("Query failed"), + }; + + // Claimable + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { + asset: token.address.to_string().clone(), + }) + .test_query(&stkd_scrt, &app) + .unwrap() + { + adapter::QueryAnswer::Claimable { amount } => { + assert_eq!(amount, Uint128::zero(), "Claimable Pre unbond fast forward"); + } + _ => panic!("Query failed"), + }; + + app.sudo(SudoMsg::Staking(StakingSudo::FastForwardUndelegate {})) + .unwrap(); + + // Claimable + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { + asset: token.address.to_string().clone(), + }) + .test_query(&stkd_scrt, &app) + .unwrap() + { + adapter::QueryAnswer::Claimable { amount } => { + assert_eq!(amount, deposit, "Claimable post fast forward"); + } + _ => panic!("Query failed"), + }; + + // Claim + adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Claim { + asset: token.address.to_string().clone().to_string(), + }) + .test_exec(&stkd_scrt, &mut app, admin.clone(), &[]) + .unwrap(); + + // Reserves + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { + asset: token.address.to_string().clone(), + }) + .test_query(&stkd_scrt, &app) + .unwrap() + { + adapter::QueryAnswer::Reserves { amount } => { + assert_eq!(amount, Uint128::zero(), "Reserves Post Claim"); + } + _ => panic!("Query failed"), + }; + + // Balance + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&stkd_scrt, &app) + .unwrap() + { + adapter::QueryAnswer::Balance { amount } => { + assert_eq!(amount, Uint128::zero(), "Balance Post Claim"); + } + _ => panic!("Query failed"), + }; + + // Unbonding + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbonding { + asset: token.address.to_string().clone(), + }) + .test_query(&stkd_scrt, &app) + .unwrap() + { + adapter::QueryAnswer::Unbonding { amount } => { + assert_eq!(amount, Uint128::zero(), "Unbonding Post Claim"); + } + _ => panic!("Query failed"), + }; + + // ensure wrapped tokens were returned + match (snip20::QueryMsg::Balance { + address: admin.to_string().clone(), + key: viewing_key.clone(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, deposit + rewards, "Final User balance"); + } + _ => { + panic!("snip20 balance query failed"); + } + }; +} + +macro_rules! basic_stkd_scrt_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + deposit, + rewards, + expected_stkd_scrt, + ) = $value; + basic_stkd_scrt_integration(deposit, rewards, expected_stkd_scrt); + } + )* + } +} + +basic_stkd_scrt_tests! { + basic_stkd_scrt_0: ( + Uint128::new(100), // deposit + Uint128::new(10), // rewards + Uint128::new(100), // reserves + Uint128::new(100), // balance + ), +}*/ diff --git a/contracts/dao/stkd_scrt/tests/unit.rs b/contracts/dao/stkd_scrt/tests/unit.rs new file mode 100644 index 0000000..ea3e8b7 --- /dev/null +++ b/contracts/dao/stkd_scrt/tests/unit.rs @@ -0,0 +1,36 @@ +/* +use cosmwasm_std::{ + coins, from_binary, to_binary, + Extern, Addr, StdError, + Binary, StdResult, HandleResponse, Env, + InitResponse, Uint128, +}; + +#[test] +fn test_function(param0, param1) { + assert_eq!(param0, param1); +} + +macro_rules! test_function_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (param0, param1) = $value; + test_function(param0, param1); + } + )* + } +} + +test_function_tests! { + test_function_0: ( + Uint128(1), + Uint128(1), + ), + test_function_1: ( + Uint128(1), + Uint128(2), + ), +} +*/ diff --git a/contracts/dao/treasury/.cargo/config b/contracts/dao/treasury/.cargo/config new file mode 100644 index 0000000..882fe08 --- /dev/null +++ b/contracts/dao/treasury/.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/dao/treasury/.circleci/config.yml b/contracts/dao/treasury/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/contracts/dao/treasury/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/dao/treasury/Cargo.toml b/contracts/dao/treasury/Cargo.toml new file mode 100644 index 0000000..921b460 --- /dev/null +++ b/contracts/dao/treasury/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "treasury" +version = "0.1.0" +authors = ["Jackson Swenson ", "Jack Sisson ./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 + +# 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/dao/treasury/README.md b/contracts/dao/treasury/README.md new file mode 100644 index 0000000..cf14494 --- /dev/null +++ b/contracts/dao/treasury/README.md @@ -0,0 +1,172 @@ +# Treasury +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Init](#Init) + * [DAO Adapter](/packages/shade_protocol/src/DAO_ADAPTER.md) + * [Interface](#Interface) + * Messages + * [Receive](#Receive) + * [UpdateConfig](#UpdateConfig) + * [RegisterAsset](#RegisterAsset) + * [RegisterManager](#RegisterManager) + * [Allowance](#Allowance) + * [AddAccount](#AddAccount) + * [CloseAccount](#CloseAccount) + * Queries + * [Config](#Config) + * [Assets](#Assets) + * [Allowances](#Allowances) + * [CurrentAllowances](#CurrentAllowances) + * [Allowance](#Allowance) + * [Account](#Account) +# Introduction +The treasury contract holds network funds from things such as mint commission and pending airdrop funds + +# Sections + +## Init +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|admin | string | contract owner/admin; a valid bech32 address; Controls funds +|viewing_key | string | viewing key for all registered snip20 assets +|sscrt | Contract | sSCRT contract for wrapping & unwrapping + +## Interface + +### Messages + +#### UpdateConfig +Updates the given values +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|config | string | New config to be set for the contract + +##### Response +```json +{ + "update_config": { + "status": "success" + } +} +``` + +#### RegisterAsset +Registers a SNIP-20 compliant asset since [RegisterReceive](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md#RegisterReceive) is called. + +Note: Will return an error if there's an asset with that address already registered. +##### Request +|Name |Type |Description | optional | +|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| +|contract | Contract | Type explained [here](#Contract) | no | +##### Response +```json +{ + "register_asset": { + "status": "success" + } +} +``` + +### Queries + +#### Config +Gets the contract's configuration +##### Response +```json +{ + "config": { + "config": { + "admin": "admin address", + "sscrt": { + "address": "", + "code_hash": "", + }, + } + } +} +``` + +#### Assets +List of assets supported +##### Response +```json +{ + "assets": { + "assets": ["asset address", ...] + } +} +``` + +#### Allowances +List of configured allowances for things like treasury_manager & rewards +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|asset | Addr | Asset to query balance of +##### Response +```json +{ + "allowances": { + "allowances": [ + { + "allowance": ... + }, + ...] + } +} +``` + +#### Allowance +List of configured allowances for things like treasury_manager & rewards +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|asset | Addr | Asset to query allowance for +|spender | Addr | Spender of allowance +##### Response +```json +{ + "allowances": { + "allowances": [ + { + "allowance": ... + }, + ... + ] + } +} +``` + +#### Accounts +List of account holders +##### Response +```json +{ + "accounts": { + "accounts": ["address0", ...], + } +} +``` + +#### Account +Balance of a given account holders assets (e.g. SHD staking) +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|holder | Addr | Holder of the account +|asset | Addr | Asset to query balance of +##### Response +```json +{ + "account": { + "account": { + "balances": Uint128, + "unbondings": Uint128, + "claimable": Uint128, + "status": ("active"|"disabled"|"closed"|"transferred"), + } + } +} +``` diff --git a/contracts/dao/treasury/Untitled Diagram.drawio b/contracts/dao/treasury/Untitled Diagram.drawio new file mode 100644 index 0000000..513b976 --- /dev/null +++ b/contracts/dao/treasury/Untitled Diagram.drawio @@ -0,0 +1 @@ +UzV2zq1wL0osyPDNT0nNUTV2VTV2LsrPL4GwciucU3NyVI0MMlNUjV1UjYwMgFjVyA2HrCFY1qAgsSg1rwSLBiADYTaQg2Y1AA== \ No newline at end of file diff --git a/contracts/dao/treasury/src/contract.rs b/contracts/dao/treasury/src/contract.rs new file mode 100644 index 0000000..0de8ab0 --- /dev/null +++ b/contracts/dao/treasury/src/contract.rs @@ -0,0 +1,128 @@ +use crate::{execute, query, storage::*}; +use shade_protocol::{ + c_std::{ + shd_entry_point, + to_binary, + Addr, + Binary, + Deps, + DepsMut, + Env, + MessageInfo, + Response, + StdResult, + }, + dao::treasury::{Config, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, RunLevel}, + utils::asset::Contract, +}; + +#[shd_entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + CONFIG.save(deps.storage, &Config { + admin_auth: msg.admin_auth.into_valid(deps.api)?, + multisig: deps.api.addr_validate(&msg.multisig)?, + })?; + + VIEWING_KEY.save(deps.storage, &msg.viewing_key)?; + RUN_LEVEL.save(deps.storage, &RunLevel::Normal)?; + + Ok(Response::new()) +} + +#[shd_entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg { + ExecuteMsg::Receive { + sender, + from, + amount, + msg, + .. + } => { + let sender = deps.api.addr_validate(&sender)?; + let from = deps.api.addr_validate(&from)?; + execute::receive(deps, env, info, sender, from, amount, msg) + } + ExecuteMsg::UpdateConfig { + admin_auth, + multisig, + } => execute::try_update_config(deps, env, info, admin_auth, multisig), + ExecuteMsg::RegisterAsset { contract } => { + let contract = contract.into_valid(deps.api)?; + execute::try_register_asset(deps, &env, info, &contract) + } + ExecuteMsg::RegisterManager { contract } => { + let mut contract = contract.into_valid(deps.api)?; + execute::register_manager(deps, &env, info, &mut contract) + } + ExecuteMsg::RegisterWrap { denom, contract } => { + let contract = contract.into_valid(deps.api)?; + execute::register_wrap(deps, &env, info, denom, &contract) + } + ExecuteMsg::Allowance { + asset, + allowance, + refresh_now, + } => { + let asset = deps.api.addr_validate(&asset)?; + let allowance = allowance.valid(deps.api)?; + execute::allowance(deps, &env, info, asset, allowance, refresh_now) + } + ExecuteMsg::Update { asset } => { + let asset = deps.api.addr_validate(&asset)?; + execute::update(deps, &env, info, asset) + } + ExecuteMsg::SetRunLevel { run_level } => { + execute::set_run_level(deps, &env, info, run_level) + } + ExecuteMsg::WrapCoins {} => execute::wrap_coins(deps, &env, info), + } +} + +#[shd_entry_point] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => to_binary(&query::config(deps)?), + QueryMsg::Assets {} => to_binary(&query::assets(deps)?), + QueryMsg::Allowances { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::allowances(deps, asset)?) + } + QueryMsg::Allowance { asset, spender } => { + let asset = deps.api.addr_validate(&asset)?; + let spender = deps.api.addr_validate(&spender)?; + to_binary(&query::allowance(deps, env, asset, spender)?) + } + QueryMsg::RunLevel => to_binary(&QueryAnswer::RunLevel { + run_level: RUN_LEVEL.load(deps.storage)?, + }), + //TODO: parse string & format manually to accept all valid date formats + QueryMsg::Metrics { + date, + epoch, + period, + } => to_binary(&query::metrics(deps, env, date, epoch, period)?), + QueryMsg::Balance { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::balance(deps, env, asset)?) + } + QueryMsg::BatchBalance { assets } => { + let mut val_assets = vec![]; + + for a in assets { + val_assets.push(deps.api.addr_validate(&a)?); + } + + to_binary(&query::batch_balance(deps, env, val_assets)?) + } + QueryMsg::Reserves { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::reserves(deps, env, asset)?) + } + } +} diff --git a/contracts/dao/treasury/src/execute.rs b/contracts/dao/treasury/src/execute.rs new file mode 100644 index 0000000..de391b2 --- /dev/null +++ b/contracts/dao/treasury/src/execute.rs @@ -0,0 +1,795 @@ +use crate::storage::*; +use shade_protocol::{ + c_std::{ + to_binary, + Addr, + Binary, + DepsMut, + Env, + MessageInfo, + Response, + StdError, + StdResult, + Uint128, + }, + contract_interfaces::{ + admin::helpers::{validate_admin, AdminPermissions}, + dao::{ + manager, + treasury::{ + Action, + Allowance, + AllowanceMeta, + AllowanceType, + Context, + ExecuteAnswer, + Metric, + RunLevel, + }, + }, + snip20, + }, + snip20::helpers::{ + allowance_query, + balance_query, + decrease_allowance_msg, + increase_allowance_msg, + register_receive, + send_msg, + set_viewing_key_msg, + }, + utils::{ + asset::{Contract, RawContract}, + cycle::{exceeds_cycle, parse_utc_datetime, utc_from_seconds, utc_now, Cycle}, + generic_response::ResponseStatus, + wrap::wrap_coin, + }, +}; +use std::collections::HashMap; + +const ONE_HUNDRED_PERCENT: Uint128 = Uint128::new(10u128.pow(18u32)); + +pub fn receive( + deps: DepsMut, + env: Env, + info: MessageInfo, + _sender: Addr, + from: Addr, + amount: Uint128, + _msg: Option, +) -> StdResult { + // Only store metrics for registered assets + if ASSET.may_load(deps.storage, info.sender.clone())?.is_some() { + METRICS.push(deps.storage, env.block.time, Metric { + action: Action::FundsReceived, + context: Context::Receive, + timestamp: env.block.time.seconds(), + token: info.sender, + amount, + user: from, + })?; + } + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Receive { + status: ResponseStatus::Success, + })?)) +} + +pub fn try_update_config( + deps: DepsMut, + _env: Env, + info: MessageInfo, + admin_auth: Option, + multisig: Option, +) -> StdResult { + let mut config = CONFIG.load(deps.storage)?; + + validate_admin( + &deps.querier, + AdminPermissions::TreasuryAdmin, + &info.sender, + &config.admin_auth, + )?; + + if let Some(admin_auth) = admin_auth { + config.admin_auth = admin_auth.into_valid(deps.api)?; + } + if let Some(multisig) = multisig { + config.multisig = deps.api.addr_validate(&multisig)?; + } + + CONFIG.save(deps.storage, &config)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { + config, + status: ResponseStatus::Success, + })?), + ) +} + +pub fn update(deps: DepsMut, env: &Env, info: MessageInfo, asset: Addr) -> StdResult { + match RUN_LEVEL.load(deps.storage)? { + RunLevel::Migrating => { + let config = CONFIG.load(deps.storage)?; + validate_admin( + &deps.querier, + AdminPermissions::TreasuryAdmin, + &info.sender, + &config.admin_auth, + )?; + migrate(deps, env, info, asset) + } + RunLevel::Deactivated => { + return Err(StdError::generic_err("Contract Deactivated")); + } + RunLevel::Normal => rebalance(deps, env, info, asset), + } +} + +fn rebalance(deps: DepsMut, env: &Env, _info: MessageInfo, asset: Addr) -> StdResult { + let viewing_key = VIEWING_KEY.load(deps.storage)?; + + let full_asset = match ASSET.may_load(deps.storage, asset.clone())? { + Some(a) => a, + None => { + return Err(StdError::generic_err("Not a registered asset")); + } + }; + + let mut allowances = ALLOWANCES.load(deps.storage, asset.clone())?; + + let mut total_balance = balance_query( + &deps.querier, + env.contract.address.clone(), + viewing_key.clone(), + &full_asset.contract.clone(), + )?; + let mut token_balance = total_balance; + + // { spender: (balance, allowance) } + let mut metadata: HashMap = HashMap::new(); + + let mut messages = vec![]; + let mut metrics = vec![]; + + let now = utc_now(&env); + + // allowances marked for removal + let mut stale_allowances = vec![]; + + for (i, a) in allowances.clone().iter().enumerate() { + let manager = MANAGER.may_load(deps.storage, a.spender.clone())?; + let mut claimable = Uint128::zero(); + let mut unbonding = Uint128::zero(); + let mut balance = Uint128::zero(); + // we can only get some of these numbers when it's a treasury manager + if let Some(m) = manager.clone() { + claimable = manager::claimable_query( + deps.querier, + &asset.clone(), + env.contract.address.clone(), + m.clone(), + )?; + // claim when not zero + if !claimable.is_zero() { + messages.push(manager::claim_msg(&asset.clone(), m.clone())?); + metrics.push(Metric { + action: Action::Claim, + context: Context::Rebalance, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: claimable, + user: m.address.clone(), + }); + } + + unbonding = manager::unbonding_query( + deps.querier, + &asset.clone(), + env.contract.address.clone(), + m.clone(), + )?; + + balance = manager::balance_query( + deps.querier, + &asset.clone(), + env.contract.address.clone(), + m, + )? + } + + // can allways get allowance for everyone + let allowance = allowance_query( + &deps.querier, + env.contract.address.clone(), + a.spender.clone(), + viewing_key.clone(), + 1, + &full_asset.contract.clone(), + )? + .allowance; + + if token_balance > allowance { + token_balance -= allowance; + } else { + token_balance = Uint128::zero(); + } + + // if all of these are zero then we need to remove the allowance at the end of the fn + if balance.is_zero() + && unbonding.is_zero() + && claimable.is_zero() + && allowance.is_zero() + && a.amount.is_zero() + { + stale_allowances.push(i); + } + + metadata.insert(a.spender.clone(), (balance, allowance)); + total_balance += balance + unbonding; + } + + /* Amounts given priority sice the array is sorted + * portions are calculated after amounts are taken from total + */ + for (i, allowance) in allowances.clone().iter().enumerate() { + let last_refresh = parse_utc_datetime(&allowance.last_refresh)?; + + // Refresh allowance if cycle is exceeded + if !exceeds_cycle(&now, &last_refresh, allowance.cycle.clone()) { + // Once allowances need 1 refresh if last_refresh == 'null' + if allowance.cycle == Cycle::Once { + if last_refresh.timestamp() != 0 { + if stale_allowances.iter().find(|&&x| x == i) == None { + stale_allowances.push(i); + stale_allowances.sort(); + } + continue; + } + } else { + continue; + } + } + + allowances[i].last_refresh = now.to_rfc3339(); + + // calculate the desired amount for the manager + let desired_amount = match allowance.allowance_type { + AllowanceType::Amount => { + // reduce total_balance so amount allowances are not used in the calculation for + // portion allowances + if total_balance >= allowance.amount { + total_balance -= allowance.amount; + } else { + total_balance = Uint128::zero(); + } + allowance.amount + } + AllowanceType::Portion => { + // This just gives a ratio of total balance where allowance.amount is the percent + total_balance.multiply_ratio(allowance.amount, ONE_HUNDRED_PERCENT) + } + }; + + let (balance, cur_allowance) = metadata[&allowance.spender]; + let total = balance + cur_allowance; + + // calculate threshold + let threshold = desired_amount.multiply_ratio(allowance.tolerance, ONE_HUNDRED_PERCENT); + + match desired_amount.cmp(&total) { + // Decrease Allowance + std::cmp::Ordering::Less => { + // decrease is cur_allow + bal - allow.amount because the current amount of funds + // the spender has access to is it's current allowance plus it balance, so to + // find the decrease, we subtract that by the amount the allowance is set to + let mut decrease = total - desired_amount; + // threshold check + if decrease <= threshold { + continue; + } + // Allowance fully covers amount needed + if cur_allowance >= decrease { + if !decrease.is_zero() { + messages.push(decrease_allowance_msg( + allowance.spender.clone(), + decrease, + None, + None, + 1, + &full_asset.contract.clone(), + vec![], + )?); + token_balance += decrease; + metrics.push(Metric { + action: Action::DecreaseAllowance, + context: Context::Rebalance, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: decrease, + user: allowance.spender.clone(), + }); + } + } + // Reduce allowance then unbond + else { + if !cur_allowance.is_zero() { + messages.push(decrease_allowance_msg( + allowance.spender.clone(), + cur_allowance, + None, + None, + 1, + &full_asset.contract.clone(), + vec![], + )?); + token_balance += cur_allowance; + metrics.push(Metric { + action: Action::DecreaseAllowance, + context: Context::Rebalance, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: cur_allowance, + user: allowance.spender.clone(), + }); + } + + decrease -= cur_allowance; + + // Unbond remaining + if !decrease.is_zero() { + if let Some(m) = + MANAGER.may_load(deps.storage, allowance.spender.clone())? + { + messages.push(manager::unbond_msg( + &asset.clone(), + decrease, + m.clone(), + )?); + metrics.push(Metric { + action: Action::Unbond, + context: Context::Rebalance, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: decrease, + user: m.address.clone(), + }); + } + } + } + } + // Increase Allowance + std::cmp::Ordering::Greater => { + let mut increase = desired_amount - total; + if increase > token_balance { + increase = token_balance; + } + token_balance -= increase; + + // threshold check + if increase <= threshold { + continue; + } + if !increase.is_zero() { + messages.push(increase_allowance_msg( + allowance.spender.clone(), + increase, + None, + None, + 1, + &full_asset.contract.clone(), + vec![], + )?); + metrics.push(Metric { + action: Action::IncreaseAllowance, + context: Context::Rebalance, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: increase, + user: allowance.spender.clone(), + }); + } + } + _ => {} + } + } + + if !stale_allowances.is_empty() { + for index in stale_allowances.iter().rev() { + allowances.remove(index.clone()); + } + } + ALLOWANCES.save(deps.storage, asset.clone(), &allowances)?; + + METRICS.append(deps.storage, env.block.time, &mut metrics)?; + + Ok(Response::new() + .add_messages(messages) + .set_data(to_binary(&ExecuteAnswer::Rebalance { + status: ResponseStatus::Success, + })?)) +} + +pub fn migrate(deps: DepsMut, env: &Env, _info: MessageInfo, asset: Addr) -> StdResult { + let mut messages = vec![]; + let mut metrics = vec![]; + + let allowances = ALLOWANCES.load(deps.storage, asset.clone())?; + let full_asset = ASSET.load(deps.storage, asset.clone())?; + let viewing_key = VIEWING_KEY.load(deps.storage)?; + + let mut claimed = Uint128::zero(); + + for allowance in allowances { + if let Some(m) = MANAGER.may_load(deps.storage, allowance.spender.clone())? { + // TODO store in metadata object for re-use + let unbondable = manager::unbondable_query( + deps.querier, + &asset, + env.contract.address.clone(), + m.clone(), + )?; + + // Unbond all if any + if !unbondable.is_zero() { + messages.push(manager::unbond_msg(&asset, unbondable, m.clone())?); + metrics.push(Metric { + action: Action::Unbond, + context: Context::Migration, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: unbondable, + user: m.address.clone(), + }); + } + + let claimable = manager::claimable_query( + deps.querier, + &asset, + env.contract.address.clone(), + m.clone(), + )?; + + // Claim if any + if !claimable.is_zero() { + messages.push(manager::claim_msg(&asset, m.clone())?); + metrics.push(Metric { + action: Action::Claim, + context: Context::Migration, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: claimable, + user: m.address.clone(), + }); + claimed += claimable; + } + } + + let cur_allowance = allowance_query( + &deps.querier, + env.contract.address.clone(), + allowance.spender.clone(), + viewing_key.clone(), + 1, + &full_asset.contract.clone(), + )? + .allowance; + + // Reduce all allowance if any + if !cur_allowance.is_zero() { + messages.push(decrease_allowance_msg( + allowance.spender.clone(), + cur_allowance, + None, + None, + 1, + &full_asset.contract.clone(), + vec![], + )?); + metrics.push(Metric { + action: Action::DecreaseAllowance, + context: Context::Migration, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: cur_allowance, + user: allowance.spender.clone(), + }); + } + } + + // Send full balance to multisig + let balance = balance_query( + &deps.querier, + env.contract.address.clone(), + viewing_key.clone(), + &full_asset.contract.clone(), + )?; + + if !(balance + claimed).is_zero() { + let config = CONFIG.load(deps.storage)?; + + //TODO: send to super admin from admin_auth -- remove multisig from config + messages.push(send_msg( + config.multisig.clone(), + balance + claimed, + None, + None, + None, + &full_asset.contract.clone(), + )?); + metrics.push(Metric { + action: Action::SendFunds, + context: Context::Migration, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: balance + claimed, + user: config.multisig.clone(), + }); + } + + METRICS.append(deps.storage, env.block.time, &mut metrics)?; + + Ok(Response::new() + .add_messages(messages) + .set_data(to_binary(&ExecuteAnswer::Migration { + status: ResponseStatus::Success, + })?)) +} + +pub fn set_run_level( + deps: DepsMut, + _env: &Env, + info: MessageInfo, + run_level: RunLevel, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + // TODO force super-admin? + validate_admin( + &deps.querier, + AdminPermissions::TreasuryAdmin, + &info.sender, + &config.admin_auth, + )?; + + RUN_LEVEL.save(deps.storage, &run_level)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RunLevel { run_level })?)) +} + +pub fn try_register_asset( + deps: DepsMut, + env: &Env, + info: MessageInfo, + contract: &Contract, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + validate_admin( + &deps.querier, + AdminPermissions::TreasuryAdmin, + &info.sender, + &config.admin_auth, + )?; + + ASSET_LIST.push(deps.storage, &contract.address.clone())?; + + ASSET.save( + deps.storage, + contract.address.clone(), + &snip20::helpers::fetch_snip20(contract, &deps.querier)?, + )?; + + ALLOWANCES.save(deps.storage, contract.address.clone(), &Vec::new())?; + + Ok(Response::new() + .add_message(register_receive( + env.contract.code_hash.clone(), + None, + contract, + )?) + .add_message(set_viewing_key_msg( + VIEWING_KEY.load(deps.storage)?, + None, + &contract.clone(), + )?) + .set_data(to_binary(&ExecuteAnswer::RegisterAsset { + status: ResponseStatus::Success, + })?)) +} + +pub fn register_wrap( + deps: DepsMut, + _env: &Env, + info: MessageInfo, + denom: String, + contract: &Contract, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + validate_admin( + &deps.querier, + AdminPermissions::TreasuryAdmin, + &info.sender, + &config.admin_auth, + )?; + + // Asset must be registered + if let Some(a) = ASSET.may_load(deps.storage, contract.address.clone())? { + // Deposit mut be enabled + if let Some(conf) = a.token_config { + if conf.deposit_enabled { + WRAP.save(deps.storage, denom, &contract.address)?; + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::RegisterWrap { + status: ResponseStatus::Success, + })?), + ) + } else { + Err(StdError::generic_err("Asset deposit not enabled")) + } + } else { + Err(StdError::generic_err("Asset has no token config")) + } + } else { + Err(StdError::generic_err("Unrecognized Asset")) + } +} + +pub fn register_manager( + deps: DepsMut, + _env: &Env, + info: MessageInfo, + contract: &mut Contract, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + validate_admin( + &deps.querier, + AdminPermissions::TreasuryAdmin, + &info.sender, + &config.admin_auth, + )?; + + // Ensure it isn't already registered + if let Some(_) = MANAGER.may_load(deps.storage, contract.address.clone())? { + return Err(StdError::generic_err("Manager already registered")); + } + + MANAGER.save(deps.storage, contract.address.clone(), &contract)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::RegisterManager { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn allowance( + deps: DepsMut, + _env: &Env, + info: MessageInfo, + asset: Addr, + allowance: Allowance, + refresh_now: bool, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + validate_admin( + &deps.querier, + AdminPermissions::TreasuryAdmin, + &info.sender, + &config.admin_auth, + )?; + + if ASSET.may_load(deps.storage, asset.clone())?.is_none() { + return Err(StdError::generic_err("Not a registered asset")); + } + + if allowance.tolerance >= ONE_HUNDRED_PERCENT { + return Err(StdError::generic_err(format!( + "Tolerance {} >= 100%", + allowance.tolerance + ))); + } + + let mut allowances = ALLOWANCES + .may_load(deps.storage, asset.clone())? + .unwrap_or(vec![]); + + // This will cause allowance refresh asap, changed below if !refresh_now + let mut last_refresh = utc_from_seconds(0).to_rfc3339(); + + // remove duplicated allowance + match allowances + .iter() + .position(|a| a.spender == allowance.spender) + { + Some(i) => { + if !refresh_now { + last_refresh = allowances[i].last_refresh.clone(); + } + allowances.swap_remove(i); + } + None => {} + }; + + allowances.push(AllowanceMeta { + spender: allowance.spender.clone(), + amount: allowance.amount, + cycle: allowance.cycle, + allowance_type: allowance.allowance_type.clone(), + // "zero/null" datetime, guarantees refresh next update + last_refresh, + tolerance: allowance.tolerance, + }); + + // ensure that the portion allocations don't go above 100% + if allowances + .iter() + .map(|a| { + if a.allowance_type == AllowanceType::Portion { + a.amount + } else { + Uint128::zero() + } + }) + .sum::() + > ONE_HUNDRED_PERCENT + { + return Err(StdError::generic_err( + "Invalid allowance total exceeding 100%", + )); + } + + // Sort list before going into storage + allowances.sort_by(|a, b| match a.allowance_type { + AllowanceType::Amount => match b.allowance_type { + AllowanceType::Amount => std::cmp::Ordering::Equal, + AllowanceType::Portion => std::cmp::Ordering::Less, + }, + AllowanceType::Portion => match b.allowance_type { + AllowanceType::Amount => std::cmp::Ordering::Greater, + AllowanceType::Portion => std::cmp::Ordering::Equal, + }, + }); + + ALLOWANCES.save(deps.storage, asset, &allowances)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::Allowance { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn wrap_coins(deps: DepsMut, env: &Env, info: MessageInfo) -> StdResult { + let coins = deps.querier.query_all_balances(&env.contract.address)?; + + let mut messages = vec![]; + let mut success = vec![]; + let mut failed = vec![]; + + for coin in coins { + if let Some(asset) = WRAP.may_load(deps.storage, coin.denom.clone())? { + let token = ASSET.load(deps.storage, asset)?; + messages.push(wrap_coin(coin.clone(), token.contract.clone())?); + success.push(coin.clone()); + METRICS.push(deps.storage, env.block.time, Metric { + action: Action::Wrap, + context: Context::Wrap, + timestamp: env.block.time.seconds(), + token: token.contract.address, + amount: coin.amount, + user: info.sender.clone(), + })?; + } else { + failed.push(coin); + } + } + + Ok(Response::new() + .add_messages(messages) + .set_data(to_binary(&ExecuteAnswer::WrapCoins { success, failed })?)) +} diff --git a/contracts/dao/treasury/src/lib.rs b/contracts/dao/treasury/src/lib.rs new file mode 100644 index 0000000..25ee0d9 --- /dev/null +++ b/contracts/dao/treasury/src/lib.rs @@ -0,0 +1,4 @@ +pub mod contract; +pub mod execute; +pub mod query; +pub mod storage; diff --git a/contracts/dao/treasury/src/query.rs b/contracts/dao/treasury/src/query.rs new file mode 100644 index 0000000..3c242a5 --- /dev/null +++ b/contracts/dao/treasury/src/query.rs @@ -0,0 +1,173 @@ +use crate::storage::*; +use shade_protocol::{ + c_std::{Addr, Deps, Env, StdError, StdResult, Uint128}, + contract_interfaces::dao::{adapter, manager, treasury}, + snip20::helpers::{allowance_query, balance_query}, + utils::{asset::Contract, cycle::parse_utc_datetime, storage::plus::period_storage::Period}, +}; +use std::collections::HashSet; + +pub fn config(deps: Deps) -> StdResult { + Ok(treasury::QueryAnswer::Config { + config: CONFIG.load(deps.storage)?, + }) +} + +pub fn metrics( + deps: Deps, + env: Env, + date: Option, + epoch: Option, + period: Period, +) -> StdResult { + if date.is_some() && epoch.is_some() { + return Err(StdError::generic_err("cannot pass both epoch and date")); + } + let key = { + if let Some(d) = date { + parse_utc_datetime(&d)?.timestamp() as u64 + } else if let Some(e) = epoch { + e.u128() as u64 + } else { + env.block.time.seconds() + } + }; + Ok(treasury::QueryAnswer::Metrics { + metrics: METRICS.load_period(deps.storage, key, period)?, + }) +} + +pub fn batch_balance(deps: Deps, env: Env, assets: Vec) -> StdResult> { + let mut balances = vec![]; + let mut managers: HashSet = HashSet::new(); + + for asset in assets.clone() { + let full_asset = match ASSET.may_load(deps.storage, asset.clone())? { + Some(a) => a, + None => { + return Err(StdError::generic_err("Unrecognized Asset")); + } + }; + + let balance = balance_query( + &deps.querier, + env.contract.address.clone(), + VIEWING_KEY.load(deps.storage)?, + &full_asset.contract.clone(), + )?; + + balances.push(balance); + + // build list of unique managers to query balances + for allowance in ALLOWANCES.load(deps.storage, asset.clone())? { + if let Some(m) = MANAGER.may_load(deps.storage, allowance.spender)? { + managers.insert(m); + } + } + } + for manager in managers { + let manager_balances = manager::batch_balance_query( + deps.querier, + &assets.clone(), + env.contract.address.clone(), + manager, + )?; + balances = balances + .into_iter() + .zip(manager_balances.into_iter()) + .map(|(a, b)| a + b) + .collect(); + } + + Ok(balances) +} + +pub fn balance(deps: Deps, env: Env, asset: Addr) -> StdResult { + let full_asset = match ASSET.may_load(deps.storage, asset.clone())? { + Some(a) => a, + None => { + return Err(StdError::generic_err("Unrecognized Asset")); + } + }; + + let allowances = ALLOWANCES.load(deps.storage, asset.clone())?; + + let mut balance = balance_query( + &deps.querier, + env.contract.address.clone(), + VIEWING_KEY.load(deps.storage)?, + &full_asset.contract.clone(), + )?; + + for allowance in allowances { + if let Some(m) = MANAGER.may_load(deps.storage, allowance.spender)? { + balance += manager::balance_query( + deps.querier, + &asset.clone(), + env.contract.address.clone(), + m, + )?; + } + } + Ok(adapter::QueryAnswer::Balance { amount: balance }) +} + +pub fn reserves(deps: Deps, env: Env, asset: Addr) -> StdResult { + //TODO: restrict to admin? + + let full_asset = match ASSET.may_load(deps.storage, asset.clone())? { + Some(a) => a, + None => { + return Err(StdError::generic_err("Unrecognized Asset")); + } + }; + + let reserves = balance_query( + &deps.querier, + env.contract.address.clone(), + VIEWING_KEY.load(deps.storage)?, + &full_asset.contract.clone(), + )?; + + Ok(adapter::QueryAnswer::Reserves { amount: reserves }) +} + +pub fn allowance( + deps: Deps, + env: Env, + asset: Addr, + spender: Addr, +) -> StdResult { + let key = VIEWING_KEY.load(deps.storage)?; + + let full_asset = match ASSET.may_load(deps.storage, asset.clone())? { + Some(a) => a, + None => { + return Err(StdError::generic_err("Unrecognized Asset")); + } + }; + + return Ok(treasury::QueryAnswer::Allowance { + amount: allowance_query( + &deps.querier, + env.contract.address, + spender.clone(), + key, + 1, + &full_asset.contract.clone(), + )? + .allowance, + }); +} + +pub fn assets(deps: Deps) -> StdResult { + Ok(treasury::QueryAnswer::Assets { + assets: ASSET_LIST.iter(deps.storage).collect(), + }) +} + +pub fn allowances(deps: Deps, asset: Addr) -> StdResult { + Ok(treasury::QueryAnswer::Allowances { + allowances: ALLOWANCES.may_load(deps.storage, asset)?.unwrap_or(vec![]), + }) +} diff --git a/contracts/dao/treasury/src/storage.rs b/contracts/dao/treasury/src/storage.rs new file mode 100644 index 0000000..339d21b --- /dev/null +++ b/contracts/dao/treasury/src/storage.rs @@ -0,0 +1,27 @@ +use shade_protocol::{ + c_std::Addr, + dao::treasury::{AllowanceMeta, Config, Metric, RunLevel}, + secret_storage_plus::{Item, Map}, + snip20::helpers::Snip20Asset, + utils::{ + asset::Contract, + storage::plus::{iter_item::IterItem, period_storage::PeriodStorage}, + }, +}; + +pub const CONFIG: Item = Item::new("config"); +pub const VIEWING_KEY: Item = Item::new("viewing_key"); + +pub const ASSET_LIST: IterItem = IterItem::new_override("asset_list", "asset_list_2"); +pub const ASSET: Map = Map::new("asset"); + +// { denom: snip20 } +pub const WRAP: Map = Map::new("wrap"); + +pub const MANAGER: Map = Map::new("managers"); +pub const ALLOWANCES: Map> = Map::new("allowances"); + +pub const RUN_LEVEL: Item = Item::new("runlevel"); + +pub const METRICS: PeriodStorage = + PeriodStorage::new("metrics-all", "metrics-recent", "metrics-timed"); diff --git a/contracts/dao/treasury/tests/dao/equilibrium.rs b/contracts/dao/treasury/tests/dao/equilibrium.rs new file mode 100644 index 0000000..4866b2f --- /dev/null +++ b/contracts/dao/treasury/tests/dao/equilibrium.rs @@ -0,0 +1,314 @@ +use shade_multi_test::interfaces::{ + dao::{ + init_dao, + mock_adapter_complete_unbonding, + system_balance_reserves, + system_balance_unbondable, + }, + treasury, + treasury_manager, + utils::{DeployedContracts, SupportedContracts}, +}; +use shade_protocol::{ + c_std::Uint128, + contract_interfaces::dao::{treasury::AllowanceType, treasury_manager::AllocationType}, + multi_test::App, + utils::cycle::Cycle, +}; + +pub fn equilibrium_test( + is_instant_unbond: bool, + initial_bals: (Uint128, Vec<(Uint128, Vec)>), +) { + let mut app = App::default(); + let mut contracts = DeployedContracts::new(); + let num_managers = 8; + init_dao( + &mut app, + "admin", + &mut contracts, + Uint128::new(1500), + "SSCRT", + vec![ + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + ], + vec![Cycle::Constant; 8], + vec![ + Uint128::new(5 * 10u128.pow(16)), // Poriton - 5% + Uint128::new(30), // Amount - 30 + Uint128::new(15 * 10u128.pow(16)), // Portion - 15% + Uint128::new(40), // Amount - 40 + Uint128::new(25 * 10u128.pow(16)), // Poriton - 25% + Uint128::new(50), // Amount - 50 + Uint128::new(35 * 10u128.pow(16)), // Portion - 35% + Uint128::new(20), // Amount - 20 + ], // Allowance amount + vec![Uint128::zero(); 8], + vec![ + vec![ + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + ]; + 8 + ], + vec![ + vec![ + Uint128::new(1), // Amount - 1 + Uint128::new(4 * 10u128.pow(16)), // Portion - 4% + Uint128::new(2), // Amount - 2 + Uint128::new(16 * 10u128.pow(16)), //Portion - 16% + Uint128::new(3), // Amount - 3 + Uint128::new(1 * 10u128.pow(17)), //Portion - 10% + Uint128::new(4), // Amount - 4 + Uint128::new(2 * 10u128.pow(17)), // Portion - 20% + ]; + 8 + ], + vec![vec![Uint128::zero(); 8]; 8], + is_instant_unbond, + true, + ) + .unwrap(); + for i in 0..20 { + let bals = { + if is_instant_unbond { + system_balance_reserves(&app, &contracts, "SSCRT") + } else { + system_balance_unbondable(&app, &contracts, "SSCRT") + } + }; + assert_eq!(bals, initial_bals, "loop: {}", i); + for tm in 0..num_managers { + treasury_manager::update_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(tm), + ) + .unwrap(); + } + treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); + if !is_instant_unbond { + let mut k = 0; + for _i in 0..num_managers { + for _j in 0..num_managers { + println!("{}", k); + mock_adapter_complete_unbonding( + &mut app, + "admin", + &contracts, + SupportedContracts::MockAdapter(k), + ) + .unwrap(); + k += 1; + } + k += 1; + } + } + } +} + +macro_rules! dao_tests_migration { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + is_instant_unbond, + initial_bals, + ) = $value; + equilibrium_test( + is_instant_unbond, + initial_bals, + ); + } + )* + } +} + +dao_tests_migration! ( + dao_test_equilibrium_instant_unbond: ( + true, + (Uint128::new(857), vec![ + (Uint128::new(0), vec![ // used - 38 + Uint128::new(1), + Uint128::new(2), + Uint128::new(2), + Uint128::new(9), + Uint128::new(3), + Uint128::new(5), + Uint128::new(4), + Uint128::new(11), + ]), + (Uint128::new(0), vec![ // used - 18 + Uint128::new(1), + Uint128::new(0), + Uint128::new(2), + Uint128::new(3), + Uint128::new(3), + Uint128::new(2), + Uint128::new(4), + Uint128::new(4), + ]), + (Uint128::new(0), vec![ // used - 105 + Uint128::new(1), + Uint128::new(7), + Uint128::new(2), + Uint128::new(31), + Uint128::new(3), + Uint128::new(19), + Uint128::new(4), + Uint128::new(38), + ]), + (Uint128::new(0), vec![ // used - 25 + Uint128::new(1), + Uint128::new(1), + Uint128::new(2), + Uint128::new(4), + Uint128::new(3), + Uint128::new(3), + Uint128::new(4), + Uint128::new(6), + ]), + (Uint128::new(0), vec![ // used - 174 + Uint128::new(1), + Uint128::new(13), + Uint128::new(2), + Uint128::new(52), + Uint128::new(3), + Uint128::new(33), + Uint128::new(4), + Uint128::new(66), + ]), + (Uint128::new(0), vec![ // used - 29 + Uint128::new(1), + Uint128::new(1), + Uint128::new(2), + Uint128::new(6), + Uint128::new(3), + Uint128::new(4), + Uint128::new(4), + Uint128::new(8), + ]), + (Uint128::new(0), vec![ // used - 241 + Uint128::new(1), + Uint128::new(18), + Uint128::new(2), + Uint128::new(74), + Uint128::new(3), + Uint128::new(46), + Uint128::new(4), + Uint128::new(93), + ]), + (Uint128::new(0), vec![ // used - 14 + Uint128::new(1), + Uint128::new(0), + Uint128::new(2), + Uint128::new(1), + Uint128::new(3), + Uint128::new(1), + Uint128::new(4), + Uint128::new(2), + ]), + ]), + ), + dao_test_equilibrium_non_instant_unbond: ( + false, + (Uint128::new(857), vec![ + (Uint128::new(0), vec![ // used - 38 + Uint128::new(1), + Uint128::new(2), + Uint128::new(2), + Uint128::new(9), + Uint128::new(3), + Uint128::new(5), + Uint128::new(4), + Uint128::new(11), + ]), + (Uint128::new(0), vec![ // used - 18 + Uint128::new(1), + Uint128::new(0), + Uint128::new(2), + Uint128::new(3), + Uint128::new(3), + Uint128::new(2), + Uint128::new(4), + Uint128::new(4), + ]), + (Uint128::new(0), vec![ // used - 105 + Uint128::new(1), + Uint128::new(7), + Uint128::new(2), + Uint128::new(31), + Uint128::new(3), + Uint128::new(19), + Uint128::new(4), + Uint128::new(38), + ]), + (Uint128::new(0), vec![ // used - 25 + Uint128::new(1), + Uint128::new(1), + Uint128::new(2), + Uint128::new(4), + Uint128::new(3), + Uint128::new(3), + Uint128::new(4), + Uint128::new(6), + ]), + (Uint128::new(0), vec![ // used - 174 + Uint128::new(1), + Uint128::new(13), + Uint128::new(2), + Uint128::new(52), + Uint128::new(3), + Uint128::new(33), + Uint128::new(4), + Uint128::new(66), + ]), + (Uint128::new(0), vec![ // used - 29 + Uint128::new(1), + Uint128::new(1), + Uint128::new(2), + Uint128::new(6), + Uint128::new(3), + Uint128::new(4), + Uint128::new(4), + Uint128::new(8), + ]), + (Uint128::new(0), vec![ // used - 241 + Uint128::new(1), + Uint128::new(18), + Uint128::new(2), + Uint128::new(74), + Uint128::new(3), + Uint128::new(46), + Uint128::new(4), + Uint128::new(93), + ]), + (Uint128::new(0), vec![ // used - 14 + Uint128::new(1), + Uint128::new(0), + Uint128::new(2), + Uint128::new(1), + Uint128::new(3), + Uint128::new(1), + Uint128::new(4), + Uint128::new(2), + ]), + ]), + ), +); diff --git a/contracts/dao/treasury/tests/dao/gains_losses.rs b/contracts/dao/treasury/tests/dao/gains_losses.rs new file mode 100644 index 0000000..60ee116 --- /dev/null +++ b/contracts/dao/treasury/tests/dao/gains_losses.rs @@ -0,0 +1,1012 @@ +use shade_multi_test::interfaces::{ + dao::{ + init_dao, + mock_adapter_complete_unbonding, + mock_adapter_sub_tokens, + system_balance_reserves, + system_balance_unbondable, + }, + snip20, + treasury, + treasury_manager, + utils::{DeployedContracts, SupportedContracts}, +}; +use shade_protocol::{ + c_std::Uint128, + contract_interfaces::dao::{treasury::AllowanceType, treasury_manager::AllocationType}, + multi_test::App, + utils::cycle::Cycle, +}; + +pub fn dao_int_gains_losses( + initial_treasury_bal: Uint128, + allow_type: Vec, + t_cycle: Vec, + allow_amount: Vec, + allow_tolerance: Vec, + alloc_type: Vec>, + alloc_amount: Vec>, + alloc_tolerance: Vec>, + is_instant_unbond: bool, + expected_after_init: (Uint128, Vec<(Uint128, Vec)>), + snip20_send_amount: Uint128, + adapters_to_send_to: Vec, + is_adapters_gain: Vec, + expected_in_between_updates: (Uint128, Vec<(Uint128, Vec)>), + expected_after_updates: (Uint128, Vec<(Uint128, Vec)>), +) { + let mut app = App::default(); + let mut contracts = DeployedContracts::new(); + let num_managers = allow_type.len(); + init_dao( + &mut app, + "admin", + &mut contracts, + initial_treasury_bal, + "SSCRT", + allow_type, + t_cycle, + allow_amount, + allow_tolerance, + alloc_type, + alloc_amount.clone(), + alloc_tolerance, + is_instant_unbond, + true, + ) + .unwrap(); + let bals = { + if is_instant_unbond { + system_balance_reserves(&app, &contracts, "SSCRT") + } else { + system_balance_unbondable(&app, &contracts, "SSCRT") + } + }; + assert_eq!(bals, expected_after_init, "AFTER INITIALIZATION"); + for (i, adap) in adapters_to_send_to.clone().iter().enumerate() { + if is_adapters_gain[i] { + snip20::send_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + contracts + .get(&SupportedContracts::MockAdapter(adap.clone())) + .unwrap() + .address + .to_string(), + snip20_send_amount, + None, + ) + .unwrap(); + } else { + mock_adapter_sub_tokens( + &mut app, + "admin", + &contracts, + snip20_send_amount, + SupportedContracts::MockAdapter(adap.clone()), + ) + .unwrap(); + } + } + // Needs 2 full cycles to reballance fully + for tm in 0..num_managers { + treasury_manager::update_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(tm), + ) + .unwrap(); + } + treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); + let bals = { + if is_instant_unbond { + let sys_bal = system_balance_reserves(&app, &contracts, "SSCRT"); + assert_eq!(sys_bal, expected_in_between_updates, "AFTER FIRST UPDATE"); + for tm in 0..num_managers { + treasury_manager::update_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(tm), + ) + .unwrap(); + } + treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); + for tm in 0..num_managers { + treasury_manager::update_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(tm), + ) + .unwrap(); + } + treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); + system_balance_reserves(&app, &contracts, "SSCRT") + } else { + let _sys_bal = system_balance_unbondable(&app, &contracts, "SSCRT"); + //assert_eq!(sys_bal, expected_in_between_updates, "AFTER FIRST UPDATE"); + let mut k = 0; + for i in 0..num_managers { + for _j in 0..alloc_amount[i].len() { + mock_adapter_complete_unbonding( + &mut app, + "admin", + &contracts, + SupportedContracts::MockAdapter(k), + ) + .unwrap(); + k += 1; + } + k += 1; + } + for tm in 0..num_managers { + treasury_manager::update_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(tm), + ) + .unwrap(); + } + treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); + for tm in 0..num_managers { + treasury_manager::update_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(tm), + ) + .unwrap(); + } + treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); + let mut k = 0; + for i in 0..num_managers { + for _j in 0..alloc_amount[i].len() { + println!("{}", k); + mock_adapter_complete_unbonding( + &mut app, + "admin", + &contracts, + SupportedContracts::MockAdapter(k), + ) + .unwrap(); + k += 1; + } + k += 1; + } + for tm in 0..num_managers { + treasury_manager::update_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(tm), + ) + .unwrap(); + } + treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); + for tm in 0..num_managers { + treasury_manager::update_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(tm), + ) + .unwrap(); + } + treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); + //update_dao(&mut app, "admin", &contracts, "SSCRT", num_managers); + system_balance_unbondable(&app, &contracts, "SSCRT") + } + }; + assert_eq!(bals, expected_after_updates, "AFTER BOTH UPDATES"); +} + +macro_rules! dao_tests_gains_losses { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + initial_treasury_bal, + allow_type, + t_cycle, + allow_amount, + allow_tolerance, + alloc_type, + alloc_amount, + alloc_tolerance, + is_instant_unbond, + expected_after_init, + snip20_send_amount, + adapters_to_send_to, + is_adapters_gain, + expected_in_between_updates, + expected_after_updates, + ) = $value; + dao_int_gains_losses( + initial_treasury_bal, + allow_type, + t_cycle, + allow_amount, + allow_tolerance, + alloc_type, + alloc_amount, + alloc_tolerance, + is_instant_unbond, + expected_after_init, + snip20_send_amount, + adapters_to_send_to, + is_adapters_gain, + expected_in_between_updates, + expected_after_updates, + ); + } + )* + } +} + +dao_tests_gains_losses! { + dao_test_gains:( + Uint128::new(1000), + vec![AllowanceType::Portion], + vec![Cycle::Constant], + vec![ + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + ], // Allowance amount + vec![Uint128::zero()], + vec![vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount, + ]], + vec![vec![ + Uint128::new(6 * 10u128.pow(17)), + Uint128::new(5), + Uint128::new(2 * 10u128.pow(17)), + Uint128::new(15), + ]], + vec![vec![Uint128::zero(); 4]], + true, + (Uint128::new(516), vec![(Uint128::new(0), vec![ + Uint128::new(348), + Uint128::new(5), + Uint128::new(116), + Uint128::new(15), + ])]), + Uint128::new(100), + vec![0, 1, 2], + vec![true, true, true], + (Uint128::new(520), vec![(Uint128::new(56), vec![ + Uint128::new(528), + Uint128::new(5), + Uint128::new(176), + Uint128::new(15), + ])]), + (Uint128::new(520), vec![(Uint128::new(152), vec![ + Uint128::new(456), + Uint128::new(5), + Uint128::new(152), + Uint128::new(15), + ])]), + ), + dao_test_gains_4_managers: ( + Uint128::new(1000), + vec![ + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + ], + vec![Cycle::Constant; 4], + vec![ + Uint128::new(50), // Amount - 50 + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + Uint128::new(100), // Amount - 100 + Uint128::new(2 * 10u128.pow(17)), // Portion - 40% + ], // Allowance amount + vec![Uint128::zero(); 4], + vec![ + vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount + ]; + 4 + ], + vec![ + vec![ + Uint128::new(6 * 10u128.pow(17)), + Uint128::new(5), + Uint128::new(2 * 10u128.pow(17)), + Uint128::new(15) + ]; + 4 + ], + vec![vec![Uint128::zero(); 4]; 4], + true, + (Uint128::new(320), vec![ + (Uint128::new(0), vec![ + Uint128::new(18), + Uint128::new(5), + Uint128::new(6), + Uint128::new(15), + ]), + (Uint128::new(0), vec![ + Uint128::new(294), + Uint128::new(5), + Uint128::new(98), + Uint128::new(15), + ]), + (Uint128::new(0), vec![ + Uint128::new(48), + Uint128::new(5), + Uint128::new(16), + Uint128::new(15), + ]), + (Uint128::new(0), vec![ + Uint128::new(90), + Uint128::new(5), + Uint128::new(30), + Uint128::new(15), + ]), + ]), + Uint128::new(100), + vec![0, 1, 2, 3, 5, 7, 10, 12, 16], + vec![true; 11], + (Uint128::new(528), vec![ + (Uint128::new(180), vec![ + Uint128::new(18), + Uint128::new(5), + Uint128::new(12), + Uint128::new(15), + ]), + (Uint128::new(60), vec![ + Uint128::new(414), + Uint128::new(5), + Uint128::new(138), + Uint128::new(15), + ]), + (Uint128::new(140), vec![ + Uint128::new(60), + Uint128::new(5), + Uint128::new(20), + Uint128::new(15), + ]), + (Uint128::new(100), vec![ + Uint128::new(120), + Uint128::new(5), + Uint128::new(30), + Uint128::new(15), + ]), + ]), + (Uint128::new(622), vec![ + (Uint128::new(6), vec![ + Uint128::new(18), + Uint128::new(5), + Uint128::new(6), + Uint128::new(15), + ]), + (Uint128::new(0), vec![ + Uint128::new(618), + Uint128::new(5), + Uint128::new(206), + Uint128::new(15), + ]), + (Uint128::new(16), vec![ + Uint128::new(48), + Uint128::new(5), + Uint128::new(16), + Uint128::new(15), + ]), + (Uint128::new(0), vec![ + Uint128::new(198), + Uint128::new(5), + Uint128::new(66), + Uint128::new(15), + ]), + ]), + ), + dao_test_losses: ( + Uint128::new(1000), + vec![ + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + ], + vec![Cycle::Constant; 4], + vec![ + Uint128::new(50), // Amount - 50 + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + Uint128::new(100), // Amount - 100 + Uint128::new(2 * 10u128.pow(17)), // Portion - 40% + ], // Allowance amount + vec![Uint128::zero(); 4], + vec![ + vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount + ]; + 4 + ], + vec![ + vec![ + Uint128::new(6 * 10u128.pow(17)), + Uint128::new(5), + Uint128::new(2 * 10u128.pow(17)), + Uint128::new(15) + ]; + 4 + ], + vec![vec![Uint128::zero(); 4]; 4], + true, + (Uint128::new(320), vec![ + (Uint128::new(0), vec![ + Uint128::new(18), + Uint128::new(5), + Uint128::new(6), + Uint128::new(15), + ]), + (Uint128::new(0), vec![ + Uint128::new(294), + Uint128::new(5), + Uint128::new(98), + Uint128::new(15), + ]), + (Uint128::new(0), vec![ + Uint128::new(48), + Uint128::new(5), + Uint128::new(16), + Uint128::new(15), + ]), + (Uint128::new(0), vec![ + Uint128::new(90), + Uint128::new(5), + Uint128::new(30), + Uint128::new(15), + ]), + ]), + Uint128::new(5), + vec![0, 1, 2, 3, 5, 7, 10, 12, 16], + vec![false; 9], + (Uint128::new(303), vec![ + (Uint128::new(7), vec![ + Uint128::new(6), + Uint128::new(5), + Uint128::new(1), + Uint128::new(11), + ]), + (Uint128::new(1), vec![ + Uint128::new(288), + Uint128::new(5), + Uint128::new(96), + Uint128::new(15), + ]), + (Uint128::new(1), vec![ + Uint128::new(42), + Uint128::new(5), + Uint128::new(14), + Uint128::new(15), + ]), + (Uint128::new(4), vec![ + Uint128::new(87), + Uint128::new(5), + Uint128::new(29), + Uint128::new(15), + ]), + ]), + (Uint128::new(282), vec![ + (Uint128::new(0), vec![ + Uint128::new(18), + Uint128::new(5), + Uint128::new(6), + Uint128::new(15), + ]), + (Uint128::new(16), vec![ + Uint128::new(277), + Uint128::new(5), + Uint128::new(92), + Uint128::new(15), + ]), + (Uint128::new(0), vec![ + Uint128::new(48), + Uint128::new(5), + Uint128::new(16), + Uint128::new(15), + ]), + (Uint128::new(8), vec![ + Uint128::new(84), + Uint128::new(5), + Uint128::new(28), + Uint128::new(15), + ]), + ]), + ), + dao_test_losses_and_gains: ( + Uint128::new(1500), + vec![ + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + ], + vec![Cycle::Constant; 4], + vec![ + Uint128::new(200), // Amount - 50 + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + Uint128::new(300), // Amount - 100 + Uint128::new(3 * 10u128.pow(17)), // Portion - 40% + ], // Allowance amount + vec![Uint128::zero(); 4], + vec![ + vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount + ]; + 4 + ], + vec![ + vec![ + Uint128::new(6 * 10u128.pow(17)), + Uint128::new(50), + Uint128::new(2 * 10u128.pow(17)), + Uint128::new(75) + ]; + 4 + ], + vec![vec![Uint128::zero(); 4]; 4], + true, + (Uint128::new(280), vec![ + (Uint128::new(0), vec![ + Uint128::new(45), + Uint128::new(50), + Uint128::new(15), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(285), + Uint128::new(50), + Uint128::new(95), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(105), + Uint128::new(50), + Uint128::new(35), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(105), + Uint128::new(50), + Uint128::new(35), + Uint128::new(75), + ]), + ]), + Uint128::new(50), + vec![0, 1, 2, 3, 5, 7, 10, 12, 16], + vec![true, false, true, false, false, true, true, true, false], + (Uint128::new(200), vec![ + (Uint128::new(100), vec![ + Uint128::new(45), + Uint128::new(15), + Uint128::new(15), + Uint128::new(25), + ]), + (Uint128::new(50), vec![ + Uint128::new(285), + Uint128::new(50), + Uint128::new(95), + Uint128::new(75), + ]), + (Uint128::new(45), vec![ + Uint128::new(132), + Uint128::new(50), + Uint128::new(43), + Uint128::new(75), + ]), + (Uint128::new(40), vec![ + Uint128::new(75), + Uint128::new(35), + Uint128::new(25), + Uint128::new(75), + ]), + ]), + (Uint128::new(218), vec![ + (Uint128::new(15), vec![ + Uint128::new(45), + Uint128::new(50), + Uint128::new(15), + Uint128::new(75), + ]), + (Uint128::new(26), vec![ + Uint128::new(303), + Uint128::new(50), + Uint128::new(101), + Uint128::new(75), + ]), + (Uint128::new(35), vec![ + Uint128::new(105), + Uint128::new(50), + Uint128::new(35), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(114), + Uint128::new(50), + Uint128::new(38), + Uint128::new(75), + ]), + ]), + ), + dao_test_gains_4_managers_with_unbond: ( + Uint128::new(1000), + vec![ + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + ], + vec![Cycle::Constant; 4], + vec![ + Uint128::new(50), // Amount - 50 + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + Uint128::new(100), // Amount - 100 + Uint128::new(2 * 10u128.pow(17)), // Portion - 40% + ], // Allowance amount + vec![Uint128::zero(); 4], + vec![ + vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount + ]; + 4 + ], + vec![ + vec![ + Uint128::new(6 * 10u128.pow(17)), + Uint128::new(5), + Uint128::new(2 * 10u128.pow(17)), + Uint128::new(15) + ]; + 4 + ], + vec![vec![Uint128::zero(); 4]; 4], + false, + (Uint128::new(320), vec![ + (Uint128::new(0), vec![ + Uint128::new(18), + Uint128::new(5), + Uint128::new(6), + Uint128::new(15), + ]), + (Uint128::new(0), vec![ + Uint128::new(294), + Uint128::new(5), + Uint128::new(98), + Uint128::new(15), + ]), + (Uint128::new(0), vec![ + Uint128::new(48), + Uint128::new(5), + Uint128::new(16), + Uint128::new(15), + ]), + (Uint128::new(0), vec![ + Uint128::new(90), + Uint128::new(5), + Uint128::new(30), + Uint128::new(15), + ]), + ]), + Uint128::new(100), + vec![0, 1, 2, 3, 5, 7, 10, 12, 16], + vec![true; 11], + (Uint128::new(528), vec![ + (Uint128::new(180), vec![ + Uint128::new(18), + Uint128::new(5), + Uint128::new(12), + Uint128::new(15), + ]), + (Uint128::new(60), vec![ + Uint128::new(414), + Uint128::new(5), + Uint128::new(138), + Uint128::new(15), + ]), + (Uint128::new(140), vec![ + Uint128::new(60), + Uint128::new(5), + Uint128::new(20), + Uint128::new(15), + ]), + (Uint128::new(100), vec![ + Uint128::new(120), + Uint128::new(5), + Uint128::new(30), + Uint128::new(15), + ]), + ]), + (Uint128::new(622), vec![ + (Uint128::new(6), vec![ + Uint128::new(18), + Uint128::new(5), + Uint128::new(6), + Uint128::new(15), + ]), + (Uint128::new(0), vec![ + Uint128::new(618), + Uint128::new(5), + Uint128::new(206), + Uint128::new(15), + ]), + (Uint128::new(16), vec![ + Uint128::new(48), + Uint128::new(5), + Uint128::new(16), + Uint128::new(15), + ]), + (Uint128::new(0), vec![ + Uint128::new(198), + Uint128::new(5), + Uint128::new(66), + Uint128::new(15), + ]), + ]), + ), + dao_test_losses_with_unbond: ( + Uint128::new(1000), + vec![ + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + ], + vec![Cycle::Constant; 4], + vec![ + Uint128::new(50), // Amount - 50 + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + Uint128::new(100), // Amount - 100 + Uint128::new(2 * 10u128.pow(17)), // Portion - 40% + ], // Allowance amount + vec![Uint128::zero(); 4], + vec![ + vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount + ]; + 4 + ], + vec![ + vec![ + Uint128::new(6 * 10u128.pow(17)), + Uint128::new(5), + Uint128::new(2 * 10u128.pow(17)), + Uint128::new(15) + ]; + 4 + ], + vec![vec![Uint128::zero(); 4]; 4], + false, + (Uint128::new(320), vec![ + (Uint128::new(0), vec![ + Uint128::new(18), + Uint128::new(5), + Uint128::new(6), + Uint128::new(15), + ]), + (Uint128::new(0), vec![ + Uint128::new(294), + Uint128::new(5), + Uint128::new(98), + Uint128::new(15), + ]), + (Uint128::new(0), vec![ + Uint128::new(48), + Uint128::new(5), + Uint128::new(16), + Uint128::new(15), + ]), + (Uint128::new(0), vec![ + Uint128::new(90), + Uint128::new(5), + Uint128::new(30), + Uint128::new(15), + ]), + ]), + Uint128::new(5), + vec![0, 1, 2, 3, 5, 7, 10, 12, 16], + vec![false; 9], + (Uint128::new(303), vec![ + (Uint128::new(7), vec![ + Uint128::new(6), + Uint128::new(5), + Uint128::new(1), + Uint128::new(11), + ]), + (Uint128::new(1), vec![ + Uint128::new(288), + Uint128::new(5), + Uint128::new(96), + Uint128::new(15), + ]), + (Uint128::new(1), vec![ + Uint128::new(42), + Uint128::new(5), + Uint128::new(14), + Uint128::new(15), + ]), + (Uint128::new(4), vec![ + Uint128::new(87), + Uint128::new(5), + Uint128::new(29), + Uint128::new(15), + ]), + ]), + (Uint128::new(275), vec![ + (Uint128::new(6), vec![ + Uint128::new(18), + Uint128::new(5), + Uint128::new(6), + Uint128::new(15), + ]), + (Uint128::new(16), vec![ + Uint128::new(277), + Uint128::new(5), + Uint128::new(92), + Uint128::new(15), + ]), + (Uint128::new(1), vec![ + Uint128::new(48), + Uint128::new(5), + Uint128::new(16), + Uint128::new(15), + ]), + (Uint128::new(8), vec![ + Uint128::new(84), + Uint128::new(5), + Uint128::new(28), + Uint128::new(15), + ]), + ]), + ), + dao_test_losses_and_gains_with_unbond: ( + Uint128::new(1500), + vec![ + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + ], + vec![Cycle::Constant; 4], + vec![ + Uint128::new(200), // Amount - 50 + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + Uint128::new(300), // Amount - 100 + Uint128::new(3 * 10u128.pow(17)), // Portion - 40% + ], // Allowance amount + vec![Uint128::zero(); 4], + vec![ + vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount + ]; + 4 + ], + vec![ + vec![ + Uint128::new(6 * 10u128.pow(17)), + Uint128::new(50), + Uint128::new(2 * 10u128.pow(17)), + Uint128::new(75) + ]; + 4 + ], + vec![vec![Uint128::zero(); 4]; 4], + false, + (Uint128::new(280), vec![ + (Uint128::new(0), vec![ + Uint128::new(45), + Uint128::new(50), + Uint128::new(15), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(285), + Uint128::new(50), + Uint128::new(95), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(105), + Uint128::new(50), + Uint128::new(35), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(105), + Uint128::new(50), + Uint128::new(35), + Uint128::new(75), + ]), + ]), + Uint128::new(50), + vec![0, 1, 2, 3, 5, 7, 10, 12, 16], + vec![true, false, true, false, false, true, true, true, false], + (Uint128::new(200), vec![ + (Uint128::new(100), vec![ + Uint128::new(45), + Uint128::new(15), + Uint128::new(15), + Uint128::new(25), + ]), + (Uint128::new(50), vec![ + Uint128::new(285), + Uint128::new(50), + Uint128::new(95), + Uint128::new(75), + ]), + (Uint128::new(45), vec![ + Uint128::new(132), + Uint128::new(50), + Uint128::new(43), + Uint128::new(75), + ]), + (Uint128::new(40), vec![ + Uint128::new(75), + Uint128::new(35), + Uint128::new(25), + Uint128::new(75), + ]), + ]), + (Uint128::new(156), vec![ + (Uint128::new(15), vec![ + Uint128::new(45), + Uint128::new(50), + Uint128::new(15), + Uint128::new(75), + ]), + (Uint128::new(50), vec![ + Uint128::new(303), + Uint128::new(50), + Uint128::new(101), + Uint128::new(75), + ]), + (Uint128::new(35), vec![ + Uint128::new(105), + Uint128::new(50), + Uint128::new(35), + Uint128::new(75), + ]), + (Uint128::new(38), vec![ + Uint128::new(114), + Uint128::new(50), + Uint128::new(38), + Uint128::new(75), + ]), + ]), + ), +} diff --git a/contracts/dao/treasury/tests/dao/mod.rs b/contracts/dao/treasury/tests/dao/mod.rs new file mode 100644 index 0000000..fe8b4df --- /dev/null +++ b/contracts/dao/treasury/tests/dao/mod.rs @@ -0,0 +1,322 @@ +pub mod equilibrium; +pub mod gains_losses; + +use shade_multi_test::interfaces::{ + dao::{ + init_dao, + mock_adapter_complete_unbonding, + system_balance_reserves, + system_balance_unbondable, + update_dao, + }, + treasury, + treasury_manager, + utils::{DeployedContracts, SupportedContracts}, +}; +use shade_protocol::{ + c_std::Uint128, + contract_interfaces::dao::{self, treasury::AllowanceType, treasury_manager::AllocationType}, + multi_test::App, + utils::cycle::Cycle, +}; + +pub fn dao_int_test( + initial_treasury_bal: Uint128, + snip20_symbol: &str, + allow_amount: Vec, + allow_type: Vec, + cycle: Vec, + allow_tolerance: Vec, + expected_allowance: Vec, + alloc_amount: Vec>, + alloc_type: Vec>, + alloc_tolerance: Vec>, + is_instant_unbond: bool, + expected_treasury: Uint128, + expected_manager: Vec, + expected_adapter: Vec>, +) { + let mut app = App::default(); + let mut contracts = DeployedContracts::new(); + let num_managers = allow_amount.len(); + init_dao( + &mut app, + "admin", + &mut contracts, + initial_treasury_bal, + snip20_symbol.clone(), + allow_type.clone(), + cycle.clone(), + allow_amount.clone(), + allow_tolerance.clone(), + alloc_type.clone(), + alloc_amount.clone(), + alloc_tolerance.clone(), + is_instant_unbond, + true, + ) + .unwrap(); + //query assets + let assets_query_res = treasury::assets_query(&app, &contracts).unwrap(); + assert!( + assets_query_res.contains( + &contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .address + ) + ); + //query allowance + for i in 0..num_managers { + assert_eq!( + expected_allowance[i], + treasury::allowance_query( + &app, + &contracts, + snip20_symbol, + SupportedContracts::TreasuryManager(i) + ) + .unwrap(), + "Treasury->Manager Allowance", + ); + } + let mut bals; + if is_instant_unbond { + bals = system_balance_reserves(&app, &contracts, snip20_symbol); + } else { + bals = system_balance_unbondable(&app, &contracts, snip20_symbol); + } + assert_eq!(bals.0, expected_treasury); + for (i, manager_tuples) in bals.1.iter().enumerate() { + assert_eq!(manager_tuples.0, expected_manager[i]); + for (j, adapter_bals) in manager_tuples.1.iter().enumerate() { + assert_eq!(adapter_bals.clone(), expected_adapter[i][j]); + } + } + let mut k = 0; + for i in 0..num_managers { + treasury::allowance_exec( + &mut app, + "admin", + &contracts, + snip20_symbol, + i, + allow_type[i].clone(), + cycle[i].clone(), + Uint128::zero(), + allow_tolerance[i].clone(), + true, + ) + .unwrap(); + for j in 0..alloc_amount[i].len() { + treasury_manager::allocate_exec( + &mut app, + "admin", + &contracts, + snip20_symbol, + Some(j.to_string()), + &SupportedContracts::MockAdapter(k), + alloc_type[i][j].clone(), + Uint128::zero(), + alloc_tolerance[i][j].clone(), + i, + ) + .unwrap(); + k += 1; + } + k += 1; + } + + update_dao(&mut app, "admin", &contracts, "SSCRT", num_managers).unwrap(); + treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); + if !is_instant_unbond { + k = 0; + for i in 0..num_managers { + for _j in 0..alloc_amount[i].len() { + println!("{}", k); + mock_adapter_complete_unbonding( + &mut app, + "admin", + &contracts, + SupportedContracts::MockAdapter(k), + ) + .unwrap(); + k += 1; + } + k += 1; + } + update_dao(&mut app, "admin", &contracts, "SSCRT", num_managers).unwrap(); + bals = system_balance_unbondable(&app, &contracts, "SSCRT"); + } else { + bals = system_balance_reserves(&app, &contracts, "SSCRT"); + } + assert_eq!(bals.0, initial_treasury_bal); + for (_i, manager_tuples) in bals.1.iter().enumerate() { + assert_eq!(manager_tuples.0, Uint128::zero()); + for (_j, adapter_bals) in manager_tuples.1.iter().enumerate() { + assert_eq!(adapter_bals.clone(), Uint128::zero()); + } + } + treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); + assert_eq!( + std::vec::Vec::::new(), + treasury::allowances_query(&app, &contracts, "SSCRT",).unwrap() + ); +} + +macro_rules! dao_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + initial_treasury_bal, + snip20_symbol, + allow_amount, + allow_type, + cycle, + allow_tolerance, + expected_allowance, + alloc_amount, + alloc_type, + alloc_tolerance, + is_instant_unbond, + expected_treasury, + expected_manager, + expected_adapter, + ) = $value; + dao_int_test( + initial_treasury_bal, + snip20_symbol, + allow_amount, + allow_type, + cycle, + allow_tolerance, + expected_allowance, + alloc_amount, + alloc_type, + alloc_tolerance, + is_instant_unbond, + expected_treasury, + expected_manager, + expected_adapter, + ); + } + )* + } +} + +dao_tests! { + dao_test_0:( + Uint128::new(1_000_000), + "SSCRT", + vec![Uint128::new(5 * 10u128.pow(17))], + vec![AllowanceType::Portion], + vec![Cycle::Constant], + vec![Uint128::zero()], + vec![Uint128::new(90_000)], + vec![vec![Uint128::new(1 * 10u128.pow(17)), Uint128::new(400_000)]], + vec![vec![AllocationType::Portion, AllocationType::Amount]], + vec![vec![Uint128::zero(), Uint128::zero()]], + true, + Uint128::new(590_000), + vec![Uint128::new(0)], + vec![vec![Uint128::new(10_000), Uint128::new(400_000)]], + ), + dao_test_1:( + Uint128::new(100_000_000), + "SSCRT", + vec![Uint128::new(50_000_000), Uint128::new(40_000_000)], + vec![AllowanceType::Amount, AllowanceType::Amount], + vec![Cycle::Constant, Cycle::Constant], + vec![Uint128::zero(), Uint128::zero()], + vec![Uint128::new(21_000_000), Uint128::new(18_000_000)], + vec![vec![Uint128::new(5 * 10u128.pow(17)), Uint128::new(4_000_000), Uint128::new(4_000_000)], vec![Uint128::new(5 * 10u128.pow(17)), Uint128::new(4_000_000)]], + vec![vec![AllocationType::Portion, AllocationType::Amount, AllocationType::Amount],vec![AllocationType::Portion, AllocationType::Amount]], + vec![vec![Uint128::zero(), Uint128::zero(), Uint128::zero()],vec![Uint128::zero(), Uint128::zero()]], + true, + Uint128::new(49_000_000), + vec![Uint128::new(0), Uint128::new(0)], + vec![vec![Uint128::new(21_000_000), Uint128::new(4_000_000), Uint128::new(4_000_000)],vec![Uint128::new(18_000_000), Uint128::new(4_000_000)]], + ), + dao_test_2:( + Uint128::new(100), + "SSCRT", + vec![Uint128::new(5 * 10u128.pow(17))], + vec![AllowanceType::Portion], + vec![Cycle::Constant], + vec![Uint128::zero()], + vec![Uint128::new(9)], + vec![vec![Uint128::new(1 * 10u128.pow(17)), Uint128::new(40)]], + vec![vec![AllocationType::Portion, AllocationType::Amount]], + vec![vec![Uint128::zero(), Uint128::zero()]], + true, + Uint128::new(59), + vec![Uint128::new(0)], + vec![vec![Uint128::new(1), Uint128::new(40)]], + ), + dao_test_3: ( + Uint128::new(1000), + "SSCRT", + vec![ + Uint128::new(50), // Amount - 50 + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + Uint128::new(100), // Amount - 100 + Uint128::new(4 * 10u128.pow(17)), // Portion - 40% + ], // Allowance amount + vec![AllowanceType::Amount, AllowanceType::Portion, AllowanceType::Amount, AllowanceType::Portion], + vec![Cycle::Constant; 4], + vec![Uint128::zero(); 4], + vec![Uint128::new(6), Uint128::new(98), Uint128::new(16), Uint128::new(64)], + vec![ + vec![Uint128::new(6 * 10u128.pow(17)), Uint128::new(5), Uint128::new(2 * 10u128.pow(17)), Uint128::new(15)];4 + ], + vec![ + vec![AllocationType::Portion, AllocationType::Amount, AllocationType::Portion, AllocationType::Amount];4 + ], + vec![ + vec![Uint128::zero(); 4]; 4 + ], + true, + Uint128::new(184), + vec![Uint128::zero(); 4], + vec![ + vec![Uint128::new(18), Uint128::new(5), Uint128::new(6), Uint128::new(15)], + vec![Uint128::new(294), Uint128::new(5), Uint128::new(98), Uint128::new(15)], + vec![Uint128::new(48), Uint128::new(5), Uint128::new(16), Uint128::new(15)], + vec![Uint128::new(192), Uint128::new(5), Uint128::new(64), Uint128::new(15)], + ] + ), + dao_test_adapter_unbonding: ( + Uint128::new(1000), + "SSCRT", + vec![ + Uint128::new(50), // Amount - 50 + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + Uint128::new(100), // Amount - 100 + Uint128::new(4 * 10u128.pow(17)), // Portion - 40% + ], // Allowance amount + vec![AllowanceType::Amount, AllowanceType::Portion, AllowanceType::Amount, AllowanceType::Portion], + vec![Cycle::Constant; 4], + vec![Uint128::zero(); 4], + vec![Uint128::new(6), Uint128::new(98), Uint128::new(16), Uint128::new(64)], + vec![ + vec![Uint128::new(6 * 10u128.pow(17)), Uint128::new(5), Uint128::new(2 * 10u128.pow(17)), Uint128::new(15)];4 + ], + vec![ + vec![AllocationType::Portion, AllocationType::Amount, AllocationType::Portion, AllocationType::Amount];4 + ], + vec![ + vec![Uint128::zero(); 4]; 4 + ], + false, + Uint128::new(184), + vec![Uint128::zero(); 4], + vec![ + vec![Uint128::new(18), Uint128::new(5), Uint128::new(6), Uint128::new(15)], + vec![Uint128::new(294), Uint128::new(5), Uint128::new(98), Uint128::new(15)], + vec![Uint128::new(48), Uint128::new(5), Uint128::new(16), Uint128::new(15)], + vec![Uint128::new(192), Uint128::new(5), Uint128::new(64), Uint128::new(15)], + ] + ), +} diff --git a/contracts/dao/treasury/tests/integration/allowance.rs b/contracts/dao/treasury/tests/integration/allowance.rs new file mode 100644 index 0000000..cbf0602 --- /dev/null +++ b/contracts/dao/treasury/tests/integration/allowance.rs @@ -0,0 +1,389 @@ +use shade_multi_test::multi::{admin::init_admin_auth, snip20::Snip20, treasury::Treasury}; +use shade_protocol::{ + c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}, + contract_interfaces::{ + dao::{treasury, treasury::AllowanceType}, + snip20, + }, + multi_test::App, + utils::{ + cycle::{parse_utc_datetime, Cycle}, + ExecuteCallback, + InstantiateCallback, + MultiTestable, + Query, + }, +}; + +fn allowance_cycle( + deposit: Uint128, + removed: Uint128, + expected: Uint128, + allowance: Uint128, + allow_type: AllowanceType, + cycle: Cycle, + start: String, + not_refreshed: String, + refreshed: String, +) { + let mut app = App::default(); + + let start = parse_utc_datetime(&start).unwrap(); + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(start.timestamp() as u64), + chain_id: "chain_id".to_string(), + }); + + let admin = Addr::unchecked("admin"); + let spender = Addr::unchecked("spender"); + let _user = Addr::unchecked("user"); + //let validator = Addr::unchecked("validator"); + let admin_auth = init_admin_auth(&mut app, &admin); + + let viewing_key = "viewing_key".to_string(); + + let token = snip20::InstantiateMsg { + name: "token".into(), + admin: Some("admin".into()), + symbol: "TKN".into(), + decimals: 6, + initial_balances: Some(vec![snip20::InitialBalance { + address: admin.to_string().clone(), + amount: deposit, + }]), + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + query_auth: None, + } + .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) + .unwrap(); + + let treasury = treasury::InstantiateMsg { + admin_auth: admin_auth.clone().into(), + viewing_key: viewing_key.clone(), + multisig: admin.to_string().clone(), + } + .test_init(Treasury::default(), &mut app, admin.clone(), "treasury", &[ + ]) + .unwrap(); + + // Set admin viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Register treasury assets + treasury::ExecuteMsg::RegisterAsset { + contract: token.clone().into(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // treasury allowance to spender + treasury::ExecuteMsg::Allowance { + asset: token.address.to_string().clone(), + allowance: treasury::RawAllowance { + //nick: "Mid-Stakes-Manager".to_string(), + spender: spender.clone().to_string(), + allowance_type: allow_type, + cycle, + amount: allowance, + // 100% (adapter balance will 2x before unbond) + tolerance: Uint128::zero(), + }, + refresh_now: true, + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Deposit funds into treasury + snip20::ExecuteMsg::Send { + recipient: treasury.address.to_string().clone(), + recipient_code_hash: None, + amount: deposit, + msg: None, + memo: None, + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update treasury + treasury::ExecuteMsg::Update { + asset: token.address.to_string().clone(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Check treasury allowance + match (treasury::QueryMsg::Allowance { + asset: token.address.to_string().clone(), + spender: spender.to_string().clone(), + } + .test_query(&treasury, &app) + .unwrap()) + { + treasury::QueryAnswer::Allowance { amount } => { + assert_eq!(amount, expected, "Initial Allowance"); + } + _ => panic!("query failed"), + }; + + // Send out of treasury to reduce allowance (user using funds) + snip20::ExecuteMsg::SendFrom { + recipient: spender.to_string().clone(), //treasury.address.to_string().clone(), + recipient_code_hash: None, + owner: treasury.address.to_string(), + amount: removed, + memo: None, + msg: None, + padding: None, + } + .test_exec(&token, &mut app, spender.clone(), &[]) + .unwrap(); + + // Send back to treasury to maintain balance/expected + snip20::ExecuteMsg::Send { + recipient: treasury.address.to_string().clone(), + recipient_code_hash: None, + amount: removed, + memo: None, + msg: None, + padding: None, + } + .test_exec(&token, &mut app, spender.clone(), &[]) + .unwrap(); + + // Check treasury allowance + match (treasury::QueryMsg::Allowance { + asset: token.address.to_string().clone(), + spender: spender.to_string().clone(), + } + .test_query(&treasury, &app) + .unwrap()) + { + treasury::QueryAnswer::Allowance { amount } => { + assert_eq!(amount, expected - removed, "Allowance after use"); + } + _ => panic!("query failed"), + }; + + // Update treasury + treasury::ExecuteMsg::Update { + asset: token.address.to_string().clone(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + let not_refreshed = parse_utc_datetime(¬_refreshed).unwrap(); + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(not_refreshed.timestamp() as u64), + chain_id: "chain_id".to_string(), + }); + + // Update treasury + treasury::ExecuteMsg::Update { + asset: token.address.to_string().clone(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Check treasury allowance + match (treasury::QueryMsg::Allowance { + asset: token.address.to_string().clone(), + spender: spender.to_string().clone(), + } + .test_query(&treasury, &app) + .unwrap()) + { + treasury::QueryAnswer::Allowance { amount } => { + assert_eq!(amount, expected - removed, "Allowance not refreshed"); + } + _ => panic!("query failed"), + }; + + let refreshed = parse_utc_datetime(&refreshed).unwrap(); + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(refreshed.timestamp() as u64), + chain_id: "chain_id".to_string(), + }); + + // Update treasury + treasury::ExecuteMsg::Update { + asset: token.address.to_string().clone(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Check treasury allowance + match (treasury::QueryMsg::Allowance { + asset: token.address.to_string().clone(), + spender: spender.to_string().clone(), + } + .test_query(&treasury, &app) + .unwrap()) + { + treasury::QueryAnswer::Allowance { amount } => { + assert_eq!(amount, expected, "Allowance refreshed"); + } + _ => panic!("query failed"), + }; +} + +macro_rules! allowance_cycle_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + deposit, + removed, + expected, + allowance, + allow_type, + cycle, + start, + not_refreshed, + refreshed, + ) = $value; + allowance_cycle( + deposit, + removed, + expected, + allowance, + allow_type, + cycle, + start.to_string(), + not_refreshed.to_string(), + refreshed.to_string(), + ); + } + )* + } +} + +allowance_cycle_tests! { + portion_seconds_30: ( + Uint128::new(100), // deposit + Uint128::new(100), // removed + Uint128::new(100), // expected + Uint128::new(1 * 10u128.pow(18)), // allowance + AllowanceType::Portion, + Cycle::Seconds { seconds: Uint128::new(30) }, + "1995-11-13T00:00:00.00Z", + "1995-11-13T00:00:29.00Z", + "1995-11-13T00:00:30.00Z", + ), + amount_seconds_30: ( + Uint128::new(100), // deposit + Uint128::new(100), // removed + Uint128::new(100), // expected + Uint128::new(100), // allowance + AllowanceType::Amount, + Cycle::Seconds { seconds: Uint128::new(30) }, + "1995-11-13T00:00:00.00Z", + "1995-11-13T00:00:29.00Z", + "1995-11-13T00:00:30.00Z", + ), + portion_minutes_30: ( + Uint128::new(100), // deposit + Uint128::new(100), // removed + Uint128::new(100), // expected + Uint128::new(1 * 10u128.pow(18)), // allowance + AllowanceType::Portion, + Cycle::Minutes { minutes: Uint128::new(30) }, + "1995-11-13T00:00:00.00Z", + "1995-11-13T00:15:00.00Z", + "1995-11-13T00:30:00.00Z", + ), + amount_minutes_30: ( + Uint128::new(100), // deposit + Uint128::new(100), // removed + Uint128::new(100), // expected + Uint128::new(100), // allowance + AllowanceType::Amount, + Cycle::Minutes { minutes: Uint128::new(30) }, + "1995-11-13T00:00:00.00Z", + "1995-11-13T00:15:00.00Z", + "1995-11-13T00:30:00.00Z", + ), + portion_daily_1: ( + Uint128::new(100), // deposit + Uint128::new(100), // removed + Uint128::new(100), // expected + Uint128::new(1 * 10u128.pow(18)), // allowance + AllowanceType::Portion, + Cycle::Daily { days: Uint128::new(1) }, + "1995-11-13T00:00:00.00Z", + "1995-11-13T12:00:00.00Z", + "1995-11-14T00:00:00.00Z", + ), + amount_daily_1: ( + Uint128::new(100), // deposit + Uint128::new(100), // removed + Uint128::new(100), // expected + Uint128::new(100), // allowance + AllowanceType::Amount, + Cycle::Daily { days: Uint128::new(1) }, + "1995-11-13T00:00:00.00Z", + "1995-11-13T12:00:00.00Z", + "1995-11-14T00:00:00.00Z", + ), + portion_monthly_1: ( + Uint128::new(100), // deposit + Uint128::new(100), // removed + Uint128::new(100), // expected + Uint128::new(1 * 10u128.pow(18)), // allowance + AllowanceType::Portion, + Cycle::Monthly { months: Uint128::new(1) }, + "1995-11-13T00:00:00.00Z", + "1995-11-13T12:00:00.00Z", + "1995-12-13T00:00:00.00Z", + ), + amount_monthly_1: ( + Uint128::new(100), // deposit + Uint128::new(100), // removed + Uint128::new(100), // expected + Uint128::new(100), // allowance + AllowanceType::Amount, + Cycle::Monthly { months: Uint128::new(1) }, + "1995-11-13T00:00:00.00Z", + "1995-11-20T00:00:00.00Z", + "1995-12-13T00:00:00.00Z", + ), + portion_yearly_1: ( + Uint128::new(100), // deposit + Uint128::new(100), // removed + Uint128::new(100), // expected + Uint128::new(1 * 10u128.pow(18)), // allowance + AllowanceType::Portion, + Cycle::Yearly { years: Uint128::new(1) }, + "1995-11-13T00:00:00.00Z", + "1995-12-29T12:00:00.00Z", + "1996-01-01T00:00:00.00Z", + ), + amount_yearly_1: ( + Uint128::new(100), // deposit + Uint128::new(100), // removed + Uint128::new(100), // expected + Uint128::new(100), // allowance + AllowanceType::Amount, + Cycle::Yearly { years: Uint128::new(1) }, + "1995-11-13T00:00:00.00Z", + "1995-12-29T12:00:00.00Z", + "1996-01-01T00:00:00.00Z", + ), +} diff --git a/contracts/dao/treasury/tests/integration/allowance_delay_refresh.rs b/contracts/dao/treasury/tests/integration/allowance_delay_refresh.rs new file mode 100644 index 0000000..ac1d272 --- /dev/null +++ b/contracts/dao/treasury/tests/integration/allowance_delay_refresh.rs @@ -0,0 +1,335 @@ +use shade_multi_test::multi::{admin::init_admin_auth, snip20::Snip20, treasury::Treasury}; +use shade_protocol::{ + c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}, + contract_interfaces::{ + dao::{treasury, treasury::AllowanceType}, + snip20, + }, + multi_test::App, + utils::{ + cycle::{parse_utc_datetime, Cycle}, + ExecuteCallback, + InstantiateCallback, + MultiTestable, + Query, + }, +}; + +fn allowance_cycle( + deposit: Uint128, + removed: Uint128, + start_expected: Uint128, + start_allowance: Uint128, + start_allow_type: AllowanceType, + start_cycle: Cycle, + updated_expected: Uint128, + updated_allowance: Uint128, + updated_allow_type: AllowanceType, + updated_cycle: Cycle, + start: String, + refreshed: String, +) { + let mut app = App::default(); + + let start = parse_utc_datetime(&start).unwrap(); + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(start.timestamp() as u64), + chain_id: "chain_id".to_string(), + }); + + let admin = Addr::unchecked("admin"); + let spender = Addr::unchecked("spender"); + let _user = Addr::unchecked("user"); + //let validator = Addr::unchecked("validator"); + let admin_auth = init_admin_auth(&mut app, &admin); + + let viewing_key = "viewing_key".to_string(); + + let token = snip20::InstantiateMsg { + name: "token".into(), + admin: Some("admin".into()), + symbol: "TKN".into(), + decimals: 6, + initial_balances: Some(vec![snip20::InitialBalance { + address: admin.to_string().clone(), + amount: deposit + deposit, + }]), + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + query_auth: None, + } + .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) + .unwrap(); + + let treasury = treasury::InstantiateMsg { + admin_auth: admin_auth.clone().into(), + viewing_key: viewing_key.clone(), + multisig: admin.to_string().clone(), + } + .test_init(Treasury::default(), &mut app, admin.clone(), "treasury", &[ + ]) + .unwrap(); + + // Set admin viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Register treasury assets + treasury::ExecuteMsg::RegisterAsset { + contract: token.clone().into(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // treasury starting allowance to spender + treasury::ExecuteMsg::Allowance { + asset: token.address.to_string().clone(), + allowance: treasury::RawAllowance { + //nick: "Mid-Stakes-Manager".to_string(), + spender: spender.clone().to_string(), + allowance_type: start_allow_type, + cycle: start_cycle, + amount: start_allowance, + // 100% (adapter balance will 2x before unbond) + tolerance: Uint128::zero(), + }, + refresh_now: false, + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Deposit funds into treasury + snip20::ExecuteMsg::Send { + recipient: treasury.address.to_string().clone(), + recipient_code_hash: None, + amount: deposit, + msg: None, + memo: None, + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update treasury + treasury::ExecuteMsg::Update { + asset: token.address.to_string().clone(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Check treasury allowance + match (treasury::QueryMsg::Allowance { + asset: token.address.to_string().clone(), + spender: spender.to_string().clone(), + } + .test_query(&treasury, &app) + .unwrap()) + { + treasury::QueryAnswer::Allowance { amount } => { + assert_eq!(amount, start_expected, "Initial Allowance"); + } + _ => panic!("query failed"), + }; + + // Send out of treasury to reduce allowance (user using funds) + snip20::ExecuteMsg::SendFrom { + recipient: spender.to_string().clone(), //treasury.address.to_string().clone(), + recipient_code_hash: None, + owner: treasury.address.to_string(), + amount: removed, + memo: None, + msg: None, + padding: None, + } + .test_exec(&token, &mut app, spender.clone(), &[]) + .unwrap(); + + // Refill treasury balance + snip20::ExecuteMsg::Send { + recipient: treasury.address.to_string().clone(), + recipient_code_hash: None, + amount: removed, + memo: None, + msg: None, + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update treasury + treasury::ExecuteMsg::Update { + asset: token.address.to_string().clone(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Check treasury allowance reflects used funds + match (treasury::QueryMsg::Allowance { + asset: token.address.to_string().clone(), + spender: spender.to_string().clone(), + } + .test_query(&treasury, &app) + .unwrap()) + { + treasury::QueryAnswer::Allowance { amount } => { + assert_eq!(amount, start_expected - removed, "Allowance after use"); + } + _ => panic!("query failed"), + }; + + // Update allowance to spender + treasury::ExecuteMsg::Allowance { + asset: token.address.to_string().clone(), + allowance: treasury::RawAllowance { + //nick: "Mid-Stakes-Manager".to_string(), + spender: spender.clone().to_string(), + allowance_type: updated_allow_type, + cycle: updated_cycle, + amount: updated_allowance, + // 100% (adapter balance will 2x before unbond) + tolerance: Uint128::zero(), + }, + refresh_now: false, + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update treasury + treasury::ExecuteMsg::Update { + asset: token.address.to_string().clone(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Check allowance hasn't changed + match (treasury::QueryMsg::Allowance { + asset: token.address.to_string().clone(), + spender: spender.to_string().clone(), + } + .test_query(&treasury, &app) + .unwrap()) + { + treasury::QueryAnswer::Allowance { amount } => { + assert_eq!( + amount, + start_expected - removed, + "Allowance after update, not refreshed" + ); + } + _ => panic!("query failed"), + }; + + let refreshed = parse_utc_datetime(&refreshed).unwrap(); + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(refreshed.timestamp() as u64), + chain_id: "chain_id".to_string(), + }); + + // Update treasury + treasury::ExecuteMsg::Update { + asset: token.address.to_string().clone(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Check treasury updated to new allowance settings + match (treasury::QueryMsg::Allowance { + asset: token.address.to_string().clone(), + spender: spender.to_string().clone(), + } + .test_query(&treasury, &app) + .unwrap()) + { + treasury::QueryAnswer::Allowance { amount } => { + assert_eq!( + amount, updated_expected, + "Allowance refreshed to updated amount" + ); + } + _ => panic!("query failed"), + }; +} + +macro_rules! allowance_delay_update_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + deposit, + removed, + start_expected, + start_allowance, + start_allow_type, + start_cycle, + updated_expected, + updated_allowance, + updated_allow_type, + updated_cycle, + start, + refreshed, + ) = $value; + allowance_cycle( + deposit, + removed, + start_expected, + start_allowance, + start_allow_type, + start_cycle, + updated_expected, + updated_allowance, + updated_allow_type, + updated_cycle, + start.to_string(), + refreshed.to_string(), + ); + } + )* + } +} + +allowance_delay_update_tests! { + portion_seconds_30: ( + Uint128::new(100), // deposit + Uint128::new(100), // used + Uint128::new(100), // start expected + Uint128::new(1 * 10u128.pow(18)), // start allowance + AllowanceType::Portion, + Cycle::Seconds { seconds: Uint128::new(30) }, + + Uint128::new(90), // updated expected + Uint128::new(9 * 10u128.pow(17)), // updated allowance + AllowanceType::Portion, + Cycle::Seconds { seconds: Uint128::new(30) }, + "1995-11-13T00:00:00.00Z", + "1995-11-13T00:00:30.00Z", + ), + amount_seconds_30: ( + Uint128::new(100), // deposit + Uint128::new(100), // used + Uint128::new(100), // start expected + Uint128::new(100), // start allowance + AllowanceType::Amount, + Cycle::Seconds { seconds: Uint128::new(30) }, + + Uint128::new(90), // updated expected + Uint128::new(9 * 10u128.pow(17)), // updated allowance + AllowanceType::Portion, + Cycle::Seconds { seconds: Uint128::new(30) }, + "1995-11-13T00:00:00.00Z", + "1995-11-13T00:00:30.00Z", + ), +} diff --git a/contracts/dao/treasury/tests/integration/batch.rs b/contracts/dao/treasury/tests/integration/batch.rs new file mode 100644 index 0000000..4ef729a --- /dev/null +++ b/contracts/dao/treasury/tests/integration/batch.rs @@ -0,0 +1,95 @@ +use shade_multi_test::multi::{admin::init_admin_auth, snip20::Snip20, treasury::Treasury}; +use shade_protocol::{ + c_std::{to_binary, Addr, Uint128}, + contract_interfaces::{dao::treasury, snip20}, + multi_test::App, + utils::{ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +//TODO test with manager +// Add other adapters here as they come +fn batch_balance_test(amounts: Vec) { + let mut app = App::default(); + + let admin = Addr::unchecked("admin"); + let _user = Addr::unchecked("user"); + let admin_auth = init_admin_auth(&mut app, &admin); + let viewing_key = "veiwing_key".to_string(); + + let mut tokens = vec![]; + + let treasury = treasury::InstantiateMsg { + admin_auth: admin_auth.clone().into(), + viewing_key: viewing_key.clone(), + multisig: admin.to_string().clone(), + } + .test_init(Treasury::default(), &mut app, admin.clone(), "treasury", &[ + ]) + .unwrap(); + + for amount in amounts.clone() { + let token = snip20::InstantiateMsg { + name: "token".into(), + admin: Some("admin".into()), + symbol: "TKN".into(), + decimals: 6, + initial_balances: Some(vec![snip20::InitialBalance { + address: treasury.address.to_string().clone(), + amount, + }]), + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + query_auth: None, + } + .test_init( + Snip20::default(), + &mut app, + admin.clone(), + &amount.to_string(), + &[], + ) + .unwrap(); + + treasury::ExecuteMsg::RegisterAsset { + contract: token.clone().into(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + tokens.push(token); + } + + // Treasury Balances + let balances: Vec = treasury::QueryMsg::BatchBalance { + assets: tokens + .iter() + .map(|t| t.address.to_string().clone()) + .collect(), + } + .test_query(&treasury, &app) + .unwrap(); + + assert!(balances == amounts, "Reported balances match inputs"); +} + +macro_rules! batch_balance_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + batch_balance_test($value.into_iter().map(|a| Uint128::new(a as u128)).collect()); + } + )* + } +} + +batch_balance_tests! { + batch_balances_0: vec![10, 23840, 8402840, 123456, 0], +} diff --git a/contracts/dao/treasury/tests/integration/config.rs b/contracts/dao/treasury/tests/integration/config.rs new file mode 100644 index 0000000..7b00f1f --- /dev/null +++ b/contracts/dao/treasury/tests/integration/config.rs @@ -0,0 +1,80 @@ +use shade_multi_test::interfaces::{dao::init_dao, treasury, utils::DeployedContracts}; +use shade_protocol::{ + c_std::{Addr, Uint128}, + contract_interfaces::dao::{self, treasury::AllowanceType, treasury_manager::AllocationType}, + multi_test::App, + utils::{ + asset::{Contract, RawContract}, + cycle::Cycle, + }, +}; + +#[test] +pub fn update_config() { + let mut app = App::default(); + let mut contracts = DeployedContracts::new(); + init_dao( + &mut app, + "admin", + &mut contracts, + Uint128::new(1500), + "SSCRT", + vec![ + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + ], + vec![Cycle::Constant; 4], + vec![ + Uint128::new(200), // Amount - 50 + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + Uint128::new(300), // Amount - 100 + Uint128::new(3 * 10u128.pow(17)), // Portion - 40% + ], // Allowance amount + vec![Uint128::zero(); 4], + vec![ + vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount + ]; + 4 + ], + vec![ + vec![ + Uint128::new(6 * 10u128.pow(17)), + Uint128::new(50), + Uint128::new(2 * 10u128.pow(17)), + Uint128::new(75), + ]; + 4 + ], + vec![vec![Uint128::zero(); 4]; 4], + true, + true, + ) + .unwrap(); + treasury::set_config( + &mut app, + "admin", + &contracts, + Some(RawContract { + address: "rando2".to_string(), + code_hash: "rando3".to_string(), + }), + Some(Addr::unchecked("rando").into()), + ) + .unwrap(); + assert_eq!( + treasury::config_query(&app, &contracts).unwrap(), + dao::treasury::Config { + admin_auth: Contract { + address: Addr::unchecked("rando2"), + code_hash: "rando3".to_string(), + }, + multisig: Addr::unchecked("rando"), + } + ); +} diff --git a/contracts/dao/treasury/tests/integration/execute_errors.rs b/contracts/dao/treasury/tests/integration/execute_errors.rs new file mode 100644 index 0000000..494c551 --- /dev/null +++ b/contracts/dao/treasury/tests/integration/execute_errors.rs @@ -0,0 +1,244 @@ +use shade_multi_test::interfaces::{ + dao::init_dao, + snip20, + treasury, + utils::{DeployedContracts, SupportedContracts}, +}; +use shade_protocol::{ + c_std::Uint128, + contract_interfaces::dao::{self, treasury::AllowanceType, treasury_manager::AllocationType}, + multi_test::App, + utils::{asset::RawContract, cycle::Cycle}, +}; + +#[test] +pub fn execute_error() { + let mut app = App::default(); + let mut contracts = DeployedContracts::new(); + init_dao( + &mut app, + "admin", + &mut contracts, + Uint128::new(1500), + "SSCRT", + vec![ + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + ], + vec![Cycle::Constant; 4], + vec![ + Uint128::new(200), // Amount - 50 + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + Uint128::new(300), // Amount - 100 + Uint128::new(3 * 10u128.pow(17)), // Portion - 40% + ], // Allowance amount + vec![Uint128::zero(); 4], + vec![ + vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount + ]; + 4 + ], + vec![ + vec![ + Uint128::new(6 * 10u128.pow(17)), + Uint128::new(50), + Uint128::new(2 * 10u128.pow(17)), + Uint128::new(75), + ]; + 4 + ], + vec![vec![Uint128::zero(); 4]; 4], + true, + true, + ) + .unwrap(); + match treasury::allowance_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + 0, + AllowanceType::Portion, + Cycle::Constant, + Uint128::new(1), + Uint128::new(10u128.pow(18u32)), + true, + ) { + Ok(_) => assert!(false), + Err(_) => assert!(true), + } + match treasury::allowance_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + 0, + AllowanceType::Portion, + Cycle::Constant, + Uint128::new(101 * 10u128.pow(16u32)), + Uint128::zero(), + true, + ) { + Ok(_) => assert!(false), + Err(_) => assert!(true), + } + snip20::init(&mut app, "admin", &mut contracts, "Shade", "SHD", 8, None).unwrap(); + match treasury::allowance_exec( + &mut app, + "admin", + &contracts, + "SHD", + 0, + AllowanceType::Portion, + Cycle::Constant, + Uint128::new(1), + Uint128::zero(), + true, + ) { + Ok(_) => assert!(false), + Err(_) => assert!(true), + } + match treasury::register_manager_exec(&mut app, "admin", &contracts, 0) { + Ok(_) => assert!(false), + Err(_) => assert!(true), + } + match treasury::register_wrap_exec( + &mut app, + "admin", + &contracts, + "SHD".to_string(), + RawContract { + address: "rando".to_string(), + code_hash: "code_hash".to_string(), + }, + ) { + Ok(_) => assert!(false), + Err(_) => assert!(true), + } + match treasury::update_exec(&mut app, "admin", &contracts, "SHD") { + Ok(_) => assert!(false), + Err(_) => assert!(true), + } + treasury::set_run_level_exec( + &mut app, + "admin", + &contracts, + dao::treasury::RunLevel::Deactivated, + ) + .unwrap(); + match treasury::update_exec(&mut app, "admin", &contracts, "SSCRT") { + Ok(_) => assert!(false), + Err(_) => assert!(true), + } + treasury::register_asset_exec(&mut app, "admin", &contracts, "SHD").unwrap(); + match treasury::register_wrap_exec( + &mut app, + "admin", + &contracts, + "SHD".to_string(), + contracts[&SupportedContracts::Snip20("SHD".to_string())] + .clone() + .into(), + ) { + Ok(_) => assert!(false), + Err(_) => assert!(true), + } +} + +#[test] +pub fn admin_errors() { + const NOT_ADMIN: &str = "not_admin"; + let mut app = App::default(); + let mut contracts = DeployedContracts::new(); + init_dao( + &mut app, + "admin", + &mut contracts, + Uint128::new(1500), + "SSCRT", + vec![ + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + ], + vec![Cycle::Constant; 4], + vec![ + Uint128::new(200), // Amount - 50 + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + Uint128::new(300), // Amount - 100 + Uint128::new(3 * 10u128.pow(17)), // Portion - 40% + ], // Allowance amount + vec![Uint128::zero(); 4], + vec![ + vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount + ]; + 4 + ], + vec![ + vec![ + Uint128::new(6 * 10u128.pow(17)), + Uint128::new(50), + Uint128::new(2 * 10u128.pow(17)), + Uint128::new(75), + ]; + 4 + ], + vec![vec![Uint128::zero(); 4]; 4], + true, + true, + ) + .unwrap(); + assert!(!treasury::set_config(&mut app, NOT_ADMIN, &contracts, None, None).is_ok()); + assert!( + !treasury::set_run_level_exec( + &mut app, + NOT_ADMIN, + &contracts, + dao::treasury::RunLevel::Migrating + ) + .is_ok() + ); + assert!(!treasury::register_asset_exec(&mut app, NOT_ADMIN, &contracts, "SSCRT").is_ok()); + assert!( + !treasury::register_wrap_exec( + &mut app, + NOT_ADMIN, + &contracts, + "SSCRT".to_string(), + RawContract { + address: "nana".to_string(), + code_hash: "nana".to_string() + } + ) + .is_ok() + ); + assert!(!treasury::register_manager_exec(&mut app, NOT_ADMIN, &contracts, 0).is_ok()); + assert!( + !treasury::allowance_exec( + &mut app, + NOT_ADMIN, + &contracts, + "SSCRT", + 0, + AllowanceType::Amount, + Cycle::Daily { + days: Uint128::new(1) + }, + Uint128::zero(), + Uint128::zero(), + true, + ) + .is_ok() + ); +} diff --git a/contracts/dao/treasury/tests/integration/migration.rs b/contracts/dao/treasury/tests/integration/migration.rs new file mode 100644 index 0000000..80fc50b --- /dev/null +++ b/contracts/dao/treasury/tests/integration/migration.rs @@ -0,0 +1,151 @@ +use shade_multi_test::interfaces::{ + dao::{ + init_dao, + mock_adapter_complete_unbonding, + system_balance_reserves, + system_balance_unbondable, + update_dao, + }, + snip20, + treasury, + utils::{DeployedContracts, SupportedContracts}, +}; +use shade_protocol::{ + c_std::{Addr, Uint128}, + contract_interfaces::dao::{self, treasury::AllowanceType, treasury_manager::AllocationType}, + multi_test::App, + utils::cycle::Cycle, +}; + +pub fn migration_test(is_instant_unbond: bool) { + const MULTISIG: &str = "multisig"; + let mut app = App::default(); + let mut contracts = DeployedContracts::new(); + init_dao( + &mut app, + "admin", + &mut contracts, + Uint128::new(1500), + "SSCRT", + vec![ + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + ], + vec![Cycle::Constant; 4], + vec![ + Uint128::new(200), // Amount - 50 + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + Uint128::new(300), // Amount - 100 + Uint128::new(3 * 10u128.pow(17)), // Portion - 40% + ], // Allowance amount + vec![Uint128::zero(); 4], + vec![ + vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount + ]; + 4 + ], + vec![ + vec![ + Uint128::new(6 * 10u128.pow(17)), + Uint128::new(50), + Uint128::new(2 * 10u128.pow(17)), + Uint128::new(75), + ]; + 4 + ], + vec![vec![Uint128::zero(); 4]; 4], + is_instant_unbond, + true, + ) + .unwrap(); + snip20::set_viewing_key_exec( + &mut app, + MULTISIG, + &contracts, + "SSCRT", + MULTISIG.to_string(), + ) + .unwrap(); + treasury::set_config( + &mut app, + "admin", + &contracts, + Some( + contracts + .get(&SupportedContracts::AdminAuth) + .unwrap() + .clone() + .into(), + ), + Some(Addr::unchecked(MULTISIG).to_string()), + ) + .unwrap(); + treasury::set_run_level_exec( + &mut app, + "admin", + &contracts, + dao::treasury::RunLevel::Migrating, + ) + .unwrap(); + update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); + if is_instant_unbond { + update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); + } else { + let mut k = 0; + for _i in 0..4 { + for _j in 0..4 { + mock_adapter_complete_unbonding( + &mut app, + "admin", + &contracts, + SupportedContracts::MockAdapter(k), + ) + .unwrap(); + k += 1; + } + k += 1; + } + update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); + update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); + } + println!( + "{:?}\n{:?}", + system_balance_reserves(&app, &contracts, "SSCRT"), + system_balance_unbondable(&app, &contracts, "SSCRT") + ); + assert_eq!( + snip20::balance_query(&app, MULTISIG, &contracts, "SSCRT", MULTISIG.to_string()).unwrap(), + Uint128::new(1500) + ); +} + +macro_rules! dao_tests_migration { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + is_instant_unbond, + ) = $value; + migration_test( + is_instant_unbond, + ); + } + )* + } +} + +dao_tests_migration! ( + dao_test_migration_instant_unbond: ( + true, + ), + dao_test_migration_non_instant_unbond: ( + false, + ), +); diff --git a/contracts/dao/treasury/tests/integration/mod.rs b/contracts/dao/treasury/tests/integration/mod.rs new file mode 100644 index 0000000..8040158 --- /dev/null +++ b/contracts/dao/treasury/tests/integration/mod.rs @@ -0,0 +1,12 @@ +pub mod allowance; +pub mod allowance_delay_refresh; +pub mod batch; +pub mod config; +pub mod execute_errors; +pub mod migration; +pub mod non_manager_allowances; +pub mod query; +pub mod scrt_staking; +pub mod tolerance; +pub mod treasury; +pub mod wrap; diff --git a/contracts/dao/treasury/tests/integration/non_manager_allowances.rs b/contracts/dao/treasury/tests/integration/non_manager_allowances.rs new file mode 100644 index 0000000..ca5160e --- /dev/null +++ b/contracts/dao/treasury/tests/integration/non_manager_allowances.rs @@ -0,0 +1,192 @@ +use shade_multi_test::interfaces::{ + dao::{init_dao, system_balance_reserves, update_dao}, + snip20, + treasury, + utils::{DeployedContracts, SupportedContracts}, +}; +use shade_protocol::{ + c_std::{Addr, Uint128}, + contract_interfaces::dao::{treasury::AllowanceType, treasury_manager::AllocationType}, + multi_test::App, + utils::{asset::Contract, cycle::Cycle}, +}; + +#[test] +pub fn non_manager_allowances() { + let mut app = App::default(); + let mut contracts = DeployedContracts::new(); + const NOT_A_MANAGER: &str = "no_manager"; + init_dao( + &mut app, + "admin", + &mut contracts, + Uint128::new(1500), + "SSCRT", + vec![ + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + ], + vec![Cycle::Constant; 4], + vec![ + Uint128::new(200), // Amount - 50 + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + Uint128::new(300), // Amount - 100 + Uint128::new(3 * 10u128.pow(17)), // Portion - 40% + ], // Allowance amount + vec![Uint128::zero(); 4], + vec![ + vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount + ]; + 4 + ], + vec![ + vec![ + Uint128::new(6 * 10u128.pow(17)), + Uint128::new(50), + Uint128::new(2 * 10u128.pow(17)), + Uint128::new(75), + ]; + 4 + ], + vec![vec![Uint128::zero(); 4]; 4], + true, + true, + ) + .unwrap(); + contracts.insert(SupportedContracts::TreasuryManager(5), Contract { + address: Addr::unchecked(NOT_A_MANAGER), + code_hash: "".to_string(), + }); + treasury::allowance_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + 5, + AllowanceType::Amount, + Cycle::Once, + Uint128::new(100), + Uint128::zero(), + true, + ) + .unwrap(); + update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); + update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); + snip20::send_from_exec( + &mut app, + NOT_A_MANAGER, + &contracts, + "SSCRT", + contracts[&SupportedContracts::Treasury] + .clone() + .address + .into(), + NOT_A_MANAGER.to_string(), + Uint128::new(100), + None, + ) + .unwrap(); + update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); + assert_eq!( + Uint128::zero(), + treasury::allowance_query( + &app, + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(5) + ) + .unwrap() + ); + match snip20::send_from_exec( + &mut app, + NOT_A_MANAGER, + &contracts, + "SSCRT", + contracts[&SupportedContracts::Treasury] + .clone() + .address + .into(), + NOT_A_MANAGER.to_string(), + Uint128::new(100), + None, + ) { + Ok(_) => assert!(false, "cycle is set to once"), + Err(_) => assert!(true), + } + println!("{:?}", system_balance_reserves(&app, &contracts, "SSCRT"),); + snip20::set_viewing_key_exec( + &mut app, + NOT_A_MANAGER, + &contracts, + "SSCRT", + NOT_A_MANAGER.to_string(), + ) + .unwrap(); + assert_eq!( + snip20::balance_query( + &app, + NOT_A_MANAGER, + &contracts, + "SSCRT", + NOT_A_MANAGER.to_string() + ) + .unwrap(), + Uint128::new(100) + ); + treasury::allowance_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + 5, + AllowanceType::Amount, + Cycle::Constant, + Uint128::new(50), + Uint128::zero(), + true, + ) + .unwrap(); + update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); + snip20::send_from_exec( + &mut app, + NOT_A_MANAGER, + &contracts, + "SSCRT", + contracts[&SupportedContracts::Treasury] + .clone() + .address + .into(), + NOT_A_MANAGER.to_string(), + Uint128::new(25), + None, + ) + .unwrap(); + assert_eq!( + snip20::balance_query( + &app, + NOT_A_MANAGER, + &contracts, + "SSCRT", + NOT_A_MANAGER.to_string() + ) + .unwrap(), + Uint128::new(125) + ); + update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); + assert_eq!( + Uint128::new(50), + treasury::allowance_query( + &app, + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(5) + ) + .unwrap() + ); +} diff --git a/contracts/dao/treasury/tests/integration/query.rs b/contracts/dao/treasury/tests/integration/query.rs new file mode 100644 index 0000000..7ad095e --- /dev/null +++ b/contracts/dao/treasury/tests/integration/query.rs @@ -0,0 +1,240 @@ +use shade_multi_test::interfaces::{ + dao::{init_dao, mock_adapter_sub_tokens, update_dao}, + snip20, + treasury, + utils::{DeployedContracts, SupportedContracts}, +}; +use shade_protocol::{ + c_std::{BlockInfo, Timestamp, Uint128}, + contract_interfaces::dao::{self, treasury::AllowanceType, treasury_manager::AllocationType}, + multi_test::App, + utils::{ + cycle::{parse_utc_datetime, Cycle}, + storage::plus::period_storage::Period, + }, +}; + +#[test] +pub fn query() { + let mut app = App::default(); + let mut contracts = DeployedContracts::new(); + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds( + parse_utc_datetime(&"1995-11-13T00:00:00.00Z".to_string()) + .unwrap() + .timestamp() as u64, + ), + chain_id: "chain_id".to_string(), + }); + init_dao( + &mut app, + "admin", + &mut contracts, + Uint128::new(1500), + "SSCRT", + vec![ + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + ], + vec![Cycle::Constant; 4], + vec![ + Uint128::new(200), // Amount - 50 + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + Uint128::new(300), // Amount - 100 + Uint128::new(3 * 10u128.pow(17)), // Portion - 40% + ], // Allowance amount + vec![Uint128::zero(); 4], + vec![ + vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount + ]; + 4 + ], + vec![ + vec![ + Uint128::new(6 * 10u128.pow(17)), + Uint128::new(50), + Uint128::new(2 * 10u128.pow(17)), + Uint128::new(75), + ]; + 4 + ], + vec![vec![Uint128::zero(); 4]; 4], + true, + true, + ) + .unwrap(); + assert_eq!( + treasury::batch_balance_query(&app, &contracts, vec!["SSCRT"]).unwrap(), + vec![Uint128::new(1500)] + ); + snip20::init(&mut app, "admin", &mut contracts, "Shade", "SHD", 8, None).unwrap(); + assert!(!treasury::batch_balance_query(&app, &contracts, vec!["SSCRT", "SHD"]).is_ok()); + assert!(!treasury::balance_query(&app, &contracts, "SHD",).is_ok()); + assert!(!treasury::reserves_query(&app, &contracts, "SHD",).is_ok()); + assert!( + !treasury::allowance_query( + &app, + &contracts, + "SHD", + SupportedContracts::TreasuryManager(0) + ) + .is_ok() + ); + treasury::register_asset_exec(&mut app, "admin", &contracts, "SHD").unwrap(); + assert_eq!( + treasury::batch_balance_query(&app, &contracts, vec!["SSCRT", "SHD"]).unwrap(), + vec![Uint128::new(1500), Uint128::zero()] + ); + assert_eq!( + treasury::run_level_query(&app, &contracts,).unwrap(), + dao::treasury::RunLevel::Normal + ); + assert!( + !treasury::metrics_query( + &app, + &contracts, + Some("1995-11-13T00:00:00.00Z".to_string()), + None, + Period::Hour, + ) + .unwrap() + .is_empty() + ); + assert!( + !treasury::metrics_query( + &app, + &contracts, + Some("1995-11-13T00:00:00.00Z".to_string()), + None, + Period::Day, + ) + .unwrap() + .is_empty() + ); + assert!( + !treasury::metrics_query( + &app, + &contracts, + Some("1995-11-13T00:00:00.00Z".to_string()), + None, + Period::Month, + ) + .unwrap() + .is_empty() + ); + assert!( + !treasury::metrics_query( + &app, + &contracts, + None, + Some(Uint128::new(816220800)), + Period::Hour, + ) + .unwrap() + .is_empty() + ); + assert!( + !treasury::metrics_query( + &app, + &contracts, + None, + Some(Uint128::new(816220800)), + Period::Day, + ) + .unwrap() + .is_empty() + ); + assert!( + !treasury::metrics_query( + &app, + &contracts, + None, + Some(Uint128::new(816220800)), + Period::Month, + ) + .unwrap() + .is_empty() + ); + assert!( + !treasury::metrics_query(&app, &contracts, None, None, Period::Month,) + .unwrap() + .is_empty() + ); + assert!( + !treasury::metrics_query( + &app, + &contracts, + Some("1995-11-13T00:00:00.00Z".to_string()), + Some(Uint128::new(816220800)), + Period::Month, + ) + .is_ok() + ); + assert!( + treasury::metrics_query( + &app, + &contracts, + None, + Some(Uint128::new( + parse_utc_datetime(&"1995-12-13T00:00:00.00Z".to_string()) + .unwrap() + .timestamp() as u128 + )), + Period::Month, + ) + .unwrap() + .is_empty() + ); + mock_adapter_sub_tokens( + &mut app, + "admin", + &contracts, + Uint128::new(10), + SupportedContracts::MockAdapter(7), + ) + .unwrap(); + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds( + parse_utc_datetime(&"1995-12-13T00:00:00.00Z".to_string()) + .unwrap() + .timestamp() as u64, + ), + chain_id: "chain_id".to_string(), + }); + update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); + update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); + assert!( + !treasury::metrics_query( + &app, + &contracts, + None, + Some(Uint128::new( + parse_utc_datetime(&"1995-12-13T00:00:00.00Z".to_string()) + .unwrap() + .timestamp() as u128 + )), + Period::Month, + ) + .unwrap() + .is_empty() + ); + assert!( + !treasury::metrics_query( + &app, + &contracts, + Some("1995-12-13T00:00:00.00Z".to_string()), + None, + Period::Month, + ) + .unwrap() + .is_empty() + ); +} diff --git a/contracts/dao/treasury/tests/integration/scrt_staking.rs b/contracts/dao/treasury/tests/integration/scrt_staking.rs new file mode 100644 index 0000000..fc53617 --- /dev/null +++ b/contracts/dao/treasury/tests/integration/scrt_staking.rs @@ -0,0 +1,861 @@ +/* +use shade_protocol::c_std::{ + coins, + from_binary, + to_binary, + Addr, + Binary, + Coin, + Decimal, + Env, + StdError, + StdResult, + Uint128, + Validator, +}; + +use shade_protocol::{ + contract_interfaces::{ + dao::{ + adapter, + manager, + scrt_staking, + treasury, + treasury::{Allowance, AllowanceType, RunLevel}, + treasury_manager::{self, Allocation, AllocationType}, + }, + snip20, + }, + utils::{ + asset::Contract, + cycle::{utc_from_timestamp, Cycle}, + storage::plus::period_storage::Period, + ExecuteCallback, + InstantiateCallback, + MultiTestable, + Query, + }, +}; + +use shade_multi_test::multi::{ + admin::init_admin_auth, + scrt_staking::ScrtStaking, + snip20::Snip20, + treasury::Treasury, + treasury_manager::TreasuryManager, +}; +use shade_protocol::multi_test::{App, BankSudo, StakingSudo, SudoMsg}; + +use serde_json; + +// Add other adapters here as they come +fn single_asset_manager_scrt_staking_integration( + deposit: Uint128, + allowance: Uint128, + expected_allowance: Uint128, + alloc_type: AllocationType, + alloc_amount: Uint128, + rewards: Uint128, + // expected balances + pre_rewards: (Uint128, Uint128, Uint128), + post_rewards: (Uint128, Uint128, Uint128), +) { + let mut app = App::default(); + + let admin = Addr::unchecked("admin"); + let user = Addr::unchecked("user"); + let validator = Addr::unchecked("validator"); + let admin_auth = init_admin_auth(&mut app, &admin); + + let viewing_key = "viewing_key".to_string(); + + let token = snip20::InstantiateMsg { + name: "secretSCRT".into(), + admin: Some("admin".into()), + symbol: "SSCRT".into(), + decimals: 6, + initial_balances: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + query_auth: None, + } + .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) + .unwrap(); + + let treasury = treasury::InstantiateMsg { + admin_auth: admin_auth.clone().into(), + viewing_key: viewing_key.clone(), + multisig: admin.to_string().clone(), + } + .test_init(Treasury::default(), &mut app, admin.clone(), "treasury", &[ + ]) + .unwrap(); + + let manager = treasury_manager::InstantiateMsg { + admin_auth: admin_auth.clone().into(), + treasury: treasury.address.to_string(), + viewing_key: viewing_key.clone(), + } + .test_init( + TreasuryManager::default(), + &mut app, + admin.clone(), + "manager", + &[], + ) + .unwrap(); + + let scrt_staking = scrt_staking::InstantiateMsg { + admin_auth: admin_auth.clone().into(), + owner: manager.address.clone().to_string(), + sscrt: token.clone().into(), + validator_bounds: None, + viewing_key: viewing_key.clone(), + } + .test_init( + ScrtStaking::default(), + &mut app, + admin.clone(), + "scrt_staking", + &[], + ) + .unwrap(); + + app.sudo(SudoMsg::Staking(StakingSudo::AddValidator { + validator: validator.to_string().clone(), + })) + .unwrap(); + + // Set admin viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Register treasury assets + treasury::ExecuteMsg::RegisterAsset { + contract: token.clone().into(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Register manager assets + treasury_manager::ExecuteMsg::RegisterAsset { + contract: token.clone().into(), + } + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // Register manager w/ treasury + treasury::ExecuteMsg::RegisterManager { + contract: manager.clone().into(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // treasury allowance to manager + treasury::ExecuteMsg::Allowance { + asset: token.address.to_string().clone(), + allowance: treasury::Allowance { + //nick: "Mid-Stakes-Manager".to_string(), + spender: manager.address.clone(), + allowance_type: AllowanceType::Portion, + cycle: Cycle::Constant, + amount: allowance, + // 100% (adapter balance will 2x before unbond) + tolerance: Uint128::zero(), + }, + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Allocate to scrt_staking from manager + treasury_manager::ExecuteMsg::Allocate { + asset: token.address.to_string().clone(), + allocation: Allocation { + nick: Some("scrt_staking".to_string()), + contract: Contract { + address: scrt_staking.address.clone(), + code_hash: scrt_staking.code_hash.clone(), + }, + alloc_type, + amount: alloc_amount, + tolerance: Uint128::zero(), + }, + } + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + let deposit_coin = Coin { + denom: "uscrt".into(), + amount: deposit, + }; + app.init_modules(|router, _, storage| { + router + .bank + .init_balance(storage, &admin.clone(), vec![deposit_coin.clone()]) + .unwrap(); + }); + + assert!(deposit_coin.amount > Uint128::zero()); + + // Wrap L1 + snip20::ExecuteMsg::Deposit { padding: None } + .test_exec(&token, &mut app, admin.clone(), &vec![deposit_coin]) + .unwrap(); + + // Deposit funds into treasury + snip20::ExecuteMsg::Send { + recipient: treasury.address.to_string().clone(), + recipient_code_hash: None, + amount: Uint128::new(deposit.u128()), + msg: None, + memo: None, + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update treasury + println!("UPDATE TREASURY"); + adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { + asset: token.address.to_string().clone(), + }) + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Check treasury allowance to manager + match (treasury::QueryMsg::Allowance { + asset: token.address.to_string().clone(), + spender: manager.address.to_string().clone(), + } + .test_query(&treasury, &app) + .unwrap()) + { + treasury::QueryAnswer::Allowance { amount } => { + assert_eq!(amount, expected_allowance, "Treasury->Manager Allowance"); + } + _ => panic!("query failed"), + }; + + // Update manager + manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { + asset: token.address.to_string().clone(), + }) + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update SCRT Staking + adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { + asset: token.address.to_string().clone(), + }) + .test_exec(&scrt_staking, &mut app, admin.clone(), &[]) + .unwrap(); + + // Treasury reserves check + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { + asset: token.address.to_string().clone(), + }) + .test_query(&treasury, &app) + .unwrap()) + { + adapter::QueryAnswer::Reserves { amount } => { + assert_eq!(amount, pre_rewards.0, "Treasury Reserves"); + } + _ => panic!("Query Failed"), + }; + + // Manager reserves + match (manager::QueryMsg::Manager(manager::SubQueryMsg::Reserves { + asset: token.address.to_string().clone(), + holder: treasury.address.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap()) + { + adapter::QueryAnswer::Reserves { amount } => { + assert_eq!(amount, pre_rewards.1, "Manager Reserves"); + } + _ => panic!("Query Failed"), + }; + + // Scrt Staking reserves should be 0 (all staked) + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap()) + { + adapter::QueryAnswer::Reserves { amount } => { + assert_eq!(amount, Uint128::zero(), "SCRT Staking Reserves"); + } + _ => panic!("Query Failed"), + }; + + // Scrt Staking balance check + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap()) + { + adapter::QueryAnswer::Balance { amount } => { + assert_eq!(amount, pre_rewards.2, "SCRT Staking Balance"); + } + _ => panic!("Query Failed"), + }; + + // Add Rewards + app.sudo(SudoMsg::Staking(StakingSudo::AddRewards { + amount: Coin { + denom: "uscrt".to_string(), + amount: rewards, + }, + })) + .unwrap(); + + // Scrt Staking Balance + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap()) + { + adapter::QueryAnswer::Balance { amount } => { + println!("L352 scrt bal {}", amount); + assert_eq!( + amount, post_rewards.2, + "SCRT Staking Balance Post-Rewards Pre-update" + ); + } + _ => panic!("Query Failed"), + }; + + // Update SCRT Staking + adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { + asset: token.address.to_string().clone(), + }) + .test_exec(&scrt_staking, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update manager + manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { + asset: token.address.to_string().clone(), + }) + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update treasury + adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { + asset: token.address.to_string().clone(), + }) + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + app.sudo(SudoMsg::Staking(StakingSudo::FastForwardUndelegate {})) + .unwrap(); + + // Scrt Staking Balance + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap()) + { + adapter::QueryAnswer::Balance { amount } => { + println!("IN THE MIDDLE scrt bal {}", amount); + assert_eq!( + amount, post_rewards.2, + "SCRT Staking Balance Post-Rewards Pre-update" + ); + } + _ => panic!("Query Failed"), + }; + + // Update SCRT Staking + adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { + asset: token.address.to_string().clone(), + }) + .test_exec(&scrt_staking, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update manager + manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { + asset: token.address.to_string().clone(), + }) + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update treasury + adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { + asset: token.address.to_string().clone(), + }) + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Scrt Staking Balance + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap()) + { + adapter::QueryAnswer::Balance { amount } => { + println!("L397 scrt bal {}", amount); + assert_eq!( + amount, post_rewards.2, + "SCRT Staking Balance Post-Rewards Post-Update" + ); + } + _ => panic!("Query Failed"), + }; + + // Scrt Staking unbondable check + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbondable { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &mut app) + .unwrap()) + { + adapter::QueryAnswer::Unbondable { amount } => { + assert_eq!(amount, post_rewards.2, "Scrt Staking Unbondable"); + } + _ => panic!("Query Failed"), + }; + + // Manager unbondable check + match (manager::QueryMsg::Manager(manager::SubQueryMsg::Unbondable { + asset: token.address.to_string().clone(), + holder: treasury.address.to_string().clone(), + }) + .test_query(&manager, &mut app) + .unwrap()) + { + manager::QueryAnswer::Unbondable { amount } => { + assert_eq!( + amount, + post_rewards.1 + post_rewards.2, + "Manager Unbondable" + ); + } + _ => panic!("Query Failed"), + }; + + // Treasury unbondable check + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbondable { + asset: token.address.to_string().clone(), + }) + .test_query(&treasury, &mut app) + .unwrap()) + { + adapter::QueryAnswer::Unbondable { amount } => { + assert_eq!( + amount, + post_rewards.1 + post_rewards.2, + "Treasury Unbondable" + ); + } + _ => panic!("Query Failed"), + }; + + // Unbond all w/ treasury + adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Unbond { + amount: post_rewards.1 + post_rewards.2, + asset: token.address.to_string().clone(), + }) + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // scrt staking balance + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &mut app) + .unwrap()) + { + adapter::QueryAnswer::Balance { amount } => { + assert_eq!( + amount, + Uint128::zero(), + "Scrt Staking Balance Pre-fastforward" + ); + } + _ => panic!("Query Failed"), + }; + + // scrt staking unbonding + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbonding { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &mut app) + .unwrap()) + { + adapter::QueryAnswer::Unbonding { amount } => { + assert_eq!( + amount, post_rewards.2, + "Scrt Staking Unbonding Pre-fastforward" + ); + } + _ => panic!("Query Failed"), + }; + + // scrt staking claimable + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &mut app) + .unwrap()) + { + adapter::QueryAnswer::Claimable { amount } => { + assert_eq!( + amount, + Uint128::zero(), + "Scrt Staking Claimable Pre-fastforward" + ); + } + _ => panic!("Query Failed"), + }; + + // Manager Claimable + match (manager::QueryMsg::Manager(manager::SubQueryMsg::Claimable { + asset: token.address.to_string().clone(), + holder: treasury.address.to_string().clone(), + }) + .test_query(&manager, &mut app) + .unwrap()) + { + manager::QueryAnswer::Claimable { amount } => { + assert_eq!(amount, Uint128::zero(), "Manager Claimable Pre-fastforward"); + } + _ => panic!("Query Failed"), + }; + + // Manager Unbonding + match (manager::QueryMsg::Manager(manager::SubQueryMsg::Unbonding { + asset: token.address.to_string().clone(), + holder: treasury.address.to_string().clone(), + }) + .test_query(&manager, &mut app) + .unwrap()) + { + manager::QueryAnswer::Unbonding { amount } => { + assert_eq!(amount, post_rewards.2, "Manager Unbonding Pre-fastforward"); + } + _ => panic!("Query Failed"), + }; + + app.sudo(SudoMsg::Staking(StakingSudo::FastForwardUndelegate {})) + .unwrap(); + + // scrt staking unbonding + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbonding { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &mut app) + .unwrap()) + { + adapter::QueryAnswer::Unbonding { amount } => { + assert_eq!( + amount, + Uint128::zero(), + "Scrt Staking Unbonding Post-fastforward" + ); + } + _ => panic!("Query Failed"), + }; + + // scrt staking claimable + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &mut app) + .unwrap()) + { + adapter::QueryAnswer::Claimable { amount } => { + assert_eq!( + amount, post_rewards.2, + "Scrt Staking Claimable Post-fastforward" + ); + } + _ => panic!("Query Failed"), + }; + + /* + // Claim Treasury Manager + manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Claim { + asset: token.address.to_string().clone(), + }) + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + */ + + // Claim Treasury + adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Claim { + asset: token.address.to_string().clone(), + }) + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + /* + // Treasury reserves check + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { + asset: token.address.to_string().clone(), + }) + .test_query(&treasury, &mut app)) + .unwrap() + { + adapter::QueryAnswer::Reserves { amount } => { + assert_eq!(amount, deposit + rewards, "Treasury Reserves Post-Claim"); + } + _ => panic!("Bad Reserves Query Response"), + }; + */ + + /* + // Manager balance check + match (manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + holder: treasury.address.to_string().clone(), + }) + .test_query(&manager, &mut app) + .unwrap()) + { + manager::QueryAnswer::Balance { amount } => { + assert_eq!(amount, deposit + rewards, "Manager Balance Post Claim"); + } + _ => panic!("Query Failed"), + }; + */ + + // Scrt Staking balance + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &mut app) + .unwrap()) + { + adapter::QueryAnswer::Balance { amount } => { + assert_eq!(amount, Uint128::zero(), "SCRT Staking Balance Post Claim "); + } + _ => panic!("Query Failed"), + }; + + // Treasury balance check + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&treasury, &mut app) + .unwrap()) + { + adapter::QueryAnswer::Balance { amount } => { + assert_eq!(amount, deposit + rewards, "Treasury Balance Post Claim"); + } + _ => panic!("Query Failed"), + }; + + // Scrt Staking reserves + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &mut app) + .unwrap()) + { + adapter::QueryAnswer::Reserves { amount } => { + assert_eq!(amount, Uint128::zero(), "SCRT Staking Reserves Post Unbond"); + } + _ => panic!("Query Failed"), + }; + + // Manager unbonding check + match (manager::QueryMsg::Manager(manager::SubQueryMsg::Unbonding { + asset: token.address.to_string().clone(), + holder: treasury.address.to_string().clone(), + }) + .test_query(&manager, &mut app) + .unwrap()) + { + manager::QueryAnswer::Unbonding { amount } => { + assert_eq!(amount, Uint128::zero(), "Manager Unbonding Post-Claim"); + } + _ => panic!("Query Failed"), + }; + + // Manager balance check + match (manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + holder: treasury.address.to_string().clone(), + }) + .test_query(&manager, &mut app) + .unwrap()) + { + manager::QueryAnswer::Balance { amount } => { + assert_eq!(amount, Uint128::zero(), "Manager Balance Post-Claim"); + } + _ => panic!("Query Failed"), + }; + + // Manager reserves check + match (manager::QueryMsg::Manager(manager::SubQueryMsg::Reserves { + asset: token.address.to_string().clone(), + holder: treasury.address.to_string().clone(), + }) + .test_query(&manager, &mut app) + .unwrap()) + { + manager::QueryAnswer::Reserves { amount } => { + assert_eq!(amount, Uint128::zero(), "Manager Reserves Post-Unbond"); + } + _ => panic!("Query Failed"), + }; + + // Treasury reserves check + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&treasury, &mut app) + .unwrap()) + { + adapter::QueryAnswer::Balance { amount } => { + assert_eq!(amount, deposit + rewards, "Treasury Balance Post-Unbond"); + } + _ => panic!("Query Failed"), + }; + + // Treasury balance check + match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&treasury, &mut app) + .unwrap()) + { + adapter::QueryAnswer::Balance { amount } => { + assert_eq!(amount, deposit + rewards, "Treasury Balance Post-Unbond"); + } + _ => panic!("Query Failed"), + }; + + // Migration + println!("Setting migration runlevel"); + treasury::ExecuteMsg::SetRunLevel { + run_level: RunLevel::Migrating, + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + //Update + adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { + asset: token.address.to_string().clone(), + }) + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Check Metrics + match (treasury::QueryMsg::Metrics { + date: None, //Some(utc_from_timestamp(app.block_info().time).to_rfc3339()), + period: Period::Hour, + } + .test_query(&treasury, &app) + .unwrap()) + { + treasury::QueryAnswer::Metrics { metrics } => { + for m in metrics.clone() { + println!("{}", serde_json::to_string(&m).unwrap()); + } + } + _ => panic!("query failed"), + }; + + match (snip20::QueryMsg::Balance { + address: admin.to_string().clone(), + key: viewing_key.clone(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, deposit + rewards, "post-migration full unbond"); + } + _ => {} + }; +} + +macro_rules! single_asset_manager_scrt_staking_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + deposit, + allowance, + expected_allowance, + alloc_type, + alloc_amount, + rewards, + pre_rewards, + post_rewards, + ) = $value; + single_asset_manager_scrt_staking_integration( + deposit, + allowance, + expected_allowance, + alloc_type, + alloc_amount, + rewards, + pre_rewards, + post_rewards, + ); + } + )* + } +} + +single_asset_manager_scrt_staking_tests! { + single_asset_portion_manager_0: ( + Uint128::new(100), // deposit + Uint128::new(1 * 10u128.pow(18)), // manager allowance 100% + Uint128::new(100), // expected manager allowance + AllocationType::Portion, + Uint128::new(1 * 10u128.pow(18)), // allocate 100% + Uint128::new(100), // rewards + // pre-rewards + ( + Uint128::new(0), // treasury 10 + Uint128::new(0), // manager 0 + Uint128::new(100), // scrt_staking 90 + ), + //post-rewards + ( + Uint128::new(0), // treasury 10 + Uint128::new(0), // manager 0 + Uint128::new(200), // scrt_staking 90 + ), + ), + single_asset_portion_manager_1: ( + Uint128::new(1000), // deposit + Uint128::new(5 * 10u128.pow(17)), // %50 manager allowance + Uint128::new(500), // expected manager allowance + AllocationType::Portion, + Uint128::new(1 * 10u128.pow(18)), // 100% allocate + Uint128::new(10), // rewards + ( + Uint128::new(500), // treasury 55 (manager won't pull unused allowance + Uint128::new(0), // manager 0 + Uint128::new(500), // scrt_staking + ), + ( + Uint128::new(500), + Uint128::new(0), + Uint128::new(510), + ), + ), +} +*/ diff --git a/contracts/dao/treasury/tests/integration/tolerance.rs b/contracts/dao/treasury/tests/integration/tolerance.rs new file mode 100644 index 0000000..94cbc06 --- /dev/null +++ b/contracts/dao/treasury/tests/integration/tolerance.rs @@ -0,0 +1,436 @@ +use shade_multi_test::multi::{admin::init_admin_auth, snip20::Snip20, treasury::Treasury}; +use shade_protocol::{ + c_std::{to_binary, Addr, Uint128}, + contract_interfaces::{ + dao::{treasury, treasury::AllowanceType}, + snip20, + }, + multi_test::App, + utils::{cycle::Cycle, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +fn underfunded_tolerance( + deposit: Uint128, + added: Uint128, + tolerance: Uint128, + + allowance: Uint128, + allow_type: AllowanceType, + + expected: Uint128, +) { + let mut app = App::default(); + + let admin = Addr::unchecked("admin"); + let spender = Addr::unchecked("spender"); + let _user = Addr::unchecked("user"); + //let validator = Addr::unchecked("validator"); + let admin_auth = init_admin_auth(&mut app, &admin); + + let viewing_key = "viewing_key".to_string(); + + let token = snip20::InstantiateMsg { + name: "token".into(), + admin: Some("admin".into()), + symbol: "TKN".into(), + decimals: 6, + initial_balances: Some(vec![snip20::InitialBalance { + address: admin.to_string().clone(), + amount: deposit + added, + }]), + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + query_auth: None, + } + .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) + .unwrap(); + + let treasury = treasury::InstantiateMsg { + admin_auth: admin_auth.clone().into(), + viewing_key: viewing_key.clone(), + multisig: admin.to_string().clone(), + } + .test_init(Treasury::default(), &mut app, admin.clone(), "treasury", &[ + ]) + .unwrap(); + + // Set admin viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Register treasury assets + treasury::ExecuteMsg::RegisterAsset { + contract: token.clone().into(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // treasury allowance to spender + treasury::ExecuteMsg::Allowance { + asset: token.address.to_string().clone(), + allowance: treasury::RawAllowance { + //nick: "Mid-Stakes-Manager".to_string(), + spender: spender.clone().to_string(), + allowance_type: allow_type, + cycle: Cycle::Constant, + amount: allowance, + // 100% (adapter balance will 2x before unbond) + tolerance, + }, + refresh_now: true, + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Deposit funds into treasury + snip20::ExecuteMsg::Send { + recipient: treasury.address.to_string().clone(), + recipient_code_hash: None, + amount: deposit, + msg: None, + memo: None, + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update treasury + treasury::ExecuteMsg::Update { + asset: token.address.to_string().clone(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Check treasury allowance + match (treasury::QueryMsg::Allowance { + asset: token.address.to_string().clone(), + spender: spender.to_string().clone(), + } + .test_query(&treasury, &app) + .unwrap()) + { + treasury::QueryAnswer::Allowance { amount } => { + assert_eq!(amount, deposit, "Initial Treasury->Manager Allowance"); + } + _ => panic!("query failed"), + }; + + // Additional funds into treasury + snip20::ExecuteMsg::Send { + recipient: treasury.address.to_string().clone(), + recipient_code_hash: None, + amount: added, + msg: None, + memo: None, + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update treasury + treasury::ExecuteMsg::Update { + asset: token.address.to_string().clone(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Check treasury allowance + match (treasury::QueryMsg::Allowance { + asset: token.address.to_string().clone(), + spender: spender.to_string().clone(), + } + .test_query(&treasury, &app) + .unwrap()) + { + treasury::QueryAnswer::Allowance { amount } => { + assert_eq!(amount, expected, "Final Treasury->Manager Allowance"); + } + _ => panic!("query failed"), + }; +} + +macro_rules! underfunded_tolerance_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + deposit, + added, + tolerance, + allowance, + allow_type, + expected, + ) = $value; + underfunded_tolerance( + deposit, + added, + tolerance, + allowance, + allow_type, + expected, + ); + } + )* + } +} + +underfunded_tolerance_tests! { + portion_tolerance_90_no_increase: ( + Uint128::new(100), // deposit + Uint128::new(50), // added + Uint128::new(9 * 10u128.pow(17)), // tolerance + Uint128::new(1 * 10u128.pow(18)), // allowance + AllowanceType::Portion, + Uint128::new(100), // expected + ), + portion_tolerance_90_will_increase: ( + Uint128::new(100), // deposit + Uint128::new(1000), // added + Uint128::new(9 * 10u128.pow(17)), // tolerance + Uint128::new(1 * 10u128.pow(18)), // allowance + AllowanceType::Portion, + Uint128::new(1100), // expected + ), + amount_tolerance_10_no_increase: ( + Uint128::new(500), // deposit + Uint128::new(20), // added + Uint128::new(9 * 10u128.pow(17)), // tolerance + Uint128::new(520), //allowance + AllowanceType::Amount, + Uint128::new(500), // expected + ), + amount_tolerance_10_will_increase: ( + Uint128::new(500), // deposit + Uint128::new(1000), // added + Uint128::new(1 * 10u128.pow(17)), // tolerance + Uint128::new(1500), //allowance + AllowanceType::Amount, + Uint128::new(1500), // expected + ), +} + +fn overfunded_tolerance( + deposit: Uint128, + tolerance: Uint128, + allowance: Uint128, + reduced: Uint128, + allow_type: AllowanceType, + expected: Uint128, +) { + let mut app = App::default(); + + let admin = Addr::unchecked("admin"); + let spender = Addr::unchecked("spender"); + let _user = Addr::unchecked("user"); + //let validator = Addr::unchecked("validator"); + let admin_auth = init_admin_auth(&mut app, &admin); + + let viewing_key = "viewing_key".to_string(); + + let token = snip20::InstantiateMsg { + name: "token".into(), + admin: Some("admin".into()), + symbol: "TKN".into(), + decimals: 6, + initial_balances: Some(vec![snip20::InitialBalance { + address: admin.to_string().clone(), + amount: deposit, + }]), + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + query_auth: None, + } + .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) + .unwrap(); + + let treasury = treasury::InstantiateMsg { + admin_auth: admin_auth.clone().into(), + viewing_key: viewing_key.clone(), + multisig: admin.to_string().clone(), + } + .test_init(Treasury::default(), &mut app, admin.clone(), "treasury", &[ + ]) + .unwrap(); + + // Set admin viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Register treasury assets + treasury::ExecuteMsg::RegisterAsset { + contract: token.clone().into(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // treasury allowance to spender + treasury::ExecuteMsg::Allowance { + asset: token.address.to_string().clone(), + allowance: treasury::RawAllowance { + //nick: "Mid-Stakes-Manager".to_string(), + spender: spender.clone().to_string(), + allowance_type: allow_type.clone(), + cycle: Cycle::Constant, + amount: allowance, + // 100% (adapter balance will 2x before unbond) + tolerance, + }, + refresh_now: true, + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Deposit funds into treasury + snip20::ExecuteMsg::Send { + recipient: treasury.address.to_string().clone(), + recipient_code_hash: None, + amount: deposit, + msg: None, + memo: None, + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update treasury + treasury::ExecuteMsg::Update { + asset: token.address.to_string().clone(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Check treasury allowance + match (treasury::QueryMsg::Allowance { + asset: token.address.to_string().clone(), + spender: spender.to_string().clone(), + } + .test_query(&treasury, &app) + .unwrap()) + { + treasury::QueryAnswer::Allowance { amount } => { + println!("INITIAL {}", amount); + assert_eq!(amount, deposit, "Initial Treasury->Manager Allowance"); + } + _ => panic!("query failed"), + }; + + // Reduce allowance to simulate overfunding + treasury::ExecuteMsg::Allowance { + asset: token.address.to_string().clone(), + allowance: treasury::RawAllowance { + spender: spender.clone().to_string(), + allowance_type: allow_type, + cycle: Cycle::Constant, + amount: reduced, + tolerance, + }, + refresh_now: true, + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update treasury + treasury::ExecuteMsg::Update { + asset: token.address.to_string().clone(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + // Check treasury allowance + match (treasury::QueryMsg::Allowance { + asset: token.address.to_string().clone(), + spender: spender.to_string().clone(), + } + .test_query(&treasury, &app) + .unwrap()) + { + treasury::QueryAnswer::Allowance { amount } => { + assert_eq!(amount, expected, "Final Treasury->Manager Allowance"); + } + _ => panic!("query failed"), + }; +} + +macro_rules! overfunded_tolerance_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + deposit, + tolerance, + allowance, + reduced, + allow_type, + expected, + ) = $value; + overfunded_tolerance( + deposit, + tolerance, + allowance, + reduced, + allow_type, + expected, + ); + } + )* + } +} + +overfunded_tolerance_tests! { + portion_tolerance_10_no_decrease: ( + Uint128::new(1000), // deposit + Uint128::new(1 * 10u128.pow(17)), // tolerance + Uint128::new(1 * 10u128.pow(18)), // allowance + Uint128::new(99 * 10u128.pow(16)), // reduced_allowance + AllowanceType::Portion, + Uint128::new(1000), // expected + ), + portion_tolerance_10_will_decrease: ( + Uint128::new(1000), // deposit + Uint128::new(1 * 10u128.pow(17)), // tolerance + Uint128::new(1 * 10u128.pow(18)), // allowance + Uint128::new(5 * 10u128.pow(17)), // reduced_allowance + AllowanceType::Portion, + Uint128::new(500), // expected + ), + amount_tolerance_10_no_decrease: ( + Uint128::new(500), // deposit + Uint128::new(1 * 10u128.pow(17)), // tolerance + Uint128::new(500), //allowance + Uint128::new(460), // reduced allowance + AllowanceType::Amount, + Uint128::new(500), // expected + ), + amount_tolerance_10_will_decrease: ( + Uint128::new(500), // deposit + Uint128::new(1 * 10u128.pow(17)), // tolerance + Uint128::new(500), //allowance + Uint128::new(400), // reduced allowance + AllowanceType::Amount, + Uint128::new(400), // expected + ), +} diff --git a/contracts/dao/treasury/tests/integration/treasury.rs b/contracts/dao/treasury/tests/integration/treasury.rs new file mode 100644 index 0000000..7266862 --- /dev/null +++ b/contracts/dao/treasury/tests/integration/treasury.rs @@ -0,0 +1,557 @@ +use mock_adapter; +use shade_multi_test::{ + interfaces::{ + self, + utils::{DeployedContracts, SupportedContracts}, + }, + multi::{ + admin::init_admin_auth, + mock_adapter::MockAdapter, + snip20::Snip20, + treasury::Treasury, + treasury_manager::TreasuryManager, + }, +}; +use shade_protocol::{ + c_std::{to_binary, Addr, Coin, Uint128}, + contract_interfaces::{ + dao::{ + treasury, + treasury::{AllowanceType, RunLevel}, + treasury_manager::{self, Allocation, AllocationType}, + }, + snip20, + }, + multi_test::{App, BankSudo, StakingSudo, SudoMsg}, + utils::{ + asset::Contract, + cycle::Cycle, + ExecuteCallback, + InstantiateCallback, + MultiTestable, + Query, + }, +}; + +// Add other adapters here as they come +fn bonded_adapter_int( + deposit: Uint128, + allowance: Uint128, + expected_allowance: Uint128, + alloc_type: AllocationType, + alloc_amount: Uint128, + rewards: Uint128, + // expected balances + pre_rewards: (Uint128, Uint128, Uint128), + post_rewards: (Uint128, Uint128, Uint128), +) { + let mut app = App::default(); + let mut contracts = DeployedContracts::new(); + + let admin = Addr::unchecked("admin"); + let admin_auth = init_admin_auth(&mut app, &admin); + + let viewing_key = "viewing_key".to_string(); + let symbol = "TKN"; + + interfaces::dao::init_dao( + &mut app, + &admin.to_string(), + &mut contracts, + deposit, + symbol, + vec![AllowanceType::Portion], + vec![Cycle::Constant], + vec![allowance], + vec![Uint128::zero()], + vec![vec![alloc_type]], + vec![vec![alloc_amount]], + vec![vec![Uint128::zero()]], + false, + false, + ); + + // Update treasury + interfaces::treasury::update_exec(&mut app, &admin.to_string(), &contracts, symbol).unwrap(); + + // Check initial allowance + assert_eq!( + interfaces::treasury::allowance_query( + &app, + &contracts, + symbol, + SupportedContracts::TreasuryManager(0), + ) + .unwrap(), + expected_allowance, + "Treasury->Manager Allowance" + ); + + // Update manager + interfaces::treasury_manager::update_exec( + &mut app, + &admin.to_string(), + &contracts, + symbol, + SupportedContracts::TreasuryManager(0), + ) + .unwrap(); + + // Update Adapter + interfaces::dao::update_exec( + &mut app, + &admin.to_string(), + &contracts, + symbol, + SupportedContracts::MockAdapter(0), + ) + .unwrap(); + + // Treasury reserves check + assert_eq!( + interfaces::treasury::reserves_query(&app, &contracts, symbol).unwrap(), + pre_rewards.0, + "Treasury Reserves", + ); + + // Manager reserves + assert_eq!( + interfaces::treasury_manager::reserves_query( + &app, + &contracts, + symbol, + SupportedContracts::TreasuryManager(0), + SupportedContracts::Treasury, + ) + .unwrap(), + pre_rewards.1, + "Manager Reserves", + ); + + // Adapter reserves should be 0 (all staked) + assert_eq!( + interfaces::dao::reserves_query( + &app, + &contracts, + symbol, + SupportedContracts::MockAdapter(0) + ) + .unwrap(), + Uint128::zero(), + "Bonded Adapter Reserves", + ); + + // Adapter balance + assert_eq!( + interfaces::dao::balance_query( + &app, + &contracts, + symbol, + SupportedContracts::MockAdapter(0) + ) + .unwrap(), + pre_rewards.2, + "Adapter Balance", + ); + + // Add Rewards + interfaces::snip20::send_exec( + &mut app, + &admin.to_string(), + &contracts, + symbol, + contracts + .get(&SupportedContracts::MockAdapter(0)) + .unwrap() + .address + .to_string(), + rewards, + None, + ) + .unwrap(); + + // Adapter Balance + assert_eq!( + interfaces::dao::balance_query( + &app, + &contracts, + symbol, + SupportedContracts::MockAdapter(0) + ) + .unwrap(), + pre_rewards.2 + rewards, + "Adapter Balance Post-Rewards Pre-Update", + ); + + // Update manager + interfaces::treasury_manager::update_exec( + &mut app, + &admin.to_string(), + &contracts, + symbol, + SupportedContracts::TreasuryManager(0), + ) + .unwrap(); + + // Update treasury + interfaces::treasury::update_exec(&mut app, &admin.to_string(), &contracts, symbol).unwrap(); + + // Adapter Balance + assert_eq!( + interfaces::dao::balance_query( + &app, + &contracts, + symbol, + SupportedContracts::MockAdapter(0) + ) + .unwrap(), + pre_rewards.2 + rewards, + "Adapter Balance Post-Rewards Post-Update" + ); + + // Adapter Unbondable + assert_eq!( + interfaces::dao::unbondable_query( + &app, + &contracts, + symbol, + SupportedContracts::MockAdapter(0) + ) + .unwrap(), + post_rewards.2, + "Adapter Unbondable", + ); + + // Manager unbondable check + assert_eq!( + interfaces::treasury_manager::unbondable_query( + &app, + &contracts, + symbol, + SupportedContracts::TreasuryManager(0), + SupportedContracts::Treasury, + ) + .unwrap(), + post_rewards.1 + post_rewards.2, + "Manager Unbondable" + ); + + // Unbond all w/ manager + interfaces::treasury_manager::unbond_exec( + &mut app, + &admin.to_string(), + &contracts, + symbol, + SupportedContracts::TreasuryManager(0), + post_rewards.1 + post_rewards.2, + ) + .unwrap(); + + // Adapter Reserves + assert_eq!( + interfaces::dao::reserves_query( + &app, + &contracts, + symbol, + SupportedContracts::MockAdapter(0) + ) + .unwrap(), + Uint128::zero(), + "Adapter Reserves Pre-fastforward" + ); + + // Adapter Unbonding + assert_eq!( + interfaces::dao::unbonding_query( + &app, + &contracts, + symbol, + SupportedContracts::MockAdapter(0) + ) + .unwrap(), + pre_rewards.2 + rewards, + "Adapter Unbonding Pre-fastforward" + ); + + // Adapter Claimable + assert_eq!( + interfaces::dao::claimable_query( + &app, + &contracts, + symbol, + SupportedContracts::MockAdapter(0) + ) + .unwrap(), + Uint128::zero(), + "Adapter Claimable Pre-fastforward", + ); + + // Manager Claimable + assert_eq!( + interfaces::treasury_manager::claimable_query( + &app, + &contracts, + symbol, + SupportedContracts::TreasuryManager(0), + SupportedContracts::Treasury, + ) + .unwrap(), + Uint128::zero(), + "Manager Claimable Pre-fastforward" + ); + + // Manager Unbonding + assert_eq!( + interfaces::treasury_manager::unbonding_query( + &app, + &contracts, + symbol, + SupportedContracts::TreasuryManager(0), + SupportedContracts::Treasury, + ) + .unwrap(), + pre_rewards.2 + rewards, + "Manager Claimable Pre-fastforward" + ); + + // Complete unbondings + interfaces::dao::mock_adapter_complete_unbonding( + &mut app, + &admin.to_string(), + &contracts, + SupportedContracts::MockAdapter(0), + ) + .unwrap(); + + // adapter unbonding + assert_eq!( + interfaces::dao::unbonding_query( + &app, + &contracts, + symbol, + SupportedContracts::MockAdapter(0) + ) + .unwrap(), + Uint128::zero(), + "Adapter Unbonding Post-fastforward" + ); + + // adapter claimable + assert_eq!( + interfaces::dao::claimable_query( + &app, + &contracts, + symbol, + SupportedContracts::MockAdapter(0) + ) + .unwrap(), + pre_rewards.2 + rewards, + "Adapter Claimable Post-fastforward" + ); + + // Claim Treasury Manager + interfaces::treasury_manager::claim_exec( + &mut app, + &admin.to_string(), + &contracts, + symbol, + SupportedContracts::TreasuryManager(0), + ) + .unwrap(); + + // Adapter balance + assert_eq!( + interfaces::dao::balance_query( + &app, + &contracts, + symbol, + SupportedContracts::MockAdapter(0) + ) + .unwrap(), + Uint128::zero(), + "Adapter Balance Post-Claim" + ); + + // Treasury balance check + assert_eq!( + interfaces::treasury::balance_query(&app, &contracts, symbol,).unwrap(), + deposit + rewards, + "Treasury Balance Post Claim" + ); + + // Adapter reserves + assert_eq!( + interfaces::dao::reserves_query( + &app, + &contracts, + symbol, + SupportedContracts::MockAdapter(0) + ) + .unwrap(), + Uint128::zero(), + "Adapter Reserves Post-Claim" + ); + + // Manager unbonding check + assert_eq!( + interfaces::treasury_manager::reserves_query( + &app, + &contracts, + symbol, + SupportedContracts::TreasuryManager(0), + SupportedContracts::Treasury, + ) + .unwrap(), + Uint128::zero(), + "Manager Unbonding Post-Claim" + ); + + // Manager balance check + assert_eq!( + interfaces::treasury_manager::balance_query( + &app, + &contracts, + symbol, + SupportedContracts::TreasuryManager(0), + SupportedContracts::Treasury, + ) + .unwrap(), + Uint128::zero(), + "Manager Balance Post-Claim" + ); + + // Manager reserves check + assert_eq!( + interfaces::treasury_manager::reserves_query( + &app, + &contracts, + symbol, + SupportedContracts::TreasuryManager(0), + SupportedContracts::Treasury, + ) + .unwrap(), + Uint128::zero(), + "Manager Reserves Post-Unbond" + ); + + // Treasury reserves check + assert_eq!( + interfaces::treasury::reserves_query(&app, &contracts, symbol,).unwrap(), + deposit + rewards, + "Treasury Reserves Post-Unbond" + ); + assert_eq!( + interfaces::treasury::balance_query(&app, &contracts, symbol,).unwrap(), + deposit + rewards, + "Treasury Balance Post-Unbond" + ); + + // Migration + interfaces::treasury::set_run_level_exec( + &mut app, + &admin.to_string(), + &contracts, + RunLevel::Migrating, + ) + .unwrap(); + + interfaces::treasury::update_exec(&mut app, &admin.to_string(), &contracts, symbol).unwrap(); + + assert_eq!( + interfaces::treasury::balance_query(&app, &contracts, symbol,).unwrap(), + Uint128::zero(), + "post-migration full unbond" + ); +} + +macro_rules! bonded_adapter_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + deposit, + allowance, + expected_allowance, + alloc_type, + alloc_amount, + rewards, + pre_rewards, + post_rewards, + ) = $value; + bonded_adapter_int( + deposit, + allowance, + expected_allowance, + alloc_type, + alloc_amount, + rewards, + pre_rewards, + post_rewards, + ); + } + )* + } +} + +bonded_adapter_tests! { + portion_with_rewards_0: ( + Uint128::new(100), // deposit + Uint128::new(1 * 10u128.pow(18)), // manager allowance 100% + Uint128::new(100), // expected manager allowance + AllocationType::Portion, + Uint128::new(1 * 10u128.pow(18)), // allocate 100% + Uint128::new(100), // rewards + // pre-rewards + ( + Uint128::new(0), // treasury 10 + Uint128::new(0), // manager 0 + Uint128::new(100), // mock_adapter 90 + ), + //post-rewards + ( + Uint128::new(0), // treasury 10 + Uint128::new(0), // manager 0 + Uint128::new(200), // mock_adapter 90 + ), + ), + portion_with_rewards_1: ( + Uint128::new(1000), // deposit + Uint128::new(5 * 10u128.pow(17)), // %50 manager allowance + Uint128::new(500), // expected manager allowance + AllocationType::Portion, + Uint128::new(1 * 10u128.pow(18)), // 100% allocate + Uint128::new(10), // rewards + ( + Uint128::new(500), // treasury 55 (manager won't pull unused allowance + Uint128::new(0), // manager 0 + Uint128::new(500), // mock_adapter + ), + ( + Uint128::new(505), + Uint128::new(0), + Uint128::new(505), + ), + ), + /* + // TODO: this needs separate test logic bc of update + amount_with_rewards_0: ( + Uint128::new(1_000_000), // deposit + Uint128::new(5 * 10u128.pow(17)), // %50 manager allowance + Uint128::new(500_000), // expected manager allowance + AllocationType::Amount, + Uint128::new(500_000), // .5 tkn (all) allocate + Uint128::new(500), // rewards + ( + Uint128::new(500_000), // treasury + Uint128::new(0), // manager 0 + Uint128::new(500_000), // mock_adapter + ), + ( + Uint128::new(500_250), + Uint128::new(250), + Uint128::new(500_000), + ), + ), + */ +} diff --git a/contracts/dao/treasury/tests/integration/wrap.rs b/contracts/dao/treasury/tests/integration/wrap.rs new file mode 100644 index 0000000..c591fab --- /dev/null +++ b/contracts/dao/treasury/tests/integration/wrap.rs @@ -0,0 +1,145 @@ +use shade_multi_test::multi::{admin::init_admin_auth, snip20::Snip20, treasury::Treasury}; +use shade_protocol::{ + c_std::{from_binary, to_binary, Addr, Coin, Uint128}, + contract_interfaces::{dao::treasury, snip20}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +// Add other adapters here as they come +fn wrap_coins_test(coins: Vec) { + let mut app = App::default(); + + let admin = Addr::unchecked("admin"); + let _user = Addr::unchecked("user"); + //let validator = Addr::unchecked("validator"); + let admin_auth = init_admin_auth(&mut app, &admin); + + let viewing_key = "viewing_key".to_string(); + + let mut tokens = vec![]; + + let fail_coin = Coin { + denom: "fail".into(), + amount: Uint128::new(100), + }; + + for coin in coins.clone() { + let token = snip20::InstantiateMsg { + name: coin.denom.clone(), + admin: Some("admin".into()), + symbol: coin.denom.to_uppercase().clone(), + decimals: 6, + initial_balances: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + query_auth: None, + } + .test_init(Snip20::default(), &mut app, admin.clone(), &coin.denom, &[]) + .unwrap(); + + tokens.push(token); + } + + let treasury = treasury::InstantiateMsg { + admin_auth: admin_auth.clone().into(), + viewing_key: viewing_key.clone(), + multisig: admin.to_string().clone(), + } + .test_init(Treasury::default(), &mut app, admin.clone(), "treasury", &[ + ]) + .unwrap(); + + /* + // Set admin viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + */ + + // Register treasury assets + for (token, coin) in tokens.iter().zip(coins.clone().iter()) { + treasury::ExecuteMsg::RegisterAsset { + contract: token.clone().into(), + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + treasury::ExecuteMsg::RegisterWrap { + denom: coin.denom.clone(), + contract: RawContract { + address: token.address.clone().into(), + code_hash: token.code_hash.clone(), + }, + } + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + } + + let mut all_coins = coins.clone(); + all_coins.push(fail_coin.clone()); + + app.init_modules(|router, _, storage| { + router + .bank + .init_balance(storage, &treasury.address.clone(), all_coins.clone()) + .unwrap(); + }); + + // Wrap + let wrap_resp = treasury::ExecuteMsg::WrapCoins {} + .test_exec(&treasury, &mut app, admin.clone(), &[]) + .unwrap(); + + match from_binary(&wrap_resp.data.unwrap()).ok().unwrap() { + treasury::ExecuteAnswer::WrapCoins { success, failed } => { + assert!(success == coins, "All coins succeed"); + assert!(failed == vec![fail_coin], "Unconfigured coin fails"); + } + _ => { + panic!("WrapCoins bad response"); + } + } + + // Treasury Balances + for (token, coin) in tokens.iter().zip(coins.iter()) { + match (treasury::QueryMsg::Balance { + asset: token.address.to_string().clone(), + } + .test_query(&treasury, &app) + .unwrap()) + { + treasury::QueryAnswer::Balance { amount } => { + assert_eq!(amount, coin.amount, "Treasury Balance"); + } + _ => panic!("Balance query Failed"), + }; + } +} + +macro_rules! wrap_coins_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let coins = $value; + wrap_coins_test(coins); + } + )* + } +} + +wrap_coins_tests! { + wrap_sscrt: vec![Coin { denom: "uscrt".into(), amount: Uint128::new(100) }], + //wrap_other: vec![Coin { denom: "other".into(), amount: Uint128::new(100) }], +} diff --git a/contracts/dao/treasury/tests/mod.rs b/contracts/dao/treasury/tests/mod.rs new file mode 100644 index 0000000..e6cb40c --- /dev/null +++ b/contracts/dao/treasury/tests/mod.rs @@ -0,0 +1,2 @@ +pub mod dao; +pub mod integration; diff --git a/contracts/dao/treasury_manager/.cargo/config b/contracts/dao/treasury_manager/.cargo/config new file mode 100644 index 0000000..882fe08 --- /dev/null +++ b/contracts/dao/treasury_manager/.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/dao/treasury_manager/.circleci/config.yml b/contracts/dao/treasury_manager/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/contracts/dao/treasury_manager/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/dao/treasury_manager/Cargo.toml b/contracts/dao/treasury_manager/Cargo.toml new file mode 100644 index 0000000..27640b1 --- /dev/null +++ b/contracts/dao/treasury_manager/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "treasury_manager" +version = "0.1.0" +authors = ["Jack Swenson ", "Jack Sisson ./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 + +# 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/dao/treasury_manager/README.md b/contracts/dao/treasury_manager/README.md new file mode 100644 index 0000000..70ec594 --- /dev/null +++ b/contracts/dao/treasury_manager/README.md @@ -0,0 +1,142 @@ +# Treasury Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [DAO Adapter](/packages/shade_protocol/src/DAO_ADAPTER.md) + * [Init](#Init) + * [Interface](#Interface) + * Messages + * [UpdateConfig](#UpdateConfig) + * [RegisterAsset](#RegisterAsset) + * [Allocate](#Allocate) + * Queries + * [Config](#Config) + * [Assets](#Assets) + * [PendingAllowance](#PendingAllowance) +# Introduction +The treasury contract holds network funds from things such as mint commission and pending airdrop funds + +# Sections + +## Init +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|admin | Addr| Admin address +|viewing_key | String | Key set on relevant SNIP-20's +|treasury | Addr | treasury that is owner of funds + +## Interface + +### Messages +#### UpdateConfig +Updates the given values +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|config | Config | New contract config +##### Response +```json +{ + "update_config": { + "status": "success" + } +} +``` + +#### RegisterAsset +Registers a supported asset. The asset must be SNIP-20 compliant since [RegisterReceive](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md#RegisterReceive) is called. + +Note: Will return an error if there's an asset with that address already registered. +##### Request +|Name |Type |Description | optional | +|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| +|contract | Contract | Type explained [here](#Contract) | no | +##### Response +```json +{ + "register_asset": { + "status": "success" + } +} +``` + +#### Allocate +Registers a supported asset. The asset must be SNIP-20 compliant since [RegisterReceive](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md#RegisterReceive) is called. + +Note: Will return an error if there's an asset with that address already registered. +##### Request +|Name |Type |Description | optional | +|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| +|asset | Addr | Desired SNIP-20 +|allocation | Allocation | Allocation data +##### Response +```json +{ + "allocate": { + "status": "success" + } +} +``` + +### Queries + +#### Config +Gets the contract's configuration variables +##### Response +```json +{ + "config": { + "config": { .. } + } +} +``` + +#### Assets +Get the list of registered assets +##### Response +```json +{ + "assets": { + "assets": ["asset address", ..], + } +} +``` + +#### Allocations +Get the allocations for a given asset + +##### Request +|Name |Type |Description | optional | +|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| +|asset | Addr | Address of desired SNIP-20 asset + +##### Response +```json +{ + "allocations": { + "allocations": [ + { + "allocation": {}, + }, + .. + ], + } +} +``` + +#### PendingAllowance +Get the pending allowance for a given asset + +##### Request +|Name |Type |Description | optional | +|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| +|asset | Addr | Address of desired SNIP-20 asset + +##### Response +```json +{ + "pending_allowance": { + "amount": "100000", + } +} +``` diff --git a/contracts/dao/treasury_manager/src/contract.rs b/contracts/dao/treasury_manager/src/contract.rs new file mode 100644 index 0000000..f757a70 --- /dev/null +++ b/contracts/dao/treasury_manager/src/contract.rs @@ -0,0 +1,160 @@ +use crate::{execute, query, storage::*}; +use shade_protocol::{ + c_std::{ + shd_entry_point, + to_binary, + Binary, + Deps, + DepsMut, + Env, + MessageInfo, + Response, + StdResult, + }, + dao::{ + manager, + treasury_manager::{Config, ExecuteMsg, Holding, InstantiateMsg, QueryMsg, Status}, + }, +}; + +#[shd_entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let treasury = deps.api.addr_validate(msg.treasury.as_str())?; + + CONFIG.save(deps.storage, &Config { + admin_auth: msg.admin_auth.into_valid(deps.api)?, + treasury: treasury.clone(), + })?; + + VIEWING_KEY.save(deps.storage, &msg.viewing_key)?; + ASSET_LIST.save(deps.storage, &Vec::new())?; + HOLDERS.save(deps.storage, &vec![treasury.clone()])?; + HOLDING.save(deps.storage, treasury, &Holding { + balances: vec![], + unbondings: vec![], + status: Status::Active, + })?; + + Ok(Response::new()) +} + +#[shd_entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg { + ExecuteMsg::Receive { + sender, + from, + amount, + msg, + .. + } => { + let sender = deps.api.addr_validate(&sender)?; + let from = deps.api.addr_validate(&from)?; + execute::receive(deps, env, info, sender, from, amount, msg) + } + ExecuteMsg::UpdateConfig { + admin_auth, + treasury, + } => execute::update_config(deps, env, info, admin_auth, treasury), + ExecuteMsg::RegisterAsset { contract } => { + let contract = contract.into_valid(deps.api)?; + execute::register_asset(deps, &env, info, &contract) + } + ExecuteMsg::Allocate { asset, allocation } => { + let asset = deps.api.addr_validate(&asset)?; + let allocation = allocation.valid(deps.api)?; + execute::allocate(deps, &env, info, asset, allocation) + } + ExecuteMsg::AddHolder { holder } => { + let holder = deps.api.addr_validate(&holder)?; + execute::add_holder(deps, &env, info, holder) + } + ExecuteMsg::RemoveHolder { holder } => { + let holder = deps.api.addr_validate(&holder)?; + execute::remove_holder(deps, &env, info, holder) + } + ExecuteMsg::Manager(a) => match a { + manager::SubExecuteMsg::Unbond { asset, amount } => { + let asset = deps.api.addr_validate(&asset)?; + execute::unbond(deps, &env, info, asset, amount) + } + manager::SubExecuteMsg::Claim { asset } => { + let asset = deps.api.addr_validate(&asset)?; + execute::claim(deps, &env, info, asset) + } + manager::SubExecuteMsg::Update { asset } => { + let asset = deps.api.addr_validate(&asset)?; + execute::update(deps, &env, info, asset) + } + }, + } +} + +#[shd_entry_point] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => to_binary(&query::config(deps)?), + QueryMsg::Assets {} => to_binary(&query::assets(deps)?), + QueryMsg::Allocations { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::allocations(deps, asset)?) + } + QueryMsg::PendingAllowance { asset } => { + let asset = deps.api.addr_validate(&asset)?; + to_binary(&query::pending_allowance(deps, env, asset)?) + } + QueryMsg::Holders {} => to_binary(&query::holders(deps)?), + QueryMsg::Holding { holder } => { + let holder = deps.api.addr_validate(&holder)?; + to_binary(&query::holding(deps, holder)?) + } + QueryMsg::Metrics { + date, + epoch, + period, + } => to_binary(&query::metrics(deps, env, date, epoch, period)?), + + QueryMsg::Manager(a) => match a { + manager::SubQueryMsg::Balance { asset, holder } => { + let asset = deps.api.addr_validate(&asset)?; + let holder = deps.api.addr_validate(&holder)?; + to_binary(&query::balance(deps, asset, holder)?) + } + manager::SubQueryMsg::BatchBalance { assets, holder } => { + let mut val_assets = vec![]; + + for a in assets { + val_assets.push(deps.api.addr_validate(&a)?); + } + let holder = deps.api.addr_validate(&holder)?; + + to_binary(&query::batch_balance(deps, val_assets, holder)?) + } + manager::SubQueryMsg::Unbonding { asset, holder } => { + let asset = deps.api.addr_validate(&asset)?; + let holder = deps.api.addr_validate(&holder)?; + to_binary(&query::unbonding(deps, asset, holder)?) + } + manager::SubQueryMsg::Unbondable { asset, holder } => { + let asset = deps.api.addr_validate(&asset)?; + let holder = deps.api.addr_validate(&holder)?; + to_binary(&query::unbondable(deps, env, asset, holder)?) + } + manager::SubQueryMsg::Claimable { asset, holder } => { + let asset = deps.api.addr_validate(&asset)?; + let holder = deps.api.addr_validate(&holder)?; + to_binary(&query::claimable(deps, env, asset, holder)?) + } + manager::SubQueryMsg::Reserves { asset, holder } => { + let asset = deps.api.addr_validate(&asset)?; + let holder = deps.api.addr_validate(&holder)?; + to_binary(&query::reserves(deps, env, asset, holder)?) + } + }, + } +} diff --git a/contracts/dao/treasury_manager/src/execute.rs b/contracts/dao/treasury_manager/src/execute.rs new file mode 100644 index 0000000..ecc9399 --- /dev/null +++ b/contracts/dao/treasury_manager/src/execute.rs @@ -0,0 +1,1446 @@ +use crate::storage::*; +use itertools::{Either, Itertools}; +use shade_protocol::{ + admin::helpers::{validate_admin, AdminPermissions}, + c_std::{ + to_binary, + Addr, + Binary, + DepsMut, + Env, + MessageInfo, + Response, + StdError, + StdResult, + Uint128, + }, + dao::{ + adapter, + treasury_manager::{ + Action, + Allocation, + AllocationMeta, + AllocationTempData, + AllocationType, + Balance, + Context, + ExecuteAnswer, + Holding, + Metric, + Status, + }, + }, + snip20, + snip20::{ + batch::{SendAction, SendFromAction}, + helpers::{ + allowance_query, + balance_query, + batch_send_from_msg, + batch_send_msg, + register_receive, + send_msg, + set_viewing_key_msg, + }, + }, + utils::{ + asset::{Contract, RawContract}, + generic_response::ResponseStatus, + }, +}; + +static ONE_HUNDRED_PERCENT: Uint128 = Uint128::new(10u128.pow(18)); + +pub fn receive( + deps: DepsMut, + env: Env, + info: MessageInfo, + _sender: Addr, + from: Addr, + amount: Uint128, + _msg: Option, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let asset = match ASSETS.may_load(deps.storage, info.sender.clone())? { + Some(a) => a, + None => { + return Err(StdError::generic_err("Not a registered asset")); + } + }; + + METRICS.push(deps.storage, env.block.time, Metric { + action: Action::FundsReceived, + context: Context::Receive, + timestamp: env.block.time.seconds(), + token: info.sender.clone(), + amount, + user: from.clone(), + })?; + + // Do nothing if its an adapter (claimed funds) + if let Some(_) = ALLOCATIONS + .load(deps.storage, info.sender.clone())? + .iter() + .find(|a| a.contract.address == from) + { + return Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Receive { + status: ResponseStatus::Success, + })?)); + } + + // Default to treasury if not sent by a holder + let holder = match HOLDERS.load(deps.storage)?.contains(&from) { + true => from.clone(), + false => config.treasury, + }; + + let mut holding = HOLDING.load(deps.storage, holder.clone())?; + if holding.status == Status::Closed { + return Err(StdError::generic_err( + "Cannot add holdings when status is closed", + )); + } + if let Some(i) = holding + .balances + .iter() + .position(|b| b.token == asset.contract.address) + { + holding.balances[i].amount += amount; + } else { + holding.balances.push(Balance { + token: asset.contract.address, + amount, + }); + } + + HOLDING.save(deps.storage, holder, &holding)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Receive { + status: ResponseStatus::Success, + })?)) +} + +pub fn update_config( + deps: DepsMut, + _env: Env, + info: MessageInfo, + admin_auth: Option, + treasury: Option, +) -> StdResult { + let mut config = CONFIG.load(deps.storage)?; + + validate_admin( + &deps.querier, + AdminPermissions::TreasuryManager, + &info.sender, + &config.admin_auth, + )?; + + if let Some(admin_auth) = admin_auth { + config.admin_auth = admin_auth.into_valid(deps.api)?; + } + if let Some(treasury) = treasury { + config.treasury = deps.api.addr_validate(&treasury)?; + } + + CONFIG.save(deps.storage, &config)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { + config, + status: ResponseStatus::Success, + })?), + ) +} + +pub fn register_asset( + deps: DepsMut, + env: &Env, + info: MessageInfo, + contract: &Contract, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + validate_admin( + &deps.querier, + AdminPermissions::TreasuryManager, + &info.sender, + &config.admin_auth, + )?; + + let mut list = ASSET_LIST.load(deps.storage)?; + list.push(contract.address.clone()); + ASSET_LIST.save(deps.storage, &list)?; + + ASSETS.save( + deps.storage, + contract.address.clone(), + &snip20::helpers::fetch_snip20(&contract, &deps.querier)?, + )?; + + ALLOCATIONS.save(deps.storage, contract.address.clone(), &Vec::new())?; + + UNBONDINGS.save(deps.storage, contract.address.clone(), &Uint128::zero())?; + + Ok(Response::new() + .add_messages(vec![ + // Register contract in asset + register_receive(env.contract.code_hash.clone(), None, &contract)?, + // Set viewing key + set_viewing_key_msg(VIEWING_KEY.load(deps.storage)?, None, &contract)?, + ]) + .set_data(to_binary(&ExecuteAnswer::RegisterAsset { + status: ResponseStatus::Success, + })?)) +} + +pub fn allocate( + deps: DepsMut, + _env: &Env, + info: MessageInfo, + asset: Addr, + allocation: Allocation, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + validate_admin( + &deps.querier, + AdminPermissions::TreasuryManager, + &info.sender, + &config.admin_auth, + )?; + + if allocation.tolerance >= ONE_HUNDRED_PERCENT { + return Err(StdError::generic_err(format!( + "Tolerance {} >= 100%", + allocation.tolerance + ))); + } + + let mut allocations = ALLOCATIONS + .may_load(deps.storage, asset.clone())? + .unwrap_or_default(); + + // adapters can't have two allocations so remove the duplicate + let stale_alloc = allocations + .iter() + .position(|a| a.contract.address == allocation.contract.address); + + match stale_alloc { + Some(i) => { + allocations.swap_remove(i); + } + None => {} + }; + + allocations.push(AllocationMeta { + nick: allocation.nick, + contract: allocation.contract, + amount: allocation.amount, + alloc_type: allocation.alloc_type, + tolerance: allocation.tolerance, + }); + + // ensure that the portion allocations don't go above 100% + if allocations + .iter() + .map(|a| { + if a.alloc_type == AllocationType::Portion { + a.amount + } else { + Uint128::zero() + } + }) + .sum::() + > ONE_HUNDRED_PERCENT + { + return Err(StdError::generic_err( + "Invalid allocation total exceeding 100%", + )); + } + + // Sort the allocations Amount < Portion + allocations.sort_by(|a, b| match a.alloc_type { + AllocationType::Amount => match b.alloc_type { + AllocationType::Amount => std::cmp::Ordering::Equal, + AllocationType::Portion => std::cmp::Ordering::Less, + }, + AllocationType::Portion => match b.alloc_type { + AllocationType::Amount => std::cmp::Ordering::Greater, + AllocationType::Portion => std::cmp::Ordering::Equal, + }, + }); + + ALLOCATIONS.save(deps.storage, asset.clone(), &allocations)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::Allocate { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn claim(deps: DepsMut, env: &Env, info: MessageInfo, asset: Addr) -> StdResult { + let full_asset = match ASSETS.may_load(deps.storage, asset.clone())? { + Some(a) => a, + None => { + return Err(StdError::generic_err("Unrecognized asset")); + } + }; + + let config = CONFIG.load(deps.storage)?; + // if the claimer isn't a holder, it should default to the treasruy + let claimer = match HOLDERS.load(deps.storage)?.contains(&info.sender) { + true => info.sender, + false => config.treasury.clone(), + }; + + let mut total_claimed = Uint128::zero(); + let mut messages = vec![]; + + // claim from adapters that have claimable value + for alloc in ALLOCATIONS.load(deps.storage, asset.clone())? { + let claim = adapter::claimable_query(deps.querier, &asset, alloc.contract.clone())?; + if claim > Uint128::zero() { + messages.push(adapter::claim_msg(&asset, alloc.contract.clone())?); + METRICS.push(deps.storage, env.block.time, Metric { + action: Action::Claim, + context: Context::Claim, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: claim, + user: claimer.clone(), + })?; + total_claimed += claim; + } + } + + let mut holding = HOLDING.load(deps.storage, claimer.clone())?; + + // get the position of the holders unbondings + let unbonding_i = match holding + .unbondings + .iter_mut() + .position(|u| u.token == asset.clone()) + { + Some(i) => i, + None => { + return Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Claim { + status: ResponseStatus::Success, + amount: Uint128::zero(), + }, + )?)); + } + }; + + let reserves = balance_query( + &deps.querier, + env.contract.address.clone(), + VIEWING_KEY.load(deps.storage)?, + &full_asset.contract.clone(), + )?; + + let send_amount = { + // if reserves and total claimed is less than the unbondings of the holder, we need to send + // all of the reserves and all that will be claimed + if holding.unbondings[unbonding_i].amount > reserves + total_claimed { + reserves + total_claimed + } else { + // otherwise just send the unbonding amount + holding.unbondings[unbonding_i].amount + } + }; + + // Adjust unbonding amount + holding.unbondings[unbonding_i].amount = holding.unbondings[unbonding_i].amount - send_amount; + + if claimer != config.treasury && holding.status == Status::Closed { + if let Some(balance_i) = holding + .balances + .iter_mut() + .position(|u| u.token == asset.clone()) + { + if holding.unbondings[unbonding_i].amount == Uint128::zero() + && holding.balances[balance_i].amount == Uint128::zero() + { + holding.unbondings.swap_remove(unbonding_i); + holding.balances.swap_remove(balance_i); + } + } + } + + HOLDING.save(deps.storage, claimer.clone(), &holding)?; + + // Send claimed funds + messages.push(send_msg( + claimer.clone(), + send_amount, + None, + None, + None, + &full_asset.contract.clone(), + )?); + + METRICS.push(deps.storage, env.block.time, Metric { + action: Action::SendFunds, + context: Context::Claim, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: send_amount, + user: claimer.clone(), + })?; + + Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Claim { + status: ResponseStatus::Success, + amount: reserves + total_claimed, + }, + )?)) +} + +pub fn update(deps: DepsMut, env: &Env, _info: MessageInfo, asset: Addr) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + let full_asset = ASSETS.load(deps.storage, asset.clone())?; + + let mut allocations = ALLOCATIONS.load(deps.storage, asset.clone())?; + + // the sum of balances on 'amount' adapters + let mut amount_total = Uint128::zero(); + // the sum of balances on 'portion' adapters + let mut portion_total = Uint128::zero(); + // allocations marked for removal + let mut stale_allocs = vec![]; + let mut messages = vec![]; + let mut adapter_info = vec![]; + + /* this loop has 2 purposes + * - check for stale allocaitons that need to be removed + * - fill the amount_total and portion_total vars with data + */ + for (i, a) in allocations.clone().iter().enumerate() { + let bal = adapter::balance_query( + deps.querier, + &full_asset.contract.address, + a.contract.clone(), + )?; + let mut unbonding = adapter::unbonding_query( + deps.querier, + &full_asset.contract.address, + a.contract.clone(), + )?; + let unbondable = adapter::unbondable_query( + deps.querier, + &full_asset.contract.address, + a.contract.clone(), + )?; + let claimable = adapter::claimable_query( + deps.querier, + &full_asset.contract.address, + a.contract.clone(), + )?; + if !claimable.is_zero() { + messages.push(adapter::claim_msg( + &full_asset.contract.address.clone(), + a.contract.clone(), + )?); + unbonding += claimable; + } + // if all these values are zero we can safely drop the alloc + if bal.is_zero() + && a.amount.is_zero() + && unbonding.is_zero() + && unbondable.is_zero() + && claimable.is_zero() + { + stale_allocs.push(i); + } + + adapter_info.push(AllocationTempData { + contract: a.contract.clone(), + alloc_type: a.alloc_type.clone(), + amount: a.amount.clone(), + tolerance: a.tolerance.clone(), + balance: bal, + unbondable, + unbonding, + }); + + // fill totals with data + match a.alloc_type { + AllocationType::Amount => amount_total += bal, + AllocationType::Portion => portion_total += bal, + }; + } + + // actually drop the stale allocs + if !stale_allocs.is_empty() { + for index in stale_allocs.iter().rev() { + // remove used here to preserve sorted vec + allocations.remove(index.clone()); + } + ALLOCATIONS.save(deps.storage, asset.clone(), &allocations)?; + } + + // the holder is the entity that actually holds the tokens that the treasury manager can spend + // holder_unbonding represents how much the holder has currently asked to unbond + let mut holder_unbonding = Uint128::zero(); + // holder_principal represents how much of the asset has came form said holder + let mut holder_principal = Uint128::zero(); + + let mut holders = HOLDERS.load(deps.storage)?; + // Withold holder unbondings + for (i, h) in holders.clone().iter().enumerate() { + // for each holder, load the respective holdings + let holding = HOLDING.load(deps.storage, h.clone())?; + // sum the data + if let Some(u) = holding.unbondings.iter().find(|u| u.token == asset) { + holder_unbonding += u.amount; + } + if let Some(b) = holding.balances.iter().find(|u| u.token == asset) { + holder_principal += b.amount; + } + if holding.status == Status::Closed + && holding.balances.len() == 0 + && holding.unbondings.len() == 0 + { + HOLDING.remove(deps.storage, h.clone()); + holders.swap_remove(i); + HOLDERS.save(deps.storage, &holders)?; + } + } + + // Batch send_from actions + let mut send_from_actions = vec![]; + let mut send_actions = vec![]; + let mut metrics = vec![]; + + let key = VIEWING_KEY.load(deps.storage)?; + + // Available treasury allowance + let mut allowance = allowance_query( + &deps.querier, + config.treasury.clone(), + env.contract.address.clone(), + key.clone(), + 1, + &full_asset.contract.clone(), + )? + .allowance; + + // snip20 balance query to get the treasury managers current snip20 balance + let mut balance = balance_query( + &deps.querier, + env.contract.address.clone(), + key.clone(), + &full_asset.contract.clone(), + )?; + + // total amount allocated to adapters + current snip20 balance + // We subtract holder_unbonding to ensure that those tokens will be claimable + let out_total = (amount_total + portion_total + balance) - holder_unbonding; + // This gives us our total allowance from the treasury, used and unused + let total = out_total + allowance; + + balance = { + if balance > holder_unbonding { + balance - holder_unbonding + } else { + Uint128::zero() + } + }; + + // setting up vars + let mut allowance_used = Uint128::zero(); + let mut balance_used = Uint128::zero(); + let mut reserved_for_amount_adapters = Uint128::zero(); + + // loop through adapters with allocations + for adapter in adapter_info { + // calculate the target balance for each + let desired_amount = match adapter.alloc_type { + AllocationType::Amount => { + reserved_for_amount_adapters += adapter.amount; + // since amount adapters' allocations are static + adapter.amount + } + AllocationType::Portion => { + // Since the list of allocations is sorted, we can ensure that type::amount + // adapters will be processed first, so we can calculate the amount available for + // allocation with total - reserved_for_amount_adapters + // If statement to prevent overflow + if total > reserved_for_amount_adapters { + adapter + .amount + .multiply_ratio(total - reserved_for_amount_adapters, ONE_HUNDRED_PERCENT) + } else { + Uint128::zero() + } + } + }; + // threshold is the desired_amount * a percentage held in adapter.tolerance, + // the treasury manager will only attempt to rebalance if the adapter crosses the threshold + // in either direction + let threshold = desired_amount.multiply_ratio(adapter.tolerance, ONE_HUNDRED_PERCENT); + + // effective balance is the adapters' actual unbondable amount + let effective_balance = { + if adapter.balance > adapter.unbonding { + adapter.balance - adapter.unbonding + } else { + // adapter balance should never be less than unbonding so if it's equal to then we + // just set effective bal to zero + Uint128::zero() + } + }; + + match desired_amount.cmp(&effective_balance) { + // Under Funded -- prioritize tm snip20 balance over allowance from treasury + std::cmp::Ordering::Greater => { + // target send amount to adapter + let mut desired_input = desired_amount - effective_balance; + // check if threshold is crossed + if desired_input <= threshold { + continue; + } + + // Fully covered by balance + if desired_input < balance { + send_actions.push(SendAction { + recipient: adapter.contract.address.clone().to_string(), + recipient_code_hash: Some(adapter.contract.code_hash.clone()), + amount: desired_input, + msg: None, + memo: None, + }); + metrics.push(Metric { + action: Action::SendFunds, + context: Context::Update, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: desired_input, + user: adapter.contract.address.clone(), + }); + + // reduce snip20 balance for future loops + balance = balance - desired_input; + balance_used += desired_input; + // at this point we know we have fufilled what this adapter needs + continue; + } + // Send all snip20 balance since the adapter needs more that the balance can fufill, + // but balance is not 0 + else if !balance.is_zero() { + send_actions.push(SendAction { + recipient: adapter.contract.address.clone().to_string(), + recipient_code_hash: Some(adapter.contract.code_hash.clone()), + amount: balance, + msg: None, + memo: None, + }); + metrics.push(Metric { + action: Action::SendFunds, + context: Context::Update, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: balance, + user: adapter.contract.address.clone(), + }); + + // reduce the desired_input to reflect the balance being sent, we know this will + // not overflow because if balance was > desired_input, we would have hit a + // continue statement + desired_input = desired_input - balance; + // reset balance since we have effectively sent everything out + balance = Uint128::zero(); + } + + if !allowance.is_zero() { + // This will only execute after snip20 balance has been used up + // Fully covered by allowance + if desired_input < allowance { + send_from_actions.push(SendFromAction { + owner: config.treasury.clone().to_string(), + recipient: adapter.contract.address.clone().to_string(), + recipient_code_hash: Some(adapter.contract.code_hash.clone()), + amount: desired_input, + msg: None, + memo: None, + }); + metrics.push(Metric { + action: Action::SendFundsFrom, + context: Context::Update, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: desired_input, + user: adapter.contract.address.clone(), + }); + + allowance_used += desired_input; + // this will not overflow due to check in if statement + allowance = allowance - desired_input; + // similarily, we know that we have fufilled what this adapter needs at this + // point but we don't want to continue since we need to account for the + // allowance used in the holder's information + } + // Send all allowance + else if !allowance.is_zero() { + send_from_actions.push(SendFromAction { + owner: config.treasury.clone().to_string(), + recipient: adapter.contract.address.clone().to_string(), + recipient_code_hash: Some(adapter.contract.code_hash.clone()), + amount: allowance, + msg: None, + memo: None, + }); + metrics.push(Metric { + action: Action::SendFundsFrom, + context: Context::Update, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: allowance, + user: adapter.contract.address.clone(), + }); + + // account for allowance being sent out + allowance_used += allowance; + allowance = Uint128::zero(); + } + } + } + // Over funded -- unbond + std::cmp::Ordering::Less => { + // balance - target balance will give the amount we need to unbond + let desired_output = effective_balance - desired_amount; + + // check to see that the threshold has been crossed + if desired_output <= threshold { + continue; + } + + if !desired_output.is_zero() { + messages.push(adapter::unbond_msg( + &asset.clone(), + desired_output.clone(), + adapter.contract.clone(), + )?); + metrics.push(Metric { + action: Action::Unbond, + context: Context::Update, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: desired_output, + user: adapter.contract.address.clone(), + }); + } + let unbondings = UNBONDINGS + .load(deps.storage, full_asset.contract.address.clone())? + + desired_output; + UNBONDINGS.save( + deps.storage, + full_asset.contract.address.clone(), + &unbondings, + )?; + } + _ => {} + } + } + + // Credit treasury balance with allowance used by adding allowance_used to the existing balance + // or creating a new balance struct with allowance_used as the balance + let mut holding = HOLDING.load(deps.storage, config.treasury.clone())?; + if let Some(i) = holding + .balances + .iter() + .position(|u| u.token == asset.clone()) + { + holding.balances[i].amount = holding.balances[i].amount + allowance_used; + } else { + holding.balances.push(Balance { + token: asset.clone(), + amount: allowance_used, + }); + } + HOLDING.save(deps.storage, config.treasury.clone(), &holding)?; + + // Determine Gainz & Losses & credit to treasury + holder_principal += allowance_used; + + // this will never overflow because total is a sum of allowance + match (total - allowance).cmp(&holder_principal) { + std::cmp::Ordering::Greater => { + let gains = (total - allowance) - holder_principal; + // debit gains to treasury + let mut holding = HOLDING.load(deps.storage, config.treasury.clone())?; + if let Some(i) = holding.balances.iter().position(|u| u.token == asset) { + holding.balances[i].amount += gains; + } + HOLDING.save(deps.storage, config.treasury.clone(), &holding)?; + metrics.push(Metric { + action: Action::RealizeGains, + context: Context::Update, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: gains, + user: config.treasury.clone(), + }); + } + std::cmp::Ordering::Less => { + let losses = holder_principal - (total - allowance); + // credit losses to treasury + let mut holding = HOLDING.load(deps.storage, config.treasury.clone())?; + if let Some(i) = holding.balances.iter().position(|u| u.token == asset) { + holding.balances[i].amount -= losses; + } + HOLDING.save(deps.storage, config.treasury.clone(), &holding)?; + metrics.push(Metric { + action: Action::RealizeLosses, + context: Context::Update, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: losses, + user: config.treasury.clone(), + }); + } + _ => {} + } + + // exec batch balance send messages + if !send_actions.is_empty() { + messages.push(batch_send_msg( + send_actions, + None, + &full_asset.contract.clone(), + )?); + } + + // exec batch allowance send messages + if !send_from_actions.is_empty() { + messages.push(batch_send_from_msg( + send_from_actions, + None, + &full_asset.contract.clone(), + )?); + } + + METRICS.append(deps.storage, env.block.time, &mut metrics)?; + + Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Update { + status: ResponseStatus::Success, + }, + )?)) +} + +pub fn unbond( + deps: DepsMut, + env: &Env, + info: MessageInfo, + asset: Addr, + amount: Uint128, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let holders = HOLDERS.load(deps.storage)?; + + // if the claimer isn't a holder, it should be an admin and default to the treasruy + let unbonder = match holders.contains(&info.sender) { + true => info.sender, + false => { + validate_admin( + &deps.querier, + AdminPermissions::TreasuryManager, + &info.sender, + &config.admin_auth, + )?; + config.treasury + } + }; + + let full_asset = ASSETS.load(deps.storage, asset.clone())?; + + // Adjust holder balance + let mut holding = HOLDING.load(deps.storage, unbonder.clone())?; + + // get the position of the balance for the asset + let balance_i = match holding + .balances + .iter() + .position(|h| h.token == asset.clone()) + { + Some(i) => i, + None => { + return Err(StdError::generic_err(format!( + "Cannot unbond, holder has no holdings of {}", + asset.clone() + ))); + } + }; + + let mut unbond_amount = amount; + // Check balance exceeds unbond amount + if holding.balances[balance_i].amount < amount { + return Err(StdError::generic_err("Not enough funds to unbond")); + } else { + if holding.status == Status::Active { + holding.balances[balance_i].amount = holding.balances[balance_i].amount - amount; + } else { + unbond_amount = holding.balances[balance_i].amount; + holding.balances[balance_i].amount = Uint128::zero(); + } + } + + // Add unbonding + if let Some(u) = holding + .unbondings + .iter() + .position(|h| h.token == asset.clone()) + { + holding.unbondings[u].amount += unbond_amount; + } else { + holding.unbondings.push(Balance { + token: asset.clone(), + amount: unbond_amount, + }); + } + + HOLDING.save(deps.storage, unbonder.clone(), &holding)?; + let allocations = ALLOCATIONS.load(deps.storage, asset.clone())?; + + // get the total amount that the adapters are currently unbonding + let mut unbonding_tot = Uint128::zero(); + for a in allocations.clone() { + unbonding_tot += + adapter::unbonding_query(deps.querier, &asset.clone(), a.contract.clone())?; + } + + // find the unbond_amount based off of amounts that the TM has unbonded independent of a holder + unbond_amount = { + let u = UNBONDINGS.load(deps.storage, full_asset.contract.address.clone())?; + // if the independent unbondings is less than what the adapters are acutally unbonding, we + // know another holder has asked to do some unbonding and the adapters are unbonding for + // that holder + if u <= unbonding_tot { + if u <= unbond_amount { + // if amount > independent unbonding, we reduce independent unbondings to + // zero and return the amount we actually want to unbond from the adapters + UNBONDINGS.save( + deps.storage, + full_asset.contract.address.clone(), + &Uint128::zero(), + )?; + unbond_amount - u + } else { + // independent unbondings covers the amount + UNBONDINGS.save( + deps.storage, + full_asset.contract.address.clone(), + &(u - unbond_amount), + )?; + Uint128::zero() + } + } else { + // We error out since this case is completely unexpected + // Independent unbonding should never be greater than what the adapters are curretnly + // unbonding + /*return Err(StdError::generic_err( + "Independent TM unbonding is greater than what the adapters are unbonding", + ));*/ + // TODO figure out why we can't throw an error here + // NOTE it has something to do with gains/losses + unbond_amount + } + }; + + // get other holders unbonding amount to hold + let mut other_unbondings = Uint128::zero(); + + for h in holders { + if h == unbonder.clone() { + continue; + } + let other_holding = HOLDING.load(deps.storage, h)?; + if let Some(u) = other_holding + .unbondings + .iter() + .find(|u| u.token == asset.clone()) + { + other_unbondings += u.amount; + } + } + + // Reserves to be sent immediately + let mut reserves = balance_query( + &deps.querier, + env.contract.address.clone(), + VIEWING_KEY.load(deps.storage)?, + &full_asset.contract.clone(), + )?; + + // Remove pending unbondings from reserves + if reserves > other_unbondings { + reserves = reserves - other_unbondings; + } else { + reserves = Uint128::zero(); + } + + let mut messages = vec![]; + let mut metrics = vec![]; + + // Send available reserves to unbonder + if reserves > Uint128::zero() { + if reserves < unbond_amount { + // reserves can't cover unbond + // Don't need batch send bc there's only one send msg + messages.push(send_msg( + unbonder.clone(), + reserves, + None, + None, + None, + &full_asset.contract.clone(), + )?); + metrics.push(Metric { + action: Action::SendFunds, + context: Context::Unbond, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: reserves, + user: unbonder.clone(), + }); + unbond_amount = unbond_amount - reserves; + + // Reflect sent funds in unbondings + let mut holding = HOLDING.load(deps.storage, unbonder.clone())?; + if let Some(i) = holding.unbondings.iter().position(|u| u.token == asset) { + holding.unbondings[i].amount = holding.unbondings[i].amount - reserves; + } + HOLDING.save(deps.storage, unbonder, &holding)?; + } else { + // reserves can cover unbond + messages.push(send_msg( + unbonder.clone(), + amount, + None, + None, + None, + &full_asset.contract.clone(), + )?); + metrics.push(Metric { + action: Action::SendFunds, + context: Context::Unbond, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount, + user: unbonder.clone(), + }); + + // Reflect sent funds in unbondings + let mut holding = HOLDING.load(deps.storage, unbonder.clone())?; + if let Some(i) = holding.unbondings.iter().position(|u| u.token == asset) { + holding.unbondings[i].amount = holding.unbondings[i].amount - amount; + } + HOLDING.save(deps.storage, unbonder, &holding)?; + + METRICS.append(deps.storage, env.block.time, &mut metrics)?; + return Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Unbond { + status: ResponseStatus::Success, + amount, + }, + )?)); + } + } + + // let full_asset = ASSETS.load(deps.storage, asset.clone())?; + + // Build metadata + let mut alloc_meta = vec![]; + let mut amount_total = Uint128::zero(); + let mut portion_total = Uint128::zero(); + let mut tot_unbond_available = Uint128::zero(); + + // Gather adapter outstanding amounts + for a in allocations { + let bal = adapter::balance_query(deps.querier, &asset, a.contract.clone())?; + let unbondable = adapter::unbondable_query(deps.querier, &asset, a.contract.clone())?; + + alloc_meta.push(AllocationTempData { + contract: a.contract.clone(), + alloc_type: a.alloc_type.clone(), + amount: a.amount.clone(), + tolerance: a.tolerance.clone(), + balance: bal, + unbondable, + unbonding: Uint128::zero(), + }); + + tot_unbond_available += unbondable; + + match a.alloc_type { + AllocationType::Amount => amount_total += bal, + AllocationType::Portion => portion_total += bal, + }; + } + + // if unbond_amount == tot_amount_unbonding, unbond all unbondable amounts and return + if unbond_amount == tot_unbond_available { + for a in alloc_meta.clone() { + messages.push(adapter::unbond_msg( + &full_asset.contract.address.clone(), + a.unbondable.clone(), + a.contract.clone(), + )?); + metrics.push(Metric { + action: Action::Unbond, + context: Context::Unbond, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: a.balance.clone(), + user: a.contract.address.clone(), + }); + } + METRICS.append(deps.storage, env.block.time, &mut metrics)?; + return Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Unbond { + status: ResponseStatus::Success, + amount, + }, + )?)); + } + + let mut total_amount_unbonding = Uint128::zero(); + let mut unbond_amounts = vec![]; + + let (amounts, portions): (Vec, Vec) = alloc_meta + .clone() + .into_iter() + .partition_map(|a| match a.alloc_type { + AllocationType::Amount => Either::Left(a), + AllocationType::Portion => Either::Right(a), + }); + + // unbond the extra tokens from the amount adapters + for meta in amounts.clone() { + if meta.unbondable > meta.amount { + total_amount_unbonding += meta.unbondable - meta.amount; + unbond_amounts.push(meta.unbondable - meta.amount); + } else { + unbond_amounts.push(Uint128::zero()) + } + } + + // if the extra tokens from the amount adapters covers the unbond request, push the messages + // and return + if unbond_amount == total_amount_unbonding { + for (i, meta) in amounts.clone().iter().enumerate() { + messages.push(adapter::unbond_msg( + &full_asset.contract.address.clone(), + unbond_amounts[i], + meta.contract.clone(), + )?); + metrics.push(Metric { + action: Action::Unbond, + context: Context::Unbond, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: unbond_amounts[i], + user: meta.contract.address.clone(), + }); + } + METRICS.append(deps.storage, env.block.time, &mut metrics)?; + return Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Unbond { + status: ResponseStatus::Success, + amount, + }, + )?)); + } else if unbond_amount < total_amount_unbonding { + // if the extra tokens are greater than the unbond request, unbond proportionally to the + // extra tokens available and return + let mut modified_total_amount_unbonding = Uint128::zero(); + for (i, meta) in amounts.clone().iter().enumerate() { + unbond_amounts[i] = + unbond_amount.multiply_ratio(unbond_amounts[i], total_amount_unbonding); + modified_total_amount_unbonding += unbond_amounts[i]; + // avoid off by one error + if i == amounts.len() - 1 + && modified_total_amount_unbonding < unbond_amount + && unbond_amounts[i] + Uint128::new(1) <= meta.unbondable + { + unbond_amounts[i] += Uint128::new(1); + } + messages.push(adapter::unbond_msg( + &full_asset.contract.address.clone(), + unbond_amounts[i], + meta.contract.clone(), + )?); + metrics.push(Metric { + action: Action::Unbond, + context: Context::Unbond, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: unbond_amounts[i], + user: meta.contract.address.clone(), + }); + } + METRICS.append(deps.storage, env.block.time, &mut metrics)?; + return Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Unbond { + status: ResponseStatus::Success, + amount, + }, + )?)); + } + + // if portion total > unbond - tot, we know the portion adapters can cover the rest + if unbond_amount - total_amount_unbonding < portion_total { + // unbond the tokens slotted for unbonding from the amount adapters + for (i, meta) in amounts.clone().iter().enumerate() { + if !unbond_amounts[i].is_zero() { + messages.push(adapter::unbond_msg( + &full_asset.contract.address.clone(), + unbond_amounts[i], + meta.contract.clone(), + )?); + metrics.push(Metric { + action: Action::Unbond, + context: Context::Unbond, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: unbond_amounts[i], + user: meta.contract.address.clone(), + }); + } + } + let amount_adapt_tot_unbonding = total_amount_unbonding; + /* For each portion adapter, unbond the amount proportional to its portion of the total + * balance + */ + for (i, meta) in portions.clone().iter().enumerate() { + let unbond_from_portion = (unbond_amount - amount_adapt_tot_unbonding) + .multiply_ratio(meta.unbondable, portion_total); + unbond_amounts.push(unbond_from_portion); + total_amount_unbonding += unbond_from_portion; + // Avoid off by 1 error + if i == portions.len() - 1 + && total_amount_unbonding < unbond_amount + && unbond_from_portion + Uint128::new(1) <= meta.unbondable + { + messages.push(adapter::unbond_msg( + &full_asset.contract.address.clone(), + unbond_from_portion + Uint128::new(1), + meta.contract.clone(), + )?); + metrics.push(Metric { + action: Action::Unbond, + context: Context::Unbond, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: unbond_from_portion + Uint128::new(1), + user: meta.contract.address.clone(), + }); + } else if !unbond_from_portion.is_zero() { + messages.push(adapter::unbond_msg( + &full_asset.contract.address.clone(), + unbond_from_portion, + meta.contract.clone(), + )?); + metrics.push(Metric { + action: Action::Unbond, + context: Context::Unbond, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: unbond_from_portion, + user: meta.contract.address.clone(), + }); + } + } + METRICS.append(deps.storage, env.block.time, &mut metrics)?; + return Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Unbond { + status: ResponseStatus::Success, + amount, + }, + )?)); + } else { + // Otherwise we need to unbond everything from the portion adapters and go back to the + // amount adapters + for meta in portions { + unbond_amounts.push(meta.unbondable); + if !meta.unbondable.is_zero() { + messages.push(adapter::unbond_msg( + &full_asset.contract.address, + meta.unbondable, + meta.contract.clone(), + )?); + metrics.push(Metric { + action: Action::Unbond, + context: Context::Unbond, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: meta.unbondable, + user: meta.contract.address.clone(), + }); + } + total_amount_unbonding += meta.unbondable; + } + // tot_amount_unbonding is equal to unbond_amount, unbonding everything from the portion + // adapters covers our requested unbonding, so we push msgs and return + if total_amount_unbonding == unbond_amount { + for (i, meta) in amounts.clone().iter().enumerate() { + if !unbond_amounts[i].is_zero() { + messages.push(adapter::unbond_msg( + &full_asset.contract.address, + unbond_amounts[i].clone(), + meta.contract.clone(), + )?); + metrics.push(Metric { + action: Action::Unbond, + context: Context::Unbond, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: unbond_amounts[i].clone(), + user: meta.contract.address.clone(), + }); + } + } + METRICS.append(deps.storage, env.block.time, &mut metrics)?; + return Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Unbond { + status: ResponseStatus::Success, + amount, + }, + )?)); + } else { + // unbond token amounts proportional to the ratio of the allocation of the adapter and + // the sum of the amount allocaitons + let mut amount_alloc = Uint128::zero(); + for meta in amounts.clone() { + amount_alloc += meta.amount; + } + let mut modified_total_amount_unbonding = total_amount_unbonding; + for (i, meta) in amounts.iter().enumerate() { + unbond_amounts[i] += (unbond_amount - total_amount_unbonding) + .multiply_ratio(meta.amount, amount_alloc); + + modified_total_amount_unbonding += meta.unbondable; + // this makes sure that the entire unbond request is fuffiled by the end of this + // block + if i == amounts.len() - 1 + && modified_total_amount_unbonding < unbond_amount + && unbond_amount - modified_total_amount_unbonding + < meta.unbondable - unbond_amounts[i] + { + unbond_amounts[i] += unbond_amount - total_amount_unbonding; + } + if !unbond_amounts[i].is_zero() { + messages.push(adapter::unbond_msg( + &full_asset.contract.address, + unbond_amounts[i], + meta.contract.clone(), + )?); + metrics.push(Metric { + action: Action::Unbond, + context: Context::Unbond, + timestamp: env.block.time.seconds(), + token: asset.clone(), + amount: unbond_amounts[i].clone(), + user: meta.contract.address.clone(), + }); + } + } + METRICS.append(deps.storage, env.block.time, &mut metrics)?; + return Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Unbond { + status: ResponseStatus::Success, + amount, + }, + )?)); + } + } +} + +pub fn add_holder( + deps: DepsMut, + env: &Env, + info: MessageInfo, + holder: Addr, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + validate_admin( + &deps.querier, + AdminPermissions::TreasuryManager, + &info.sender, + &config.admin_auth, + )?; + + let mut holders = HOLDERS.load(deps.storage)?; + if holders.contains(&holder.clone()) { + return Err(StdError::generic_err("Holder already exists")); + } + holders.push(holder.clone()); + HOLDERS.save(deps.storage, &holders)?; + + HOLDING.save(deps.storage, holder.clone(), &Holding { + balances: Vec::new(), + unbondings: Vec::new(), + status: Status::Active, + })?; + + METRICS.push(deps.storage, env.block.time, Metric { + action: Action::AddHolder, + context: Context::Holders, + timestamp: env.block.time.seconds(), + token: Addr::unchecked(""), + amount: Uint128::zero(), + user: holder, + })?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::AddHolder { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn remove_holder( + deps: DepsMut, + env: &Env, + info: MessageInfo, + holder: Addr, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + validate_admin( + &deps.querier, + AdminPermissions::TreasuryManager, + &info.sender, + &config.admin_auth, + )?; + + if holder == config.treasury { + return Err(StdError::generic_err("Cannot remove treasury as a holder")); + } + + if let Some(mut holding) = HOLDING.may_load(deps.storage, holder.clone())? { + holding.status = Status::Closed; + HOLDING.save(deps.storage, holder.clone(), &holding)?; + } else { + return Err(StdError::generic_err("Not an authorized holder")); + } + + METRICS.push(deps.storage, env.block.time, Metric { + action: Action::RemoveHolder, + context: Context::Holders, + timestamp: env.block.time.seconds(), + token: Addr::unchecked(""), + amount: Uint128::zero(), + user: holder, + })?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::RemoveHolder { + status: ResponseStatus::Success, + })?), + ) +} diff --git a/contracts/dao/treasury_manager/src/lib.rs b/contracts/dao/treasury_manager/src/lib.rs new file mode 100644 index 0000000..25ee0d9 --- /dev/null +++ b/contracts/dao/treasury_manager/src/lib.rs @@ -0,0 +1,4 @@ +pub mod contract; +pub mod execute; +pub mod query; +pub mod storage; diff --git a/contracts/dao/treasury_manager/src/query.rs b/contracts/dao/treasury_manager/src/query.rs new file mode 100644 index 0000000..e0af3f8 --- /dev/null +++ b/contracts/dao/treasury_manager/src/query.rs @@ -0,0 +1,292 @@ +use crate::storage::*; +use shade_protocol::{ + c_std::{Addr, Deps, Env, StdError, StdResult, Uint128}, + dao::{adapter, manager, treasury_manager}, + snip20::helpers::{allowance_query, balance_query}, + utils::{cycle::parse_utc_datetime, storage::plus::period_storage::Period}, +}; + +pub fn config(deps: Deps) -> StdResult { + Ok(treasury_manager::QueryAnswer::Config { + config: CONFIG.load(deps.storage)?, + }) +} + +pub fn metrics( + deps: Deps, + env: Env, + date: Option, + epoch: Option, + period: Period, +) -> StdResult { + if date.is_some() && epoch.is_some() { + return Err(StdError::generic_err("cannot pass both epoch and date")); + } + let key = { + if let Some(d) = date { + parse_utc_datetime(&d)?.timestamp() as u64 + } else if let Some(e) = epoch { + e.u128() as u64 + } else { + env.block.time.seconds() + } + }; + Ok(treasury_manager::QueryAnswer::Metrics { + metrics: METRICS.load_period(deps.storage, key, period)?, + }) +} + +pub fn pending_allowance( + deps: Deps, + env: Env, + asset: Addr, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let full_asset = match ASSETS.may_load(deps.storage, asset)? { + Some(a) => a, + None => { + return Err(StdError::generic_err("Not a registered asset")); + } + }; + + let allowance = allowance_query( + &deps.querier, + config.treasury, + env.contract.address, + VIEWING_KEY.load(deps.storage)?, + 1, + &full_asset.contract.clone(), + )? + .allowance; + + Ok(treasury_manager::QueryAnswer::PendingAllowance { amount: allowance }) +} + +pub fn reserves( + deps: Deps, + env: Env, + asset: Addr, + _holder: Addr, +) -> StdResult { + if let Some(full_asset) = ASSETS.may_load(deps.storage, asset)? { + let reserves = balance_query( + &deps.querier, + env.contract.address, + VIEWING_KEY.load(deps.storage)?, + &full_asset.contract.clone(), + )?; + + return Ok(manager::QueryAnswer::Reserves { amount: reserves }); + } + + Err(StdError::generic_err("Not a registered asset")) +} + +pub fn assets(deps: Deps) -> StdResult { + Ok(treasury_manager::QueryAnswer::Assets { + assets: ASSET_LIST.load(deps.storage)?, + }) +} + +pub fn allocations(deps: Deps, asset: Addr) -> StdResult { + Ok(treasury_manager::QueryAnswer::Allocations { + allocations: match ALLOCATIONS.may_load(deps.storage, asset)? { + None => vec![], + Some(a) => a, + }, + }) +} + +pub fn unbonding(deps: Deps, asset: Addr, holder: Addr) -> StdResult { + if ASSETS.may_load(deps.storage, asset.clone())?.is_none() { + return Err(StdError::generic_err("Not a registered asset")); + } + + let _config = CONFIG.load(deps.storage)?; + + match HOLDING.may_load(deps.storage, holder)? { + Some(holder) => Ok(manager::QueryAnswer::Unbonding { + amount: match holder.unbondings.iter().find(|u| u.token == asset.clone()) { + Some(u) => u.amount, + None => Uint128::zero(), + }, + }), + None => { + return Err(StdError::generic_err("Invalid holder")); + } + } +} + +pub fn claimable( + deps: Deps, + env: Env, + asset: Addr, + holder: Addr, +) -> StdResult { + let full_asset = match ASSETS.may_load(deps.storage, asset.clone())? { + Some(a) => a, + None => { + return Err(StdError::generic_err("Not a registered asset")); + } + }; + let allocations = match ALLOCATIONS.may_load(deps.storage, asset.clone())? { + Some(a) => a, + None => vec![], + }; + //TODO claiming needs ordered unbondings so other holders don't get bumped + + let mut claimable = balance_query( + &deps.querier, + env.contract.address, + VIEWING_KEY.load(deps.storage)?, + &full_asset.contract.clone(), + )?; + + for alloc in allocations { + claimable += adapter::claimable_query(deps.querier, &asset, alloc.contract.clone())?; + } + + match HOLDING.may_load(deps.storage, holder)? { + Some(holder) => { + let unbonding = match holder.unbondings.iter().find(|u| u.token == asset) { + Some(u) => u.amount, + None => Uint128::zero(), + }; + + if claimable > unbonding { + Ok(manager::QueryAnswer::Claimable { amount: unbonding }) + } else { + Ok(manager::QueryAnswer::Claimable { amount: claimable }) + } + } + None => Err(StdError::generic_err("Invalid holder")), + } +} + +pub fn unbondable( + deps: Deps, + env: Env, + asset: Addr, + holder: Addr, +) -> StdResult { + let full_asset = match ASSETS.may_load(deps.storage, asset.clone())? { + Some(a) => a, + None => { + return Err(StdError::generic_err("Not a registered asset")); + } + }; + let mut holder_balance = Uint128::zero(); + + match HOLDING.may_load(deps.storage, holder.clone())? { + Some(h) => { + if let Some(b) = h.balances.iter().find(|b| b.token == asset.clone()) { + holder_balance += b.amount; + } + } + None => { + return Err(StdError::generic_err("Invalid holder")); + } + } + + if holder_balance.is_zero() { + return Ok(manager::QueryAnswer::Unbondable { + amount: holder_balance, + }); + } + + let mut unbondable = balance_query( + &deps.querier, + env.contract.address, + VIEWING_KEY.load(deps.storage)?, + &full_asset.contract.clone(), + )?; + + let allocations = ALLOCATIONS + .may_load(deps.storage, asset.clone())? + .unwrap_or(vec![]); + + for alloc in allocations { + unbondable += adapter::unbondable_query(deps.querier, &asset, alloc.contract)?; + if unbondable > holder_balance { + break; + } + } + + if unbondable > holder_balance { + unbondable = holder_balance; + } + + return Ok(manager::QueryAnswer::Unbondable { amount: unbondable }); +} + +pub fn batch_balance( + deps: Deps, + assets: Vec, + holder: Addr, +) -> StdResult { + let holding = match HOLDING.may_load(deps.storage, holder.clone())? { + Some(h) => h, + None => { + return Err(StdError::generic_err("Invalid Holder")); + } + }; + + let mut balances = vec![]; + + for asset in assets { + if let Some(asset) = ASSETS.may_load(deps.storage, asset)? { + balances.push( + match holding + .balances + .iter() + .find(|b| b.token == asset.contract.address) + { + Some(b) => b.amount, + None => Uint128::zero(), + }, + ); + } else { + balances.push(Uint128::zero()); + } + } + + Ok(manager::QueryAnswer::BatchBalance { amounts: balances }) +} + +pub fn balance(deps: Deps, asset: Addr, holder: Addr) -> StdResult { + if let Some(asset) = ASSETS.may_load(deps.storage, asset)? { + let holding = match HOLDING.may_load(deps.storage, holder.clone())? { + Some(h) => h, + None => { + return Err(StdError::generic_err("Invalid Holder")); + } + }; + // TODO include unbonding so balance is more 'stable' + // likely requires treasury rebalance changes + let balance = match holding + .balances + .iter() + .find(|b| b.token == asset.contract.address) + { + Some(b) => b.amount, + None => Uint128::zero(), + }; + + Ok(manager::QueryAnswer::Balance { amount: balance }) + } else { + Err(StdError::generic_err("Not a registered asset")) + } +} + +pub fn holders(deps: Deps) -> StdResult { + Ok(treasury_manager::QueryAnswer::Holders { + holders: HOLDERS.load(deps.storage)?, + }) +} + +pub fn holding(deps: Deps, holder: Addr) -> StdResult { + match HOLDING.may_load(deps.storage, holder)? { + Some(h) => Ok(treasury_manager::QueryAnswer::Holding { holding: h }), + None => Err(StdError::generic_err("Not a holder")), + } +} diff --git a/contracts/dao/treasury_manager/src/storage.rs b/contracts/dao/treasury_manager/src/storage.rs new file mode 100644 index 0000000..16989e3 --- /dev/null +++ b/contracts/dao/treasury_manager/src/storage.rs @@ -0,0 +1,21 @@ +use shade_protocol::{ + c_std::{Addr, Uint128}, + dao::treasury_manager::{AllocationMeta, Config, Holding, Metric}, + secret_storage_plus::{Item, Map}, + snip20::helpers::Snip20Asset, + utils::storage::plus::period_storage::PeriodStorage, +}; + +pub const CONFIG: Item = Item::new("config"); +pub const VIEWING_KEY: Item = Item::new("viewing_key"); + +pub const ASSET_LIST: Item> = Item::new("asset_list"); +pub const ASSETS: Map = Map::new("assets"); + +pub const ALLOCATIONS: Map> = Map::new("allocations"); +pub const HOLDERS: Item> = Item::new("holders"); +pub const HOLDING: Map = Map::new("holding"); +pub const UNBONDINGS: Map = Map::new("unbondings"); + +pub const METRICS: PeriodStorage = + PeriodStorage::new("metrics-all", "metrics-recent", "metrics-timed"); diff --git a/contracts/dao/treasury_manager/tests/integration/batch.rs b/contracts/dao/treasury_manager/tests/integration/batch.rs new file mode 100644 index 0000000..ae68f30 --- /dev/null +++ b/contracts/dao/treasury_manager/tests/integration/batch.rs @@ -0,0 +1,138 @@ +use shade_multi_test::{ + multi::{admin::init_admin_auth, snip20::Snip20, treasury_manager::TreasuryManager}, +}; +use shade_protocol::{ + c_std::{ + to_binary, + Addr, + Uint128, + }, + multi_test::{App}, +}; +use shade_protocol::{ + contract_interfaces::{ + dao::{ + manager, + treasury_manager::{self}, + }, + snip20, + }, + utils::{ + ExecuteCallback, + InstantiateCallback, + MultiTestable, + Query, + }, +}; + +// Add other adapters here as they come +fn batch_balance_test(balances: Vec) { + let mut app = App::default(); + + let admin = Addr::unchecked("admin"); + let _user = Addr::unchecked("user"); + let admin_auth = init_admin_auth(&mut app, &admin); + + let viewing_key = "viewing_key".to_string(); + + let manager = treasury_manager::InstantiateMsg { + admin_auth: admin_auth.clone().into(), + viewing_key: viewing_key.clone(), + treasury: admin.to_string().clone(), + } + .test_init( + TreasuryManager::default(), + &mut app, + admin.clone(), + "treasury", + &[], + ) + .unwrap(); + + let mut tokens = vec![]; + + for bal in balances.clone() { + let token = snip20::InstantiateMsg { + name: "token".into(), + admin: Some("admin".into()), + symbol: "TKN".into(), + decimals: 6, + initial_balances: Some(vec![snip20::InitialBalance { + address: admin.to_string().clone(), + amount: bal.clone(), + }]), + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + query_auth: None, + } + .test_init( + Snip20::default(), + &mut app, + admin.clone(), + &bal.to_string(), + &[], + ) + .unwrap(); + + treasury_manager::ExecuteMsg::RegisterAsset { + contract: token.clone().into(), + } + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // Deposit funds as treasury + snip20::ExecuteMsg::Send { + recipient: manager.address.to_string().clone(), + recipient_code_hash: None, + amount: bal, + msg: None, + memo: None, + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + tokens.push(token); + } + + // Treasury Balances + match manager::QueryMsg::Manager(manager::SubQueryMsg::BatchBalance { + assets: tokens + .iter() + .map(|t| t.address.to_string().clone()) + .collect(), + holder: admin.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::BatchBalance { amounts } => { + assert!(amounts == balances, "Reported balances match inputs"); + } + _ => { + panic!("Failed to query batch balances"); + } + } +} + +macro_rules! batch_balance_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + batch_balance_test($value.into_iter().map(|a| Uint128::new(a as u128)).collect()); + } + )* + } +} + +batch_balance_tests! { + batch_balances_0: vec![10, 23840, 8402840, 123456, 0], +} diff --git a/contracts/dao/treasury_manager/tests/integration/config.rs b/contracts/dao/treasury_manager/tests/integration/config.rs new file mode 100644 index 0000000..5d7c69b --- /dev/null +++ b/contracts/dao/treasury_manager/tests/integration/config.rs @@ -0,0 +1,86 @@ +use shade_multi_test::interfaces::{ + dao::init_dao, + treasury_manager, + utils::{DeployedContracts, SupportedContracts}, +}; +use shade_protocol::{ + c_std::{Addr, Uint128}, + contract_interfaces::dao::{self, treasury::AllowanceType, treasury_manager::AllocationType}, + multi_test::App, + utils::{ + asset::{Contract, RawContract}, + cycle::Cycle, + }, +}; + +#[test] +pub fn update_config() { + let mut app = App::default(); + let mut contracts = DeployedContracts::new(); + init_dao( + &mut app, + "admin", + &mut contracts, + Uint128::new(1500), + "SSCRT", + vec![ + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + ], + vec![Cycle::Constant; 4], + vec![ + Uint128::new(200), // Amount - 50 + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + Uint128::new(300), // Amount - 100 + Uint128::new(3 * 10u128.pow(17)), // Portion - 40% + ], // Allowance amount + vec![Uint128::zero(); 4], + vec![ + vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount + ]; + 4 + ], + vec![ + vec![ + Uint128::new(6 * 10u128.pow(17)), + Uint128::new(50), + Uint128::new(2 * 10u128.pow(17)), + Uint128::new(75), + ]; + 4 + ], + vec![vec![Uint128::zero(); 4]; 4], + true, + true, + ) + .unwrap(); + treasury_manager::update_config_exec( + &mut app, + "admin", + &contracts, + SupportedContracts::TreasuryManager(0), + Some(RawContract { + address: "rando2".to_string(), + code_hash: "rando3".to_string(), + }), + Some(Addr::unchecked("rando").into()), + ) + .unwrap(); + assert_eq!( + treasury_manager::config_query(&app, &contracts, SupportedContracts::TreasuryManager(0)) + .unwrap(), + dao::treasury_manager::Config { + admin_auth: Contract { + address: Addr::unchecked("rando2"), + code_hash: "rando3".to_string(), + }, + treasury: Addr::unchecked("rando"), + } + ); +} diff --git a/contracts/dao/treasury_manager/tests/integration/execute_error.rs b/contracts/dao/treasury_manager/tests/integration/execute_error.rs new file mode 100644 index 0000000..c464713 --- /dev/null +++ b/contracts/dao/treasury_manager/tests/integration/execute_error.rs @@ -0,0 +1,183 @@ +use shade_multi_test::interfaces::{ + dao::init_dao, + snip20, + treasury_manager, + utils::{DeployedContracts, SupportedContracts}, +}; +use shade_protocol::{ + c_std::Uint128, + contract_interfaces::dao::{treasury::AllowanceType, treasury_manager::AllocationType}, + multi_test::App, + utils::cycle::Cycle, +}; + +#[test] +pub fn execute_error() { + let mut app = App::default(); + let mut contracts = DeployedContracts::new(); + init_dao( + &mut app, + "admin", + &mut contracts, + Uint128::new(1500), + "SSCRT", + vec![ + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + ], + vec![Cycle::Constant; 4], + vec![ + Uint128::new(200), // Amount - 50 + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + Uint128::new(300), // Amount - 100 + Uint128::new(3 * 10u128.pow(17)), // Portion - 40% + ], // Allowance amount + vec![Uint128::zero(); 4], + vec![ + vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount + ]; + 4 + ], + vec![ + vec![ + Uint128::new(6 * 10u128.pow(17)), + Uint128::new(50), + Uint128::new(2 * 10u128.pow(17)), + Uint128::new(75), + ]; + 4 + ], + vec![vec![Uint128::zero(); 4]; 4], + true, + true, + ) + .unwrap(); + assert!( + !treasury_manager::allocate_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + None, + &SupportedContracts::MockAdapter(0), + AllocationType::Amount, + Uint128::new(1), + Uint128::new(10u128.pow(18u32)), + 0, + ) + .is_ok() + ); + assert!( + !treasury_manager::allocate_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + None, + &SupportedContracts::MockAdapter(0), + AllocationType::Portion, + Uint128::new(10u128.pow(18u32)), + Uint128::new(1), + 0, + ) + .is_ok() + ); + snip20::init(&mut app, "admin", &mut contracts, "Shade", "SHD", 8, None).unwrap(); + assert!( + !treasury_manager::claim_exec( + &mut app, + "admin", + &contracts, + "SHD", + SupportedContracts::TreasuryManager(0) + ) + .is_ok() + ); + treasury_manager::register_holder_exec( + &mut app, + "admin", + &contracts, + SupportedContracts::TreasuryManager(0), + "holder", + ) + .unwrap(); + assert!( + !treasury_manager::unbond_exec( + &mut app, + "holder", + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(0), + Uint128::new(1) + ) + .is_ok() + ); + snip20::send_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + "holder".to_string(), + Uint128::new(2), + None, + ) + .unwrap(); + snip20::send_exec( + &mut app, + "holder", + &contracts, + "SSCRT", + contracts[&SupportedContracts::TreasuryManager(0)] + .address + .clone() + .into(), + Uint128::new(1), + None, + ) + .unwrap(); + assert!( + !treasury_manager::unbond_exec( + &mut app, + "holder", + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(0), + Uint128::new(2) + ) + .is_ok() + ); + treasury_manager::register_asset_exec( + &mut app, + "admin", + &contracts, + "SHD", + SupportedContracts::TreasuryManager(0), + ) + .unwrap(); + assert!( + !treasury_manager::register_holder_exec( + &mut app, + "admin", + &contracts, + SupportedContracts::TreasuryManager(0), + "holder", + ) + .is_ok() + ); + assert!( + !treasury_manager::remove_holder_exec( + &mut app, + "admin", + &contracts, + SupportedContracts::TreasuryManager(0), + "not_a_holdler" + ) + .is_ok() + ); +} diff --git a/contracts/dao/treasury_manager/tests/integration/holder_integration.rs b/contracts/dao/treasury_manager/tests/integration/holder_integration.rs new file mode 100644 index 0000000..966de76 --- /dev/null +++ b/contracts/dao/treasury_manager/tests/integration/holder_integration.rs @@ -0,0 +1,345 @@ +use shade_multi_test::multi::admin::init_admin_auth; +use shade_protocol::c_std::{ + to_binary, + Addr, + Uint128, +}; + +//use shade_protocol::secret_toolkit::snip20; + +use shade_multi_test::multi::{snip20::Snip20, treasury_manager::TreasuryManager}; +use shade_protocol::{ + dao::{manager, treasury_manager}, + multi_test::App, + snip20, + utils::{ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +/* No adapters configured + * All assets will sit on manager unused as "reserves" + * No need to "claim" as "unbond" will send up to "reserves" + */ +fn single_asset_holder_no_adapters(initial: Uint128, deposit: Uint128) { + let mut app = App::default(); + + let viewing_key = "unguessable".to_string(); + + let admin = Addr::unchecked("admin"); + let holder = Addr::unchecked("holder"); + let treasury = Addr::unchecked("treasury"); + let admin_auth = init_admin_auth(&mut app, &admin); + + let token = snip20::InstantiateMsg { + name: "token".into(), + admin: Some("admin".into()), + symbol: "TKN".into(), + decimals: 6, + initial_balances: Some(vec![snip20::InitialBalance { + address: holder.to_string().clone(), + amount: initial, + }]), + prng_seed: to_binary("").ok().unwrap(), + config: None, + query_auth: None, + } + .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) + .unwrap(); + + let manager = treasury_manager::InstantiateMsg { + admin_auth: admin_auth.into(), + treasury: treasury.clone().into(), + viewing_key: viewing_key.clone(), + } + .test_init( + TreasuryManager::default(), + &mut app, + admin.clone(), + "manager", + &[], + ) + .unwrap(); + + // set holder viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, holder.clone(), &[]) + .unwrap(); + + // Register manager assets + treasury_manager::ExecuteMsg::RegisterAsset { + contract: token.clone().into(), + } + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // Add 'holder' as holder + treasury_manager::ExecuteMsg::AddHolder { + holder: holder.to_string().clone(), + } + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // Deposit funds into manager + snip20::ExecuteMsg::Send { + recipient: manager.address.to_string().clone(), + recipient_code_hash: None, + amount: deposit, + msg: None, + memo: None, + padding: None, + } + .test_exec(&token, &mut app, holder.clone(), &[]) + .unwrap(); + + // Balance Checks + + // manager reported holder balance + match manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Balance { amount } => { + assert_eq!(amount, deposit, "Pre-unbond Manager Holder Balance"); + } + _ => panic!("Query failed"), + }; + + // manager reported treasury balance + match manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + holder: treasury.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Balance { amount } => { + assert_eq!( + amount, + Uint128::zero(), + "Pre-unbond Manager Treasury Balance" + ); + } + _ => panic!("Query failed"), + }; + + // Manager reported total asset balance + match manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Balance { amount } => { + assert_eq!(amount, deposit, "Pre-unbond Manager Total Balance"); + } + _ => panic!("Query failed"), + }; + + // holder snip20 bal + match (snip20::QueryMsg::Balance { + address: holder.to_string().clone(), + key: viewing_key.clone(), + } + .test_query(&token, &app) + .unwrap()) + { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!( + amount.u128(), + initial.u128() - deposit.u128(), + "Pre-unbond Holder Snip20 balance" + ); + } + _ => { + panic!("Query failed"); + } + }; + + // Unbondable + match manager::QueryMsg::Manager(manager::SubQueryMsg::Unbondable { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Unbondable { amount } => { + assert_eq!(amount, deposit, "Pre-unbond unbondable"); + } + _ => panic!("Query failed"), + }; + + // Reserves + match manager::QueryMsg::Manager(manager::SubQueryMsg::Reserves { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Reserves { amount } => { + assert_eq!(amount, deposit, "Pre-unbond reserves"); + } + _ => panic!("Query failed"), + }; + + let unbond_amount = Uint128::new(deposit.u128() / 2); + + // unbond from manager + manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Unbond { + asset: token.address.to_string().clone().to_string(), + amount: unbond_amount, + }) + .test_exec(&manager, &mut app, holder.clone(), &[]) + .unwrap(); + + // Unbondable + match manager::QueryMsg::Manager(manager::SubQueryMsg::Unbondable { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Unbondable { amount } => { + assert_eq!( + amount, + Uint128::new(deposit.u128() - unbond_amount.u128()), + "Post-unbond total unbondable" + ); + } + _ => panic!("Query failed"), + }; + + match manager::QueryMsg::Manager(manager::SubQueryMsg::Unbondable { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Unbondable { amount } => { + assert_eq!( + amount, + Uint128::new(deposit.u128() - unbond_amount.u128()), + "Post-unbond holder unbondable" + ); + } + _ => panic!("Query failed"), + }; + + // Unbonding + match manager::QueryMsg::Manager(manager::SubQueryMsg::Unbonding { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Unbonding { amount } => { + assert_eq!(amount, Uint128::zero(), "Post-unbond total unbonding"); + } + _ => panic!("Query failed"), + }; + + match manager::QueryMsg::Manager(manager::SubQueryMsg::Unbonding { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Unbonding { amount } => { + assert_eq!(amount, Uint128::zero(), "Post-unbond Holder Unbonding"); + } + _ => panic!("Query failed"), + }; + + // Claimable (zero as its immediately claimed) + match manager::QueryMsg::Manager(manager::SubQueryMsg::Claimable { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Claimable { amount } => { + assert_eq!(amount, Uint128::zero(), "Post-unbond total claimable"); + } + _ => panic!("Query failed"), + }; + + match manager::QueryMsg::Manager(manager::SubQueryMsg::Claimable { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Claimable { amount } => { + assert_eq!(amount, Uint128::zero(), "Post-unbond holder claimable"); + } + _ => panic!("Query failed"), + }; + + // Manager reflects unbonded + match manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Balance { amount } => { + assert_eq!(amount.u128(), deposit.u128() - unbond_amount.u128()); + } + _ => { + panic!("Query failed"); + } + }; + + // user received unbonded + match (snip20::QueryMsg::Balance { + address: holder.to_string().clone(), + key: viewing_key.clone(), + } + .test_query(&token, &app) + .unwrap()) + { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!( + amount.u128(), + (initial.u128() - deposit.u128()) + unbond_amount.u128(), + "Post-claim holder snip20 balance" + ); + } + _ => { + panic!("Query failed"); + } + }; +} + +macro_rules! single_asset_holder_no_adapters_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (initial, deposit) = $value; + single_asset_holder_no_adapters(initial, deposit); + } + )* + } +} + +single_asset_holder_no_adapters_tests! { + single_asset_holder_no_adapters_0: ( + Uint128::new(100_000_000), + Uint128::new(50_000_000), + ), +} diff --git a/contracts/dao/treasury_manager/tests/integration/mod.rs b/contracts/dao/treasury_manager/tests/integration/mod.rs new file mode 100644 index 0000000..89deaae --- /dev/null +++ b/contracts/dao/treasury_manager/tests/integration/mod.rs @@ -0,0 +1,9 @@ +pub mod batch; +pub mod config; +pub mod execute_error; +pub mod holder_integration; +pub mod multiple_holders; +pub mod query; +pub mod scrt_staking_integration; +pub mod tm_unbond; +pub mod tolerance; diff --git a/contracts/dao/treasury_manager/tests/integration/multiple_holders.rs b/contracts/dao/treasury_manager/tests/integration/multiple_holders.rs new file mode 100644 index 0000000..5be17c2 --- /dev/null +++ b/contracts/dao/treasury_manager/tests/integration/multiple_holders.rs @@ -0,0 +1,416 @@ +use shade_multi_test::interfaces::{ + dao::{ + init_dao, + mock_adapter_complete_unbonding, + system_balance_reserves, + system_balance_unbondable, + update_dao, + }, + snip20, + treasury_manager, + utils::{DeployedContracts, SupportedContracts}, +}; +use shade_protocol::{ + c_std::Uint128, + contract_interfaces::dao::{treasury::AllowanceType, treasury_manager::AllocationType}, + multi_test::App, + utils::cycle::Cycle, +}; + +pub fn multiple_holders( + is_instant_unbond: bool, + after_holder_adds_tokens: (Uint128, Vec<(Uint128, Vec)>), + after_holder_removed: (Uint128, Vec<(Uint128, Vec)>), +) { + let num_managers = 4; + const HOLDER: &str = "holder"; + let mut app = App::default(); + let mut contracts = DeployedContracts::new(); + init_dao( + &mut app, + "admin", + &mut contracts, + Uint128::new(1500), + "SSCRT", + vec![ + AllowanceType::Amount, + AllowanceType::Portion, + AllowanceType::Amount, + AllowanceType::Portion, + ], + vec![Cycle::Constant; 4], + vec![ + Uint128::new(200), // Amount - 50 + Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% + Uint128::new(300), // Amount - 100 + Uint128::new(3 * 10u128.pow(17)), // Portion - 40% + ], // Allowance amount + vec![Uint128::zero(); 4], + vec![ + vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount + ]; + 4 + ], + vec![ + vec![ + Uint128::new(6 * 10u128.pow(17)), + Uint128::new(50), + Uint128::new(2 * 10u128.pow(17)), + Uint128::new(75), + ]; + 4 + ], + vec![vec![Uint128::zero(); 4]; 4], + is_instant_unbond, + true, + ) + .unwrap(); + let bals = { + if is_instant_unbond { + system_balance_reserves(&app, &contracts, "SSCRT") + } else { + system_balance_unbondable(&app, &contracts, "SSCRT") + } + }; + assert_eq!(bals, after_holder_removed); + snip20::set_viewing_key_exec(&mut app, HOLDER, &contracts, "SSCRT", HOLDER.to_string()) + .unwrap(); + snip20::send_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + HOLDER.to_string(), + Uint128::new(1000), + None, + ) + .unwrap(); + treasury_manager::register_holder_exec( + &mut app, + "admin", + &contracts, + SupportedContracts::TreasuryManager(0), + HOLDER, + ) + .unwrap(); + snip20::send_exec( + &mut app, + HOLDER, + &contracts, + "SSCRT", + contracts[&SupportedContracts::TreasuryManager(0)] + .address + .to_string(), + Uint128::new(200), + None, + ) + .unwrap(); + snip20::send_exec( + &mut app, + HOLDER, + &contracts, + "SSCRT", + contracts[&SupportedContracts::TreasuryManager(0)] + .address + .to_string(), + Uint128::new(300), + None, + ) + .unwrap(); + assert_eq!( + Uint128::new(500), + treasury_manager::holding_query( + &app, + &contracts, + SupportedContracts::TreasuryManager(0), + HOLDER.to_string(), + ) + .unwrap() + .balances[0] + .amount + ); + update_dao(&mut app, "admin", &contracts, "SSCRT", num_managers).unwrap(); + let bals = { + if is_instant_unbond { + system_balance_reserves(&app, &contracts, "SSCRT") + } else { + system_balance_unbondable(&app, &contracts, "SSCRT") + } + }; + assert_eq!(bals, after_holder_adds_tokens); + treasury_manager::unbond_exec( + &mut app, + HOLDER, + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(0), + Uint128::new(300), + ) + .unwrap(); + if !is_instant_unbond { + update_dao(&mut app, "admin", &contracts, "SSCRT", num_managers).unwrap(); + let mut k = 0; + for _i in 0..num_managers { + for _j in 0..4 { + mock_adapter_complete_unbonding( + &mut app, + "admin", + &contracts, + SupportedContracts::MockAdapter(k), + ) + .unwrap(); + k += 1; + } + k += 1; + } + } + update_dao(&mut app, "admin", &contracts, "SSCRT", num_managers).unwrap(); + treasury_manager::claim_exec( + &mut app, + HOLDER, + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(0), + ) + .unwrap(); + match treasury_manager::remove_holder_exec( + &mut app, + "rando", + &contracts, + SupportedContracts::TreasuryManager(0), + HOLDER.clone(), + ) { + Ok(_) => assert!(false, "unauthorized removing of HOLDER"), + Err(_) => assert!(true), + } + treasury_manager::remove_holder_exec( + &mut app, + "admin", + &contracts, + SupportedContracts::TreasuryManager(0), + HOLDER.clone(), + ) + .unwrap(); + match treasury_manager::remove_holder_exec( + &mut app, + "admin", + &contracts, + SupportedContracts::TreasuryManager(0), + &contracts[&SupportedContracts::Treasury].address.to_string(), + ) { + Ok(_) => assert!(false, "removed treasury as a HOLDER"), + Err(_) => assert!(true), + } + match snip20::send_exec( + &mut app, + HOLDER, + &contracts, + "SSCRT", + contracts[&SupportedContracts::TreasuryManager(0)] + .address + .to_string(), + Uint128::new(300), + None, + ) { + Ok(_) => assert!(false, "closed HOLDERs shouldn't be able to send to TM"), + Err(_) => assert!(true), + } + treasury_manager::unbond_exec( + &mut app, + HOLDER, + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(0), + Uint128::zero(), + ) + .unwrap(); + if !is_instant_unbond { + let mut k = 0; + for _i in 0..num_managers { + for _j in 0..4 { + mock_adapter_complete_unbonding( + &mut app, + "admin", + &contracts, + SupportedContracts::MockAdapter(k), + ) + .unwrap(); + k += 1; + } + k += 1; + } + } + treasury_manager::claim_exec( + &mut app, + HOLDER, + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(0), + ) + .unwrap(); + update_dao(&mut app, "admin", &contracts, "SSCRT", num_managers).unwrap(); + if !is_instant_unbond { + let mut k = 0; + for _i in 0..num_managers { + for _j in 0..4 { + mock_adapter_complete_unbonding( + &mut app, + "admin", + &contracts, + SupportedContracts::MockAdapter(k), + ) + .unwrap(); + k += 1; + } + k += 1; + } + treasury_manager::claim_exec( + &mut app, + HOLDER, + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(0), + ) + .unwrap(); + } + update_dao(&mut app, "admin", &contracts, "SSCRT", num_managers).unwrap(); + match treasury_manager::holding_query( + &app, + &contracts, + SupportedContracts::TreasuryManager(0), + HOLDER.to_string(), + ) { + Ok(_) => assert!(false, "HOLDER was not removed"), + Err(_) => assert!(true), + } + let bals = { + if is_instant_unbond { + system_balance_reserves(&app, &contracts, "SSCRT") + } else { + system_balance_unbondable(&app, &contracts, "SSCRT") + } + }; + assert_eq!(bals, after_holder_removed); +} + +#[test] +pub fn mul_holders() { + multiple_holders( + true, + (Uint128::new(280), vec![ + (Uint128::new(100), vec![ + Uint128::new(345), + Uint128::new(50), + Uint128::new(115), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(285), + Uint128::new(50), + Uint128::new(95), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(105), + Uint128::new(50), + Uint128::new(35), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(105), + Uint128::new(50), + Uint128::new(35), + Uint128::new(75), + ]), + ]), + (Uint128::new(280), vec![ + (Uint128::new(0), vec![ + Uint128::new(45), + Uint128::new(50), + Uint128::new(15), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(285), + Uint128::new(50), + Uint128::new(95), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(105), + Uint128::new(50), + Uint128::new(35), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(105), + Uint128::new(50), + Uint128::new(35), + Uint128::new(75), + ]), + ]), + ); +} + +#[test] +pub fn mul_holders_unbond() { + multiple_holders( + false, + (Uint128::new(280), vec![ + (Uint128::new(100), vec![ + Uint128::new(345), + Uint128::new(50), + Uint128::new(115), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(285), + Uint128::new(50), + Uint128::new(95), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(105), + Uint128::new(50), + Uint128::new(35), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(105), + Uint128::new(50), + Uint128::new(35), + Uint128::new(75), + ]), + ]), + (Uint128::new(280), vec![ + (Uint128::new(0), vec![ + Uint128::new(45), + Uint128::new(50), + Uint128::new(15), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(285), + Uint128::new(50), + Uint128::new(95), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(105), + Uint128::new(50), + Uint128::new(35), + Uint128::new(75), + ]), + (Uint128::new(0), vec![ + Uint128::new(105), + Uint128::new(50), + Uint128::new(35), + Uint128::new(75), + ]), + ]), + ); +} diff --git a/contracts/dao/treasury_manager/tests/integration/query.rs b/contracts/dao/treasury_manager/tests/integration/query.rs new file mode 100644 index 0000000..0531c58 --- /dev/null +++ b/contracts/dao/treasury_manager/tests/integration/query.rs @@ -0,0 +1,400 @@ +use shade_multi_test::interfaces::{ + dao::{init_dao, mock_adapter_sub_tokens, update_dao}, + snip20, + treasury_manager, + utils::{DeployedContracts, SupportedContracts}, +}; +use shade_protocol::{ + c_std::{BlockInfo, Timestamp, Uint128}, + contract_interfaces::dao::{treasury::AllowanceType, treasury_manager::AllocationType}, + multi_test::App, + utils::{ + cycle::{parse_utc_datetime, Cycle}, + storage::plus::period_storage::Period, + }, +}; + +#[test] +pub fn query() { + let mut app = App::default(); + let mut contracts = DeployedContracts::new(); + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds( + parse_utc_datetime(&"1995-11-13T00:00:00.00Z".to_string()) + .unwrap() + .timestamp() as u64, + ), + chain_id: "chain_id".to_string(), + }); + init_dao( + &mut app, + "admin", + &mut contracts, + Uint128::new(1500), + "SSCRT", + vec![AllowanceType::Amount], + vec![Cycle::Constant], + vec![ + Uint128::new(1500), // Amount - 50 + ], // Allowance amount + vec![Uint128::zero(); 4], + vec![ + vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount + ]; + 4 + ], + vec![ + vec![ + Uint128::new(6 * 10u128.pow(17)), + Uint128::new(50), + Uint128::new(2 * 10u128.pow(17)), + Uint128::new(75), + ]; + 4 + ], + vec![vec![Uint128::zero(); 4]; 4], + true, + true, + ) + .unwrap(); + assert_eq!( + treasury_manager::batch_balance_query( + &app, + &contracts, + vec!["SSCRT"], + SupportedContracts::TreasuryManager(0), + SupportedContracts::Treasury + ) + .unwrap()[0] + + treasury_manager::pending_allowance_query( + &app, + &contracts, + SupportedContracts::TreasuryManager(0), + "SSCRT" + ) + .unwrap(), + Uint128::new(1500) + ); + snip20::init(&mut app, "admin", &mut contracts, "Shade", "SHD", 8, None).unwrap(); + assert!( + !treasury_manager::pending_allowance_query( + &app, + &contracts, + SupportedContracts::TreasuryManager(0), + "SHD" + ) + .is_ok() + ); + assert!( + !treasury_manager::assets_query(&app, &contracts, SupportedContracts::TreasuryManager(0),) + .unwrap() + .is_empty() + ); + assert_eq!( + treasury_manager::allocations_query( + &app, + &contracts, + SupportedContracts::TreasuryManager(0), + "SHD" + ) + .unwrap(), + vec![] + ); + assert!( + !treasury_manager::allocations_query( + &app, + &contracts, + SupportedContracts::TreasuryManager(0), + "SSCRT" + ) + .unwrap() + .is_empty(), + ); + assert!( + !treasury_manager::holders_query(&app, &contracts, SupportedContracts::TreasuryManager(0),) + .unwrap() + .is_empty(), + ); + assert_eq!( + treasury_manager::batch_balance_query( + &app, + &contracts, + vec!["SSCRT", "SHD"], + SupportedContracts::TreasuryManager(0), + SupportedContracts::Treasury + ) + .unwrap(), + vec![Uint128::new(1225), Uint128::zero()] + ); + assert!( + !treasury_manager::batch_balance_query( + &app, + &contracts, + vec!["SSCRT", "SHD"], + SupportedContracts::TreasuryManager(0), + SupportedContracts::AdminAuth + ) + .is_ok() + ); + assert!( + !treasury_manager::balance_query( + &app, + &contracts, + "SHD", + SupportedContracts::TreasuryManager(0), + SupportedContracts::Treasury + ) + .is_ok() + ); + assert!( + !treasury_manager::balance_query( + &app, + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(0), + SupportedContracts::AdminAuth + ) + .is_ok() + ); + assert!( + !treasury_manager::unbonding_query( + &app, + &contracts, + "SHD", + SupportedContracts::TreasuryManager(0), + SupportedContracts::Treasury + ) + .is_ok() + ); + assert!( + !treasury_manager::unbonding_query( + &app, + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(0), + SupportedContracts::AdminAuth + ) + .is_ok() + ); + assert!( + !treasury_manager::unbondable_query( + &app, + &contracts, + "SHD", + SupportedContracts::TreasuryManager(0), + SupportedContracts::Treasury + ) + .is_ok() + ); + assert!( + !treasury_manager::unbondable_query( + &app, + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(0), + SupportedContracts::AdminAuth + ) + .is_ok() + ); + assert!( + !treasury_manager::reserves_query( + &app, + &contracts, + "SHD", + SupportedContracts::TreasuryManager(0), + SupportedContracts::Treasury + ) + .is_ok() + ); + assert!( + !treasury_manager::claimable_query( + &app, + &contracts, + "SHD", + SupportedContracts::TreasuryManager(0), + SupportedContracts::Treasury + ) + .is_ok() + ); + assert!( + !treasury_manager::claimable_query( + &app, + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(0), + SupportedContracts::AdminAuth + ) + .is_ok() + ); + treasury_manager::register_asset_exec( + &mut app, + "admin", + &contracts, + "SHD", + SupportedContracts::TreasuryManager(0), + ) + .unwrap(); + assert!( + !treasury_manager::metrics_query( + &app, + &contracts, + SupportedContracts::TreasuryManager(0), + Some("1995-11-13T00:00:00.00Z".to_string()), + None, + Period::Hour, + ) + .unwrap() + .is_empty() + ); + assert!( + !treasury_manager::metrics_query( + &app, + &contracts, + SupportedContracts::TreasuryManager(0), + Some("1995-11-13T00:00:00.00Z".to_string()), + None, + Period::Day, + ) + .unwrap() + .is_empty() + ); + assert!( + !treasury_manager::metrics_query( + &app, + &contracts, + SupportedContracts::TreasuryManager(0), + Some("1995-11-13T00:00:00.00Z".to_string()), + None, + Period::Month, + ) + .unwrap() + .is_empty() + ); + assert!( + !treasury_manager::metrics_query( + &app, + &contracts, + SupportedContracts::TreasuryManager(0), + None, + Some(Uint128::new(816220800)), + Period::Hour, + ) + .unwrap() + .is_empty() + ); + assert!( + !treasury_manager::metrics_query( + &app, + &contracts, + SupportedContracts::TreasuryManager(0), + None, + Some(Uint128::new(816220800)), + Period::Day, + ) + .unwrap() + .is_empty() + ); + assert!( + !treasury_manager::metrics_query( + &app, + &contracts, + SupportedContracts::TreasuryManager(0), + None, + Some(Uint128::new(816220800)), + Period::Month, + ) + .unwrap() + .is_empty() + ); + assert!( + !treasury_manager::metrics_query( + &app, + &contracts, + SupportedContracts::TreasuryManager(0), + None, + None, + Period::Month, + ) + .unwrap() + .is_empty() + ); + assert!( + !treasury_manager::metrics_query( + &app, + &contracts, + SupportedContracts::TreasuryManager(0), + Some("1995-11-13T00:00:00.00Z".to_string()), + Some(Uint128::new(816220800)), + Period::Month, + ) + .is_ok() + ); + assert!( + treasury_manager::metrics_query( + &app, + &contracts, + SupportedContracts::TreasuryManager(0), + None, + Some(Uint128::new( + parse_utc_datetime(&"1995-12-13T00:00:00.00Z".to_string()) + .unwrap() + .timestamp() as u128 + )), + Period::Month, + ) + .unwrap() + .is_empty() + ); + mock_adapter_sub_tokens( + &mut app, + "admin", + &contracts, + Uint128::new(10), + SupportedContracts::MockAdapter(3), + ) + .unwrap(); + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds( + parse_utc_datetime(&"1995-12-13T00:00:00.00Z".to_string()) + .unwrap() + .timestamp() as u64, + ), + chain_id: "chain_id".to_string(), + }); + update_dao(&mut app, "admin", &contracts, "SSCRT", 1).unwrap(); + update_dao(&mut app, "admin", &contracts, "SSCRT", 1).unwrap(); + assert!( + !treasury_manager::metrics_query( + &app, + &contracts, + SupportedContracts::TreasuryManager(0), + None, + Some(Uint128::new( + parse_utc_datetime(&"1995-12-13T00:00:00.00Z".to_string()) + .unwrap() + .timestamp() as u128 + )), + Period::Month, + ) + .unwrap() + .is_empty() + ); + assert!( + !treasury_manager::metrics_query( + &app, + &contracts, + SupportedContracts::TreasuryManager(0), + Some("1995-12-13T00:00:00.00Z".to_string()), + None, + Period::Month, + ) + .unwrap() + .is_empty() + ); +} diff --git a/contracts/dao/treasury_manager/tests/integration/scrt_staking_integration.rs b/contracts/dao/treasury_manager/tests/integration/scrt_staking_integration.rs new file mode 100644 index 0000000..033fd95 --- /dev/null +++ b/contracts/dao/treasury_manager/tests/integration/scrt_staking_integration.rs @@ -0,0 +1,548 @@ +/* +use shade_protocol::{ + c_std::{to_binary, Addr, Coin, Decimal, Uint128, Validator}, + contract_interfaces::{ + dao::{ + adapter, + manager, + scrt_staking, + treasury_manager::{self, Allocation, AllocationType}, + }, + snip20, + }, + utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shade_multi_test::multi::{ + admin::init_admin_auth, + scrt_staking::ScrtStaking, + snip20::Snip20, + treasury_manager::TreasuryManager, +}; +use shade_protocol::multi_test::{App, BankSudo, StakingSudo, SudoMsg}; + +/* No adapters configured + * All assets will sit on manager unused as "reserves" + * No need to "claim" as "unbond" will send up to "reserves" + */ +fn single_holder_scrt_staking_adapter( + deposit: Uint128, + alloc_type: AllocationType, + alloc_amount: Uint128, + rewards: Uint128, + expected_scrt_staking: Uint128, + expected_manager_holder: Uint128, + expected_manager_treasury: Uint128, + unbond_amount: Uint128, +) { + let mut app = App::default(); + let viewing_key = "unguessable".to_string(); + + let admin = Addr::unchecked("admin"); + let holder = Addr::unchecked("holder"); + let treasury = Addr::unchecked("treasury"); + let validator = Addr::unchecked("validator"); + let admin_auth = init_admin_auth(&mut app, &admin); + + app.sudo(SudoMsg::Staking(StakingSudo::AddValidator { + validator: validator.to_string().clone(), + })) + .unwrap(); + + let token = snip20::InstantiateMsg { + name: "secretSCRT".into(), + admin: Some("admin".into()), + symbol: "SSCRT".into(), + decimals: 6, + initial_balances: None, + prng_seed: to_binary("").ok().unwrap(), + query_auth: None, + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) + .unwrap(); + + let manager = treasury_manager::InstantiateMsg { + admin_auth: admin_auth.clone().into(), + treasury: treasury.clone().into(), + viewing_key: viewing_key.clone(), + } + .test_init( + TreasuryManager::default(), + &mut app, + admin.clone(), + "manager", + &[], + ) + .unwrap(); + + let scrt_staking = scrt_staking::InstantiateMsg { + admin_auth: admin_auth.into(), + owner: manager.address.to_string().clone().into(), + sscrt: token.clone().into(), + validator_bounds: None, + viewing_key: viewing_key.clone(), + } + .test_init( + ScrtStaking::default(), + &mut app, + admin.clone(), + "scrt_staking", + &[], + ) + .unwrap(); + println!("scrt staking {}", scrt_staking.address.clone()); + + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, holder.clone(), &[]) + .unwrap(); + + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, treasury.clone(), &[]) + .unwrap(); + + // Register manager assets + treasury_manager::ExecuteMsg::RegisterAsset { + contract: token.clone().into(), + } + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // Add 'holder' as holder + treasury_manager::ExecuteMsg::AddHolder { + holder: holder.to_string().clone().into(), + } + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // Allocate to scrt_staking from manager + treasury_manager::ExecuteMsg::Allocate { + asset: token.address.to_string().clone(), + allocation: Allocation { + nick: Some("scrt_staking".to_string()), + contract: Contract { + address: scrt_staking.address.clone(), + code_hash: scrt_staking.code_hash.clone(), + }, + alloc_type, + amount: alloc_amount, + tolerance: Uint128::zero(), + }, + } + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + let deposit_coin = Coin { + denom: "uscrt".into(), + amount: deposit, + }; + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: holder.to_string().clone(), + amount: vec![deposit_coin.clone()], + })) + .unwrap(); + + assert!(deposit_coin.amount > Uint128::zero()); + + // Wrap L1 + &snip20::ExecuteMsg::Deposit { padding: None } + .test_exec(&token, &mut app, holder.clone(), &[deposit_coin]) + .unwrap(); + + // Deposit funds into manager + println!("deposit to manager"); + snip20::ExecuteMsg::Send { + recipient: manager.address.to_string().clone(), + recipient_code_hash: None, + amount: deposit, + msg: None, + memo: None, + padding: None, + } + .test_exec(&token, &mut app, holder.clone(), &[]) + .unwrap(); + + // Update manager + manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { + asset: token.address.to_string().clone(), + }) + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // Balance Checks + + // manager reported holder balance + match manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Balance { amount } => { + assert_eq!(amount, deposit, "Pre-unbond Manager Holder Balance"); + } + _ => assert!(false), + }; + + // manager reported treasury balance + match manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + holder: treasury.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Balance { amount } => { + assert_eq!( + amount, + Uint128::zero(), + "Pre-unbond Manager Treasury Balance" + ); + } + _ => assert!(false), + }; + + // scrt staking balance + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap() + { + manager::QueryAnswer::Balance { amount } => { + assert_eq!( + amount, expected_scrt_staking, + "Pre-unbond scrt staking balance" + ); + } + _ => assert!(false), + }; + + // manager unbondable + match manager::QueryMsg::Manager(manager::SubQueryMsg::Unbondable { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Unbondable { amount } => { + assert_eq!(amount, deposit, "Pre-unbond unbondable"); + } + _ => assert!(false), + }; + + let mut reserves = Uint128::zero(); + + // Reserves + match manager::QueryMsg::Manager(manager::SubQueryMsg::Reserves { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Reserves { amount } => { + reserves = amount; + assert_eq!(amount, expected_manager_holder, "Pre-unbond reserves"); + } + _ => assert!(false), + }; + + // Claimable + match manager::QueryMsg::Manager(manager::SubQueryMsg::Claimable { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Claimable { amount } => { + assert_eq!(amount, Uint128::zero(), "Pre-unbond claimable"); + } + _ => assert!(false), + }; + + // Add Rewards + app.sudo(SudoMsg::Staking(StakingSudo::AddRewards { + amount: Coin { + amount: rewards, + denom: "uscrt".into(), + }, + })) + .unwrap(); + + // Update scrt staking to claim & restake rewards + adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { + asset: token.address.to_string().clone(), + }) + .test_exec(&scrt_staking, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update manager to detect & rebalance after gainz + manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { + asset: token.address.to_string().clone(), + }) + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // holder unbond from manager + println!("manager unbond {}", unbond_amount); + manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Unbond { + asset: token.address.to_string().clone(), + amount: unbond_amount, + }) + .test_exec(&manager, &mut app, holder.clone(), &[]) + .unwrap(); + + // scrt staking Unbondable + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbondable { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap() + { + adapter::QueryAnswer::Unbondable { amount } => { + assert_eq!( + amount, + deposit - unbond_amount, + "Post-unbond scrt staking unbondable" + ); + } + _ => assert!(false), + }; + + // manager Unbondable + match manager::QueryMsg::Manager(manager::SubQueryMsg::Unbondable { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Unbondable { amount } => { + assert_eq!( + amount, + deposit - unbond_amount, + "Post-unbond manager holder unbondable" + ); + } + _ => assert!(false), + }; + + // Unbonding + match manager::QueryMsg::Manager(manager::SubQueryMsg::Unbonding { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Unbonding { amount } => { + assert_eq!( + amount, + unbond_amount - reserves, + "Post-unbond manager unbonding" + ); + } + _ => assert!(false), + }; + + // Manager Claimable + match manager::QueryMsg::Manager(manager::SubQueryMsg::Claimable { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Claimable { amount } => { + assert_eq!(amount, Uint128::zero(), "Pre-fastforward manager claimable"); + } + _ => assert!(false), + }; + + app.sudo(SudoMsg::Staking(StakingSudo::FastForwardUndelegate {})) + .unwrap(); + + // Scrt Staking Claimable + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { + asset: token.address.to_string().clone(), + }) + .test_query(&scrt_staking, &app) + .unwrap() + { + adapter::QueryAnswer::Claimable { amount } => { + assert_eq!( + amount, + unbond_amount - reserves + rewards, + "Post-fastforward scrt staking claimable" + ); + } + _ => assert!(false), + }; + + // Manager Claimable + match manager::QueryMsg::Manager(manager::SubQueryMsg::Claimable { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Claimable { amount } => { + assert_eq!( + amount, + unbond_amount - reserves, + "Post-fastforward manager claimable" + ); + } + _ => assert!(false), + }; + + // Claim + manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Claim { + asset: token.address.to_string().clone(), + }) + .test_exec(&manager, &mut app, holder.clone(), &[]) + .unwrap(); + + // Manager Holder Balance + match manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + holder: holder.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Balance { amount } => { + assert_eq!(amount, deposit - unbond_amount); + } + _ => { + assert!(false); + } + }; + + // Manager Treasury Balance + match manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + holder: treasury.to_string().clone(), + }) + .test_query(&manager, &app) + .unwrap() + { + manager::QueryAnswer::Balance { amount } => { + assert_eq!(amount, expected_manager_treasury); + } + _ => assert!(false), + }; + + // user received unbonded + match (snip20::QueryMsg::Balance { + address: holder.to_string().clone(), + key: viewing_key.clone(), + }) + .test_query(&token, &app) + .unwrap() + { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!( + amount.u128(), + unbond_amount.u128(), + "Post-claim holder snip20 balance" + ); + } + _ => { + assert!(false); + } + }; + + /* + // treasury received gainz + match (snip20::QueryMsg::Balance { + address: treasury.to_string().clone(), + key: viewing_key.clone(), + }).test_query(&token, &app).unwrap() { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, expected_manager_treasury, "treasury snip20 balance"); + }, + _ => assert!(false), + }; + */ +} + +macro_rules! single_holder_scrt_staking_adapter_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (deposit, alloc_type, alloc_amount, rewards, + expected_scrt_staking, expected_manager_holder, expected_manager_treasury, unbond_amount) = $value; + single_holder_scrt_staking_adapter(deposit, alloc_type, alloc_amount, rewards, expected_scrt_staking, expected_manager_holder, expected_manager_treasury, unbond_amount); + } + )* + } +} + +single_holder_scrt_staking_adapter_tests! { + single_holder_scrt_staking_portion: ( + // 100 + Uint128::new(100_000_000), + // % 50 alloc + AllocationType::Portion, + Uint128::new(5u128 * 10u128.pow(17)), + // 0 rewards + Uint128::zero(), + // 50/50 + Uint128::new(50_000_000), + Uint128::new(50_000_000), + Uint128::zero(), + // unbond 75 + Uint128::new(75_000_000), + ), + single_holder_scrt_staking_amount: ( + // 100 + Uint128::new(100_000_000), + // 50 alloc + AllocationType::Amount, + Uint128::new(50_000_000), + // 0 rewards + Uint128::zero(), + // 50/50 + Uint128::new(50_000_000), + Uint128::new(50_000_000), + Uint128::zero(), + // unbond 75 + Uint128::new(75_000_000), + ), + single_holder_scrt_staking_amount_rewards: ( + // 100 + Uint128::new(100_000_000), + // 50 alloc + AllocationType::Amount, + Uint128::new(50_000_000), + // 0 rewards + Uint128::new(100_000_000), + // 50/50 + Uint128::new(50_000_000), + Uint128::new(50_000_000), + Uint128::new(100_000_000), + // unbond 75 + Uint128::new(75_000_000), + ), +} +*/ diff --git a/contracts/dao/treasury_manager/tests/integration/tm_unbond.rs b/contracts/dao/treasury_manager/tests/integration/tm_unbond.rs new file mode 100644 index 0000000..5d0fe7e --- /dev/null +++ b/contracts/dao/treasury_manager/tests/integration/tm_unbond.rs @@ -0,0 +1,222 @@ +use shade_multi_test::interfaces::{ + dao::{init_dao, system_balance_reserves}, + snip20, + treasury_manager, + utils::{DeployedContracts, SupportedContracts}, +}; +use shade_protocol::{ + c_std::Uint128, + contract_interfaces::dao::{treasury::AllowanceType, treasury_manager::AllocationType}, + multi_test::App, + utils::cycle::Cycle, +}; + +pub fn test_tm_unbond( + unbond_amount: Uint128, + adapter_gain_amount: Option, + amount_adapter_bal: (Uint128, Uint128), + expected_before_unbond: (Uint128, Vec<(Uint128, Vec)>), + expected_after_unbond: (Uint128, Vec<(Uint128, Vec)>), +) { + let mut app = App::default(); + let mut contracts = DeployedContracts::new(); + init_dao( + &mut app, + "admin", + &mut contracts, + Uint128::new(1000), + "SSCRT", + vec![AllowanceType::Amount], + vec![Cycle::Constant], + vec![ + Uint128::new(500), // Amount - 500 + ], // Allowance amount + vec![Uint128::zero()], + vec![ + vec![ + AllocationType::Portion, + AllocationType::Amount, + AllocationType::Portion, + AllocationType::Amount + ]; + 4 + ], + vec![ + vec![ + Uint128::new(6 * 10u128.pow(17)), + amount_adapter_bal.0, + Uint128::new(2 * 10u128.pow(17)), + amount_adapter_bal.1, + ]; + 4 + ], + vec![vec![Uint128::zero(); 4]; 4], + true, + true, + ) + .unwrap(); + let bals = system_balance_reserves(&app, &contracts, "SSCRT"); + assert_eq!(bals, expected_before_unbond); + match adapter_gain_amount { + Some(x) => { + for i in vec![1, 3] { + snip20::send_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + contracts + .get(&SupportedContracts::MockAdapter(i)) + .unwrap() + .address + .to_string(), + x, + None, + ) + .unwrap(); + } + } + None => {} + } + treasury_manager::unbond_exec( + &mut app, + "admin", + &contracts, + "SSCRT", + SupportedContracts::TreasuryManager(0), + unbond_amount, + ) + .unwrap(); + + let bals = system_balance_reserves(&app, &contracts, "SSCRT"); + assert_eq!(bals, expected_after_unbond); +} + +macro_rules! dao_tests_tm_unbond { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + unbond_amount, + adapter_gain_amount, + amount_adapter_bal, + expected_before_unbond, + expected_after_unbond, + ) = $value; + test_tm_unbond( + unbond_amount, + adapter_gain_amount, + amount_adapter_bal, + expected_before_unbond, + expected_after_unbond, + ); + } + )* + } +} + +dao_tests_tm_unbond! { + unbond_only_from_amount_adapters:( + Uint128::new(7), + Some(Uint128::new(10)), + (Uint128::new(10), Uint128::new(20)), + (Uint128::new(594), vec![(Uint128::new(0), vec![ + Uint128::new(282), + Uint128::new(10), + Uint128::new(94), + Uint128::new(20), + ])]), + (Uint128::new(594), vec![(Uint128::new(7), vec![ + Uint128::new(282), + Uint128::new(17), + Uint128::new(94), + Uint128::new(26) + ])]) + ), + unbond_only_extra_from_amount_adapters:( + Uint128::new(20), + Some(Uint128::new(10)), + (Uint128::new(10), Uint128::new(20)), + (Uint128::new(594), vec![(Uint128::new(0), vec![ + Uint128::new(282), + Uint128::new(10), + Uint128::new(94), + Uint128::new(20), + ])]), + (Uint128::new(594), vec![(Uint128::new(20), vec![ + Uint128::new(282), + Uint128::new(10), + Uint128::new(94), + Uint128::new(20) + ])]) + ), + unbond_case_extra_from_amount_adapters_and_some_from_portion_adapters:( + Uint128::new(21), + Some(Uint128::new(10)), + (Uint128::new(10), Uint128::new(20)), + (Uint128::new(594), vec![(Uint128::new(0), vec![ + Uint128::new(282), + Uint128::new(10), + Uint128::new(94), + Uint128::new(20), + ])]), + (Uint128::new(594), vec![(Uint128::new(21), vec![ + Uint128::new(282), + Uint128::new(10), + Uint128::new(93), + Uint128::new(20) + ])]) + ), + unbond_case_extra_from_amount_adapters_and_all_from_portion_adapters:( + Uint128::new(396), + Some(Uint128::new(10)), + (Uint128::new(10), Uint128::new(20)), + (Uint128::new(594), vec![(Uint128::new(0), vec![ + Uint128::new(282), + Uint128::new(10), + Uint128::new(94), + Uint128::new(20), + ])]), + (Uint128::new(594), vec![(Uint128::new(396), vec![ + Uint128::new(0), + Uint128::new(10), + Uint128::new(0), + Uint128::new(20) + ])]) + ), + unbond_case_extra_and_some_from_amount_adapters_and_all_from_portion_adapters:( + Uint128::new(391), + None, + (Uint128::new(100), Uint128::new(200)), + (Uint128::new(540), vec![(Uint128::new(0), vec![ + Uint128::new(120), + Uint128::new(100), + Uint128::new(40), + Uint128::new(200), + ])]), + (Uint128::new(540), vec![(Uint128::new(391), vec![ + Uint128::new(0), + Uint128::new(23), + Uint128::new(0), + Uint128::new(46) + ])]) + ), + unbond_all:( + Uint128::new(459), + None, + (Uint128::new(101), Uint128::new(200)), + (Uint128::new(541), vec![(Uint128::new(0), vec![ + Uint128::new(119), + Uint128::new(101), + Uint128::new(39), + Uint128::new(200), + ])]), + (Uint128::new(541), vec![(Uint128::new(459), vec![ + Uint128::new(0), + Uint128::new(0), + Uint128::new(0), + Uint128::new(0) + ])]) + ), +} diff --git a/contracts/dao/treasury_manager/tests/integration/tolerance.rs b/contracts/dao/treasury_manager/tests/integration/tolerance.rs new file mode 100644 index 0000000..820e97a --- /dev/null +++ b/contracts/dao/treasury_manager/tests/integration/tolerance.rs @@ -0,0 +1,467 @@ +use mock_adapter; +use shade_multi_test::multi::{ + admin::init_admin_auth, + mock_adapter::MockAdapter, + snip20::Snip20, + treasury_manager::TreasuryManager, +}; +use shade_protocol::{ + c_std::{to_binary, Addr, Uint128}, + contract_interfaces::{ + dao::{ + adapter, + manager, + treasury_manager::{self, AllocationType, RawAllocation}, + }, + snip20, + }, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +fn underfunded_tolerance( + deposit: Uint128, + added: Uint128, + tolerance: Uint128, + allocation: Uint128, + alloc_type: AllocationType, + expected: Uint128, +) { + let mut app = App::default(); + + let admin = Addr::unchecked("admin"); + let _spender = Addr::unchecked("spender"); + let treasury = Addr::unchecked("treasury"); + let _user = Addr::unchecked("user"); + //let validator = Addr::unchecked("validator"); + let admin_auth = init_admin_auth(&mut app, &admin); + + let viewing_key = "viewing_key".to_string(); + + let token = snip20::InstantiateMsg { + name: "token".into(), + admin: Some("admin".into()), + symbol: "TKN".into(), + decimals: 6, + initial_balances: Some(vec![snip20::InitialBalance { + address: admin.to_string().clone(), + amount: deposit + added, + }]), + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + query_auth: None, + } + .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) + .unwrap(); + + let manager = treasury_manager::InstantiateMsg { + admin_auth: admin_auth.clone().into(), + viewing_key: viewing_key.clone(), + treasury: treasury.to_string().clone(), + } + .test_init( + TreasuryManager::default(), + &mut app, + admin.clone(), + "manager", + &[], + ) + .unwrap(); + + let adapter = mock_adapter::contract::Config { + owner: manager.address.clone(), + instant: true, + token: token.clone().into(), + } + .test_init( + MockAdapter::default(), + &mut app, + admin.clone(), + "adapter", + &[], + ) + .unwrap(); + + // Set admin viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Register treasury assets + treasury_manager::ExecuteMsg::RegisterAsset { + contract: token.clone().into(), + } + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // treasury allocation to spender + treasury_manager::ExecuteMsg::Allocate { + asset: token.address.to_string().clone(), + allocation: RawAllocation { + nick: Some("Manager".to_string()), + contract: RawContract::from(adapter.clone()), + alloc_type, + amount: allocation, + // 100% (adapter balance will 2x before unbond) + tolerance, + }, + } + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // Deposit funds into treasury + snip20::ExecuteMsg::Send { + recipient: manager.address.to_string().clone(), + recipient_code_hash: None, + amount: deposit, + msg: None, + memo: None, + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update manager + manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { + asset: token.address.to_string().clone(), + }) + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // Check adapter balance + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&adapter, &app) + .unwrap() + { + manager::QueryAnswer::Balance { amount } => { + assert_eq!(amount, deposit, "Adapter Balance"); + } + _ => panic!("query failed"), + }; + + // Additional funds into manager + snip20::ExecuteMsg::Send { + recipient: manager.address.to_string().clone(), + recipient_code_hash: None, + amount: added, + msg: None, + memo: None, + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update manager + manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { + asset: token.address.to_string().clone(), + }) + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // Check adapter balance + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&adapter, &app) + .unwrap() + { + manager::QueryAnswer::Balance { amount } => { + assert_eq!(amount, expected, "Final Adapter Balance"); + } + _ => panic!("query failed"), + }; +} + +macro_rules! underfunded_tolerance_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + deposit, + added, + tolerance, + allocation, + alloc_type, + expected, + ) = $value; + underfunded_tolerance( + deposit, + added, + tolerance, + allocation, + alloc_type, + expected, + ); + } + )* + } +} + +underfunded_tolerance_tests! { + tolerance_portion_90_no_increase: ( + Uint128::new(100), // deposit + Uint128::new(50), // added + Uint128::new(9 * 10u128.pow(17)), // tolerance + Uint128::new(1 * 10u128.pow(18)), // allocation + AllocationType::Portion, + Uint128::new(100), // expected + ), + tolerance_portion_90_will_increase: ( + Uint128::new(100), // deposit + Uint128::new(1000), // added + Uint128::new(9 * 10u128.pow(17)), // tolerance + Uint128::new(1 * 10u128.pow(18)), // allowance + AllocationType::Portion, + Uint128::new(1100), // expected + ), + tolerance_amount_10_no_increase: ( + Uint128::new(100), // deposit + Uint128::new(5), // added + Uint128::new(1 * 10u128.pow(17)), // tolerance + Uint128::new(105), //allowance + AllocationType::Amount, + Uint128::new(100), // expected + ), +} + +fn overfunded_tolerance( + deposit: Uint128, + tolerance: Uint128, + allocation: Uint128, + reduced: Uint128, + alloc_type: AllocationType, + expected: Uint128, +) { + let mut app = App::default(); + + let admin = Addr::unchecked("admin"); + let _spender = Addr::unchecked("spender"); + let treasury = Addr::unchecked("treasury"); + let _user = Addr::unchecked("user"); + //let validator = Addr::unchecked("validator"); + let admin_auth = init_admin_auth(&mut app, &admin); + + let viewing_key = "viewing_key".to_string(); + + let token = snip20::InstantiateMsg { + name: "token".into(), + admin: Some("admin".into()), + symbol: "TKN".into(), + decimals: 6, + initial_balances: Some(vec![snip20::InitialBalance { + address: admin.to_string().clone(), + amount: deposit, + }]), + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: Some(false), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + query_auth: None, + } + .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) + .unwrap(); + + let manager = treasury_manager::InstantiateMsg { + admin_auth: admin_auth.clone().into(), + viewing_key: viewing_key.clone(), + treasury: treasury.to_string().clone(), + } + .test_init( + TreasuryManager::default(), + &mut app, + admin.clone(), + "manager", + &[], + ) + .unwrap(); + + let adapter = mock_adapter::contract::Config { + owner: manager.address.clone(), + instant: true, + token: token.clone().into(), + } + .test_init( + MockAdapter::default(), + &mut app, + admin.clone(), + "adapter", + &[], + ) + .unwrap(); + + // Set admin viewing key + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Register treasury assets + treasury_manager::ExecuteMsg::RegisterAsset { + contract: token.clone().into(), + } + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // treasury allocation to spender + treasury_manager::ExecuteMsg::Allocate { + asset: token.address.to_string().clone(), + allocation: RawAllocation { + nick: Some("Manager".to_string()), + contract: RawContract::from(adapter.clone()), + alloc_type: alloc_type.clone(), + amount: allocation, + // 100% (adapter balance will 2x before unbond) + tolerance, + }, + } + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // Deposit funds into treasury + snip20::ExecuteMsg::Send { + recipient: manager.address.to_string().clone(), + recipient_code_hash: None, + amount: deposit, + msg: None, + memo: None, + padding: None, + } + .test_exec(&token, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update manager + manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { + asset: token.address.to_string().clone(), + }) + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // Check adapter balance + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&adapter, &app) + .unwrap() + { + manager::QueryAnswer::Balance { amount } => { + assert_eq!(amount, deposit, "Adapter Balance"); + } + _ => panic!("query failed"), + }; + + // reduce allocation + treasury_manager::ExecuteMsg::Allocate { + asset: token.address.to_string().clone(), + allocation: RawAllocation { + nick: Some("Manager".to_string()), + contract: RawContract::from(adapter.clone()), + alloc_type, + amount: reduced, + // 100% (adapter balance will 2x before unbond) + tolerance, + }, + } + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // Update manager + manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { + asset: token.address.to_string().clone(), + }) + .test_exec(&manager, &mut app, admin.clone(), &[]) + .unwrap(); + + // Check adapter balance + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: token.address.to_string().clone(), + }) + .test_query(&adapter, &app) + .unwrap() + { + manager::QueryAnswer::Balance { amount } => { + assert_eq!(amount, expected, "Final Adapter Balance"); + } + _ => panic!("query failed"), + }; +} + +macro_rules! overfunded_tolerance_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let ( + deposit, + tolerance, + allocation, + reduced, + alloc_type, + expected, + ) = $value; + overfunded_tolerance( + deposit, + tolerance, + allocation, + reduced, + alloc_type, + expected, + ); + } + )* + } +} + +overfunded_tolerance_tests! { + over_portion_tolerance_10_no_decrease: ( + Uint128::new(100), // deposit + Uint128::new(1 * 10u128.pow(17)), // tolerance + Uint128::new(1 * 10u128.pow(18)), // allocation + Uint128::new(95 * 10u128.pow(16)), // reduced allocation + AllocationType::Portion, + Uint128::new(100), // expected + ), + over_portion_tolerance_10_will_decrease: ( + Uint128::new(100), // deposit + Uint128::new(1 * 10u128.pow(17)), // tolerance + Uint128::new(1 * 10u128.pow(18)), // allocation + Uint128::new(1 * 10u128.pow(17)), // reduced allocation + AllocationType::Portion, + Uint128::new(10), // expected + ), + over_amount_tolerance_10_no_decrease: ( + Uint128::new(100), // deposit + Uint128::new(1 * 10u128.pow(17)), // tolerance + Uint128::new(100), // allocation + Uint128::new(95), // reduced allocation + AllocationType::Amount, + Uint128::new(100), // expected + ), + over_amount_tolerance_10_will_decrease: ( + Uint128::new(100), // deposit + Uint128::new(1 * 10u128.pow(17)), // tolerance + Uint128::new(100), // allocation + Uint128::new(80), // reduced allocation + AllocationType::Amount, + Uint128::new(80), // expected + ), +} diff --git a/contracts/dao/treasury_manager/tests/mod.rs b/contracts/dao/treasury_manager/tests/mod.rs new file mode 100644 index 0000000..5155b77 --- /dev/null +++ b/contracts/dao/treasury_manager/tests/mod.rs @@ -0,0 +1 @@ +pub mod integration; diff --git a/contracts/headstash-contract/.cargo/config b/contracts/headstash-contract/.cargo/config deleted file mode 100644 index de2d36a..0000000 --- a/contracts/headstash-contract/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[alias] -wasm = "build --release --lib --target wasm32-unknown-unknown" -wasm-debug = "build --lib --target wasm32-unknown-unknown" -unit-test = "test --lib" -schema = "run --bin schema" diff --git a/contracts/headstash-contract/.gitignore b/contracts/headstash-contract/.gitignore deleted file mode 100644 index dfdaaa6..0000000 --- a/contracts/headstash-contract/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -# Build results -/target - -# 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/headstash-contract/Cargo.toml b/contracts/headstash-contract/Cargo.toml deleted file mode 100644 index 4729731..0000000 --- a/contracts/headstash-contract/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "headstash-contract" -version = { workspace = true } -authors = ["A Hardnett "] -edition = { workspace = true } -description = "An Airdrop contract for allowing users to claim rewards with Merkle Tree based proof" -license = { workspace = true } - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -backtraces = ["cosmwasm-std/backtraces"] -library = [] - -[dependencies] -ethereum-verify = "3.3.0" -cw-storage-plus = { workspace = true } -cw-utils = { workspace = true } -cw2 = { workspace = true } -cosmwasm-std = { workspace = true } -hex = "0.4" -schemars = { workspace = true } -serde = { workspace = true } -sha2 = { version = "0.9.5", default-features = false } -thiserror = { workspace = true } - -[dev-dependencies] -cosmwasm-schema = { workspace = true } -serde_json = "1" diff --git a/contracts/headstash-contract/helpers/.eslintignore b/contracts/headstash-contract/helpers/.eslintignore deleted file mode 100644 index 502167f..0000000 --- a/contracts/headstash-contract/helpers/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -/lib diff --git a/contracts/headstash-contract/helpers/.eslintrc b/contracts/headstash-contract/helpers/.eslintrc deleted file mode 100644 index 7b84619..0000000 --- a/contracts/headstash-contract/helpers/.eslintrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": [ - "oclif", - "oclif-typescript" - ] -} diff --git a/contracts/headstash-contract/helpers/.gitignore b/contracts/headstash-contract/helpers/.gitignore deleted file mode 100644 index 9d6ea2c..0000000 --- a/contracts/headstash-contract/helpers/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -*-debug.log -*-error.log -/.nyc_output -/dist -/lib -/package-lock.json -/tmp -node_modules diff --git a/contracts/headstash-contract/helpers/README.md b/contracts/headstash-contract/helpers/README.md deleted file mode 100644 index c9e4a97..0000000 --- a/contracts/headstash-contract/helpers/README.md +++ /dev/null @@ -1,47 +0,0 @@ -merkle-airdrop-cli -================== - -This is a helper client shipped along contract. -Use this to generate root, generate proofs and verify proofs - -## Installation - -```shell -yarn install -yarn link -``` - -Binary will be placed to path. - -## Airdrop file format - -```json -[ - { "address": "wasm1k9hwzxs889jpvd7env8z49gad3a3633vg350tq", "amount": "100"}, - { "address": "wasm1uy9ucvgerneekxpnfwyfnpxvlsx5dzdpf0mzjd", "amount": "1010"} -] -``` - -## Commands - -**Generate Root:** -```shell -merkle-airdrop-cli generateRoot --file ../testdata/airdrop_stage_2_list.json -``` - -**Generate proof:** -```shell -merkle-airdrop-cli generateProofs --file ../testdata/airdrop_stage_2_list.json \ - --address wasm1ylna88nach9sn5n7qe7u5l6lh7dmt6lp2y63xx \ - --amount 1000000000 -``` - -**Verify proof:** -```shell -PROOFS='[ "27e9b1ec8cb64709d0a8d3702344561674199fe81b885f1f9c9b2fb268795962","280777995d054081cbf208bccb70f8d736c1766b81d90a1fd21cd97d2d83a5cc","3946ea1758a5a2bf55bae1186168ad35aa0329805bc8bff1ca3d51345faec04a" -]' -merkle-airdrop-cli verifyProofs --file ../testdata/airdrop.json \ - --address wasm1k9hwzxs889jpvd7env8z49gad3a3633vg350tq \ - --amount 100 \ - --proofs $PROOFS -``` diff --git a/contracts/headstash-contract/helpers/bin/run b/contracts/headstash-contract/helpers/bin/run deleted file mode 100644 index 30b14e1..0000000 --- a/contracts/headstash-contract/helpers/bin/run +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node - -require('@oclif/command').run() -.then(require('@oclif/command/flush')) -.catch(require('@oclif/errors/handle')) diff --git a/contracts/headstash-contract/helpers/bin/run.cmd b/contracts/headstash-contract/helpers/bin/run.cmd deleted file mode 100644 index 968fc30..0000000 --- a/contracts/headstash-contract/helpers/bin/run.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@echo off - -node "%~dp0\run" %* diff --git a/contracts/headstash-contract/helpers/package.json b/contracts/headstash-contract/helpers/package.json deleted file mode 100644 index 8e249ee..0000000 --- a/contracts/headstash-contract/helpers/package.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "name": "merkle-airdrop-cli", - "version": "0.1.0", - "author": "Orkun Külçe @orkunkl", - "bin": { - "merkle-airdrop-cli": "./bin/run" - }, - "dependencies": { - "@cosmjs/crypto": "^0.25.5", - "@cosmjs/encoding": "^0.25.5", - "@oclif/command": "^1", - "@oclif/config": "^1", - "@oclif/plugin-help": "^3", - "@types/crypto-js": "^4.0.2", - "ethereumjs-util": "^7.1.0", - "merkletreejs": "^0.2.23", - "tslib": "^1" - }, - "devDependencies": { - "@oclif/dev-cli": "^1", - "@types/node": "^10", - "eslint": "^5.13", - "eslint-config-oclif": "^3.1", - "eslint-config-oclif-typescript": "^0.1", - "globby": "^10", - "ts-node": "^8", - "typescript": "^3.3" - }, - "engines": { - "node": ">=8.0.0" - }, - "files": [ - "/bin", - "/lib", - "/npm-shrinkwrap.json", - "/oclif.manifest.json" - ], - "keywords": [ - "cosmwasm", - "cw20" - ], - "license": "Apache-2.0", - "main": "lib/index.js", - "oclif": { - "commands": "./lib/commands", - "bin": "merkle-airdrop-cli", - "plugins": [ - "@oclif/plugin-help" - ] - }, - "repository": "CosmWasm/cosmwasm-plus/cw20-merkle-airdrop/merkle-airdrop-cli", - "scripts": { - "postpack": "rm -f oclif.manifest.json", - "posttest": "eslint . --ext .ts --config .eslintrc", - "prepack": "rm -rf lib && tsc -b && oclif-dev manifest && oclif-dev readme", - "test": "echo NO TESTS", - "version": "oclif-dev readme && git add README.md" - }, - "types": "lib/index.d.ts" -} diff --git a/contracts/headstash-contract/helpers/src/airdrop.ts b/contracts/headstash-contract/helpers/src/airdrop.ts deleted file mode 100644 index 87972b0..0000000 --- a/contracts/headstash-contract/helpers/src/airdrop.ts +++ /dev/null @@ -1,44 +0,0 @@ -import sha256 from 'crypto-js/sha256' -import { MerkleTree } from 'merkletreejs'; - -class Airdrop { - private tree: MerkleTree; - - constructor(accounts: Array<{ address: string; amount: string }>) { - const leaves = accounts.map((a) => sha256(a.address + a.amount)); - this.tree = new MerkleTree(leaves, sha256, { sort: true }); - } - - public getMerkleRoot(): string { - return this.tree.getHexRoot().replace('0x', ''); - } - - public getMerkleProof(account: { - address: string; - amount: string; - }): string[] { - return this.tree - .getHexProof(sha256(account.address + account.amount).toString()) - .map((v) => v.replace('0x', '')); - } - - public verify( - proof: string[], - account: { address: string; amount: string } - ): boolean { - let hashBuf = Buffer.from(sha256(account.address + account.amount).toString()) - - proof.forEach((proofElem) => { - const proofBuf = Buffer.from(proofElem, 'hex'); - if (hashBuf < proofBuf) { - hashBuf = Buffer.from(sha256(Buffer.concat([hashBuf, proofBuf]).toString())); - } else { - hashBuf = Buffer.from(sha256(Buffer.concat([proofBuf, hashBuf]).toString())); - } - }); - - return this.getMerkleRoot() === hashBuf.toString('hex'); - } -} - -export {Airdrop} diff --git a/contracts/headstash-contract/helpers/src/commands/generateProofs.ts b/contracts/headstash-contract/helpers/src/commands/generateProofs.ts deleted file mode 100644 index 6181e1d..0000000 --- a/contracts/headstash-contract/helpers/src/commands/generateProofs.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {Command, flags} from '@oclif/command' -import { readFileSync } from 'fs'; -import {Airdrop} from '../airdrop'; - -export default class GenerateProof extends Command { - static description = 'Generates merkle proofs for given address' - - static examples = [ - `$ merkle-airdrop-cli generateProofs --file ../testdata/airdrop_stage_2.json \ - --address wasm1ylna88nach9sn5n7qe7u5l6lh7dmt6lp2y63xx \ - --amount 1000000000 -`, - ] - - static flags = { - help: flags.help({char: 'h'}), - file: flags.string({char: 'f', description: 'airdrop file location'}), - address: flags.string({char: 'a', description: 'address'}), - amount: flags.string({char: 'b', description: 'amount'}), - } - - async run() { - const {flags} = this.parse(GenerateProof) - - if (!flags.file) { - this.error(new Error('Airdrop file location not defined')) - } - if (!flags.address) { - this.error(new Error('Address not defined')) - } - if (!flags.amount) { - this.error(new Error('Amount not defined')) - } - - let file; - try { - file = readFileSync(flags.file, 'utf-8'); - } catch (e) { - this.error(e) - } - - let receivers: Array<{ address: string; amount: string }> = JSON.parse(file); - - let airdrop = new Airdrop(receivers) - let proof = airdrop.getMerkleProof({address: flags.address, amount: flags.amount}) - console.log(proof) - } -} diff --git a/contracts/headstash-contract/helpers/src/commands/generateRoot.ts b/contracts/headstash-contract/helpers/src/commands/generateRoot.ts deleted file mode 100644 index a8875fe..0000000 --- a/contracts/headstash-contract/helpers/src/commands/generateRoot.ts +++ /dev/null @@ -1,37 +0,0 @@ -import {Command, flags} from '@oclif/command' -import { readFileSync } from 'fs'; -import {Airdrop} from '../airdrop'; - -export default class GenerateRoot extends Command { - static description = 'Generates merkle root' - - static examples = [ - `$ merkle-airdrop-cli generateRoot --file ../testdata/airdrop_stage_2.json -`, - ] - - static flags = { - help: flags.help({char: 'h'}), - file: flags.string({char: 'f', description: 'Airdrop file location'}), - } - - async run() { - const {flags} = this.parse(GenerateRoot) - - if (!flags.file) { - this.error(new Error('Airdrop file location not defined')) - } - - let file; - try { - file = readFileSync(flags.file, 'utf-8'); - } catch (e) { - this.error(e) - } - - let receivers: Array<{ address: string; amount: string }> = JSON.parse(file); - - let airdrop = new Airdrop(receivers) - console.log(airdrop.getMerkleRoot()) - } -} diff --git a/contracts/headstash-contract/helpers/src/commands/verifyProofs.ts b/contracts/headstash-contract/helpers/src/commands/verifyProofs.ts deleted file mode 100644 index 3915ae4..0000000 --- a/contracts/headstash-contract/helpers/src/commands/verifyProofs.ts +++ /dev/null @@ -1,55 +0,0 @@ -import {Command, flags} from '@oclif/command' -import { readFileSync } from 'fs'; -import {Airdrop} from '../airdrop'; - -export default class VerifyProof extends Command { - static description = 'Verifies merkle proofs for given address' - - static examples = [ - `$ PROOFS='[ "27e9b1ec8cb64709d0a8d3702344561674199fe81b885f1f9c9b2fb268795962","280777995d054081cbf208bccb70f8d736c1766b81d90a1fd21cd97d2d83a5cc","3946ea1758a5a2bf55bae1186168ad35aa0329805bc8bff1ca3d51345faec04a"]' - $ merkle-airdrop-cli verifyProofs --file ../testdata/airdrop.json \ - --address wasm1k9hwzxs889jpvd7env8z49gad3a3633vg350tq \ - --amount 100 - --proofs $PROOFS -`, - ] - - static flags = { - help: flags.help({char: 'h'}), - file: flags.string({char: 'f', description: 'airdrop file location'}), - proofs: flags.string({char: 'p', description: 'proofs in json format'}), - address: flags.string({char: 'a', description: 'address'}), - amount: flags.string({char: 'b', description: 'amount'}), - } - - async run() { - const {flags} = this.parse(VerifyProof) - - if (!flags.file) { - this.error(new Error('Airdrop file location not defined')) - } - if (!flags.proofs) { - this.error(new Error('Proofs not defined')) - } - if (!flags.address) { - this.error(new Error('Address not defined')) - } - if (!flags.amount) { - this.error(new Error('Amount not defined')) - } - - let file; - try { - file = readFileSync(flags.file, 'utf-8'); - } catch (e) { - this.error(e) - } - - let receivers: Array<{ address: string; amount: string }> = JSON.parse(file); - - let airdrop = new Airdrop(receivers) - let proofs: string[] = JSON.parse(flags.proofs) - - console.log(airdrop.verify(proofs, {address: flags.address, amount: flags.amount})) - } -} diff --git a/contracts/headstash-contract/helpers/src/index.ts b/contracts/headstash-contract/helpers/src/index.ts deleted file mode 100644 index 4caa481..0000000 --- a/contracts/headstash-contract/helpers/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {run} from '@oclif/command' diff --git a/contracts/headstash-contract/helpers/tsconfig.json b/contracts/headstash-contract/helpers/tsconfig.json deleted file mode 100644 index 8964312..0000000 --- a/contracts/headstash-contract/helpers/tsconfig.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "compilerOptions": { - "importHelpers": true, - "outDir": "lib", - "rootDir": "src", - "strict": true, - "target": "es2017", - "allowSyntheticDefaultImports": true, - "alwaysStrict": true, - "baseUrl": "./", - "declaration": true, - "esModuleInterop": true, - "lib": ["es2015", "es2016", "es2017", "dom"], - "module": "commonjs", - "moduleResolution": "node", - "noFallthroughCasesInSwitch": true, - "noImplicitAny": true, - "noImplicitReturns": true, - "noImplicitThis": true, - "noUnusedLocals": false, - "noUnusedParameters": true, - "sourceMap": true, - "strictFunctionTypes": true, - "strictNullChecks": true, - "strictPropertyInitialization": true, - "paths": { - "*": ["src/*"] - } - }, - "include": [ - "src/**/*" - ] -} diff --git a/contracts/headstash-contract/helpers/yarn.lock b/contracts/headstash-contract/helpers/yarn.lock deleted file mode 100644 index a074719..0000000 --- a/contracts/headstash-contract/helpers/yarn.lock +++ /dev/null @@ -1,2767 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@babel/code-frame@^7.0.0": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" - integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== - dependencies: - "@babel/highlight" "^7.14.5" - -"@babel/helper-validator-identifier@^7.14.5": - version "7.14.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" - integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== - -"@babel/highlight@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" - integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== - dependencies: - "@babel/helper-validator-identifier" "^7.14.5" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@cosmjs/crypto@^0.25.5": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.25.6.tgz#695d2d0d2195bdbdd5825d415385646244900bbb" - integrity sha512-ec+YcQLrg2ibcxtNrh4FqQnG9kG9IE/Aik2NH6+OXQdFU/qFuBTxSFcKDgzzBOChwlkXwydllM9Jjbp+dgIzRw== - dependencies: - "@cosmjs/encoding" "^0.25.6" - "@cosmjs/math" "^0.25.6" - "@cosmjs/utils" "^0.25.6" - bip39 "^3.0.2" - bn.js "^4.11.8" - elliptic "^6.5.3" - js-sha3 "^0.8.0" - libsodium-wrappers "^0.7.6" - ripemd160 "^2.0.2" - sha.js "^2.4.11" - -"@cosmjs/encoding@^0.25.5", "@cosmjs/encoding@^0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.25.6.tgz#da741a33eaf063a6d3611d7d68db5ca3938e0ef5" - integrity sha512-0imUOB8XkUstI216uznPaX1hqgvLQ2Xso3zJj5IV5oJuNlsfDj9nt/iQxXWbJuettc6gvrFfpf+Vw2vBZSZ75g== - dependencies: - base64-js "^1.3.0" - bech32 "^1.1.4" - readonly-date "^1.0.0" - -"@cosmjs/math@^0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.25.6.tgz#25c7b106aaded889a5b80784693caa9e654b0c28" - integrity sha512-Fmyc9FJ8KMU34n7rdapMJrT/8rx5WhMw2F7WLBu7AVLcBh0yWsXIcMSJCoPHTOnMIiABjXsnrrwEaLrOOBfu6A== - dependencies: - bn.js "^4.11.8" - -"@cosmjs/utils@^0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.25.6.tgz#934d9a967180baa66163847616a74358732227ca" - integrity sha512-ofOYiuxVKNo238vCPPlaDzqPXy2AQ/5/nashBo5rvPZJkxt9LciGfUEQWPCOb1BIJDNx2Dzu0z4XCf/dwzl0Dg== - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@oclif/command@^1", "@oclif/command@^1.5.20", "@oclif/command@^1.6.0", "@oclif/command@^1.8.0": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.8.0.tgz#c1a499b10d26e9d1a611190a81005589accbb339" - integrity sha512-5vwpq6kbvwkQwKqAoOU3L72GZ3Ta8RRrewKj9OJRolx28KLJJ8Dg9Rf7obRwt5jQA9bkYd8gqzMTrI7H3xLfaw== - dependencies: - "@oclif/config" "^1.15.1" - "@oclif/errors" "^1.3.3" - "@oclif/parser" "^3.8.3" - "@oclif/plugin-help" "^3" - debug "^4.1.1" - semver "^7.3.2" - -"@oclif/config@^1", "@oclif/config@^1.15.1", "@oclif/config@^1.17.0": - version "1.17.0" - resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.17.0.tgz#ba8639118633102a7e481760c50054623d09fcab" - integrity sha512-Lmfuf6ubjQ4ifC/9bz1fSCHc6F6E653oyaRXxg+lgT4+bYf9bk+nqrUpAbrXyABkCqgIBiFr3J4zR/kiFdE1PA== - dependencies: - "@oclif/errors" "^1.3.3" - "@oclif/parser" "^3.8.0" - debug "^4.1.1" - globby "^11.0.1" - is-wsl "^2.1.1" - tslib "^2.0.0" - -"@oclif/dev-cli@^1": - version "1.26.0" - resolved "https://registry.yarnpkg.com/@oclif/dev-cli/-/dev-cli-1.26.0.tgz#e3ec294b362c010ffc8948003d3770955c7951fd" - integrity sha512-272udZP+bG4qahoAcpWcMTJKiA+V42kRMqQM7n4tgW35brYb2UP5kK+p08PpF8sgSfRTV8MoJVJG9ax5kY82PA== - dependencies: - "@oclif/command" "^1.8.0" - "@oclif/config" "^1.17.0" - "@oclif/errors" "^1.3.3" - "@oclif/plugin-help" "^3.2.0" - cli-ux "^5.2.1" - debug "^4.1.1" - find-yarn-workspace-root "^2.0.0" - fs-extra "^8.1" - github-slugger "^1.2.1" - lodash "^4.17.11" - normalize-package-data "^3.0.0" - qqjs "^0.3.10" - tslib "^2.0.3" - -"@oclif/errors@^1.2.1", "@oclif/errors@^1.2.2", "@oclif/errors@^1.3.3": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@oclif/errors/-/errors-1.3.5.tgz#a1e9694dbeccab10fe2fe15acb7113991bed636c" - integrity sha512-OivucXPH/eLLlOT7FkCMoZXiaVYf8I/w1eTAM1+gKzfhALwWTusxEx7wBmW0uzvkSg/9ovWLycPaBgJbM3LOCQ== - dependencies: - clean-stack "^3.0.0" - fs-extra "^8.1" - indent-string "^4.0.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -"@oclif/linewrap@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@oclif/linewrap/-/linewrap-1.0.0.tgz#aedcb64b479d4db7be24196384897b5000901d91" - integrity sha512-Ups2dShK52xXa8w6iBWLgcjPJWjais6KPJQq3gQ/88AY6BXoTX+MIGFPrWQO1KLMiQfoTpcLnUwloN4brrVUHw== - -"@oclif/parser@^3.8.0", "@oclif/parser@^3.8.3": - version "3.8.5" - resolved "https://registry.yarnpkg.com/@oclif/parser/-/parser-3.8.5.tgz#c5161766a1efca7343e1f25d769efbefe09f639b" - integrity sha512-yojzeEfmSxjjkAvMRj0KzspXlMjCfBzNRPkWw8ZwOSoNWoJn+OCS/m/S+yfV6BvAM4u2lTzX9Y5rCbrFIgkJLg== - dependencies: - "@oclif/errors" "^1.2.2" - "@oclif/linewrap" "^1.0.0" - chalk "^2.4.2" - tslib "^1.9.3" - -"@oclif/plugin-help@^3", "@oclif/plugin-help@^3.2.0": - version "3.2.2" - resolved "https://registry.yarnpkg.com/@oclif/plugin-help/-/plugin-help-3.2.2.tgz#063ee08cee556573a5198fbdfdaa32796deba0ed" - integrity sha512-SPZ8U8PBYK0n4srFjCLedk0jWU4QlxgEYLCXIBShJgOwPhTTQknkUlsEwaMIevvCU4iCQZhfMX+D8Pz5GZjFgA== - dependencies: - "@oclif/command" "^1.5.20" - "@oclif/config" "^1.15.1" - "@oclif/errors" "^1.2.2" - chalk "^4.1.0" - indent-string "^4.0.0" - lodash.template "^4.4.0" - string-width "^4.2.0" - strip-ansi "^6.0.0" - widest-line "^3.1.0" - wrap-ansi "^4.0.0" - -"@oclif/screen@^1.0.3": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@oclif/screen/-/screen-1.0.4.tgz#b740f68609dfae8aa71c3a6cab15d816407ba493" - integrity sha512-60CHpq+eqnTxLZQ4PGHYNwUX572hgpMHGPtTWMjdTMsAvlm69lZV/4ly6O3sAYkomo4NggGcomrDpBe34rxUqw== - -"@types/bn.js@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.0.tgz#32c5d271503a12653c62cf4d2b45e6eab8cebc68" - integrity sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA== - dependencies: - "@types/node" "*" - -"@types/crypto-js@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.0.2.tgz#4524325a175bf819fec6e42560c389ce1fb92c97" - integrity sha512-sCVniU+h3GcGqxOmng11BRvf9TfN9yIs8KKjB8C8d75W69cpTfZG80gau9yTx5SxF3gvHGbJhdESzzvnjtf3Og== - -"@types/eslint-visitor-keys@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" - integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== - -"@types/glob@^7.1.1": - version "7.1.4" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.4.tgz#ea59e21d2ee5c517914cb4bc8e4153b99e566672" - integrity sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA== - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - -"@types/json-schema@^7.0.3": - version "7.0.9" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" - integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== - -"@types/minimatch@*": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" - integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== - -"@types/node@*": - version "16.4.11" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.4.11.tgz#245030af802c776c31f00eb0cdde40ee615db462" - integrity sha512-nWSFUbuNiPKJEe1IViuodSI+9cM+vpM8SWF/O6dJK7wmGRNq55U7XavJHrlRrPkSMuUZUFzg1xaZ1B+ZZCrRWw== - -"@types/node@11.11.6": - version "11.11.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a" - integrity sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ== - -"@types/node@^10": - version "10.17.60" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" - integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== - -"@types/pbkdf2@^3.0.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.1.0.tgz#039a0e9b67da0cdc4ee5dab865caa6b267bb66b1" - integrity sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ== - dependencies: - "@types/node" "*" - -"@types/secp256k1@^4.0.1": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.3.tgz#1b8e55d8e00f08ee7220b4d59a6abe89c37a901c" - integrity sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w== - dependencies: - "@types/node" "*" - -"@typescript-eslint/eslint-plugin@^2.6.1": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9" - integrity sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ== - dependencies: - "@typescript-eslint/experimental-utils" "2.34.0" - functional-red-black-tree "^1.0.1" - regexpp "^3.0.0" - tsutils "^3.17.1" - -"@typescript-eslint/experimental-utils@2.34.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" - integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== - dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.34.0" - eslint-scope "^5.0.0" - eslint-utils "^2.0.0" - -"@typescript-eslint/parser@^2.6.1": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.34.0.tgz#50252630ca319685420e9a39ca05fe185a256bc8" - integrity sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA== - dependencies: - "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.34.0" - "@typescript-eslint/typescript-estree" "2.34.0" - eslint-visitor-keys "^1.1.0" - -"@typescript-eslint/typescript-estree@2.34.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5" - integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== - dependencies: - debug "^4.1.1" - eslint-visitor-keys "^1.1.0" - glob "^7.1.6" - is-glob "^4.0.1" - lodash "^4.17.15" - semver "^7.3.2" - tsutils "^3.17.1" - -acorn-jsx@^5.0.0: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn@^6.0.7: - version "6.4.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" - integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== - -ajv@^6.10.2, ajv@^6.9.1: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ansi-escapes@^3.1.0, ansi-escapes@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" - integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== - -ansi-escapes@^4.3.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== - -ansi-styles@^3.2.0, ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0, ansi-styles@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansicolors@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" - integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -astral-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" - integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base-x@^3.0.2: - version "3.0.8" - resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d" - integrity sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA== - dependencies: - safe-buffer "^5.0.1" - -base64-js@^1.3.0, base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -bech32@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" - integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== - -bignumber.js@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" - integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA== - -bip39@^3.0.2: - version "3.0.4" - resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.4.tgz#5b11fed966840b5e1b8539f0f54ab6392969b2a0" - integrity sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw== - dependencies: - "@types/node" "11.11.6" - create-hash "^1.1.0" - pbkdf2 "^3.0.9" - randombytes "^2.0.1" - -bl@^4.0.3: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -blakejs@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.1.1.tgz#bf313053978b2cd4c444a48795710be05c785702" - integrity sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg== - -bn.js@4.11.6: - version "4.11.6" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" - integrity sha1-UzRK2xRhehP26N0s4okF0cC6MhU= - -bn.js@^4.11.1, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9: - version "4.12.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" - integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== - -bn.js@^5.1.2: - version "5.2.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" - integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -brorand@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= - -browserify-aes@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" - integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== - dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" - -bs58@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" - integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo= - dependencies: - base-x "^3.0.2" - -bs58check@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" - integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA== - dependencies: - bs58 "^4.0.0" - create-hash "^1.1.0" - safe-buffer "^5.1.2" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer-reverse@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-reverse/-/buffer-reverse-1.0.1.tgz#49283c8efa6f901bc01fa3304d06027971ae2f60" - integrity sha1-SSg8jvpvkBvAH6MwTQYCeXGuL2A= - -buffer-to-arraybuffer@^0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz#6064a40fa76eb43c723aba9ef8f6e1216d10511a" - integrity sha1-YGSkD6dutDxyOrqe+PbhIW0QURo= - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= - -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -cardinal@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" - integrity sha1-fMEFXYItISlU0HsIXeolHMe8VQU= - dependencies: - ansicolors "~0.3.2" - redeyed "~2.1.0" - -chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -clean-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7" - integrity sha1-jffHquUf02h06PjQW5GAvBGj/tc= - dependencies: - escape-string-regexp "^1.0.5" - -clean-stack@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-3.0.1.tgz#155bf0b2221bf5f4fba89528d24c5953f17fe3a8" - integrity sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg== - dependencies: - escape-string-regexp "4.0.0" - -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= - dependencies: - restore-cursor "^2.0.0" - -cli-progress@^3.4.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.9.0.tgz#25db83447deb812e62d05bac1af9aec5387ef3d4" - integrity sha512-g7rLWfhAo/7pF+a/STFH/xPyosaL1zgADhI0OM83hl3c7S43iGvJWEAV2QuDOnQ8i6EMBj/u4+NTd0d5L+4JfA== - dependencies: - colors "^1.1.2" - string-width "^4.2.0" - -cli-ux@^5.2.1: - version "5.6.3" - resolved "https://registry.yarnpkg.com/cli-ux/-/cli-ux-5.6.3.tgz#eecdb2e0261171f2b28f2be6b18c490291c3a287" - integrity sha512-/oDU4v8BiDjX2OKcSunGH0iGDiEtj2rZaGyqNuv9IT4CgcSMyVWAMfn0+rEHaOc4n9ka78B0wo1+N1QX89f7mw== - dependencies: - "@oclif/command" "^1.6.0" - "@oclif/errors" "^1.2.1" - "@oclif/linewrap" "^1.0.0" - "@oclif/screen" "^1.0.3" - ansi-escapes "^4.3.0" - ansi-styles "^4.2.0" - cardinal "^2.1.1" - chalk "^4.1.0" - clean-stack "^3.0.0" - cli-progress "^3.4.0" - extract-stack "^2.0.0" - fs-extra "^8.1" - hyperlinker "^1.0.0" - indent-string "^4.0.0" - is-wsl "^2.2.0" - js-yaml "^3.13.1" - lodash "^4.17.11" - natural-orderby "^2.0.1" - object-treeify "^1.1.4" - password-prompt "^1.1.2" - semver "^7.3.2" - string-width "^4.2.0" - strip-ansi "^6.0.0" - supports-color "^8.1.0" - supports-hyperlinks "^2.1.0" - tslib "^2.0.0" - -cli-width@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" - integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -colors@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" - integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -content-type@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - -create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -create-hmac@^1.1.4, create-hmac@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" - integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -cross-spawn@^6.0.0, cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -crypto-js@^3.1.9-1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.3.0.tgz#846dd1cce2f68aacfa156c8578f926a609b7976b" - integrity sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q== - -debug@^4.0.1, debug@^4.1.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== - dependencies: - ms "2.1.2" - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= - dependencies: - mimic-response "^1.0.0" - -deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= - -detect-indent@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" - integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dom-walk@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" - integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== - -elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.3: - version "6.5.4" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" - integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" - -"emoji-regex@>=6.0.0 <=6.1.1": - version "6.1.1" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e" - integrity sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4= - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -end-of-stream@^1.1.0, end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -escape-string-regexp@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -eslint-ast-utils@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/eslint-ast-utils/-/eslint-ast-utils-1.1.0.tgz#3d58ba557801cfb1c941d68131ee9f8c34bd1586" - integrity sha512-otzzTim2/1+lVrlH19EfQQJEhVJSu0zOb9ygb3iapN6UlyaDtyRq4b5U1FuW0v1lRa9Fp/GJyHkSwm6NqABgCA== - dependencies: - lodash.get "^4.4.2" - lodash.zip "^4.2.0" - -eslint-config-oclif-typescript@^0.1: - version "0.1.0" - resolved "https://registry.yarnpkg.com/eslint-config-oclif-typescript/-/eslint-config-oclif-typescript-0.1.0.tgz#c310767c5ee8916ea5d08cf027d0317dd52ed8ba" - integrity sha512-BjXNJcH2F02MdaSFml9vJskviUFVkLHbTPGM5tinIt98H6klFNKP7/lQ+fB/Goc2wB45usEuuw6+l/fwAv9i7g== - dependencies: - "@typescript-eslint/eslint-plugin" "^2.6.1" - "@typescript-eslint/parser" "^2.6.1" - eslint-config-oclif "^3.1.0" - eslint-config-xo-space "^0.20.0" - eslint-plugin-mocha "^5.2.0" - eslint-plugin-node "^7.0.1" - eslint-plugin-unicorn "^6.0.1" - -eslint-config-oclif@^3.1, eslint-config-oclif@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/eslint-config-oclif/-/eslint-config-oclif-3.1.0.tgz#cbc207ced09e31676dcee2f724fc509cd20eb0bd" - integrity sha512-Tqgy43cNXsSdhTLWW4RuDYGFhV240sC4ISSv/ZiUEg/zFxExSEUpRE6J+AGnkKY9dYwIW4C9b2YSUVv8z/miMA== - dependencies: - eslint-config-xo-space "^0.20.0" - eslint-plugin-mocha "^5.2.0" - eslint-plugin-node "^7.0.1" - eslint-plugin-unicorn "^6.0.1" - -eslint-config-xo-space@^0.20.0: - version "0.20.0" - resolved "https://registry.yarnpkg.com/eslint-config-xo-space/-/eslint-config-xo-space-0.20.0.tgz#75e1fb86d1b052fc1cc3036ca2fa441fa92b85e4" - integrity sha512-bOsoZA8M6v1HviDUIGVq1fLVnSu3mMZzn85m2tqKb73tSzu4GKD4Jd2Py4ZKjCgvCbRRByEB5HPC3fTMnnJ1uw== - dependencies: - eslint-config-xo "^0.24.0" - -eslint-config-xo@^0.24.0: - version "0.24.2" - resolved "https://registry.yarnpkg.com/eslint-config-xo/-/eslint-config-xo-0.24.2.tgz#f61b8ce692e9f9519bdb6edc4ed7ebcd5be48f48" - integrity sha512-ivQ7qISScW6gfBp+p31nQntz1rg34UCybd3uvlngcxt5Utsf4PMMi9QoAluLFcPUM5Tvqk4JGraR9qu3msKPKQ== - -eslint-plugin-es@^1.3.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.4.1.tgz#12acae0f4953e76ba444bfd1b2271081ac620998" - integrity sha512-5fa/gR2yR3NxQf+UXkeLeP8FBBl6tSgdrAz1+cF84v1FMM4twGwQoqTnn+QxFLcPOrF4pdKEJKDB/q9GoyJrCA== - dependencies: - eslint-utils "^1.4.2" - regexpp "^2.0.1" - -eslint-plugin-mocha@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-mocha/-/eslint-plugin-mocha-5.3.0.tgz#cf3eb18ae0e44e433aef7159637095a7cb19b15b" - integrity sha512-3uwlJVLijjEmBeNyH60nzqgA1gacUWLUmcKV8PIGNvj1kwP/CTgAWQHn2ayyJVwziX+KETkr9opNwT1qD/RZ5A== - dependencies: - ramda "^0.26.1" - -eslint-plugin-node@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-7.0.1.tgz#a6e054e50199b2edd85518b89b4e7b323c9f36db" - integrity sha512-lfVw3TEqThwq0j2Ba/Ckn2ABdwmL5dkOgAux1rvOk6CO7A6yGyPI2+zIxN6FyNkp1X1X/BSvKOceD6mBWSj4Yw== - dependencies: - eslint-plugin-es "^1.3.1" - eslint-utils "^1.3.1" - ignore "^4.0.2" - minimatch "^3.0.4" - resolve "^1.8.1" - semver "^5.5.0" - -eslint-plugin-unicorn@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-6.0.1.tgz#4a97f0bc9449e20b82848dad12094ee2ba72347e" - integrity sha512-hjy9LhTdtL7pz8WTrzS0CGXRkWK3VAPLDjihofj8JC+uxQLfXm0WwZPPPB7xKmcjRyoH+jruPHOCrHNEINpG/Q== - dependencies: - clean-regexp "^1.0.0" - eslint-ast-utils "^1.0.0" - import-modules "^1.1.0" - lodash.camelcase "^4.1.1" - lodash.kebabcase "^4.0.1" - lodash.snakecase "^4.0.1" - lodash.upperfirst "^4.2.0" - safe-regex "^1.1.0" - -eslint-scope@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" - integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -eslint-scope@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -eslint-utils@^1.3.1, eslint-utils@^1.4.2: - version "1.4.3" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" - integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== - dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-utils@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" - integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== - dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - -eslint@^5.13: - version "5.16.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea" - integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg== - dependencies: - "@babel/code-frame" "^7.0.0" - ajv "^6.9.1" - chalk "^2.1.0" - cross-spawn "^6.0.5" - debug "^4.0.1" - doctrine "^3.0.0" - eslint-scope "^4.0.3" - eslint-utils "^1.3.1" - eslint-visitor-keys "^1.0.0" - espree "^5.0.1" - esquery "^1.0.1" - esutils "^2.0.2" - file-entry-cache "^5.0.1" - functional-red-black-tree "^1.0.1" - glob "^7.1.2" - globals "^11.7.0" - ignore "^4.0.6" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - inquirer "^6.2.2" - js-yaml "^3.13.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.3.0" - lodash "^4.17.11" - minimatch "^3.0.4" - mkdirp "^0.5.1" - natural-compare "^1.4.0" - optionator "^0.8.2" - path-is-inside "^1.0.2" - progress "^2.0.0" - regexpp "^2.0.1" - semver "^5.5.1" - strip-ansi "^4.0.0" - strip-json-comments "^2.0.1" - table "^5.2.3" - text-table "^0.2.0" - -espree@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a" - integrity sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A== - dependencies: - acorn "^6.0.7" - acorn-jsx "^5.0.0" - eslint-visitor-keys "^1.0.0" - -esprima@^4.0.0, esprima@~4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.0.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.1.0, esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" - integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -eth-lib@0.2.8: - version "0.2.8" - resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.2.8.tgz#b194058bef4b220ad12ea497431d6cb6aa0623c8" - integrity sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw== - dependencies: - bn.js "^4.11.6" - elliptic "^6.4.0" - xhr-request-promise "^0.1.2" - -ethereum-bloom-filters@^1.0.6: - version "1.0.10" - resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz#3ca07f4aed698e75bd134584850260246a5fed8a" - integrity sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA== - dependencies: - js-sha3 "^0.8.0" - -ethereum-cryptography@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz#8d6143cfc3d74bf79bbd8edecdf29e4ae20dd191" - integrity sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ== - dependencies: - "@types/pbkdf2" "^3.0.0" - "@types/secp256k1" "^4.0.1" - blakejs "^1.1.0" - browserify-aes "^1.2.0" - bs58check "^2.1.2" - create-hash "^1.2.0" - create-hmac "^1.1.7" - hash.js "^1.1.7" - keccak "^3.0.0" - pbkdf2 "^3.0.17" - randombytes "^2.1.0" - safe-buffer "^5.1.2" - scrypt-js "^3.0.0" - secp256k1 "^4.0.1" - setimmediate "^1.0.5" - -ethereumjs-util@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.0.tgz#e2b43a30bfcdbcb432a4eb42bd5f2393209b3fd5" - integrity sha512-kR+vhu++mUDARrsMMhsjjzPduRVAeundLGXucGRHF3B4oEltOUspfgCVco4kckucj3FMlLaZHUl9n7/kdmr6Tw== - dependencies: - "@types/bn.js" "^5.1.0" - bn.js "^5.1.2" - create-hash "^1.1.2" - ethereum-cryptography "^0.1.3" - ethjs-util "0.1.6" - rlp "^2.2.4" - -ethjs-unit@0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" - integrity sha1-xmWSHkduh7ziqdWIpv4EBbLEFpk= - dependencies: - bn.js "4.11.6" - number-to-bn "1.7.0" - -ethjs-util@0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" - integrity sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w== - dependencies: - is-hex-prefixed "1.0.0" - strip-hex-prefix "1.0.0" - -evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - -execa@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50" - integrity sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw== - dependencies: - cross-spawn "^6.0.0" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - -extract-stack@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/extract-stack/-/extract-stack-2.0.0.tgz#11367bc865bfcd9bc0db3123e5edb57786f11f9b" - integrity sha512-AEo4zm+TenK7zQorGK1f9mJ8L14hnTDi2ZQPR+Mub1NX8zimka1mXpV5LpH8x9HoUmFSHZCfLHqWvp0Y4FxxzQ== - -fast-deep-equal@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-glob@^3.0.3, fast-glob@^3.1.1: - version "3.2.7" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" - integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - -fastq@^1.6.0: - version "1.11.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.1.tgz#5d8175aae17db61947f8b162cfc7f63264d22807" - integrity sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw== - dependencies: - reusify "^1.0.4" - -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= - dependencies: - escape-string-regexp "^1.0.5" - -file-entry-cache@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" - integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== - dependencies: - flat-cache "^2.0.1" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-yarn-workspace-root@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" - integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== - dependencies: - micromatch "^4.0.2" - -flat-cache@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" - integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== - dependencies: - flatted "^2.0.0" - rimraf "2.6.3" - write "1.0.3" - -flatted@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" - integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== - -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - -fs-extra@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-6.0.1.tgz#8abc128f7946e310135ddc93b98bddb410e7a34b" - integrity sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-extra@^8.1: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= - -get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= - -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - -github-slugger@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.3.0.tgz#9bd0a95c5efdfc46005e82a906ef8e2a059124c9" - integrity sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q== - dependencies: - emoji-regex ">=6.0.0 <=6.1.1" - -glob-parent@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob@^7.1.2, glob@^7.1.3, glob@^7.1.6: - version "7.1.7" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global@~4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" - integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== - dependencies: - min-document "^2.19.0" - process "^0.11.10" - -globals@^11.7.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globby@^10, globby@^10.0.1: - version "10.0.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" - integrity sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg== - dependencies: - "@types/glob" "^7.1.1" - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.0.3" - glob "^7.1.3" - ignore "^5.1.1" - merge2 "^1.2.3" - slash "^3.0.0" - -globby@^11.0.1: - version "11.0.4" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" - integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" - slash "^3.0.0" - -graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: - version "4.2.6" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" - integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hash-base@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" - integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== - dependencies: - inherits "^2.0.4" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - -hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - -hmac-drbg@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -hosted-git-info@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961" - integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg== - dependencies: - lru-cache "^6.0.0" - -http-call@^5.1.2: - version "5.3.0" - resolved "https://registry.yarnpkg.com/http-call/-/http-call-5.3.0.tgz#4ded815b13f423de176eb0942d69c43b25b148db" - integrity sha512-ahwimsC23ICE4kPl9xTBjKB4inbRaeLyZeRunC/1Jy/Z6X8tv22MEAjK+KBOMSVLaqXPTTmd8638waVIKLGx2w== - dependencies: - content-type "^1.0.4" - debug "^4.1.1" - is-retry-allowed "^1.1.0" - is-stream "^2.0.0" - parse-json "^4.0.0" - tunnel-agent "^0.6.0" - -hyperlinker@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e" - integrity sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ== - -iconv-lite@^0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ieee754@^1.1.13: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -ignore@^4.0.2, ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - -ignore@^5.1.1, ignore@^5.1.4: - version "5.1.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" - integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== - -import-fresh@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-modules@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/import-modules/-/import-modules-1.1.0.tgz#748db79c5cc42bb9701efab424f894e72600e9dc" - integrity sha1-dI23nFzEK7lwHvq0JPiU5yYA6dw= - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inquirer@^6.2.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" - integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== - dependencies: - ansi-escapes "^3.2.0" - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^2.0.0" - lodash "^4.17.12" - mute-stream "0.0.7" - run-async "^2.2.0" - rxjs "^6.4.0" - string-width "^2.1.0" - strip-ansi "^5.1.0" - through "^2.3.6" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= - -is-core-module@^2.2.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" - integrity sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg== - dependencies: - has "^1.0.3" - -is-docker@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-function@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08" - integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ== - -is-glob@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - -is-hex-prefixed@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" - integrity sha1-fY035q135dEnFIkTxXPggtd39VQ= - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-plain-obj@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - -is-retry-allowed@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" - integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== - -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-wsl@^2.1.1, is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -js-sha3@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" - integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.0, js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -json-parse-better-errors@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= - optionalDependencies: - graceful-fs "^4.1.6" - -keccak@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.1.tgz#ae30a0e94dbe43414f741375cff6d64c8bea0bff" - integrity sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA== - dependencies: - node-addon-api "^2.0.0" - node-gyp-build "^4.2.0" - -levn@^0.3.0, levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - -libsodium-wrappers@^0.7.6: - version "0.7.9" - resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.9.tgz#4ffc2b69b8f7c7c7c5594a93a4803f80f6d0f346" - integrity sha512-9HaAeBGk1nKTRFRHkt7nzxqCvnkWTjn1pdjKgcUnZxj0FyOP4CnhgFhMdrFfgNsukijBGyBLpP2m2uKT1vuWhQ== - dependencies: - libsodium "^0.7.0" - -libsodium@^0.7.0: - version "0.7.9" - resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.9.tgz#4bb7bcbf662ddd920d8795c227ae25bbbfa3821b" - integrity sha512-gfeADtR4D/CM0oRUviKBViMGXZDgnFdMKMzHsvBdqLBHd9ySi6EtYnmuhHVDDYgYpAO8eU8hEY+F8vIUAPh08A== - -lines-and-columns@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" - integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= - -load-json-file@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-6.2.0.tgz#5c7770b42cafa97074ca2848707c61662f4251a1" - integrity sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ== - dependencies: - graceful-fs "^4.1.15" - parse-json "^5.0.0" - strip-bom "^4.0.0" - type-fest "^0.6.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= - -lodash.camelcase@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= - -lodash.get@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= - -lodash.kebabcase@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" - integrity sha1-hImxyw0p/4gZXM7KRI/21swpXDY= - -lodash.snakecase@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" - integrity sha1-OdcUo1NXFHg3rv1ktdy7Fr7Nj40= - -lodash.template@^4.4.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" - integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.templatesettings "^4.0.0" - -lodash.templatesettings@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" - integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== - dependencies: - lodash._reinterpolate "^3.0.0" - -lodash.upperfirst@^4.2.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" - integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984= - -lodash.zip@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.zip/-/lodash.zip-4.2.0.tgz#ec6662e4896408ed4ab6c542a3990b72cc080020" - integrity sha1-7GZi5IlkCO1KtsVCo5kLcswIACA= - -lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -make-dir@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -make-error@^1.1.1: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -md5.js@^1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" - integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -merge2@^1.2.3, merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -merkletreejs@^0.2.23: - version "0.2.24" - resolved "https://registry.yarnpkg.com/merkletreejs/-/merkletreejs-0.2.24.tgz#6dc52b3e0946846c25816216f1b60094a18a5e7a" - integrity sha512-JUv2zSFuTpMj9uxqNXAOAQz6LKXL/AUalyuDzvqyf0fV09VeU7WjNDMDD+wbdtrA1mNEbV5w1XDWXMud8aNYTg== - dependencies: - bignumber.js "^9.0.1" - buffer-reverse "^1.0.1" - crypto-js "^3.1.9-1" - treeify "^1.1.0" - web3-utils "^1.3.4" - -micromatch@^4.0.2, micromatch@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== - dependencies: - braces "^3.0.1" - picomatch "^2.2.3" - -mimic-fn@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" - integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== - -mimic-response@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - -min-document@^2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" - integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU= - dependencies: - dom-walk "^0.1.0" - -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== - -mkdirp-classic@^0.5.2: - version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== - -mkdirp@^0.5.1: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -mute-stream@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" - integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= - -natural-orderby@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/natural-orderby/-/natural-orderby-2.0.3.tgz#8623bc518ba162f8ff1cdb8941d74deb0fdcc016" - integrity sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q== - -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - -node-addon-api@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" - integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== - -node-gyp-build@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739" - integrity sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg== - -normalize-package-data@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.2.tgz#cae5c410ae2434f9a6c1baa65d5bc3b9366c8699" - integrity sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg== - dependencies: - hosted-git-info "^4.0.1" - resolve "^1.20.0" - semver "^7.3.4" - validate-npm-package-license "^3.0.1" - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" - -number-to-bn@1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" - integrity sha1-uzYjWS9+X54AMLGXe9QaDFP+HqA= - dependencies: - bn.js "4.11.6" - strip-hex-prefix "1.0.0" - -object-assign@^4.1.0, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object-treeify@^1.1.4: - version "1.1.33" - resolved "https://registry.yarnpkg.com/object-treeify/-/object-treeify-1.1.33.tgz#f06fece986830a3cba78ddd32d4c11d1f76cdf40" - integrity sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A== - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= - dependencies: - mimic-fn "^1.0.0" - -optionator@^0.8.2: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-headers@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.3.tgz#5e8e7512383d140ba02f0c7aa9f49b4399c92515" - integrity sha512-QhhZ+DCCit2Coi2vmAKbq5RGTRcQUOE2+REgv8vdyu7MnYx2eZztegqtTx99TZ86GTIwqiy3+4nQTWZ2tgmdCA== - -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - -parse-json@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -password-prompt@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/password-prompt/-/password-prompt-1.1.2.tgz#85b2f93896c5bd9e9f2d6ff0627fa5af3dc00923" - integrity sha512-bpuBhROdrhuN3E7G/koAju0WjVw9/uQOG5Co5mokNj0MiOSBVZS1JTwM4zl55hu0WFmIEFvO9cU9sJQiBIYeIA== - dependencies: - ansi-escapes "^3.1.0" - cross-spawn "^6.0.5" - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-is-inside@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= - -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - -path-parse@^1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -pbkdf2@^3.0.17, pbkdf2@^3.0.9: - version "3.1.2" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" - integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== - dependencies: - create-hash "^1.1.2" - create-hmac "^1.1.4" - ripemd160 "^2.0.1" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -picomatch@^2.2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= - -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -qqjs@^0.3.10: - version "0.3.11" - resolved "https://registry.yarnpkg.com/qqjs/-/qqjs-0.3.11.tgz#795b9f7d00807d75c391b1241b5be3077143d9ea" - integrity sha512-pB2X5AduTl78J+xRSxQiEmga1jQV0j43jOPs/MTgTLApGFEOn6NgdE2dEjp7nvDtjkIOZbvFIojAiYUx6ep3zg== - dependencies: - chalk "^2.4.1" - debug "^4.1.1" - execa "^0.10.0" - fs-extra "^6.0.1" - get-stream "^5.1.0" - glob "^7.1.2" - globby "^10.0.1" - http-call "^5.1.2" - load-json-file "^6.2.0" - pkg-dir "^4.2.0" - tar-fs "^2.0.0" - tmp "^0.1.0" - write-json-file "^4.1.1" - -query-string@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" - integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== - dependencies: - decode-uri-component "^0.2.0" - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -ramda@^0.26.1: - version "0.26.1" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" - integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ== - -randombytes@^2.0.1, randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readonly-date@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/readonly-date/-/readonly-date-1.0.0.tgz#5af785464d8c7d7c40b9d738cbde8c646f97dcd9" - integrity sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ== - -redeyed@~2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-2.1.1.tgz#8984b5815d99cb220469c99eeeffe38913e6cc0b" - integrity sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs= - dependencies: - esprima "~4.0.0" - -regexpp@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" - integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== - -regexpp@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve@^1.20.0, resolve@^1.8.1: - version "1.20.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" - integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== - dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" - -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= - dependencies: - onetime "^2.0.0" - signal-exit "^3.0.2" - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rimraf@2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" - -rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -rlp@^2.2.4: - version "2.2.6" - resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.6.tgz#c80ba6266ac7a483ef1e69e8e2f056656de2fb2c" - integrity sha512-HAfAmL6SDYNWPUOJNrM500x4Thn4PZsEy5pijPh40U9WfNk0z15hUYzO9xVIMAdIHdFtD8CBDHd75Td1g36Mjg== - dependencies: - bn.js "^4.11.1" - -run-async@^2.2.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -rxjs@^6.4.0: - version "6.6.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" - integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== - dependencies: - tslib "^1.9.0" - -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -scrypt-js@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" - integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== - -secp256k1@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.2.tgz#15dd57d0f0b9fdb54ac1fa1694f40e5e9a54f4a1" - integrity sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg== - dependencies: - elliptic "^6.5.2" - node-addon-api "^2.0.0" - node-gyp-build "^4.2.0" - -semver@^5.5.0, semver@^5.5.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -semver@^6.0.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^7.3.2, semver@^7.3.4: - version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== - dependencies: - lru-cache "^6.0.0" - -setimmediate@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= - -sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - -signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" - integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== - -simple-concat@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" - integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== - -simple-get@^2.7.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.1.tgz#0e22e91d4575d87620620bc91308d57a77f44b5d" - integrity sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw== - dependencies: - decompress-response "^3.3.0" - once "^1.3.1" - simple-concat "^1.0.0" - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -slice-ansi@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" - integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== - dependencies: - ansi-styles "^3.2.0" - astral-regex "^1.0.0" - is-fullwidth-code-point "^2.0.0" - -sort-keys@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-4.2.0.tgz#6b7638cee42c506fff8c1cecde7376d21315be18" - integrity sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg== - dependencies: - is-plain-obj "^2.0.0" - -source-map-support@^0.5.17: - version "0.5.19" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.9" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz#8a595135def9592bda69709474f1cbeea7c2467f" - integrity sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= - -string-width@^2.1.0, string-width@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string-width@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" - integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== - dependencies: - ansi-regex "^5.0.0" - -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - -strip-hex-prefix@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" - integrity sha1-DF8VX+8RUTczd96du1iNoFUA428= - dependencies: - is-hex-prefixed "1.0.0" - -strip-json-comments@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.0.0, supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.1.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-hyperlinks@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" - integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - -table@^5.2.3: - version "5.4.6" - resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" - integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== - dependencies: - ajv "^6.10.2" - lodash "^4.17.14" - slice-ansi "^2.1.0" - string-width "^3.0.0" - -tar-fs@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" - integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.1.4" - -tar-stream@^2.1.4: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== - dependencies: - bl "^4.0.3" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= - -through@^2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= - -timed-out@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= - -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -tmp@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" - integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw== - dependencies: - rimraf "^2.6.3" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -treeify@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" - integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A== - -ts-node@^8: - version "8.10.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" - integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== - dependencies: - arg "^4.1.0" - diff "^4.0.1" - make-error "^1.1.1" - source-map-support "^0.5.17" - yn "3.1.1" - -tslib@^1, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tslib@^2.0.0, tslib@^2.0.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" - integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== - -tsutils@^3.17.1: - version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= - dependencies: - prelude-ls "~1.1.2" - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" - integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== - -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - -typescript@^3.3: - version "3.9.10" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" - integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== - -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -url-set-query@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/url-set-query/-/url-set-query-1.0.0.tgz#016e8cfd7c20ee05cafe7795e892bd0702faa339" - integrity sha1-AW6M/Xwg7gXK/neV6JK9BwL6ozk= - -utf8@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" - integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -web3-utils@^1.3.4: - version "1.5.0" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.5.0.tgz#48c8ba0d95694e73b9a6d473d955880cd4758e4a" - integrity sha512-hNyw7Oxi6TM3ivXmv4hK5Cvyi9ML3UoKtcCYvLF9woPWh5v2dwCCVO1U3Iq5HHK7Dqq28t1d4CxWHqUfOfAkgg== - dependencies: - bn.js "^4.11.9" - eth-lib "0.2.8" - ethereum-bloom-filters "^1.0.6" - ethjs-unit "0.1.6" - number-to-bn "1.7.0" - randombytes "^2.1.0" - utf8 "3.0.0" - -which@^1.2.9: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== - dependencies: - string-width "^4.0.0" - -word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - -wrap-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-4.0.0.tgz#b3570d7c70156159a2d42be5cc942e957f7b1131" - integrity sha512-uMTsj9rDb0/7kk1PbcbCcwvHUxp60fGDB/NNXpVa0Q+ic/e7y5+BwTxKfQ33VYgDppSwi/FBzpetYzo8s6tfbg== - dependencies: - ansi-styles "^3.2.0" - string-width "^2.1.1" - strip-ansi "^4.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -write-json-file@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-4.3.0.tgz#908493d6fd23225344af324016e4ca8f702dd12d" - integrity sha512-PxiShnxf0IlnQuMYOPPhPkhExoCQuTUNPOa/2JWCYTmBquU9njyyDuwRKN26IZBlp4yn1nt+Agh2HOOBl+55HQ== - dependencies: - detect-indent "^6.0.0" - graceful-fs "^4.1.15" - is-plain-obj "^2.0.0" - make-dir "^3.0.0" - sort-keys "^4.0.0" - write-file-atomic "^3.0.0" - -write@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" - integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== - dependencies: - mkdirp "^0.5.1" - -xhr-request-promise@^0.1.2: - version "0.1.3" - resolved "https://registry.yarnpkg.com/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz#2d5f4b16d8c6c893be97f1a62b0ed4cf3ca5f96c" - integrity sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg== - dependencies: - xhr-request "^1.1.0" - -xhr-request@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/xhr-request/-/xhr-request-1.1.0.tgz#f4a7c1868b9f198723444d82dcae317643f2e2ed" - integrity sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA== - dependencies: - buffer-to-arraybuffer "^0.0.5" - object-assign "^4.1.1" - query-string "^5.0.1" - simple-get "^2.7.0" - timed-out "^4.0.1" - url-set-query "^1.0.0" - xhr "^2.0.4" - -xhr@^2.0.4: - version "2.6.0" - resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.6.0.tgz#b69d4395e792b4173d6b7df077f0fc5e4e2b249d" - integrity sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA== - dependencies: - global "~4.4.0" - is-function "^1.0.1" - parse-headers "^2.0.0" - xtend "^4.0.0" - -xtend@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== diff --git a/contracts/headstash-contract/src/contract.rs b/contracts/headstash-contract/src/contract.rs deleted file mode 100644 index 8f9409f..0000000 --- a/contracts/headstash-contract/src/contract.rs +++ /dev/null @@ -1,353 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - attr, coin,to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, - Uint128, BankMsg, CosmosMsg, DistributionMsg, -}; -use cw2::{get_contract_version, set_contract_version}; -use crate::contract::validation::validate_claim; -use sha2::Digest; -use std::convert::TryInto; - -use crate::error::ContractError; -use crate::msg::{ - ConfigResponse, ExecuteMsg, InstantiateMsg, IsClaimedResponse, - MerkleRootResponse, MigrateMsg, QueryMsg, TotalClaimedResponse, -}; -use crate::state::{ - Config, NATIVE_FEE_DENOM, NATIVE_BOND_DENOM, CLAIM, CONFIG, MERKLE_ROOT, AMOUNT_CLAIMED, - PAUSED, AMOUNT -}; - -// Version info, for migration info -const CONTRACT_NAME: &str = "crates.io:headstash-contract"; -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> Result { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - // set paused state to false - PAUSED.save(deps.storage, &false)?; - - // define config - let config = Config { - owner: info.sender, - claim_msg_plaintext: msg.claim_msg_plaintext, - }; - CONFIG.save(deps.storage, &config)?; - - // check merkle root length - let mut root_buf: [u8; 32] = [0; 32]; - hex::decode_to_slice(&msg.merkle_root, &mut root_buf)?; - - MERKLE_ROOT.save(deps.storage, &msg.merkle_root)?; - - // save total airdropped amount - let amount = msg.total_amount.unwrap_or_else(Uint128::zero); - AMOUNT.save(deps.storage, &amount)?; - AMOUNT_CLAIMED.save(deps.storage, &Uint128::zero())?; - - Ok(Response::new().add_attributes(vec![ - attr("action", "instantiate"), - attr("merkle_root", msg.merkle_root), - attr("total_amount", amount), - ])) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::Claim { amount, eth_pubkey, eth_sig , proof } => execute_claim(deps, env, info, amount, eth_pubkey, eth_sig, proof ), - ExecuteMsg::ClawBack { recipient } => {execute_clawback(deps, env, info, Some(recipient))}, - ExecuteMsg::Pause {} => execute_pause(deps, env, info), - ExecuteMsg::Resume {} => execute_resume(deps,env,info) - } -} - -pub fn execute_claim( - deps: DepsMut, - _env: Env, - info: MessageInfo, - amount: Uint128, - eth_pubkey: String, - eth_sig: String, - proof: Vec, -) -> Result { - - let is_paused = PAUSED.load(deps.storage)?; - if is_paused { - return Err(ContractError::Paused {}); - } - - // verify not claimed - let claimed = CLAIM.may_load(deps.storage, eth_pubkey.clone())?; - if claimed.is_some() { - return Err(ContractError::Claimed {}); - } - - // verify merkle root - let config = CONFIG.load(deps.storage)?; - let merkle_root = MERKLE_ROOT.load(deps.storage)?; - - // validate the eth_sig was generated with the eth_pubkey provided - validate_claim( &deps, - info.clone(), - eth_pubkey.clone(), - eth_sig, - config.clone(), - )?; - - // generate merkleTree leaf with eth_pubkey & amount - let user_input = format!("{}{}", eth_pubkey, amount); - let hash = sha2::Sha256::digest(user_input.as_bytes()) - .as_slice() - .try_into() - .map_err(|_| ContractError::WrongLength {})?; - - let hash = proof.into_iter().try_fold(hash, |hash, p| { - let mut proof_buf = [0; 32]; - hex::decode_to_slice(p, &mut proof_buf)?; - let mut hashes = [hash, proof_buf]; - hashes.sort_unstable(); - sha2::Sha256::digest(&hashes.concat()) - .as_slice() - .try_into() - .map_err(|_| ContractError::WrongLength {}) - })?; - - let mut root_buf: [u8; 32] = [0; 32]; - hex::decode_to_slice(merkle_root, &mut root_buf)?; - if root_buf != hash { - return Err(ContractError::VerificationFailed {}); - } - - // update claim index - CLAIM.save(deps.storage, eth_pubkey, &true)?; - - // Update total claimed to reflect - let mut claimed_amount = AMOUNT_CLAIMED.load(deps.storage)?; - claimed_amount += amount; - AMOUNT_CLAIMED.save(deps.storage, &claimed_amount)?; - - let bank_msg = CosmosMsg::Bank(BankMsg::Send { - to_address: info.sender.to_string(), - amount: vec![coin(amount.u128(), NATIVE_BOND_DENOM), - coin(amount.u128(), NATIVE_FEE_DENOM),], - }); - - let res = Response::new() - .add_message(bank_msg) - .add_attributes(vec![ - attr("action", "claim"), - attr("address", info.sender), - attr("amount", amount), - ]); - Ok(res) -} - -pub fn execute_clawback( - deps: DepsMut, - _env: Env, - info: MessageInfo, - _recipient: Option, -) -> Result { - let cfg = CONFIG.load(deps.storage)?; - - // authorize owner - if info.sender != cfg.owner { - return Err(ContractError::Unauthorized {}) - } - - // error if contract is paused - let is_paused = PAUSED.load(deps.storage)?; - if is_paused { - return Err(ContractError::Paused {}); - } - - let claimed = AMOUNT_CLAIMED.load(deps.storage)?; - let total_amount = AMOUNT.load(deps.storage)?; - // get balance - let balance_to_burn = total_amount.checked_sub(claimed)?; - - // clawback to community pool - let clawback_msg = CosmosMsg::Distribution(DistributionMsg::FundCommunityPool { - amount: vec![coin(balance_to_burn.u128(), NATIVE_BOND_DENOM), - coin(balance_to_burn.u128(), NATIVE_FEE_DENOM),], - }); - - // Burn the tokens and response - let mut res = Response::new().add_attribute("action", "clawback"); - - res = res - .add_message(clawback_msg) - .add_attributes(vec![ - attr("amount", balance_to_burn), - ]); - - Ok(res) -} - -pub fn execute_pause( - deps: DepsMut, - _env: Env, - info: MessageInfo, -) -> Result { - let cfg = CONFIG.load(deps.storage)?; - if info.sender != cfg.owner { - return Err(ContractError::Unauthorized {}); - } - - PAUSED.save(deps.storage, &true)?; - Ok(Response::new().add_attributes(vec![attr("action", "pause"), attr("paused", "true")])) -} - -pub fn execute_resume( - deps: DepsMut, - _env: Env, - info: MessageInfo, -) -> Result { - // authorize owner - let cfg = CONFIG.load(deps.storage)?; - if info.sender != cfg.owner { - return Err(ContractError::Unauthorized {}); - } - - let is_paused = PAUSED.load(deps.storage)?; - if !is_paused { - return Err(ContractError::NotPaused {}); - } - - PAUSED.save(deps.storage, &false)?; - Ok(Response::new().add_attributes(vec![attr("action", "resume"), attr("paused", "false")])) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_json_binary(&query_config(deps)?), - QueryMsg::MerkleRoot {} => to_json_binary(&query_merkle_root(deps)?), - QueryMsg::IsClaimed { address } => { - to_json_binary(&query_is_claimed(deps, address)?) - } - QueryMsg::TotalClaimed {} => to_json_binary(&query_total_claimed(deps)?), - } -} - -pub fn query_config(deps: Deps) -> StdResult { - let cfg = CONFIG.load(deps.storage)?; - Ok(ConfigResponse { - owner: Some(cfg.owner.to_string()), - claim_msg_plaintext: cfg.claim_msg_plaintext.to_string(), - }) -} - -pub fn query_merkle_root(deps: Deps) -> StdResult { - let merkle_root = MERKLE_ROOT.load(deps.storage)?; - let total_amount = AMOUNT.load(deps.storage)?; - - let resp = MerkleRootResponse { - merkle_root, - total_amount, - }; - - Ok(resp) -} - -pub fn query_is_claimed(deps: Deps, address: String) -> StdResult { - let is_claimed = CLAIM.may_load(deps.storage, address)?.unwrap_or(false); - let resp = IsClaimedResponse { is_claimed }; - - Ok(resp) -} - -pub fn query_total_claimed(deps: Deps) -> StdResult { - let total_claimed = AMOUNT_CLAIMED.load(deps.storage)?; - let resp = TotalClaimedResponse { total_claimed }; - - Ok(resp) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - let version = get_contract_version(deps.storage)?; - if version.contract != CONTRACT_NAME { - return Err(ContractError::CannotMigrate { - previous_contract: version.contract, - }); - } - Ok(Response::default()) -} - -// src: https://github.com/public-awesome/launchpad/blob/main/contracts/sg-eth-airdrop/src/claim_airdrop.rs#L85 -mod validation { - use super::*; - use cosmwasm_std::StdError; - use ethereum_verify::verify_ethereum_text; - - use crate::state::Config; - - pub fn compute_plaintext_msg(config: &Config, info: MessageInfo) -> String { - str::replace( - &config.claim_msg_plaintext, - "{wallet}", - info.sender.as_ref(), - ) - } - - pub fn validate_claim( - deps: &DepsMut, - info: MessageInfo, - eth_pubkey: String, - eth_sig: String, - config: Config, - ) -> Result<(), ContractError> { - validate_eth_sig(deps, info, eth_pubkey.clone(), eth_sig, config)?; - Ok(()) - } - - fn validate_eth_sig( - deps: &DepsMut, - info: MessageInfo, - eth_pubkey: String, - eth_sig: String, - config: Config, - ) -> Result<(), ContractError> { - let valid_eth_sig = - validate_ethereum_text(deps, info, &config, eth_sig, eth_pubkey.clone())?; - match valid_eth_sig { - true => Ok(()), - false => Err(ContractError::AddressNotEligible { - eth_pubkey: eth_pubkey, - }), - } - } - - pub fn validate_ethereum_text( - deps: &DepsMut, - info: MessageInfo, - config: &Config, - eth_sig: String, - eth_pubkey: String, - ) -> StdResult { - let plaintext_msg = compute_plaintext_msg(config, info); - match hex::decode(eth_sig.clone()) { - Ok(eth_sig_hex) => { - verify_ethereum_text(deps.as_ref(), &plaintext_msg, ð_sig_hex, ð_pubkey) - } - Err(_) => Err(StdError::InvalidHex { - msg: format!("Could not decode {eth_sig}"), - }), - } - } -} diff --git a/contracts/headstash-contract/src/error.rs b/contracts/headstash-contract/src/error.rs deleted file mode 100644 index 9cf4209..0000000 --- a/contracts/headstash-contract/src/error.rs +++ /dev/null @@ -1,62 +0,0 @@ -use cosmwasm_std::{OverflowError, StdError}; -use hex::FromHexError; -use thiserror::Error; - -#[derive(Error, Debug, PartialEq)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("{0}")] - Hex(#[from] FromHexError), - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("Already claimed")] - Claimed {}, - - #[error("Wrong length")] - WrongLength {}, - - #[error("Verification failed")] - VerificationFailed {}, - - #[error("Cannot migrate from different contract type: {previous_contract}")] - CannotMigrate { previous_contract: String }, - - #[error("Invalid input")] - InvalidInput {}, - - #[error("Airdrop expired at {expiration}")] - Expired { expiration: u64 }, - - - #[error("Address {eth_pubkey} is not eligible")] - AddressNotEligible { eth_pubkey: String }, - - #[error("withdraw_all is unavailable, it will become available at {available_at}")] - WithdrawAllUnavailable { available_at: u64 }, - - #[error("Airdrop begins at {start}")] - NotBegun { start: u64 }, - - #[error("Airdrop is paused")] - Paused {}, - - #[error("Airdrop is not paused")] - NotPaused {}, - - #[error("Semver parsing error: {0}")] - SemVer(String), - - #[error("Airdrop has not yet expired, it will become available at {available_at}")] - ClawBackUnavailable {available_at: u64}, -} - - -impl From for ContractError { - fn from(err: OverflowError) -> Self { - ContractError::Std(err.into()) - } -} diff --git a/contracts/headstash-contract/src/lib.rs b/contracts/headstash-contract/src/lib.rs deleted file mode 100644 index e6e9265..0000000 --- a/contracts/headstash-contract/src/lib.rs +++ /dev/null @@ -1,28 +0,0 @@ -//#![warn(missing_docs)] -#![doc(html_logo_url = "../../../uml/logo.png")] -//! # WYND Vesting Airdrop -//! -//! ## Description -//! -//! We need a project that allow us to launch the WYND TOKEN and distribute to our community in a vested way. -//! -//! ## Objectives -//! -//! The main goal of the **WYND airdrop** is to: -//! - Distribute WYND TOKEN allowing community members to claim it. -//! - Vest the airdrop tokens to avoid sell presure and make the price of the token more stable at release -//! - -/// Main vesting-airdrop Module -pub mod contract; - -/// custom error handler -mod error; - -/// custom input output messages -pub mod msg; - -/// state on the blockchain -pub mod state; - -pub use crate::error::ContractError; diff --git a/contracts/headstash-contract/src/msg.rs b/contracts/headstash-contract/src/msg.rs deleted file mode 100644 index 8fc34d4..0000000 --- a/contracts/headstash-contract/src/msg.rs +++ /dev/null @@ -1,74 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use cosmwasm_std::Uint128; - -#[derive(Serialize, Deserialize, JsonSchema)] -pub struct InstantiateMsg { - /// Owner if none set to info.sender. - pub owner: Option, - /// {address} - pub claim_msg_plaintext: String, - /// merkle root - pub merkle_root: String, - /// total amount - pub total_amount: Option, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - /// Claim does not check if contract has enough funds, owner must ensure it. - Claim { - amount: Uint128, - /// pubkey (0x...) - eth_pubkey: String, - /// signed by pubkey - eth_sig: String, - /// Proof is hex-encoded merkle proof. - proof: Vec, - }, - /// Recycle the remaining tokens to specified address after expire time (only owner). - /// Don't use Option to avoid typo turning ClawBack into Burn - ClawBack { recipient: String }, - Pause {}, - Resume {}, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - Config {}, - MerkleRoot {}, - IsClaimed { address: String }, - TotalClaimed {}, -} - -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] -#[serde(rename_all = "snake_case")] -pub struct ConfigResponse { - pub owner: Option, - pub claim_msg_plaintext: String, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct MerkleRootResponse { - /// MerkleRoot is hex-encoded merkle root. - pub merkle_root: String, - pub total_amount: Uint128, -} - - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct IsClaimedResponse { - pub is_claimed: bool, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct TotalClaimedResponse { - pub total_claimed: Uint128, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct MigrateMsg {} - - diff --git a/contracts/headstash-contract/src/state.rs b/contracts/headstash-contract/src/state.rs deleted file mode 100644 index fe78621..0000000 --- a/contracts/headstash-contract/src/state.rs +++ /dev/null @@ -1,36 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{Addr, Uint128}; -use cw_storage_plus::{Item, Map}; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct Config { - /// Owner If None set, contract is frozen. - pub owner: Addr, - pub claim_msg_plaintext: String, -} -pub const CONFIG: Item = Item::new("config"); - -pub const NATIVE_FEE_DENOM: &str = "uterp"; -pub const NATIVE_BOND_DENOM: &str = "uthiol"; - -// saves external network airdrop accounts -pub const ACCOUNT_MAP_KEY: &str = "account_map"; -// external_address -> host_address -pub const ACCOUNT_MAP: Map = Map::new(ACCOUNT_MAP_KEY); - -pub const MERKLE_ROOT_PREFIX: &str = "merkle_root"; -pub const MERKLE_ROOT: Item = Item::new(MERKLE_ROOT_PREFIX); - -pub const AMOUNT_KEY: &str = "amount"; -pub const AMOUNT: Item = Item::new(AMOUNT_KEY); - -pub const CLAIM_PREFIX: &str = "claim"; -pub const CLAIM: Map = Map::new(CLAIM_PREFIX); - -pub const AMOUNT_CLAIMED_KEY: &str = "claimed_amount"; -pub const AMOUNT_CLAIMED: Item = Item::new(AMOUNT_CLAIMED_KEY); - -pub const PAUSED_KEY: &str = "paused"; -pub const PAUSED: Item = Item::new(PAUSED_KEY); \ No newline at end of file diff --git a/contracts/headstash-contract/testdata/airdrop_stage_1_list.json b/contracts/headstash-contract/testdata/airdrop_stage_1_list.json deleted file mode 100644 index 08368d7..0000000 --- a/contracts/headstash-contract/testdata/airdrop_stage_1_list.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { "address": "wasm1k9hwzxs889jpvd7env8z49gad3a3633vg350tq", "amount": "100"}, - { "address": "wasm1uy9ucvgerneekxpnfwyfnpxvlsx5dzdpf0mzjd", "amount": "1010"}, - { "address": "wasm1a4x6au55s0fusctyj2ulrxvfpmjcxa92k7ze2v", "amount": "10220"}, - { "address": "wasm1ylna88nach9sn5n7qe7u5l6lh7dmt6lp2y63xx", "amount": "10333"}, - { "address": "wasm1qzy8rg0f406uvvl54dlww6ptlh30303xq2u3xu", "amount": "10220"}, - { "address": "wasm1xn46zz5m3fhymcrcwe82m0ac8ytt588dkpaeas", "amount": "10220"} -] - diff --git a/contracts/headstash-contract/testdata/airdrop_stage_1_test_data.json b/contracts/headstash-contract/testdata/airdrop_stage_1_test_data.json deleted file mode 100644 index 2c8fa8c..0000000 --- a/contracts/headstash-contract/testdata/airdrop_stage_1_test_data.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "account": "wasm1k9hwzxs889jpvd7env8z49gad3a3633vg350tq", - "amount": "100", - "root": "b45c1ea28b26adb13e412933c9e055b01fdf7585304b00cd8f1cb220aa6c5e88", - "proofs": [ - "a714186eaedddde26b08b9afda38cf62fdf88d68e3aa0d5a4b55033487fe14a1", - "fb57090a813128eeb953a4210dd64ee73d2632b8158231effe2f0a18b2d3b5dd", - "c30992d264c74c58b636a31098c6c27a5fc08b3f61b7eafe2a33dcb445822343" - ] -} diff --git a/contracts/headstash-contract/testdata/airdrop_stage_1_test_multi_data.json b/contracts/headstash-contract/testdata/airdrop_stage_1_test_multi_data.json deleted file mode 100644 index 128d0a7..0000000 --- a/contracts/headstash-contract/testdata/airdrop_stage_1_test_multi_data.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "total_amount": "42103", - "total_claimed_amount": "21663", - "root": "b45c1ea28b26adb13e412933c9e055b01fdf7585304b00cd8f1cb220aa6c5e88", - "accounts": [ - { - "account": "wasm1k9hwzxs889jpvd7env8z49gad3a3633vg350tq", - "amount": "100", - "proofs": [ - "a714186eaedddde26b08b9afda38cf62fdf88d68e3aa0d5a4b55033487fe14a1", - "fb57090a813128eeb953a4210dd64ee73d2632b8158231effe2f0a18b2d3b5dd", - "c30992d264c74c58b636a31098c6c27a5fc08b3f61b7eafe2a33dcb445822343" - ] - }, - { - "account": "wasm1uy9ucvgerneekxpnfwyfnpxvlsx5dzdpf0mzjd", - "amount": "1010", - "proofs": [ - "d496b14f0a6207db1c9a1be70d5f3684d3c76f27c0bc75ee979f3e2a71a97ed0", - "e3746c7f0e1d1f60708f9e5facaaee77424a8c5f6527f1813f60e8c3755d3b5d", - "c30992d264c74c58b636a31098c6c27a5fc08b3f61b7eafe2a33dcb445822343" - ] - }, - { - "account": "wasm1a4x6au55s0fusctyj2ulrxvfpmjcxa92k7ze2v", - "amount": "10220", - "proofs": [ - "b69c5239d434753af2f6c3eab47f4e78c436f862f14e6989be5c9027c2b6dfe2", - "e3746c7f0e1d1f60708f9e5facaaee77424a8c5f6527f1813f60e8c3755d3b5d", - "c30992d264c74c58b636a31098c6c27a5fc08b3f61b7eafe2a33dcb445822343" - ] - }, - { - "account": "wasm1ylna88nach9sn5n7qe7u5l6lh7dmt6lp2y63xx", - "amount": "10333", - "proofs": [ - "f89c4ec6a98e26fb5690e50e16e189f9942f0576a5ba711ed75fe01140ddb2af", - "374f1a32b0a5d5dab16f8fbed8c248e183448732f897002375e0d4ca6e13ad73" - ] - } - ] -} \ No newline at end of file diff --git a/contracts/headstash-contract/testdata/airdrop_stage_2_list.json b/contracts/headstash-contract/testdata/airdrop_stage_2_list.json deleted file mode 100644 index 74437e3..0000000 --- a/contracts/headstash-contract/testdata/airdrop_stage_2_list.json +++ /dev/null @@ -1,13 +0,0 @@ -[ - { "address": "wasm1k9hwzxs889jpvd7env8z49gad3a3633vg350tq", "amount": "666666666"}, - { "address": "wasm1uy9ucvgerneekxpnfwyfnpxvlsx5dzdpf0mzjd", "amount": "1010"}, - { "address": "wasm1a4x6au55s0fusctyj2ulrxvfpmjcxa92k7ze2v", "amount": "999"}, - { "address": "wasm1ylna88nach9sn5n7qe7u5l6lh7dmt6lp2y63xx", "amount": "1000000000"}, - { "address": "wasm1qzy8rg0f406uvvl54dlww6ptlh30303xq2u3xu", "amount": "10220"}, - { "address": "wasm1c99d6aw39e027fmy5f2gj38g8p8c3cf0vn3qqn", "amount": "1322"}, - { "address": "wasm1uwcjkghqlz030r989clzqs8zlaujwyphx0yumy", "amount": "14"}, - { "address": "wasm1yggt0x0r3x5ujk96kfeps6v4yakgun8mdth90j", "amount": "9000000"}, - { "address": "wasm1f6s77fjplerjrh4yjj08msqdq36mam4xv9tjvs", "amount": "12333"}, - { "address": "wasm1xn46zz5m3fhymcrcwe82m0ac8ytt588dkpaeas", "amount": "1322"} -] - diff --git a/contracts/headstash-contract/testdata/airdrop_stage_2_test_data.json b/contracts/headstash-contract/testdata/airdrop_stage_2_test_data.json deleted file mode 100644 index 78646d8..0000000 --- a/contracts/headstash-contract/testdata/airdrop_stage_2_test_data.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "account": "wasm1uwcjkghqlz030r989clzqs8zlaujwyphx0yumy", - "amount": "14", - "root": "a5587bd4d158618b83badf57b1a4206f86e33407e18797ef690c931d73b36232", - "proofs": [ - "a714186eaedddde26b08b9afda38cf62fdf88d68e3aa0d5a4b55033487fe14a1", - "1eb08e61c40d5ba334f3c32f3f136e714f0841e5d53af6b78ec94e3b29a01e74", - "fe570ffb0015447c01bffdcd266fe4ee21a23eb6b499461b9ced5a03c6a9b2f0", - "fa0224da936bcebd0f018a46ba15a5a9fc2d637f72f7c14b31aeffd8964983b5" - ] -} \ No newline at end of file diff --git a/contracts/mock/mock_adapter/.cargo/config b/contracts/mock/mock_adapter/.cargo/config new file mode 100644 index 0000000..882fe08 --- /dev/null +++ b/contracts/mock/mock_adapter/.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/mock/mock_adapter/.circleci/config.yml b/contracts/mock/mock_adapter/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/contracts/mock/mock_adapter/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/mock/mock_adapter/Cargo.toml b/contracts/mock/mock_adapter/Cargo.toml new file mode 100644 index 0000000..64d7190 --- /dev/null +++ b/contracts/mock/mock_adapter/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "mock_adapter" +version = "0.1.0" +authors = [ + "Jack Swenson ", +] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ + "adapter", + "storage_plus", + "dao", + "snip20", +] } +cosmwasm-schema = { git = "https://github.com/CosmWasm/cosmwasm", commit = "1e05e7e" } +serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] } +schemars = "0.8.9" + +[dev-dependencies] +shade-multi-test = { path = "../../../packages/multi_test", features = [ + "dao", +] } diff --git a/contracts/mock/mock_adapter/Makefile b/contracts/mock/mock_adapter/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/contracts/mock/mock_adapter/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/mock/mock_adapter/README.md b/contracts/mock/mock_adapter/README.md new file mode 100644 index 0000000..8fad675 --- /dev/null +++ b/contracts/mock/mock_adapter/README.md @@ -0,0 +1,21 @@ +# Mock Band Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [User](#User) + * Queries + * [GetReferenceData](#GetReferenceData) +# Introduction +The Mocked Band contract is used to test locally when there is no official band contract available + +### Queries + +#### GetReferenceData +Get a hardcoded sample from an ETH query for testing locally +##### Response +```json +{ + "rate": "3119154999999000000000", + "last_updated_base": 1628548483, + "last_updated_quote": 3377610 +} +``` diff --git a/contracts/mock/mock_adapter/src/contract.rs b/contracts/mock/mock_adapter/src/contract.rs new file mode 100644 index 0000000..b174975 --- /dev/null +++ b/contracts/mock/mock_adapter/src/contract.rs @@ -0,0 +1,326 @@ +use cosmwasm_schema::cw_serde; +use shade_protocol::{ + c_std::{ + shd_entry_point, + to_binary, + Addr, + Binary, + Deps, + DepsMut, + Env, + MessageInfo, + Response, + StdError, + StdResult, + Uint128, + }, + contract_interfaces::dao::adapter, + snip20::helpers::{balance_query, register_receive, send_msg, set_viewing_key_msg}, + utils::{ + asset::Contract, + generic_response::ResponseStatus, + storage::plus::Item, + ExecuteCallback, + InstantiateCallback, + Query, + }, +}; + +#[cw_serde] +pub struct Config { + pub owner: Addr, + pub instant: bool, + pub token: Contract, +} + +impl InstantiateCallback for Config { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + Receive { + sender: Addr, + from: Addr, + amount: Uint128, + memo: Option, + msg: Option, + }, + GiveMeMoney { + amount: Uint128, + }, + CompleteUnbonding {}, + Adapter(adapter::SubExecuteMsg), +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryMsg { + Config, + Adapter(adapter::SubQueryMsg), +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + Config { config: Config }, + Adapter(adapter::SubQueryMsg), +} + +const VIEWING_KEY: &str = "jUsTfOrTeStInG"; + +const CONFIG: Item = Item::new("config"); +const ADDRESS: Item = Item::new("address"); +const REWARDS: Item = Item::new("rewards"); + +const UNBONDING: Item = Item::new("unbonding"); +const CLAIMABLE: Item = Item::new("claimable"); + +#[shd_entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: Config, +) -> StdResult { + CONFIG.save(deps.storage, &msg)?; + ADDRESS.save(deps.storage, &env.contract.address)?; + //BLOCK.save(deps.storage, &Uint128::new(env.block.height as u128))?; + + UNBONDING.save(deps.storage, &Uint128::zero())?; + CLAIMABLE.save(deps.storage, &Uint128::zero())?; + REWARDS.save(deps.storage, &Uint128::zero())?; + + Ok(Response::new().add_messages(vec![ + set_viewing_key_msg(VIEWING_KEY.to_string(), None, &msg.token.clone())?, + register_receive(env.contract.code_hash.clone(), None, &msg.token.clone())?, + ])) +} + +#[shd_entry_point] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + //BLOCK.save(deps.storage, &Uint128::new(env.block.height as u128))?; + + match msg { + ExecuteMsg::Receive { + sender: _, + from, + amount, + memo: _, + msg: _, + } => { + if info.sender != config.token.address { + return Err(StdError::generic_err("Unrecognized Asset")); + } + + // If sender is not manager, consider rewards + if from != config.owner { + let rew = REWARDS.load(deps.storage)?; + REWARDS.save(deps.storage, &(rew + amount))?; + } + + Ok(Response::new()) + } + ExecuteMsg::GiveMeMoney { amount } => Ok(Response::new().add_message(send_msg( + info.sender, + amount, + None, + None, + None, + &config.token, + )?)), + ExecuteMsg::CompleteUnbonding {} => { + let unbonding = UNBONDING.load(deps.storage)?; + let claimable = CLAIMABLE.load(deps.storage)?; + + UNBONDING.save(deps.storage, &Uint128::zero())?; + CLAIMABLE.save(deps.storage, &(claimable + unbonding))?; + Ok(Response::new()) + } + ExecuteMsg::Adapter(adapter) => match adapter { + adapter::SubExecuteMsg::Unbond { asset, amount } => { + if asset != config.token.address { + return Err(StdError::generic_err("Unrecognized Asset")); + } + + let balance = balance_query( + &deps.querier, + ADDRESS.load(deps.storage)?, + VIEWING_KEY.to_string(), + &config.token.clone(), + )?; + + //let rewards = REWARDS.load(deps.storage)?; + + let unbonding = UNBONDING.load(deps.storage)?; + let claimable = CLAIMABLE.load(deps.storage)?; + let rewards = REWARDS.load(deps.storage)?; + + let available = (balance + rewards) - (unbonding + claimable); + + if available < amount { + return Err(StdError::generic_err(format!( + "Cannot unbond {}, {} available", + amount, available + ))); + } + + let mut messages = vec![]; + + if config.instant { + messages.push(send_msg( + config.owner.clone(), + amount, + None, + None, + None, + &config.token.clone(), + )?); + } else { + UNBONDING.save(deps.storage, &(unbonding + amount))?; + } + + Ok(Response::new().add_messages(messages).set_data(to_binary( + &adapter::ExecuteAnswer::Unbond { + status: ResponseStatus::Success, + amount, + }, + )?)) + } + adapter::SubExecuteMsg::Claim { asset } => { + if asset != config.token.address { + return Err(StdError::generic_err("Unrecognized Asset")); + } + let claimable = CLAIMABLE.load(deps.storage)?; + CLAIMABLE.save(deps.storage, &Uint128::zero())?; + REWARDS.save(deps.storage, &Uint128::zero())?; + + Ok(Response::new() + .add_message(send_msg( + config.owner.clone(), + claimable, + None, + None, + None, + &config.token.clone(), + )?) + .set_data(to_binary(&adapter::ExecuteAnswer::Claim { + status: ResponseStatus::Success, + amount: claimable, + })?)) + } + adapter::SubExecuteMsg::Update { asset } => { + if asset != config.token.address { + return Err(StdError::generic_err("Unrecognized Asset")); + } + // 'claim & restake' rewards + REWARDS.save(deps.storage, &Uint128::zero())?; + + Ok( + Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Update { + status: ResponseStatus::Success, + })?), + ) + } + }, + } +} + +#[shd_entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + match msg { + QueryMsg::Config => to_binary(&QueryAnswer::Config { config }), + QueryMsg::Adapter(adapter) => to_binary(&match adapter { + adapter::SubQueryMsg::Balance { asset } => { + if asset != config.token.address { + return Err(StdError::generic_err("Unrecognized Asset")); + } + let balance = balance_query( + &deps.querier, + ADDRESS.load(deps.storage)?, + VIEWING_KEY.to_string(), + &config.token.clone(), + )?; + + adapter::QueryAnswer::Balance { amount: balance } + } + adapter::SubQueryMsg::Unbonding { asset } => { + if asset != config.token.address { + return Err(StdError::generic_err("Unrecognized Asset")); + } + adapter::QueryAnswer::Unbonding { + amount: UNBONDING.load(deps.storage)?, + } + } + adapter::SubQueryMsg::Claimable { asset } => { + if asset != config.token.address { + return Err(StdError::generic_err("Unrecognized Asset")); + } + + let _c = CLAIMABLE.load(deps.storage)?; + + adapter::QueryAnswer::Claimable { + amount: CLAIMABLE.load(deps.storage)?, + } + } + adapter::SubQueryMsg::Unbondable { asset } => { + if asset != config.token.address { + return Err(StdError::generic_err("Unrecognized Asset")); + } + let unbonding = UNBONDING.load(deps.storage)?; + let claimable = CLAIMABLE.load(deps.storage)?; + let balance = balance_query( + &deps.querier, + ADDRESS.load(deps.storage)?, + VIEWING_KEY.to_string(), + &config.token.clone(), + )?; + + adapter::QueryAnswer::Unbondable { + amount: balance - (unbonding + claimable), + } + } + adapter::SubQueryMsg::Reserves { asset } => { + if asset != config.token.address { + return Err(StdError::generic_err("Unrecognized Asset")); + } + + let reserves = match config.instant { + true => { + balance_query( + &deps.querier, + ADDRESS.load(deps.storage)?, + VIEWING_KEY.to_string(), + &config.token.clone(), + )? - (UNBONDING.load(deps.storage)? + CLAIMABLE.load(deps.storage)?) + } + false => { + let rewards = REWARDS.load(deps.storage)?; + let unbonding = UNBONDING.load(deps.storage)?; + if rewards > unbonding { + rewards - unbonding + } else { + Uint128::zero() + } + } + }; + + adapter::QueryAnswer::Reserves { amount: reserves } + } + }), + } +} diff --git a/contracts/mock/mock_adapter/src/execute.rs b/contracts/mock/mock_adapter/src/execute.rs new file mode 100644 index 0000000..f41fc38 --- /dev/null +++ b/contracts/mock/mock_adapter/src/execute.rs @@ -0,0 +1,200 @@ +use shade_protocol::c_std::{ + to_binary, + MessageInfo, + Api, + BalanceResponse, + BankQuery, + Binary, + Coin, + CosmosMsg, + Env, + DepsMut, + Response, + Addr, + Querier, + StakingMsg, + StdError, + StdResult, + Storage, + Uint128, + Validator, +}; + +use shade_protocol::snip20::helpers::{ + deposit_msg, + redeem_msg, + register_receive, + send_from_msg, + set_viewing_key_msg, +}; + +use shade_protocol::{ + contract_interfaces::{ + dao::{ + adapter, + rewards_emission::{Config, ExecuteAnswer, Reward}, + }, + snip20::helpers::{fetch_snip20, Snip20Asset}, + }, + utils::{ + asset::{scrt_balance, Contract}, + generic_response::ResponseStatus, + cycle::{Cycle, exceeds_cycle, utc_now, parse_utc_datetime}, + }, +}; + +use crate::{ + query, + storage::*, +}; + +pub fn receive( + deps: DepsMut, + env: Env, + info: MessageInfo, + _sender: Addr, + _from: Addr, + amount: Uint128, + _msg: Option, +) -> StdResult { + //TODO: forward to distributor (quick fix mechanism) + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Receive { + status: ResponseStatus::Success, + })?)) +} + +pub fn try_update_config( + deps: DepsMut, + env: Env, + info: MessageInfo, + config: Config, +) -> StdResult { + let cur_config = CONFIG.load(deps.storage)?; + + if !cur_config.admins.contains(&info.sender) { + return Err(StdError::generic_err("unauthorized")); + } + + CONFIG.save(deps.storage, &config)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { + status: ResponseStatus::Success, + })?)) +} + +pub fn refill_rewards( + deps: DepsMut, + env: Env, + info: MessageInfo, +) -> StdResult { + + let config = CONFIG.load(deps.storage)?; + let mut messages = vec![]; + + if let Some(mut reward) = REWARD.may_load(deps.storage, info.sender.clone())? { + + let token = TOKEN.load(deps.storage)?; + let now = utc_now(&env); + + // Check expiration + if let Some(expiry) = reward.expiration.clone() { + if now > parse_utc_datetime(&expiry)? { + return Err(StdError::generic_err(format!("Rewards expired on {}", expiry))); + } + } + + if exceeds_cycle(&now, &parse_utc_datetime(&reward.last_refresh.clone())?, reward.cycle.clone()) { + reward.last_refresh = now.to_rfc3339(); + REWARD.save(deps.storage, info.sender, &reward)?; + // Send from treasury + messages.push(send_from_msg( + config.treasury.clone(), + reward.distributor.address.clone(), + reward.amount, + None, + None, + None, + &token.contract.clone(), + )?); + } + else { + return Err(StdError::generic_err(format!("Last rewards were requested on {}", reward.last_refresh))); + } + } + else { + return Err(StdError::generic_err("No rewards for you")); + } + + Ok(Response::new() + .add_messages(messages) + .set_data(to_binary(&ExecuteAnswer::RefillRewards { + status: ResponseStatus::Success, + })?) + ) +} + +pub fn register_rewards( + deps: DepsMut, + env: Env, + info: MessageInfo, + token: Addr, + distributor: Contract, + amount: Uint128, + cycle: Cycle, + expiration: Option, +) -> StdResult { + + if token != TOKEN.load(deps.storage)?.contract.address { + return Err(StdError::generic_err("Invalid token")); + } + + REWARD.save(deps.storage, info.sender, &Reward { + distributor, + amount, + cycle, + //TODO change to null/zero for first refresh + last_refresh: utc_now(&env).to_rfc3339(), + expiration, + })?; + + Ok(Response::new() + .set_data(to_binary(&ExecuteAnswer::RegisterReward{ + status: ResponseStatus::Success, + })?) + ) +} + +/* +pub fn update( + deps: DepsMut, + env: Env, + info: MessageInfo, + asset: Addr, +) -> StdResult { + Ok(Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Update { + status: ResponseStatus::Success, + })?)) +} + +pub fn claim( + deps: DepsMut, + _env: Env, + asset: Addr, +) -> StdResult { + match asset_r(deps.storage).may_load(&asset.as_str().as_bytes())? { + Some(_) => Ok(Response { + messages: vec![], + log: vec![], + data: Some(to_binary(&adapter::ExecuteAnswer::Claim { + status: ResponseStatus::Success, + amount: Uint128::zero(), + })?), + }), + None => Err(StdError::generic_err(format!( + "Unrecognized Asset {}", + asset + ))), + } +} +*/ diff --git a/contracts/mock/mock_adapter/src/lib.rs b/contracts/mock/mock_adapter/src/lib.rs new file mode 100644 index 0000000..2943dbb --- /dev/null +++ b/contracts/mock/mock_adapter/src/lib.rs @@ -0,0 +1 @@ +pub mod contract; diff --git a/contracts/mock/mock_adapter/src/storage.rs b/contracts/mock/mock_adapter/src/storage.rs new file mode 100644 index 0000000..a6847bb --- /dev/null +++ b/contracts/mock/mock_adapter/src/storage.rs @@ -0,0 +1,23 @@ +use shade_protocol::c_std::{Addr, Storage, Uint128}; +use shade_protocol::storage::{ + bucket, + bucket_read, + singleton, + singleton_read, + Bucket, + ReadonlyBucket, + ReadonlySingleton, + Singleton, +}; +use shade_protocol::contract_interfaces::{dao::rewards_emission, snip20::helpers::Snip20Asset}; + +use shade_protocol::{ + secret_storage_plus::{Map, Item}, +}; + +pub const CONFIG: Item = Item::new("config"); +pub const SELF_ADDRESS: Item = Item::new("self_address"); +pub const VIEWING_KEY: Item = Item::new("viewing_key"); +pub const TOKEN: Item = Item::new("token"); +pub const REWARD: Map = Map::new("rewards"); + diff --git a/contracts/mock/mock_sienna_pair/.cargo/config b/contracts/mock/mock_sienna_pair/.cargo/config new file mode 100644 index 0000000..882fe08 --- /dev/null +++ b/contracts/mock/mock_sienna_pair/.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/mock/mock_sienna_pair/.circleci/config.yml b/contracts/mock/mock_sienna_pair/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/contracts/mock/mock_sienna_pair/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/mock/mock_sienna_pair/Cargo.toml b/contracts/mock/mock_sienna_pair/Cargo.toml new file mode 100644 index 0000000..5508629 --- /dev/null +++ b/contracts/mock/mock_sienna_pair/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "mock_sienna_pair" +version = "0.1.0" +authors = ["Jack Swenson "] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +cosmwasm-schema = "1.1.5" +shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ + "dex", +] } diff --git a/contracts/mock/mock_sienna_pair/Makefile b/contracts/mock/mock_sienna_pair/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/contracts/mock/mock_sienna_pair/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/mock/mock_sienna_pair/README.md b/contracts/mock/mock_sienna_pair/README.md new file mode 100644 index 0000000..8fad675 --- /dev/null +++ b/contracts/mock/mock_sienna_pair/README.md @@ -0,0 +1,21 @@ +# Mock Band Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [User](#User) + * Queries + * [GetReferenceData](#GetReferenceData) +# Introduction +The Mocked Band contract is used to test locally when there is no official band contract available + +### Queries + +#### GetReferenceData +Get a hardcoded sample from an ETH query for testing locally +##### Response +```json +{ + "rate": "3119154999999000000000", + "last_updated_base": 1628548483, + "last_updated_quote": 3377610 +} +``` diff --git a/contracts/mock/mock_sienna_pair/src/contract.rs b/contracts/mock/mock_sienna_pair/src/contract.rs new file mode 100644 index 0000000..6344be6 --- /dev/null +++ b/contracts/mock/mock_sienna_pair/src/contract.rs @@ -0,0 +1,303 @@ +use shade_protocol::{ + c_std::{ + shd_entry_point, from_binary, to_binary, + Addr, Binary, Decimal, Deps, DepsMut, + Env, MessageInfo, Response, StdError, + StdResult, QuerierWrapper, Uint128, + }, + contract_interfaces::{ + dex::{ + dex::pool_take_amount, + sienna::{ + self, + Pair, + TokenType, + }, + }, + snip20::helpers::{balance_query, send_msg, set_viewing_key_msg}, + }, + cosmwasm_schema::cw_serde, + utils::{ + asset::Contract, ExecuteCallback, InstantiateCallback, + storage::plus::{Item, ItemStorage}, + }, +}; +pub use shade_protocol::dex::sienna::{ + PairQuery as QueryMsg, + PairInfoResponse, + SimulationResponse, + ReceiverCallbackMsg, +}; + +#[cw_serde] +pub struct Config { + pub address: Addr, + pub viewing_key: String, + pub commission: Decimal, +} + +impl ItemStorage for Config { + const ITEM: Item<'static, Self> = Item::new("item-config"); +} + +#[cw_serde] +pub struct PairInfo { + pub token_0: Contract, + pub token_1: Contract, +} + +impl ItemStorage for PairInfo { + const ITEM: Item<'static, Self> = Item::new("item-pair"); +} + +#[cw_serde] +pub struct InstantiateMsg { + pub token_0: Contract, + pub token_1: Contract, + pub viewing_key: String, + pub commission: Decimal, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[shd_entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg +) -> StdResult { + let pair_info = PairInfo { + token_0: msg.token_0.clone(), + token_1: msg.token_1.clone(), + }; + pair_info.save(deps.storage)?; + + let config = Config { + address: env.contract.address, + viewing_key: msg.viewing_key.clone(), + commission: msg.commission, + }; + config.save(deps.storage)?; + + let messages = vec![ + set_viewing_key_msg( + msg.viewing_key.clone(), + None, + &msg.token_0, + )?, + set_viewing_key_msg( + msg.viewing_key, + None, + &msg.token_1, + )?, + ]; + Ok(Response::default() + .add_messages(messages)) +} + +#[cw_serde] +pub enum ExecuteMsg { + MockPool { + token_a: Contract, + token_b: Contract, + }, + // SNIP20 receiver interface + Receive { + sender: Addr, + from: Addr, + msg: Option, + amount: Uint128, + }, +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[shd_entry_point] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg +) -> StdResult { + match msg { + ExecuteMsg::MockPool { + token_a, + token_b, + } => { + let pair_info = PairInfo { + token_0: token_a, + token_1: token_b, + }; + + pair_info.save(deps.storage)?; + Ok(Response::default()) + } + + // Swap + ExecuteMsg::Receive { + from, + msg, + amount, + .. + } => { + let msg = msg.ok_or_else(|| { + StdError::generic_err("Receiver callback \"msg\" parameter cannot be empty.") + })?; + + match from_binary(&msg)? { + ReceiverCallbackMsg::Swap { expected_return, to } => { + let config = Config::load(deps.storage)?; + let pair = PairInfo::load(deps.storage)?; + + let (in_token, out_token) = if info.sender == pair.token_0.address { + (pair.token_0, pair.token_1) + } else if info.sender == pair.token_1.address { + (pair.token_1, pair.token_0) + } else { + return Err(StdError::generic_err("unauthorized")); + }; + + let (in_pool, out_pool) = query_pool_amounts( + &deps.querier, + &config, + in_token.clone(), + out_token.clone(), + )?; + + // Sienna takes commission before swap + let swap_amount = amount - (amount * config.commission); + let return_amount = pool_take_amount( + swap_amount, + in_pool - amount, // amount has already been added to this pool + out_pool, + ); + + if return_amount < expected_return.unwrap_or(Uint128::zero()) { + return Err(StdError::generic_err( + "Operation fell short of expected_return" + )); + } + + // send tokens + let return_addr = to.unwrap_or(from); + return Ok(Response::default() + .add_message(send_msg( + return_addr, + return_amount, + None, + None, + None, + &out_token, + )?)) + }, + } + } + } + +} + +#[shd_entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::PairInfo => { + let config = Config::load(deps.storage)?; + let pair_info = PairInfo::load(deps.storage)?; + let (amount_0, amount_1) = query_pool_amounts( + &deps.querier, + &config, + pair_info.token_0.clone(), + pair_info.token_1.clone(), + )?; + + to_binary(&PairInfoResponse { + pair_info: sienna::PairInfo { + liquidity_token: Contract { + address: Addr::unchecked("lp_token"), + code_hash: "hash".to_string(), + }, + factory: Contract { + address: Addr::unchecked("factory"), + code_hash: "hash".to_string(), + }, + pair: Pair { + token_0: TokenType::CustomToken { + contract_addr: pair_info.token_0.address, + token_code_hash: pair_info.token_0.code_hash, + }, + token_1: TokenType::CustomToken { + contract_addr: pair_info.token_1.address, + token_code_hash: pair_info.token_1.code_hash, + } + }, + amount_0, + amount_1, + total_liquidity: Uint128::zero(), + contract_version: 0, + }, + }) + }, + QueryMsg::SwapSimulation { offer } => { + let config = Config::load(deps.storage)?; + let pair = PairInfo::load(deps.storage)?; + let token_0 = pair.token_0; + let token_1 = pair.token_1; + + let (in_token, out_token) = match offer.token { + TokenType::CustomToken { contract_addr, .. } => { + if contract_addr == token_0.address { + (token_0, token_1) + } else if contract_addr == token_1.address { + (token_1, token_0) + } else { + return Err(StdError::generic_err(format!( + "The supplied token {}, is not managed by this contract", + contract_addr + ))) + } + }, + _ => { + return Err(StdError::generic_err("Only CustomToken supported")); + } + }; + + let (amount_0, amount_1) = query_pool_amounts( + &deps.querier, + &config, + in_token.clone(), + out_token.clone(), + )?; + + // Sienna takes commission before swap + let commission = offer.amount * config.commission; + let swap_amount = offer.amount - commission; + + return to_binary(&SimulationResponse { + return_amount: pool_take_amount( + swap_amount, + amount_0, + amount_1, + ), + spread_amount: Uint128::zero(), + commission_amount: commission, + }); + + } + } +} + +fn query_pool_amounts( + querier: &QuerierWrapper, + config: &Config, + token_0: Contract, + token_1: Contract, +) -> StdResult<(Uint128, Uint128)> { + Ok(( + balance_query(querier, config.address.clone(), config.viewing_key.clone(), &token_0)?, + balance_query(querier, config.address.clone(), config.viewing_key.clone(), &token_1)?, + )) +} diff --git a/contracts/mock/mock_sienna_pair/src/lib.rs b/contracts/mock/mock_sienna_pair/src/lib.rs new file mode 100644 index 0000000..2943dbb --- /dev/null +++ b/contracts/mock/mock_sienna_pair/src/lib.rs @@ -0,0 +1 @@ +pub mod contract; diff --git a/contracts/mock/mock_stkd_derivative/Cargo.toml b/contracts/mock/mock_stkd_derivative/Cargo.toml new file mode 100644 index 0000000..e14004e --- /dev/null +++ b/contracts/mock/mock_stkd_derivative/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "mock_stkd_derivative" +version = "0.1.0" +authors = ["Aidan St. George "] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ + "dex", + "stkd", +] } +cosmwasm-schema = "1.1.5" + +[dev-dependencies] +mock_sienna_pair = { version = "0.1.0", path = "../mock_sienna_pair" } +shade-multi-test = { version = "0.1.0", path = "../../../packages/multi_test", features = [ + "mock_sienna", + "mock_stkd", + "snip20", +] } + diff --git a/contracts/mock/mock_stkd_derivative/Makefile b/contracts/mock/mock_stkd_derivative/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/contracts/mock/mock_stkd_derivative/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/mock/mock_stkd_derivative/README.md b/contracts/mock/mock_stkd_derivative/README.md new file mode 100644 index 0000000..2c1f6e7 --- /dev/null +++ b/contracts/mock/mock_stkd_derivative/README.md @@ -0,0 +1 @@ +# Mock stkd-SCRT Derivative Contract diff --git a/contracts/mock/mock_stkd_derivative/src/contract.rs b/contracts/mock/mock_stkd_derivative/src/contract.rs new file mode 100644 index 0000000..446a3df --- /dev/null +++ b/contracts/mock/mock_stkd_derivative/src/contract.rs @@ -0,0 +1,386 @@ +use shade_protocol::{ + Contract, + c_std::{ + shd_entry_point, + to_binary, + Addr, + BankMsg, + Binary, + Coin, + Decimal, + Deps, + DepsMut, + Env, + MessageInfo, + Response, + StdResult, + StdError, + Uint128, + }, + cosmwasm_schema::cw_serde, + contract_interfaces::snip20::ReceiverHandleMsg, + utils::{ + ExecuteCallback, + InstantiateCallback, + storage::plus::{ + Item, + ItemStorage, + Map, + MapStorage, + } + }, +}; + +pub use shade_protocol::contract_interfaces::stkd::{ + HandleAnswer as ExecuteAnswer, + HandleMsg as ExecuteMsg, + QueryAnswer, + QueryMsg, + Unbond, +}; + +#[cw_serde] +struct Unbonding { + amount: Uint128, + // Time for maturity, when bonding is claimable + maturity: u32, +} + +// Keep track of a user's balance +#[cw_serde] +#[derive(Default)] +struct Balance (pub Uint128); + +impl MapStorage<'static, Addr> for Balance { + const MAP: Map<'static, Addr, Self> = Map::new("balance-"); +} + +// Keep track of a user's unbondings +#[cw_serde] +#[derive(Default)] +struct Unbondings(pub Vec); + +impl MapStorage<'static, Addr> for Unbondings { + const MAP: Map<'static, Addr, Self> = Map::new("unbondings-"); +} + +#[cw_serde] +struct ViewingKey(pub String); + +impl MapStorage<'static, Addr> for ViewingKey { + const MAP: Map<'static, Addr, Self> = Map::new("vk-"); +} + +#[cw_serde] +struct Price(pub Uint128); + +impl ItemStorage for Price { + const ITEM: Item<'static, Self> = Item::new("item-price"); +} + +// Global time tracker +#[cw_serde] +struct Time(pub u32); + +impl ItemStorage for Time { + const ITEM: Item<'static, Self> = Item::new("item-time"); +} + +#[cw_serde] +pub struct Config { + name: String, + symbol: String, + decimals: u8, + admin: Addr, + unbonding_time: u32, + unbonding_batch_interval: u32, + staking_commission: Decimal, + unbond_commission: Decimal, +} + +impl ItemStorage for Config { + const ITEM: Item<'static, Self> = Item::new("item-config"); +} + +// INSTANTIATE + +#[cw_serde] +pub struct InstantiateMsg { + pub name: String, + pub symbol: String, + pub decimals: u8, + pub price: Uint128, + pub unbonding_time: u32, + pub unbonding_batch_interval: u32, + pub staking_commission: Decimal, + pub unbond_commission: Decimal, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[shd_entry_point] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let config = Config { + name: msg.name, + symbol: msg.symbol, + decimals: msg.decimals, + admin: info.sender.clone(), + unbonding_time: msg.unbonding_time, + unbonding_batch_interval: msg.unbonding_batch_interval, + staking_commission: msg.staking_commission, + unbond_commission: msg.unbond_commission, + }; + config.save(deps.storage)?; + + // Adjust price relative to uscrt for the off chance that msg.decimals isn't 6 + let mut price = msg.price; + if msg.decimals != 6 { + if msg.decimals > 6 { + price = price / Uint128::new(10).pow(msg.decimals as u32 - 6); + } else { + price = price * Uint128::new(10).pow(6 - msg.decimals as u32); + } + } + Price(price).save(deps.storage)?; + + Time(0).save(deps.storage)?; + + Ok(Response::new()) +} + +// EXECUTE + +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg +) -> StdResult { + match msg { + ExecuteMsg::Send { recipient, amount, recipient_code_hash, msg, .. } => { + let my_balance = Balance::load(deps.storage, info.sender.clone()) + .map_err(|_| StdError::generic_err("Insufficient funds"))?.0; + let their_balance = Balance::load(deps.storage, recipient.clone()) + .unwrap_or_default().0; + + Balance(my_balance.checked_sub(amount) + .map_err(|_| StdError::generic_err("Insufficient funds"))?) + .save(deps.storage, info.sender.clone())?; + Balance(their_balance + amount).save(deps.storage, recipient.clone())?; + + let mut messages = vec![]; + if let Some(receiver_hash) = recipient_code_hash { + let recipient_addr = Addr::unchecked(recipient); + messages.push( + ReceiverHandleMsg::new( + info.sender.to_string(), + info.sender.to_string(), + amount, + None, + msg + ).to_cosmos_msg( + &Contract { + address: recipient_addr, + code_hash: receiver_hash, + }, + vec![], + )? + ); + } + Ok(Response::default() + .add_messages(messages)) + } + // TODO: fees + ExecuteMsg::Stake {} => { + let mut amount = Uint128::zero(); + for coin in info.funds { + if coin.denom == "uscrt".to_string() { + amount += coin.amount; + } + } + if amount.is_zero() { + return Err(StdError::generic_err("No SCRT was sent for staking")); + } + + let config = Config::load(deps.storage)?; + let amount = amount - (amount * config.staking_commission); + let deriv_amount = amount.multiply_ratio(Uint128::from(1_000_000u32), Price::load(deps.storage)?.0); + + let balance = Balance::load(deps.storage, info.sender.clone()) + .unwrap_or_default().0; + Balance(balance + deriv_amount).save(deps.storage, info.sender)?; + + Ok(Response::default() + .set_data(to_binary(&ExecuteAnswer::Stake { + scrt_staked: amount, + tokens_returned: deriv_amount, + })?) + ) + }, + ExecuteMsg::Unbond { redeem_amount } => { + let balance = Balance::load(deps.storage, info.sender.clone()) + .unwrap_or_default().0; + if balance < redeem_amount { + return Err(StdError::generic_err(format!( + "insufficient funds to burn: balance={}, required={}", balance, redeem_amount + ))); + } + + let config = Config::load(deps.storage)?; + let time = Time::load(deps.storage)?.0; + let maturity = time + config.unbonding_time + + config.unbonding_batch_interval - (time % config.unbonding_batch_interval); + let unbond_amount = redeem_amount - (redeem_amount * config.unbond_commission); + let unbonding = Unbonding { + amount: unbond_amount, + maturity, + }; + + let mut unbondings = Unbondings::load(deps.storage, info.sender.clone()) + .unwrap_or_default().0; + unbondings.push(unbonding); + Unbondings(unbondings).save(deps.storage, info.sender.clone())?; + + Balance(balance - redeem_amount).save(deps.storage, info.sender)?; + let scrt_amount = redeem_amount + .multiply_ratio(Price::load(deps.storage)?.0, Uint128::from(1_000_000u32)); + Ok(Response::default() + .set_data(to_binary(&ExecuteAnswer::Unbond { + tokens_redeemed: redeem_amount, + scrt_to_be_received: scrt_amount, + estimated_time_of_maturity: maturity as u64, + })?) + ) + + }, + ExecuteMsg::Claim {} => { + let mut claimable = Uint128::zero(); + let unbondings = Unbondings::load(deps.storage, info.sender.clone())?.0; + let time = Time::load(deps.storage)?.0; + let mut new_unbondings = vec![]; + for unbonding in unbondings { + if unbonding.maturity <= time { + claimable += unbonding.amount; + } else { + new_unbondings.push(unbonding); + } + } + let returned = claimable.multiply_ratio(Price::load(deps.storage)?.0, Uint128::new(1_000_000)); + Unbondings(new_unbondings).save(deps.storage, info.sender.clone())?; + + Ok(Response::default() + .set_data(to_binary(&ExecuteAnswer::Claim { + withdrawn: claimable, + fees: Uint128::zero(), // no fees + })?) + .add_message(BankMsg::Send { + to_address: info.sender.to_string(), + amount: vec![Coin { + amount: returned, + denom: "uscrt".to_string(), + }] + })) + }, + ExecuteMsg::SetViewingKey { key, .. } => { + ViewingKey(key).save(deps.storage, info.sender)?; + Ok(Response::default()) + }, + ExecuteMsg::MockFastForward { steps } => { + let time = Time::load(deps.storage)?.0; + Time(time + steps).save(deps.storage)?; + Ok(Response::default()) + } + } +} + +// QUERY + +pub fn query( + deps: Deps, + _env: Env, + msg: QueryMsg, +) -> StdResult { + match msg { + QueryMsg::Balance { address, key } => { + if key != ViewingKey::load(deps.storage, address.clone())?.0 { + return Err(StdError::generic_err("unauthorized")); + } + + to_binary(&QueryAnswer::Balance { + amount: Balance::load(deps.storage, address).unwrap_or_default().0, + }) + }, + QueryMsg::StakingInfo { .. } => { + let time = Time::load(deps.storage)?.0; + let config = Config::load(deps.storage)?; + let next_unbonding_batch_time = time + config.unbonding_batch_interval + - (time % config.unbonding_batch_interval); + + // Convert back to basis of 6 decimals + let mut price = Price::load(deps.storage)?.0; + if config.decimals != 6 { + if config.decimals > 6 { + price = price * Uint128::new(10).pow(config.decimals as u32 - 6); + } else { + price = price / Uint128::new(10).pow(6 - config.decimals as u32); + } + } + + to_binary(&QueryAnswer::StakingInfo { + validators: vec![], + unbonding_time: config.unbonding_time, + unbonding_batch_interval: config.unbonding_batch_interval, + next_unbonding_batch_time: next_unbonding_batch_time as u64, + // Not supported by mock stkd + unbond_amount_of_next_batch: Uint128::zero(), + batch_unbond_in_progress: false, + bonded_scrt: Uint128::zero(), + reserved_scrt: Uint128::zero(), + available_scrt: Uint128::zero(), + rewards: Uint128::zero(), + total_derivative_token_supply: Uint128::zero(), + price, + }) + }, + QueryMsg::Unbonding { address, key, .. } => { + if key != ViewingKey::load(deps.storage, address.clone())?.0 { + return Err(StdError::generic_err("unauthorized")); + } + + let mut count: u64 = 0; + let mut unbonds = vec![]; + let mut amount_in_next_batch = Uint128::zero(); + let time = Time::load(deps.storage)?.0; + let config = Config::load(deps.storage)?; + let unbondings = Unbondings::load(deps.storage, address).unwrap_or_default().0; + for unbonding in unbondings { + if unbonding.maturity <= time + config.unbonding_time { + count += 1; + unbonds.push(Unbond { + amount: unbonding.amount, + unbonds_at: unbonding.maturity as u64, + is_mature: None, + }); + } else { + amount_in_next_batch += unbonding.amount; + } + } + + to_binary(&QueryAnswer::Unbonding { + count, + claimable_scrt: None, + unbondings: unbonds, + unbond_amount_in_next_batch: amount_in_next_batch, + estimated_time_of_maturity_for_next_batch: None, + }) + }, + } +} + diff --git a/contracts/mock/mock_stkd_derivative/src/lib.rs b/contracts/mock/mock_stkd_derivative/src/lib.rs new file mode 100644 index 0000000..de375de --- /dev/null +++ b/contracts/mock/mock_stkd_derivative/src/lib.rs @@ -0,0 +1,5 @@ +pub mod contract; + +#[cfg(test)] +mod tests; + diff --git a/contracts/mock/mock_stkd_derivative/src/tests.rs b/contracts/mock/mock_stkd_derivative/src/tests.rs new file mode 100644 index 0000000..afcbb22 --- /dev/null +++ b/contracts/mock/mock_stkd_derivative/src/tests.rs @@ -0,0 +1,510 @@ +use shade_protocol::{ + c_std::{ + coins, from_binary, to_binary, + Addr, Coin, StdError, + Binary, StdResult, Env, + Uint128, QueryRequest, BankQuery, + BalanceResponse, Decimal + }, + contract_interfaces::dex::sienna::{ + Pair, PairInfo, TokenType, + }, + utils::{ + asset::Contract, + MultiTestable, + InstantiateCallback, + ExecuteCallback, + Query, + }, + snip20, +}; +use shade_protocol::multi_test::App; +use shade_multi_test::multi::{ + mock_stkd::MockStkd, + mock_sienna::MockSienna, + snip20::Snip20, +}; +use crate::contract as stkd; + +use mock_sienna_pair::contract as mock_sienna; + + +#[test] +fn test() { + let mut chain = App::default(); + + let admin = Addr::unchecked("admin"); + let user = Addr::unchecked("user"); + let other = Addr::unchecked("other-user"); + + let init_scrt = Coin { + denom: "uscrt".to_string(), + amount: Uint128::new(1000), + }; + + let some_scrt = Coin { + denom: "uscrt".to_string(), + amount: Uint128::new(100), + }; + + // Init balances + chain.init_modules(|router, _, storage| { + router.bank.init_balance(storage, &user, vec![init_scrt.clone()]).unwrap(); + router.bank.init_balance(storage, &admin, vec![init_scrt.clone()]).unwrap(); + }); + + let stkd = stkd::InstantiateMsg { + name: "Staking Derivative".to_string(), + symbol: "stkd-SCRT".to_string(), + decimals: 6, + price: Uint128::from(2_000_000u64), + unbonding_time: 21, + unbonding_batch_interval: 3, + staking_commission: Decimal::permille(2), + unbond_commission: Decimal::from_ratio(5u32, 10_000u32), + }.test_init(MockStkd::default(), &mut chain, admin.clone(), "stkd", &[]).unwrap(); + + // Test Staking + stkd::ExecuteMsg::Stake {} + .test_exec(&stkd, &mut chain, user.clone(), &[some_scrt]).unwrap(); + + stkd::ExecuteMsg::SetViewingKey { + key: "password".to_string(), + padding: None, + }.test_exec(&stkd, &mut chain, user.clone(), &[]).unwrap(); + + assert_eq!( + stkd::QueryMsg::StakingInfo { + time: 0u64, + }.test_query::(&stkd, &chain).unwrap(), + stkd::QueryAnswer::StakingInfo { + validators: vec![], + unbonding_time: 21u32, + unbonding_batch_interval: 3u32, + next_unbonding_batch_time: 3u64, + unbond_amount_of_next_batch: Uint128::zero(), + batch_unbond_in_progress: false, + bonded_scrt: Uint128::zero(), + reserved_scrt: Uint128::zero(), + available_scrt: Uint128::zero(), + rewards: Uint128::zero(), + total_derivative_token_supply: Uint128::zero(), + price: Uint128::from(2_000_000u64), + }, + ); + + assert_eq!( + stkd::QueryMsg::Balance { + address: user.clone(), + key: "password".to_string(), + }.test_query::(&stkd, &chain).unwrap(), + stkd::QueryAnswer::Balance { + amount: Uint128::new(50), + }, + ); + + assert_eq!( // right amount of scrt left + chain.wrap().query::(&QueryRequest::Bank(BankQuery::Balance { + address: user.to_string(), + denom: "uscrt".to_string(), + })).unwrap(), + BalanceResponse { + amount: Coin { + amount: Uint128::new(900), + denom: "uscrt".to_string(), + }, + }, + ); + + // Test Unbonding + stkd::ExecuteMsg::Unbond { + redeem_amount: Uint128::new(25), + }.test_exec(&stkd, &mut chain, user.clone(), &[]).unwrap(); + + stkd::ExecuteMsg::MockFastForward { + steps: 1, + }.test_exec(&stkd, &mut chain, admin.clone(), &[]).unwrap(); + + assert_eq!( + stkd::QueryMsg::Unbonding { + address: user.clone(), + key: "password".to_string(), + page: None, + page_size: None, + time: None, + }.test_query::(&stkd, &chain).unwrap(), + stkd::QueryAnswer::Unbonding { + count: 0, + claimable_scrt: None, + unbondings: vec![], + unbond_amount_in_next_batch: Uint128::new(25), + estimated_time_of_maturity_for_next_batch: None, + }, + ); + + stkd::ExecuteMsg::MockFastForward { + steps: 2, + }.test_exec(&stkd, &mut chain, admin.clone(), &[]).unwrap(); + + assert_eq!( + stkd::QueryMsg::Unbonding { + address: user.clone(), + key: "password".to_string(), + page: None, + page_size: None, + time: None, + }.test_query::(&stkd, &chain).unwrap(), + stkd::QueryAnswer::Unbonding { + count: 1, + claimable_scrt: None, + unbondings: vec![stkd::Unbond { + amount: Uint128::new(25), + unbonds_at: 24u64, + is_mature: None, + }], + unbond_amount_in_next_batch: Uint128::zero(), + estimated_time_of_maturity_for_next_batch: None, + }, + ); + + stkd::ExecuteMsg::MockFastForward { + steps: 1 + }.test_exec(&stkd, &mut chain, admin.clone(), &[]).unwrap(); + + assert_eq!( + stkd::QueryMsg::Unbonding { + address: user.clone(), + key: "password".to_string(), + page: None, + page_size: None, + time: None, + }.test_query::(&stkd, &chain).unwrap(), + stkd::QueryAnswer::Unbonding { + count: 1, + claimable_scrt: None, + unbondings: vec![stkd::Unbond { + amount: Uint128::new(25), + unbonds_at: 24u64, + is_mature: None, + }], + unbond_amount_in_next_batch: Uint128::zero(), + estimated_time_of_maturity_for_next_batch: None, + }, + ); + + stkd::ExecuteMsg::MockFastForward { + steps: 21 + }.test_exec(&stkd, &mut chain, admin.clone(), &[]).unwrap(); + + assert_eq!( + stkd::QueryMsg::Unbonding { + address: user.clone(), + key: "password".to_string(), + page: None, + page_size: None, + time: None, + }.test_query::(&stkd, &chain).unwrap(), + stkd::QueryAnswer::Unbonding { + count: 1, + claimable_scrt: None, + unbondings: vec![stkd::Unbond { + amount: Uint128::new(25), + unbonds_at: 24u64, + is_mature: None, + }], + unbond_amount_in_next_batch: Uint128::zero(), + estimated_time_of_maturity_for_next_batch: None, + }, + ); + + // Test Claiming + stkd::ExecuteMsg::Claim {} + .test_exec(&stkd, &mut chain, user.clone(), &[]).unwrap(); + + assert_eq!( + stkd::QueryMsg::Balance { + address: user.clone(), + key: "password".to_string(), + }.test_query::(&stkd, &chain).unwrap(), + stkd::QueryAnswer::Balance { + amount: Uint128::new(25), + }, + ); + + assert_eq!( // right amount of scrt returned + chain.wrap().query::(&QueryRequest::Bank(BankQuery::Balance { + address: user.to_string(), + denom: "uscrt".to_string(), + })).unwrap(), + BalanceResponse { + amount: Coin { + amount: Uint128::new(950), + denom: "uscrt".to_string(), + }, + }, + ); + + // Test wrong viewing key + assert_eq!( + stkd::QueryMsg::Balance { + address: user.clone(), + key: "not password".to_string(), + }.test_query::(&stkd, &chain), + Err(StdError::generic_err("Querier contract error: Generic error: unauthorized")), + ); + + assert_eq!( + stkd::QueryMsg::Unbonding { + address: other.clone(), + key: "password".to_string(), + page: None, + page_size: None, + time: None, + }.test_query::(&stkd, &chain), + Err(StdError::generic_err("Querier contract error: mock_stkd_derivative::contract::ViewingKey not found")), + ); + + // Test Sending + stkd::ExecuteMsg::Send { + recipient: other.clone(), + recipient_code_hash: None, + amount: Uint128::new(25), + msg: None, + memo: None, + padding: None, + }.test_exec(&stkd, &mut chain, user.clone(), &[]).unwrap(); + + assert_eq!( + stkd::QueryMsg::Balance { + address: user.clone(), + key: "password".to_string(), + }.test_query::(&stkd, &chain).unwrap(), + stkd::QueryAnswer::Balance { + amount: Uint128::new(0), + }, + ); + + stkd::ExecuteMsg::SetViewingKey { + key: "other password".to_string(), + padding: None, + }.test_exec(&stkd, &mut chain, other.clone(), &[]).unwrap(); + + assert_eq!( + stkd::QueryMsg::Balance { + address: other.clone(), + key: "other password".to_string(), + }.test_query::(&stkd, &chain).unwrap(), + stkd::QueryAnswer::Balance { + amount: Uint128::new(25), + }, + ); + + // Test swap + let other_snip = snip20::InstantiateMsg { + name: "other_token".into(), + admin: None, + symbol: "OTHER".into(), + decimals: 6, + initial_balances: Some(vec![ + snip20::InitialBalance { + address: user.to_string(), + amount: Uint128::new(1000), + }, + snip20::InitialBalance { + address: admin.to_string(), + amount: Uint128::new(1000), + }, + ]), + prng_seed: Binary::from("random".as_bytes()), + config: None, + query_auth: None, + } + .test_init( + Snip20::default(), + &mut chain, + Addr::unchecked("admin"), + "snip20", + &[], + ).unwrap(); + + let sienna_pair = mock_sienna::InstantiateMsg { + token_0: stkd.clone().into(), + token_1: other_snip.clone().into(), + viewing_key: "key".into(), + commission: Decimal::permille(3), + }.test_init( + MockSienna::default(), + &mut chain, + Addr::unchecked("admin"), + "stkd pair", + &[], + ).unwrap(); + + stkd::ExecuteMsg::Stake {} // get stkd to seed pair + .test_exec(&stkd, &mut chain, admin.clone(), &[init_scrt]).unwrap(); + + stkd::ExecuteMsg::Send { // seed pair + recipient: sienna_pair.address.clone(), + recipient_code_hash: None, + amount: Uint128::new(499), + msg: None, + memo: None, + padding: None, + }.test_exec(&stkd, &mut chain, admin.clone(), &[]).unwrap(); + + snip20::ExecuteMsg::Send { // seed pair + recipient: sienna_pair.address.to_string(), + recipient_code_hash: None, + amount: Uint128::new(998), + msg: None, + memo: None, + padding: None, + }.test_exec(&other_snip, &mut chain, admin.clone(), &[]).unwrap(); + + mock_sienna::ExecuteMsg::MockPool { + token_a: Contract { + address: stkd.address.clone(), + code_hash: stkd.code_hash.clone(), + }, + token_b: Contract { + address: other_snip.address.clone(), + code_hash: other_snip.code_hash.clone(), + }, + }.test_exec(&sienna_pair, &mut chain, user.clone(), &[]).unwrap(); + + assert_eq!( + mock_sienna::QueryMsg::PairInfo {} + .test_query::(&sienna_pair, &chain).unwrap(), + mock_sienna::PairInfoResponse { + pair_info: PairInfo { + liquidity_token: Contract { + address: Addr::unchecked("lp_token"), + code_hash: "hash".to_string(), + }, + factory: Contract { + address: Addr::unchecked("factory"), + code_hash: "hash".to_string(), + }, + pair: Pair { + token_0: TokenType::CustomToken { + contract_addr: stkd.address.clone(), + token_code_hash: stkd.code_hash.clone(), + }, + token_1: TokenType::CustomToken { + contract_addr: other_snip.address.clone(), + token_code_hash: other_snip.code_hash.clone(), + }, + }, + amount_0: Uint128::new(499), + amount_1: Uint128::new(998), + total_liquidity: Uint128::new(0), + contract_version: 0, + }, + }, + ); + + snip20::ExecuteMsg::SetViewingKey { + key: "password".to_string(), + padding: None, + }.test_exec(&other_snip, &mut chain, user.clone(), &[]).unwrap(); + + assert_eq!( + snip20::QueryMsg::Balance { + address: user.to_string(), + key: "password".to_string(), + }.test_query::(&other_snip, &chain).unwrap(), + snip20::QueryAnswer::Balance { + amount: Uint128::new(1000), + }, + ); + + assert_eq!( + stkd::QueryMsg::Balance { + address: user.clone(), + key: "password".to_string(), + }.test_query::(&stkd, &chain).unwrap(), + stkd::QueryAnswer::Balance { + amount: Uint128::zero(), + }, + ); + + snip20::ExecuteMsg::Send { + recipient: sienna_pair.address.to_string(), + recipient_code_hash: Some(sienna_pair.clone().code_hash), + amount: Uint128::new(10), + msg: Some(to_binary(&mock_sienna::ReceiverCallbackMsg::Swap { + expected_return: None, + to: None, + }).unwrap()), + memo: None, + padding: None, + }.test_exec(&other_snip, &mut chain, user.clone(), &[]).unwrap(); + + assert_eq!( + snip20::QueryMsg::Balance { + address: user.to_string(), + key: "password".to_string(), + }.test_query::(&other_snip, &chain).unwrap(), + snip20::QueryAnswer::Balance { + amount: Uint128::new(990), + }, + ); + + assert_eq!( + stkd::QueryMsg::Balance { + address: user.clone(), + key: "password".to_string(), + }.test_query::(&stkd, &chain).unwrap(), + stkd::QueryAnswer::Balance { + amount: Uint128::new(5), + }, + ); + + assert_eq!( + mock_sienna::QueryMsg::PairInfo {} + .test_query::(&sienna_pair, &chain).unwrap(), + mock_sienna::PairInfoResponse { + pair_info: PairInfo { + liquidity_token: Contract { + address: Addr::unchecked("lp_token"), + code_hash: "hash".to_string(), + }, + factory: Contract { + address: Addr::unchecked("factory"), + code_hash: "hash".to_string(), + }, + pair: Pair { + token_0: TokenType::CustomToken { + contract_addr: stkd.address.clone(), + token_code_hash: stkd.code_hash.clone(), + }, + token_1: TokenType::CustomToken { + contract_addr: other_snip.address.clone(), + token_code_hash: other_snip.code_hash.clone(), + }, + }, + amount_0: Uint128::new(494), + amount_1: Uint128::new(1_008), + total_liquidity: Uint128::new(0), + contract_version: 0, + }, + }, + ); + + // test No balance + stkd::ExecuteMsg::SetViewingKey { + key: "key".into(), + padding: None, + }.test_exec(&stkd, &mut chain, Addr::unchecked("new"), &[]).unwrap(); + + assert_eq!( + stkd::QueryMsg::Balance { + address: Addr::unchecked("new"), + key: "key".to_string(), + }.test_query::(&stkd, &chain).unwrap(), + stkd::QueryAnswer::Balance { + amount: Uint128::zero(), + }, + ); + +} diff --git a/contracts/query_auth/.cargo/config b/contracts/query_auth/.cargo/config new file mode 100644 index 0000000..c1e7c50 --- /dev/null +++ b/contracts/query_auth/.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" \ No newline at end of file diff --git a/contracts/query_auth/.circleci/config.yml b/contracts/query_auth/.circleci/config.yml new file mode 100644 index 0000000..a6f10d6 --- /dev/null +++ b/contracts/query_auth/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.46 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/query_auth/Cargo.toml b/contracts/query_auth/Cargo.toml new file mode 100644 index 0000000..7cc8fcd --- /dev/null +++ b/contracts/query_auth/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "query_auth" +version = "0.1.0" +authors = ["Guy Garcia "] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ + "query_auth_impl", + "admin" +] } +cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" } +schemars = "0.7" + +[dev-dependencies] +shade-multi-test = { version = "0.1.0", path = "../../packages/multi_test", features = [ "query_auth", "admin" ] } diff --git a/contracts/query_auth/README.md b/contracts/query_auth/README.md new file mode 100644 index 0000000..d8f5fca --- /dev/null +++ b/contracts/query_auth/README.md @@ -0,0 +1,202 @@ +# Query Authentication +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Init](#Init) + * [Admin](#Admin) + * Messages + * [SetAdmin](#SetAdmin) + * [SetRunState](#SetRunState) + * [User](#User) + * Messages + * [SetViewingKey](#SetViewingKey) + * [CreateViewingKey](#CreateViewingKey) + * [BlockPermitKey](#BlockPermitKey) + * Queries + * [Config](#Config) + * [ValidateViewingKey](#ValidateViewingKey) + * [ValidatePermit](#ValidatePermit) + +# Introduction +User authentication manager that allows for validation for permits and viewing keys, making all smart contracts +share one viewing key. +# Sections + +## Init +##### Request +| Name | Type | Description | optional | +|-----------|-----------|------------------------------------------------|----------| +| admin | Addr | Contract admin | yes | +| prng_seed | Binary | Randomness seed for the viewing key generation | no | + +## Admin + +### Messages + +#### SetAdmin +Changes the current admin +##### Request +| Name | Type | Description | optional | +|---------|-----------|------------------------------------------------------|----------| +| admin | Addr | New contract admin; SHOULD be a valid bech32 address | no | +| padding | String | Randomly generated data to pad the message | yes | + + +##### Response +``` json +{ + "update_config": { + "status": "success" + } +} +``` + +#### SetRunState +Limits the smart contract's run state +##### Request +| Name | Type | Description | optional | +|---------|----------------|-------------------------------------------------------------------|----------| +| state | ContractStatus | Limits what queries / handlemsgs can be triggered in the contract | no | +| padding | String | Randomly generated data to pad the message | yes | + +#### ContractStatus +* Default +* DisablePermit +* DisableVK +* DisableAll + +##### Response +``` json +{ + "update_config": { + "status": "success" + } +} +``` + +## User + +### Messages + +#### SetViewingKey +Sets the signers viewing key +##### Request +| Name | Type | Description | optional | +|---------|--------|--------------------------------------------|----------| +| key | String | The new viewing key | no | +| padding | String | Randomly generated data to pad the message | yes | + +##### Response +``` json +{ + "update_config": { + "status": "success" + } +} +``` + +#### CreateViewingKey +Generated the signers viewing key with the given entropy +##### Request +| Name | Type | Description | optional | +|---------|--------|--------------------------------------------|----------| +| entropy | String | The entropy used for VK generation | no | +| padding | String | Randomly generated data to pad the message | yes | + +##### Response +``` json +{ + "update_config": { + "key": "new VK" + } +} +``` + +#### BlockPermitKey +Blocks a permit key, whenever a permit with that key is queried then it will return that its not valid +##### Request +| Name | Type | Description | optional | +|---------|--------|--------------------------------------------|----------| +| key | String | Permit key to block | no | +| padding | String | Randomly generated data to pad the message | yes | + +##### Response +``` json +{ + "update_config": { + "status": "success" + } +} +``` + +### Queries + +#### Config +Get the contracts config + +##### Response +```json +{ + "config": { + "admin": "address", + "state": "contract state" + } +} +``` + +#### ValidateViewingKey +Validates the users viewing key + +##### Request +| Name | Type | Description | optional | +|------|-----------|--------------------|----------| +| user | Addr | User to verify | no | +| key | String | User's viewing key | no | + +##### Response +```json +{ + "validate_viewing_key": { + "is_valid": true + } +} +``` + +#### ValidatePermit +Validates the users permit + +##### Request +| Name | Type | Description | optional | +|--------------|------------|-----------------------------|----------| +| permit | Permit | User's signed permit | no | + +#### Permit +```json +{ + "params": { + "data": "base64 data specific to the contract", + "key": "permit key" + }, + "signature": { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "Secp256k1 PubKey" + }, + "signature": "base64 signature of permit" + }, + "account_number": "optional account number", + "chain_id": "optional chain id", + "sequence": "optional sequence", + "memo": "Optional memo" +} +``` + +##### Response +NOTE: is revoked refers to if the permit's key has been blocked +```json +{ + "validate_permit": { + "user": "Signer's address", + "is_revoked": false + } +} +``` \ No newline at end of file diff --git a/contracts/query_auth/src/contract.rs b/contracts/query_auth/src/contract.rs new file mode 100644 index 0000000..856b1a5 --- /dev/null +++ b/contracts/query_auth/src/contract.rs @@ -0,0 +1,130 @@ +use crate::{handle, query}; +use shade_protocol::{ + c_std::{ + shd_entry_point, + to_binary, + Binary, + Deps, + DepsMut, + Env, + MessageInfo, + Response, + StdError, + StdResult, + }, + contract_interfaces::query_auth::{ + Admin, + ContractStatus, + ExecuteMsg, + InstantiateMsg, + QueryMsg, + RngSeed, + }, + utils::{pad_handle_result, pad_query_result, storage::plus::ItemStorage}, +}; + +// Used to pad up responses for better privacy. +pub const RESPONSE_BLOCK_SIZE: usize = 256; + +#[shd_entry_point] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + Admin(msg.admin_auth).save(deps.storage)?; + + RngSeed::new(msg.prng_seed).save(deps.storage)?; + + ContractStatus::Default.save(deps.storage)?; + + Ok(Response::new()) +} + +#[shd_entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + // Check what msgs are allowed + let status = ContractStatus::load(deps.storage)?; + match status { + // Do nothing + ContractStatus::Default => {} + // No permit interactions + ContractStatus::DisablePermit => match msg { + ExecuteMsg::BlockPermitKey { .. } => return Err(StdError::generic_err("unauthorized")), + _ => {} + }, + // No VK interactions + ContractStatus::DisableVK => match msg { + ExecuteMsg::CreateViewingKey { .. } | ExecuteMsg::SetViewingKey { .. } => { + return Err(StdError::generic_err("unauthorized")); + } + _ => {} + }, + // Nothing + ContractStatus::DisableAll => match msg { + ExecuteMsg::CreateViewingKey { .. } + | ExecuteMsg::SetViewingKey { .. } + | ExecuteMsg::BlockPermitKey { .. } => { + return Err(StdError::generic_err("unauthorized")); + } + _ => {} + }, + } + + pad_handle_result( + match msg { + ExecuteMsg::SetAdminAuth { admin, .. } => handle::try_set_admin(deps, env, info, admin), + ExecuteMsg::SetRunState { state, .. } => { + handle::try_set_run_state(deps, env, info, state) + } + ExecuteMsg::SetViewingKey { key, .. } => { + handle::try_set_viewing_key(deps, env, info, key) + } + ExecuteMsg::CreateViewingKey { entropy, .. } => { + handle::try_create_viewing_key(deps, env, info, entropy) + } + ExecuteMsg::BlockPermitKey { key, .. } => { + handle::try_block_permit_key(deps, env, info, key) + } + }, + RESPONSE_BLOCK_SIZE, + ) +} + +#[shd_entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + let status = ContractStatus::load(deps.storage)?; + match status { + // Do nothing + ContractStatus::Default => {} + // No permit interactions + ContractStatus::DisablePermit => { + if let QueryMsg::ValidatePermit { .. } = msg { + return Err(StdError::generic_err("unauthorized")); + } + } + // No VK interactions + ContractStatus::DisableVK => { + if let QueryMsg::ValidateViewingKey { .. } = msg { + return Err(StdError::generic_err("unauthorized")); + } + } + // Nothing + ContractStatus::DisableAll => { + if let QueryMsg::Config { .. } = msg { + } else { + return Err(StdError::generic_err("unauthorized")); + } + } + } + + pad_query_result( + to_binary(&match msg { + QueryMsg::Config { .. } => query::config(deps)?, + QueryMsg::ValidateViewingKey { user, key } => query::validate_vk(deps, user, key)?, + QueryMsg::ValidatePermit { permit } => query::validate_permit(deps, permit)?, + }), + RESPONSE_BLOCK_SIZE, + ) +} diff --git a/contracts/query_auth/src/handle.rs b/contracts/query_auth/src/handle.rs new file mode 100644 index 0000000..bde98ee --- /dev/null +++ b/contracts/query_auth/src/handle.rs @@ -0,0 +1,95 @@ +use shade_protocol::{ + admin::helpers::{validate_admin, AdminPermissions}, + c_std::{to_binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}, + contract_interfaces::query_auth::{ + auth::{HashedKey, Key, PermitKey}, + Admin, + ContractStatus, + ExecuteAnswer, + RngSeed, + }, + query_authentication::viewing_keys::ViewingKey, + utils::{ + generic_response::ResponseStatus::Success, + storage::plus::{ItemStorage, MapStorage}, + }, +}; + +use shade_protocol::utils::asset::Contract; + +fn user_authorized(deps: &Deps, _env: Env, info: &MessageInfo) -> StdResult<()> { + let contract = Admin::load(deps.storage)?.0; + + validate_admin( + &deps.querier, + AdminPermissions::QueryAuthAdmin, + info.sender.clone(), + &contract, + ) +} + +pub fn try_set_admin( + deps: DepsMut, + env: Env, + info: MessageInfo, + admin: Contract, +) -> StdResult { + user_authorized(&deps.as_ref(), env, &info)?; + + Admin(admin).save(deps.storage)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetAdminAuth { status: Success })?)) +} + +pub fn try_set_run_state( + deps: DepsMut, + env: Env, + info: MessageInfo, + state: ContractStatus, +) -> StdResult { + user_authorized(&deps.as_ref(), env, &info)?; + + state.save(deps.storage)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetRunState { status: Success })?)) +} + +pub fn try_create_viewing_key( + deps: DepsMut, + env: Env, + info: MessageInfo, + entropy: String, +) -> StdResult { + let seed = RngSeed::load(deps.storage)?.0; + + let key = Key::generate(&info, &env, seed.as_slice(), &entropy.as_ref()); + + HashedKey(key.hash()).save(deps.storage, info.sender)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::CreateViewingKey { key: key.0 })?)) +} + +pub fn try_set_viewing_key( + deps: DepsMut, + _env: Env, + info: MessageInfo, + key: String, +) -> StdResult { + HashedKey(Key(key).hash()).save(deps.storage, info.sender)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetViewingKey { status: Success })?)) +} + +pub fn try_block_permit_key( + deps: DepsMut, + _env: Env, + info: MessageInfo, + key: String, +) -> StdResult { + PermitKey::revoke(deps.storage, key, info.sender)?; + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::BlockPermitKey { + status: Success, + })?), + ) +} diff --git a/contracts/query_auth/src/lib.rs b/contracts/query_auth/src/lib.rs new file mode 100644 index 0000000..ff47582 --- /dev/null +++ b/contracts/query_auth/src/lib.rs @@ -0,0 +1,6 @@ +pub mod contract; +pub mod handle; +pub mod query; + +#[cfg(test)] +mod tests; diff --git a/contracts/query_auth/src/query.rs b/contracts/query_auth/src/query.rs new file mode 100644 index 0000000..b052201 --- /dev/null +++ b/contracts/query_auth/src/query.rs @@ -0,0 +1,33 @@ +use shade_protocol::{ + c_std::{Addr, Deps, StdResult}, + contract_interfaces::query_auth::{ + auth::{Key, PermitKey}, + Admin, + ContractStatus, + QueryAnswer, + QueryPermit, + }, + utils::storage::plus::{ItemStorage, MapStorage}, +}; + +pub fn config(deps: Deps) -> StdResult { + Ok(QueryAnswer::Config { + admin: Admin::load(deps.storage)?.0, + state: ContractStatus::load(deps.storage)?, + }) +} + +pub fn validate_vk(deps: Deps, user: Addr, key: String) -> StdResult { + Ok(QueryAnswer::ValidateViewingKey { + is_valid: Key::verify(deps.storage, user, key)?, + }) +} + +pub fn validate_permit(deps: Deps, permit: QueryPermit) -> StdResult { + let user = permit.validate(deps.api, None)?.as_addr(None)?; + + Ok(QueryAnswer::ValidatePermit { + user: user.clone(), + is_revoked: PermitKey::may_load(deps.storage, (user, permit.params.key))?.is_some(), + }) +} diff --git a/contracts/query_auth/src/tests/handle.rs b/contracts/query_auth/src/tests/handle.rs new file mode 100644 index 0000000..608db9f --- /dev/null +++ b/contracts/query_auth/src/tests/handle.rs @@ -0,0 +1,274 @@ +use crate::tests::{get_config, init_contract, validate_permit, validate_vk}; +use shade_protocol::{ + c_std::{from_binary, Addr}, + contract_interfaces::{query_auth, query_auth::ContractStatus}, + utils::{asset::Contract, ExecuteCallback}, +}; + +#[test] +fn set_admin() { + let (mut chain, auth) = init_contract().unwrap(); + + let msg = query_auth::ExecuteMsg::SetAdminAuth { + admin: Contract { + address: Addr::unchecked("some_addr"), + code_hash: "some_hash".to_string(), + }, + padding: None, + }; + + assert!( + &msg.test_exec(&auth, &mut chain, Addr::unchecked("not_admin"), &[]) + .is_err() + ); + + assert!( + &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) + .is_ok() + ); + + match get_config(&chain, &auth) { + Ok((admin, _)) => assert_eq!(admin.address, Addr::unchecked("some_addr")), + Err(_) => assert!(false), + }; +} + +#[test] +fn set_runstate() { + let (mut chain, auth) = init_contract().unwrap(); + + let msg = query_auth::ExecuteMsg::SetRunState { + state: ContractStatus::DisableAll, + padding: None, + }; + + assert!( + &msg.test_exec(&auth, &mut chain, Addr::unchecked("not_admin"), &[]) + .is_err() + ); + + assert!( + &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) + .is_ok() + ); + + match get_config(&chain, &auth) { + Ok((_, state)) => assert_eq!(state, ContractStatus::DisableAll), + Err(_) => assert!(false), + }; +} + +#[test] +fn runstate_block_permits() { + let (mut chain, auth) = init_contract().unwrap(); + + // Validate permits + + let msg = query_auth::ExecuteMsg::SetRunState { + state: ContractStatus::DisablePermit, + padding: None, + }; + + assert!( + &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) + .is_ok() + ); + + let msg = query_auth::ExecuteMsg::BlockPermitKey { + key: "key".to_string(), + padding: None, + }; + + assert!( + &msg.test_exec(&auth, &mut chain, Addr::unchecked("user"), &[]) + .is_err() + ); + + let msg = query_auth::ExecuteMsg::SetViewingKey { + key: "key".to_string(), + padding: None, + }; + + assert!( + &msg.test_exec(&auth, &mut chain, Addr::unchecked("user"), &[]) + .is_ok() + ); + + let msg = query_auth::ExecuteMsg::CreateViewingKey { + entropy: "random".to_string(), + padding: None, + }; + + assert!( + &msg.test_exec(&auth, &mut chain, Addr::unchecked("user"), &[]) + .is_ok() + ); + + assert!(validate_permit(&chain, &auth).is_err()); + + assert!(validate_vk(&chain, &auth, "user", "key").is_ok()); +} + +#[test] +fn runstate_block_vks() { + let (mut chain, auth) = init_contract().unwrap(); + + // Validate permits + + let msg = query_auth::ExecuteMsg::SetRunState { + state: ContractStatus::DisableVK, + padding: None, + }; + + assert!( + &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) + .is_ok() + ); + + let msg = query_auth::ExecuteMsg::BlockPermitKey { + key: "key".to_string(), + padding: None, + }; + + assert!( + &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) + .is_ok() + ); + + let msg = query_auth::ExecuteMsg::SetViewingKey { + key: "key".to_string(), + padding: None, + }; + + assert!( + &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) + .is_err() + ); + + let msg = query_auth::ExecuteMsg::CreateViewingKey { + entropy: "random".to_string(), + padding: None, + }; + + assert!( + &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) + .is_err() + ); + + assert!(validate_permit(&chain, &auth).is_ok()); + + assert!(validate_vk(&chain, &auth, "user", "key").is_err()); +} + +#[test] +fn runstate_block_all() { + let (mut chain, auth) = init_contract().unwrap(); + + // Validate permits + + let msg = query_auth::ExecuteMsg::SetRunState { + state: ContractStatus::DisableAll, + padding: None, + }; + + assert!( + &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) + .is_ok() + ); + + let msg = query_auth::ExecuteMsg::BlockPermitKey { + key: "key".to_string(), + padding: None, + }; + + assert!( + &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) + .is_err() + ); + + let msg = query_auth::ExecuteMsg::SetViewingKey { + key: "key".to_string(), + padding: None, + }; + + assert!( + &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) + .is_err() + ); + + let msg = query_auth::ExecuteMsg::CreateViewingKey { + entropy: "random".to_string(), + padding: None, + }; + + assert!( + &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) + .is_err() + ); + + assert!(validate_permit(&chain, &auth).is_err()); + + assert!(validate_vk(&chain, &auth, "user", "key").is_err()); +} + +#[test] +fn set_vk() { + let (mut chain, auth) = init_contract().unwrap(); + + assert!( + query_auth::ExecuteMsg::SetViewingKey { + key: "password".to_string(), + padding: None + } + .test_exec(&auth, &mut chain, Addr::unchecked("user"), &[]) + .is_ok() + ); +} + +#[test] +fn create_vk() { + let (mut chain, auth) = init_contract().unwrap(); + + let data = query_auth::ExecuteMsg::CreateViewingKey { + entropy: "blah".to_string(), + padding: None, + } + .test_exec(&auth, &mut chain, Addr::unchecked("user"), &[]) + .unwrap() + .data + .unwrap(); + + let msg: query_auth::ExecuteAnswer = from_binary(&data).unwrap(); + + let key = match msg { + query_auth::ExecuteAnswer::CreateViewingKey { key, .. } => key, + _ => { + assert!(false); + "doesnt_work".to_string() + } + }; + + assert!(validate_vk(&chain, &auth, "user", &key).unwrap()); +} + +#[test] +fn block_permit_key() { + let (mut chain, auth) = init_contract().unwrap(); + + let msg = query_auth::ExecuteMsg::BlockPermitKey { + key: "key".to_string(), + padding: None, + }; + + assert!( + msg.test_exec( + &auth, + &mut chain, + Addr::unchecked("secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq"), + &[] + ) + .is_ok() + ); + + assert!(validate_permit(&chain, &auth).unwrap().1); +} diff --git a/contracts/query_auth/src/tests/mod.rs b/contracts/query_auth/src/tests/mod.rs new file mode 100644 index 0000000..266ea2e --- /dev/null +++ b/contracts/query_auth/src/tests/mod.rs @@ -0,0 +1,119 @@ +pub mod handle; +pub mod query; + +use shade_multi_test::multi::{admin::Admin, query_auth::QueryAuth}; +use shade_protocol::{ + admin::{self, helpers::AdminPermissions}, + c_std::{Addr, Binary, ContractInfo, StdError, StdResult}, + contract_interfaces::query_auth::{self, PermitData, QueryPermit}, + multi_test::{App}, + query_auth::{ContractStatus}, + query_authentication::transaction::{PermitSignature, PubKey}, + utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +pub fn init_contract() -> StdResult<(App, ContractInfo)> { + let mut chain = App::default(); + + let admin = admin::InstantiateMsg { + super_admin: Some("admin".into()), + } + .test_init( + Admin::default(), + &mut chain, + Addr::unchecked("admin"), + "admin_auth", + &[], + ) + .unwrap(); + + let auth = query_auth::InstantiateMsg { + admin_auth: Contract { + address: admin.address.clone(), + code_hash: admin.code_hash.clone(), + }, + prng_seed: Binary::from("random".as_bytes()), + } + .test_init( + QueryAuth::default(), + &mut chain, + Addr::unchecked("admin"), + "query_auth", + &[], + ) + .unwrap(); + + admin::ExecuteMsg::UpdateRegistryBulk { + actions: vec![ + admin::RegistryAction::RegisterAdmin { + user: "admin".to_string(), + }, + admin::RegistryAction::GrantAccess { + permissions: vec![AdminPermissions::QueryAuthAdmin.into_string()], + user: "admin".to_string(), + }, + ], + } + .test_exec(&admin, &mut chain, Addr::unchecked("admin"), &[]) + .unwrap(); + + Ok((chain, auth)) +} + +pub fn get_permit() -> QueryPermit { + QueryPermit { + params: PermitData { + key: "key".to_string(), + data: Binary::from_base64("c29tZSBzdHJpbmc=").unwrap() + }, + signature: PermitSignature { + pub_key: PubKey::new( + Binary::from_base64( + "A9NjbriiP7OXCpoTov9ox/35+h5k0y1K0qCY/B09YzAP" + ).unwrap() + ), + signature: Binary::from_base64( + "XRzykrPmMs0ZhksNXX+eU0TM21fYBZXZogr5wYZGGy11t2ntfySuQNQJEw6D4QKvPsiU9gYMsQ259dOzMZNAEg==" + ).unwrap() + }, + account_number: None, + chain_id: Some(String::from("chain")), + sequence: None, + memo: None + } +} + +pub fn get_config(chain: &App, auth: &ContractInfo) -> StdResult<(Contract, ContractStatus)> { + let query: query_auth::QueryAnswer = + query_auth::QueryMsg::Config {}.test_query(&auth, &chain)?; + + match query { + query_auth::QueryAnswer::Config { admin, state } => Ok((admin, state)), + _ => Err(StdError::generic_err("Config not found")), + } +} + +pub fn validate_vk(chain: &App, auth: &ContractInfo, user: &str, key: &str) -> StdResult { + let query: query_auth::QueryAnswer = query_auth::QueryMsg::ValidateViewingKey { + user: Addr::unchecked(user), + key: key.to_string(), + } + .test_query(&auth, &chain)?; + + match query { + query_auth::QueryAnswer::ValidateViewingKey { is_valid } => Ok(is_valid), + _ => Err(StdError::generic_err("VK not found")), + } +} + +pub fn validate_permit(chain: &App, auth: &ContractInfo) -> StdResult<(Addr, bool)> { + let query: query_auth::QueryAnswer = query_auth::QueryMsg::ValidatePermit { + permit: get_permit(), + } + .test_query(&auth, &chain)?; + + match query { + query_auth::QueryAnswer::ValidatePermit { user, is_revoked } => Ok((user, is_revoked)), + _ => Err(StdError::generic_err("VK not found")), + } +} diff --git a/contracts/query_auth/src/tests/query.rs b/contracts/query_auth/src/tests/query.rs new file mode 100644 index 0000000..de86a25 --- /dev/null +++ b/contracts/query_auth/src/tests/query.rs @@ -0,0 +1,50 @@ +use crate::tests::{get_config, get_permit, init_contract, validate_permit, validate_vk}; +use shade_protocol::{ + c_std::{Addr}, + contract_interfaces::{query_auth, query_auth::ContractStatus}, + utils::{ExecuteCallback}, +}; + +#[test] +fn config() { + let (chain, auth) = init_contract().unwrap(); + + let (_admin, state) = get_config(&chain, &auth).unwrap(); + + assert_eq!(state, ContractStatus::Default); +} + +#[test] +fn vk_validation() { + let (mut chain, auth) = init_contract().unwrap(); + + assert!(!validate_vk(&chain, &auth, "user", "password").unwrap()); + + assert!( + query_auth::ExecuteMsg::SetViewingKey { + key: "password".to_string(), + padding: None + } + .test_exec(&auth, &mut chain, Addr::unchecked("user"), &[]) + .is_ok() + ); + + assert!(!validate_vk(&chain, &auth, "user", "not_password").unwrap()); + + assert!(validate_vk(&chain, &auth, "user", "password").unwrap()); +} + +#[test] +fn permit_validation() { + let _permit = get_permit(); + + let (chain, auth) = init_contract().unwrap(); + + let (user, is_revoked) = validate_permit(&chain, &auth).unwrap(); + + assert!(!is_revoked); + assert_eq!( + user, + Addr::unchecked("secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq") + ); +} diff --git a/contracts/snip20/.cargo/config b/contracts/snip20/.cargo/config new file mode 100644 index 0000000..c1e7c50 --- /dev/null +++ b/contracts/snip20/.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" \ No newline at end of file diff --git a/contracts/snip20/.circleci/config.yml b/contracts/snip20/.circleci/config.yml new file mode 100644 index 0000000..a6f10d6 --- /dev/null +++ b/contracts/snip20/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.46 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/snip20/Cargo.toml b/contracts/snip20/Cargo.toml new file mode 100644 index 0000000..4694cba --- /dev/null +++ b/contracts/snip20/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "snip20" +version = "0.1.0" +authors = ["Guy Garcia "] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ + "storage", + "math", + "storage_plus", + "snip20-impl", + "query_auth", +] } +cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" } + +[dev-dependencies] +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ "multi-test", "admin" ] } +shade-multi-test = { path = "../../packages/multi_test", features = [ "snip20", "query_auth", "admin" ] } diff --git a/contracts/snip20/Makefile b/contracts/snip20/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/contracts/snip20/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/snip20/src/batch.rs b/contracts/snip20/src/batch.rs new file mode 100644 index 0000000..84e0e17 --- /dev/null +++ b/contracts/snip20/src/batch.rs @@ -0,0 +1,53 @@ +//! Types used in batch operations + +use shade_protocol::cosmwasm_schema::cw_serde; + +use shade_protocol::c_std::{Addr, Binary, Uint128}; + +#[cw_serde] +pub struct TransferAction { + pub recipient: Addr, + pub amount: Uint128, + pub memo: Option, +} + +#[cw_serde] +pub struct SendAction { + pub recipient: Addr, + pub recipient_code_hash: Option, + pub amount: Uint128, + pub msg: Option, + pub memo: Option, +} + +#[cw_serde] +pub struct TransferFromAction { + pub owner: Addr, + pub recipient: Addr, + pub amount: Uint128, + pub memo: Option, +} + +#[cw_serde] +pub struct SendFromAction { + pub owner: Addr, + pub recipient: Addr, + pub recipient_code_hash: Option, + pub amount: Uint128, + pub msg: Option, + pub memo: Option, +} + +#[cw_serde] +pub struct MintAction { + pub recipient: Addr, + pub amount: Uint128, + pub memo: Option, +} + +#[cw_serde] +pub struct BurnFromAction { + pub owner: Addr, + pub amount: Uint128, + pub memo: Option, +} diff --git a/contracts/snip20/src/contract.rs b/contracts/snip20/src/contract.rs new file mode 100644 index 0000000..94656b0 --- /dev/null +++ b/contracts/snip20/src/contract.rs @@ -0,0 +1,443 @@ +use crate::{ + handle::{ + allowance::{ + try_batch_send_from, + try_batch_transfer_from, + try_decrease_allowance, + try_increase_allowance, + try_send_from, + try_transfer_from, + }, + burning::{try_batch_burn_from, try_burn, try_burn_from}, + minting::{try_add_minters, try_batch_mint, try_mint, try_remove_minters, try_set_minters}, + transfers::{try_batch_send, try_batch_transfer, try_send, try_transfer}, + try_change_admin, + try_create_viewing_key, + try_deposit, + try_redeem, + try_register_receive, + try_revoke_permit, + try_set_contract_status, + try_set_viewing_key, + try_update_query_auth, + }, + query, +}; +use shade_protocol::{ + c_std::{ + shd_entry_point, + to_binary, + Addr, + Binary, + Deps, + DepsMut, + Env, + MessageInfo, + Response, + StdResult, + }, + contract_interfaces::snip20::{ + errors::{ + action_disabled, + invalid_viewing_key, + not_authenticated_msg, + permit_revoked, + unauthorized_permit, + }, + manager::{ContractStatusLevel, Key, PermitKey}, + ExecuteMsg, + InstantiateMsg, + Permission, + QueryMsg, + QueryWithPermit, + }, + query_auth::helpers::{authenticate_permit, authenticate_vk, PermitAuthentication}, + snip20::{errors::permit_not_found, manager::QueryAuth, PermitParams}, + utils::{ + asset::validate_vec, + pad_handle_result, + pad_query_result, + storage::plus::{ItemStorage, MapStorage}, + }, +}; + +// Used to pad up responses for better privacy. +pub const RESPONSE_BLOCK_SIZE: usize = 256; +pub const PREFIX_REVOKED_PERMITS: &str = "revoked_permits"; + +#[shd_entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + msg.save(deps.storage, deps.api, env, info)?; + Ok(Response::new()) +} + +#[shd_entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + // Check if transfers are allowed + let status = ContractStatusLevel::load(deps.storage)?; + match status { + // Ignore if normal run + ContractStatusLevel::NormalRun => {} + // Allow only status level updates or redeeming + ContractStatusLevel::StopAllButRedeems | ContractStatusLevel::StopAll => match msg { + ExecuteMsg::Redeem { .. } => { + if status != ContractStatusLevel::StopAllButRedeems { + return Err(action_disabled()); + } + } + ExecuteMsg::SetContractStatus { .. } => {} + _ => return Err(action_disabled()), + }, + } + + pad_handle_result( + match msg { + ExecuteMsg::Redeem { + amount, denom: _, .. + } => try_redeem(deps, env, info, amount), + + ExecuteMsg::Deposit { .. } => try_deposit(deps, env, info), + + ExecuteMsg::Transfer { + recipient, + amount, + memo, + .. + } => { + let recipient = deps.api.addr_validate(recipient.as_str())?; + try_transfer(deps, env, info, recipient, amount, memo) + } + + ExecuteMsg::Send { + recipient, + recipient_code_hash, + amount, + msg, + memo, + .. + } => { + let recipient = deps.api.addr_validate(recipient.as_str())?; + try_send( + deps, + env, + info, + recipient, + recipient_code_hash, + amount, + memo, + msg, + ) + } + + ExecuteMsg::BatchTransfer { actions, .. } => { + try_batch_transfer(deps, env, info, actions) + } + + ExecuteMsg::BatchSend { actions, .. } => try_batch_send(deps, env, info, actions), + + ExecuteMsg::Burn { amount, memo, .. } => try_burn(deps, env, info, amount, memo), + + ExecuteMsg::RegisterReceive { code_hash, .. } => { + try_register_receive(deps, env, info, code_hash) + } + + ExecuteMsg::CreateViewingKey { entropy, .. } => { + try_create_viewing_key(deps, env, info, entropy) + } + + ExecuteMsg::SetViewingKey { key, .. } => try_set_viewing_key(deps, env, info, key), + + ExecuteMsg::IncreaseAllowance { + spender, + amount, + expiration, + .. + } => { + let spender = deps.api.addr_validate(spender.as_str())?; + try_increase_allowance(deps, env, info, spender, amount, expiration) + } + ExecuteMsg::DecreaseAllowance { + spender, + amount, + expiration, + .. + } => { + let spender = deps.api.addr_validate(spender.as_str())?; + try_decrease_allowance(deps, env, info, spender, amount, expiration) + } + ExecuteMsg::TransferFrom { + owner, + recipient, + amount, + memo, + .. + } => { + let owner = deps.api.addr_validate(owner.as_str())?; + let recipient = deps.api.addr_validate(recipient.as_str())?; + try_transfer_from(deps, env, info, owner, recipient, amount, memo) + } + ExecuteMsg::SendFrom { + owner, + recipient, + recipient_code_hash, + amount, + msg, + memo, + .. + } => { + let owner = deps.api.addr_validate(owner.as_str())?; + let recipient = deps.api.addr_validate(recipient.as_str())?; + try_send_from( + deps, + env, + info, + owner, + recipient, + recipient_code_hash, + amount, + msg, + memo, + ) + } + ExecuteMsg::BatchTransferFrom { actions, .. } => { + try_batch_transfer_from(deps, env, info, actions) + } + + ExecuteMsg::BatchSendFrom { actions, .. } => { + try_batch_send_from(deps, env, info, actions) + } + + ExecuteMsg::BurnFrom { + owner, + amount, + memo, + .. + } => { + let owner = deps.api.addr_validate(owner.as_str())?; + try_burn_from(deps, env, info, owner, amount, memo) + } + ExecuteMsg::BatchBurnFrom { actions, .. } => { + try_batch_burn_from(deps, env, info, actions) + } + ExecuteMsg::Mint { + recipient, + amount, + memo, + .. + } => { + let recipient = deps.api.addr_validate(recipient.as_str())?; + try_mint(deps, env, info, recipient, amount, memo) + } + ExecuteMsg::BatchMint { actions, .. } => try_batch_mint(deps, env, info, actions), + ExecuteMsg::AddMinters { minters, .. } => { + let minters = validate_vec(deps.api, minters)?; + try_add_minters(deps, env, info, minters) + } + ExecuteMsg::RemoveMinters { minters, .. } => { + let minters = validate_vec(deps.api, minters)?; + try_remove_minters(deps, env, info, minters) + } + ExecuteMsg::SetMinters { minters, .. } => { + let minters = validate_vec(deps.api, minters)?; + try_set_minters(deps, env, info, minters) + } + ExecuteMsg::ChangeAdmin { address, .. } => { + let address = deps.api.addr_validate(address.as_str())?; + try_change_admin(deps, env, info, address) + } + ExecuteMsg::UpdateQueryAuth { auth } => try_update_query_auth(deps, env, info, auth), + ExecuteMsg::SetContractStatus { level, .. } => { + try_set_contract_status(deps, env, info, level) + } + + ExecuteMsg::RevokePermit { permit_name, .. } => { + try_revoke_permit(deps, env, info, permit_name) + } + }, + RESPONSE_BLOCK_SIZE, + ) +} + +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + pad_query_result( + to_binary(&match msg { + QueryMsg::TokenInfo {} => query::token_info(deps)?, + QueryMsg::TokenConfig {} => query::token_config(deps)?, + QueryMsg::ContractStatus {} => query::contract_status(deps)?, + QueryMsg::ExchangeRate {} => query::exchange_rate(deps)?, + QueryMsg::Minters {} => query::minters(deps)?, + + QueryMsg::WithPermit { + permit, + auth_permit, + query, + } => { + // Verify which authentication setting is set + let account: Addr; + let params: PermitParams; + + match QueryAuth::may_load(deps.storage)? { + None => { + if let Some(permit) = permit { + // Validate permit and get account + account = permit.validate(deps.api, None)?.as_addr(None)?; + + // Check that permit is not revoked + if PermitKey::may_load( + deps.storage, + (account.clone(), permit.params.permit_name.clone()), + )? + .is_some() + { + return Err(permit_revoked(permit.params.permit_name)); + } + + params = permit.params; + } else { + return Err(permit_not_found()); + } + } + Some(authenticator) => { + if let Some(permit) = auth_permit { + let res: PermitAuthentication = + authenticate_permit(permit, &deps.querier, authenticator.0)?; + + if res.revoked { + return Err(permit_revoked(res.data.permit_name)); + } + + account = res.sender; + params = res.data; + } else { + return Err(permit_not_found()); + } + } + }; + + match query { + QueryWithPermit::Allowance { owner, spender, .. } => { + let owner = deps.api.addr_validate(&owner)?; + let spender = deps.api.addr_validate(&spender)?; + + if !params.contains(Permission::Allowance) { + return Err(unauthorized_permit(Permission::Allowance)); + } + + if owner != account && spender != account { + return Err(unauthorized_permit(Permission::Allowance)); + } + + query::allowance(deps, owner, spender)? + } + QueryWithPermit::Balance {} => { + if !params.contains(Permission::Balance) { + return Err(unauthorized_permit(Permission::Balance)); + } + + query::balance(deps, account.clone())? + } + QueryWithPermit::TransferHistory { page, page_size } => { + if !params.contains(Permission::History) { + return Err(unauthorized_permit(Permission::History)); + } + + query::transfer_history( + deps, + account.clone(), + page.unwrap_or(0), + page_size, + )? + } + QueryWithPermit::TransactionHistory { page, page_size } => { + if !params.contains(Permission::History) { + return Err(unauthorized_permit(Permission::History)); + } + + query::transaction_history( + deps, + account.clone(), + page.unwrap_or(0), + page_size, + )? + } + } + } + + _ => match msg { + QueryMsg::Allowance { + owner, + spender, + key, + } => { + let owner = deps.api.addr_validate(&owner)?; + let spender = deps.api.addr_validate(&spender)?; + if try_authenticate_vk(&deps, owner.clone(), key.clone())? + || try_authenticate_vk(&deps, spender.clone(), key)? + { + query::allowance(deps, owner, spender)? + } else { + return Err(invalid_viewing_key()); + } + } + QueryMsg::Balance { address, key } => { + let address = deps.api.addr_validate(&address)?; + if try_authenticate_vk(&deps, address.clone(), key.clone())? { + query::balance(deps, address.clone())? + } else { + return Err(invalid_viewing_key()); + } + } + QueryMsg::TransferHistory { + address, + key, + page, + page_size, + } => { + let address = deps.api.addr_validate(&address)?; + if try_authenticate_vk(&deps, address.clone(), key.clone())? { + query::transfer_history( + deps, + address.clone(), + page.unwrap_or(0), + page_size, + )? + } else { + return Err(invalid_viewing_key()); + } + } + QueryMsg::TransactionHistory { + address, + key, + page, + page_size, + } => { + let address = deps.api.addr_validate(&address)?; + if try_authenticate_vk(&deps, address.clone(), key.clone())? { + query::transaction_history( + deps, + address.clone(), + page.unwrap_or(0), + page_size, + )? + } else { + return Err(invalid_viewing_key()); + } + } + _ => return Err(not_authenticated_msg()), + }, + }), + RESPONSE_BLOCK_SIZE, + ) +} + +fn try_authenticate_vk(deps: &Deps, address: Addr, key: String) -> StdResult { + match QueryAuth::may_load(deps.storage)? { + None => Key::verify(deps.storage, address, key), + Some(authenticator) => authenticate_vk(address, key, &deps.querier, &authenticator.0), + } +} diff --git a/contracts/snip20/src/handle/allowance.rs b/contracts/snip20/src/handle/allowance.rs new file mode 100644 index 0000000..69458d7 --- /dev/null +++ b/contracts/snip20/src/handle/allowance.rs @@ -0,0 +1,203 @@ +use crate::handle::transfers::{try_send_impl, try_transfer_impl}; +use shade_protocol::{ + c_std::{to_binary, Addr, Binary, DepsMut, Env, MessageInfo, Response, StdResult, Uint128}, + contract_interfaces::snip20::{ + batch, + manager::{Allowance, CoinInfo}, + ExecuteAnswer, + }, + utils::{ + generic_response::ResponseStatus::Success, + storage::plus::{ItemStorage, MapStorage}, + }, +}; + +pub fn try_increase_allowance( + deps: DepsMut, + env: Env, + info: MessageInfo, + spender: Addr, + amount: Uint128, + expiration: Option, +) -> StdResult { + let owner = info.sender; + let mut allowance = Allowance::may_load(deps.storage, (owner.clone(), spender.clone()))? + .unwrap_or(Allowance::default()); + + // Reset allowance if its expired + if allowance.is_expired(&env.block) { + allowance.amount = amount; + allowance.expiration = None; + } else { + allowance.amount = match allowance.amount.checked_add(amount) { + Ok(amount) => amount, + Err(_) => Uint128::MAX, + } + } + + if expiration.is_some() { + allowance.expiration = expiration; + } + + allowance.save(deps.storage, (owner.clone(), spender.clone()))?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::IncreaseAllowance { + spender, + owner, + allowance: allowance.amount, + })?), + ) +} + +pub fn try_decrease_allowance( + deps: DepsMut, + env: Env, + info: MessageInfo, + spender: Addr, + amount: Uint128, + expiration: Option, +) -> StdResult { + let owner = info.sender; + + let mut allowance = Allowance::load(deps.storage, (owner.clone(), spender.clone()))?; + + // Reset allowance if its expired + if allowance.is_expired(&env.block) { + allowance = Allowance::default(); + } else { + allowance.amount = match allowance.amount.checked_sub(amount) { + Ok(amount) => amount, + Err(_) => Uint128::zero(), + } + } + + if expiration.is_some() { + allowance.expiration = expiration; + } + + allowance.save(deps.storage, (owner.clone(), spender.clone()))?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::IncreaseAllowance { + spender, + owner, + allowance: allowance.amount, + })?), + ) +} + +pub fn try_transfer_from( + deps: DepsMut, + env: Env, + info: MessageInfo, + owner: Addr, + recipient: Addr, + amount: Uint128, + memo: Option, +) -> StdResult { + let denom = CoinInfo::load(deps.storage)?.symbol; + try_transfer_impl( + deps.storage, + &info.sender, + Some(&owner), + &recipient, + amount, + memo, + denom, + &env.block, + )?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::TransferFrom { status: Success })?)) +} + +pub fn try_batch_transfer_from( + deps: DepsMut, + env: Env, + info: MessageInfo, + actions: Vec, +) -> StdResult { + let denom = CoinInfo::load(deps.storage)?.symbol; + let block = &env.block; + for action in actions { + try_transfer_impl( + deps.storage, + &info.sender, + Some(&deps.api.addr_validate(action.owner.as_str())?), + &deps.api.addr_validate(action.recipient.as_str())?, + action.amount, + action.memo, + denom.clone(), + block, + )?; + } + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::BatchTransferFrom { + status: Success, + })?), + ) +} + +pub fn try_send_from( + deps: DepsMut, + env: Env, + info: MessageInfo, + owner: Addr, + recipient: Addr, + recipient_code_hash: Option, + amount: Uint128, + msg: Option, + memo: Option, +) -> StdResult { + let mut messages = vec![]; + let denom = CoinInfo::load(deps.storage)?.symbol; + try_send_impl( + deps.storage, + &mut messages, + &info.sender, + Some(&owner), + &recipient, + recipient_code_hash, + amount, + memo, + msg, + denom, + &env.block, + )?; + + Ok(Response::new() + .set_data(to_binary(&ExecuteAnswer::SendFrom { status: Success })?) + .add_submessages(messages)) +} + +pub fn try_batch_send_from( + deps: DepsMut, + env: Env, + info: MessageInfo, + actions: Vec, +) -> StdResult { + let mut messages = vec![]; + let sender = info.sender; + let denom = CoinInfo::load(deps.storage)?.symbol; + + for action in actions { + try_send_impl( + deps.storage, + &mut messages, + &sender, + Some(&deps.api.addr_validate(action.owner.as_str())?), + &deps.api.addr_validate(action.recipient.as_str())?, + action.recipient_code_hash, + action.amount, + action.memo, + action.msg, + denom.clone(), + &env.block, + )?; + } + + Ok(Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchSendFrom { status: Success })?) + .add_submessages(messages)) +} diff --git a/contracts/snip20/src/handle/burning.rs b/contracts/snip20/src/handle/burning.rs new file mode 100644 index 0000000..e0e817f --- /dev/null +++ b/contracts/snip20/src/handle/burning.rs @@ -0,0 +1,127 @@ +use shade_protocol::{ + c_std::{to_binary, Addr, DepsMut, Env, MessageInfo, Response, StdResult, Uint128}, + contract_interfaces::snip20::{ + batch, + errors::burning_disabled, + manager::{Allowance, Balance, CoinInfo, Config, TotalSupply}, + transaction_history::store_burn, + ExecuteAnswer, + }, + utils::{generic_response::ResponseStatus::Success, storage::plus::ItemStorage}, +}; + +pub fn try_burn( + deps: DepsMut, + env: Env, + info: MessageInfo, + amount: Uint128, + memo: Option, +) -> StdResult { + let sender = &info.sender; + let denom = CoinInfo::load(deps.storage)?.symbol; + + // Burn enabled + if !Config::burn_enabled(deps.storage)? { + return Err(burning_disabled()); + } + + Balance::sub(deps.storage, amount, sender)?; + // Dec total supply + TotalSupply::sub(deps.storage, amount)?; + + store_burn( + deps.storage, + &sender, + &sender, + amount, + denom, + memo, + &env.block, + )?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Burn { status: Success })?)) +} + +pub fn try_burn_from( + deps: DepsMut, + env: Env, + info: MessageInfo, + owner: Addr, + amount: Uint128, + memo: Option, +) -> StdResult { + let sender = &info.sender; + let denom = CoinInfo::load(deps.storage)?.symbol; + + // Burn enabled + if !Config::burn_enabled(deps.storage)? { + return Err(burning_disabled()); + } + + Allowance::spend(deps.storage, &owner, &sender, amount, &env.block)?; + Balance::sub(deps.storage, amount, &owner)?; + // Dec total supply + TotalSupply::sub(deps.storage, amount)?; + + store_burn( + deps.storage, + &owner, + &sender, + amount, + denom, + memo, + &env.block, + )?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::BurnFrom { status: Success })?)) +} + +pub fn try_batch_burn_from( + deps: DepsMut, + env: Env, + info: MessageInfo, + actions: Vec, +) -> StdResult { + let sender = &info.sender; + let denom = CoinInfo::load(deps.storage)?.symbol; + + // Burn enabled + if !Config::burn_enabled(deps.storage)? { + return Err(burning_disabled()); + } + + let mut supply = TotalSupply::load(deps.storage)?; + + for action in actions { + Allowance::spend( + deps.storage, + &deps.api.addr_validate(action.owner.as_str())?, + &sender, + action.amount, + &env.block, + )?; + + Balance::sub( + deps.storage, + action.amount, + &deps.api.addr_validate(action.owner.as_str())?, + )?; + + // Dec total supply + supply.0 = supply.0.checked_sub(action.amount)?; + + store_burn( + deps.storage, + &deps.api.addr_validate(action.owner.as_str())?, + &sender, + action.amount, + denom.clone(), + action.memo, + &env.block, + )?; + } + + supply.save(deps.storage)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::BatchBurnFrom { status: Success })?)) +} diff --git a/contracts/snip20/src/handle/minting.rs b/contracts/snip20/src/handle/minting.rs new file mode 100644 index 0000000..422a863 --- /dev/null +++ b/contracts/snip20/src/handle/minting.rs @@ -0,0 +1,158 @@ +use shade_protocol::{ + c_std::{to_binary, Addr, DepsMut, Env, MessageInfo, Response, StdResult, Storage, Uint128}, + contract_interfaces::snip20::{ + batch, + errors::{minting_disabled, not_admin, not_minter}, + manager::{Admin, Balance, CoinInfo, Config, Minters, TotalSupply}, + transaction_history::store_mint, + ExecuteAnswer, + }, + utils::{generic_response::ResponseStatus::Success, storage::plus::ItemStorage}, +}; + +fn try_mint_impl( + storage: &mut dyn Storage, + minter: &Addr, + recipient: &Addr, + amount: Uint128, + denom: String, + memo: Option, + block: &shade_protocol::c_std::BlockInfo, +) -> StdResult<()> { + Balance::add(storage, amount, recipient)?; + store_mint(storage, minter, recipient, amount, denom, memo, block)?; + Ok(()) +} + +pub fn try_mint( + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: Addr, + amount: Uint128, + memo: Option, +) -> StdResult { + // Mint enabled + if !Config::mint_enabled(deps.storage)? { + return Err(minting_disabled()); + } + // User is minter + if !Minters::load(deps.storage)?.0.contains(&info.sender) { + return Err(not_minter(&info.sender)); + } + // Inc total supply + TotalSupply::add(deps.storage, amount)?; + let sender = info.sender; + let block = env.block; + let denom = CoinInfo::load(deps.storage)?.symbol; + try_mint_impl( + deps.storage, + &sender, + &recipient, + amount, + denom, + memo, + &block, + )?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?)) +} + +pub fn try_batch_mint( + deps: DepsMut, + env: Env, + info: MessageInfo, + actions: Vec, +) -> StdResult { + // Mint enabled + if !Config::mint_enabled(deps.storage)? { + return Err(minting_disabled()); + } + // User is minter + if !Minters::load(deps.storage)?.0.contains(&info.sender) { + return Err(not_minter(&info.sender)); + } + + let sender = info.sender; + let block = env.block; + let denom = CoinInfo::load(deps.storage)?.symbol; + let supply = TotalSupply::load(deps.storage)?; + for action in actions { + supply.0.checked_add(action.amount)?; + try_mint_impl( + deps.storage, + &sender, + &deps.api.addr_validate(action.recipient.as_str())?, + action.amount, + denom.clone(), + action.memo, + &block, + )?; + } + supply.save(deps.storage)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::BatchMint { status: Success })?)) +} + +pub fn try_add_minters( + deps: DepsMut, + _env: Env, + info: MessageInfo, + new_minters: Vec, +) -> StdResult { + // Mint enabled + if !Config::mint_enabled(deps.storage)? { + return Err(minting_disabled()); + } + if Admin::load(deps.storage)?.0 != info.sender { + return Err(not_admin()); + } + + let mut minters = Minters::load(deps.storage)?; + minters.0.extend(new_minters); + minters.save(deps.storage)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::AddMinters { status: Success })?)) +} + +pub fn try_remove_minters( + deps: DepsMut, + _env: Env, + info: MessageInfo, + minters_to_remove: Vec, +) -> StdResult { + // Mint enabled + if !Config::mint_enabled(deps.storage)? { + return Err(minting_disabled()); + } + if Admin::load(deps.storage)?.0 != info.sender { + return Err(not_admin()); + } + + let mut minters = Minters::load(deps.storage)?; + for minter in minters_to_remove { + minters.0.retain(|x| x != &minter); + } + minters.save(deps.storage)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RemoveMinters { status: Success })?)) +} + +pub fn try_set_minters( + deps: DepsMut, + _env: Env, + info: MessageInfo, + minters: Vec, +) -> StdResult { + // Mint enabled + if !Config::mint_enabled(deps.storage)? { + return Err(minting_disabled()); + } + if Admin::load(deps.storage)?.0 != info.sender { + return Err(not_admin()); + } + + Minters(minters).save(deps.storage)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetMinters { status: Success })?)) +} diff --git a/contracts/snip20/src/handle/mod.rs b/contracts/snip20/src/handle/mod.rs new file mode 100644 index 0000000..fb1cbdd --- /dev/null +++ b/contracts/snip20/src/handle/mod.rs @@ -0,0 +1,238 @@ +pub mod allowance; +pub mod burning; +pub mod minting; +pub mod transfers; + +use shade_protocol::{ + c_std::{ + to_binary, + Addr, + BankMsg, + Coin, + CosmosMsg, + DepsMut, + Env, + MessageInfo, + Response, + StdResult, + Uint128, + }, + contract_interfaces::snip20::{ + errors::{ + deposit_disabled, + no_tokens_received, + not_admin, + not_enough_tokens, + redeem_disabled, + unsupported_token, + }, + manager::{ + Admin, + Balance, + CoinInfo, + Config, + ContractStatusLevel, + HashedKey, + Key, + PermitKey, + RandSeed, + ReceiverHash, + TotalSupply, + }, + transaction_history::{store_deposit, store_redeem}, + ExecuteAnswer, + }, + query_authentication::viewing_keys::ViewingKey, + snip20::manager::QueryAuth, + utils::{ + generic_response::ResponseStatus::Success, + storage::plus::{ItemStorage, MapStorage}, + }, + Contract, +}; + +pub fn try_redeem( + deps: DepsMut, + env: Env, + info: MessageInfo, + amount: Uint128, +) -> StdResult { + let sender = info.sender; + + if !Config::redeem_enabled(deps.storage)? { + return Err(redeem_disabled()); + } + + Balance::sub(deps.storage, amount, &sender)?; + TotalSupply::sub(deps.storage, amount)?; + + let token_reserve = Uint128::from( + deps.querier + .query_balance(&env.contract.address, "uscrt")? + .amount, + ); + if amount > token_reserve { + return Err(not_enough_tokens(amount, token_reserve)); + } + + let withdrawal_coins: Vec = vec![Coin { + denom: "uscrt".to_string(), + amount: amount.into(), + }]; + + let denom = CoinInfo::load(deps.storage)?.symbol; + + store_redeem(deps.storage, &sender, amount, denom, &env.block)?; + + Ok(Response::new() + .add_message(CosmosMsg::Bank(BankMsg::Send { + to_address: sender.into(), + amount: withdrawal_coins, + })) + .set_data(to_binary(&ExecuteAnswer::Redeem { status: Success })?)) +} + +pub fn try_deposit(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { + let sender = info.sender; + let mut amount = Uint128::zero(); + for coin in &info.funds { + // TODO: implement IBC coins + if coin.denom == "uscrt" { + amount = Uint128::from(coin.amount) + } else { + return Err(unsupported_token()); + } + } + + if amount.is_zero() { + return Err(no_tokens_received()); + } + + if !Config::deposit_enabled(deps.storage)? { + return Err(deposit_disabled()); + } + + TotalSupply::add(deps.storage, amount)?; + Balance::add(deps.storage, amount, &sender)?; + + store_deposit( + deps.storage, + &sender, + amount, + "uscrt".to_string(), + &env.block, + )?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Deposit { status: Success })?)) +} + +pub fn try_change_admin( + deps: DepsMut, + _env: Env, + info: MessageInfo, + address: Addr, +) -> StdResult { + if info.sender != Admin::load(deps.storage)?.0 { + return Err(not_admin()); + } + + Admin(address).save(deps.storage)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::ChangeAdmin { status: Success })?)) +} + +pub fn try_update_query_auth( + deps: DepsMut, + _env: Env, + info: MessageInfo, + auth: Option, +) -> StdResult { + if info.sender != Admin::load(deps.storage)?.0 { + return Err(not_admin()); + } + + if let Some(auth) = auth { + QueryAuth(auth).save(deps.storage)?; + } else { + QueryAuth::remove(deps.storage); + } + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::UpdateQueryAuth { + status: Success, + })?), + ) +} + +pub fn try_set_contract_status( + deps: DepsMut, + _env: Env, + info: MessageInfo, + status_level: ContractStatusLevel, +) -> StdResult { + if info.sender != Admin::load(deps.storage)?.0 { + return Err(not_admin()); + } + + status_level.save(deps.storage)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::SetContractStatus { + status: Success, + })?), + ) +} + +pub fn try_register_receive( + deps: DepsMut, + _env: Env, + info: MessageInfo, + code_hash: String, +) -> StdResult { + ReceiverHash(code_hash).save(deps.storage, info.sender)?; + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::RegisterReceive { + status: Success, + })?), + ) +} + +pub fn try_create_viewing_key( + deps: DepsMut, + env: Env, + info: MessageInfo, + entropy: String, +) -> StdResult { + let seed = RandSeed::load(deps.storage)?.0; + + let key = Key::generate(&info, &env, seed.as_slice(), (&entropy).as_ref()); + + HashedKey(key.hash()).save(deps.storage, info.sender)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::CreateViewingKey { key: key.0 })?)) +} + +pub fn try_set_viewing_key( + deps: DepsMut, + _env: Env, + info: MessageInfo, + key: String, +) -> StdResult { + // TODO: review this + //let seed = RandSeed::load(deps.storage)?.0; + + HashedKey(Key(key).hash()).save(deps.storage, info.sender)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetViewingKey { status: Success })?)) +} + +pub fn try_revoke_permit( + deps: DepsMut, + _env: Env, + info: MessageInfo, + permit_name: String, +) -> StdResult { + PermitKey::revoke(deps.storage, permit_name, info.sender)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RevokePermit { status: Success })?)) +} diff --git a/contracts/snip20/src/handle/transfers.rs b/contracts/snip20/src/handle/transfers.rs new file mode 100644 index 0000000..5f9e0b1 --- /dev/null +++ b/contracts/snip20/src/handle/transfers.rs @@ -0,0 +1,239 @@ +use shade_protocol::{ + c_std::{ + to_binary, + Addr, + Binary, + DepsMut, + Env, + MessageInfo, + Response, + StdResult, + Storage, + SubMsg, + Uint128, + }, + contract_interfaces::snip20::{ + batch, + errors::transfer_disabled, + manager::{Allowance, Balance, CoinInfo, Config, ReceiverHash}, + transaction_history::store_transfer, + ExecuteAnswer, + ReceiverHandleMsg, + }, + utils::{ + generic_response::ResponseStatus::Success, + storage::plus::{ItemStorage, MapStorage}, + ExecuteCallback, + }, + Contract, +}; + +pub fn try_transfer_impl( + storage: &mut dyn Storage, + sender: &Addr, //spender when using from + owner: Option<&Addr>, + recipient: &Addr, + amount: Uint128, + memo: Option, + denom: String, + block: &shade_protocol::c_std::BlockInfo, +) -> StdResult<()> { + if !Config::transfer_enabled(storage)? { + return Err(transfer_disabled()); + } + + let some_owner = match owner { + None => sender, + Some(owner) => { + Allowance::spend(storage, owner, sender, amount, block)?; + owner + } + }; + + Balance::transfer(storage, amount, some_owner, recipient)?; + + store_transfer( + storage, some_owner, sender, recipient, amount, denom, memo, block, + )?; + Ok(()) +} + +pub fn try_transfer( + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: Addr, + amount: Uint128, + memo: Option, +) -> StdResult { + let denom = CoinInfo::load(deps.storage)?.symbol; + try_transfer_impl( + deps.storage, + &info.sender, + None, + &recipient, + amount, + memo, + denom, + &env.block, + )?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?)) +} + +pub fn try_batch_transfer( + deps: DepsMut, + env: Env, + info: MessageInfo, + actions: Vec, +) -> StdResult { + let sender = info.sender; + let block = env.block; + let denom = CoinInfo::load(deps.storage)?.symbol; + for action in actions { + try_transfer_impl( + deps.storage, + &sender, + None, + &deps.api.addr_validate(action.recipient.as_str())?, + action.amount, + action.memo, + denom.clone(), + &block, + )?; + } + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::BatchTransfer { status: Success })?)) +} + +#[allow(clippy::too_many_arguments)] +fn try_add_receiver_api_callback( + storage: &dyn Storage, + messages: &mut Vec, + recipient: Addr, + recipient_code_hash: Option, + msg: Option, + sender: Addr, + from: Addr, + amount: Uint128, + memo: Option, +) -> StdResult<()> { + let receiver_hash = match recipient_code_hash { + None => ReceiverHash::may_load(storage, recipient.clone())?, + Some(hash) => Some(ReceiverHash(hash)), + }; + + if let Some(hash) = receiver_hash { + messages.push(SubMsg::new( + ReceiverHandleMsg::new(sender.to_string(), from.to_string(), amount, memo, msg) + .to_cosmos_msg( + &Contract { + address: recipient, + code_hash: hash.0, + }, + vec![], + )?, + )); + } + Ok(()) +} + +pub fn try_send_impl( + storage: &mut dyn Storage, + messages: &mut Vec, + sender: &Addr, + owner: Option<&Addr>, + recipient: &Addr, + recipient_code_hash: Option, + amount: Uint128, + memo: Option, + msg: Option, + denom: String, + block: &shade_protocol::c_std::BlockInfo, +) -> StdResult<()> { + try_transfer_impl( + storage, + &sender, + owner, + &recipient, + amount, + memo.clone(), + denom, + block, + )?; + try_add_receiver_api_callback( + storage, + messages, + recipient.clone(), + recipient_code_hash, + msg, + sender.clone(), + sender.clone(), + amount, + memo, + )?; + + Ok(()) +} + +pub fn try_send( + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: Addr, + recipient_code_hash: Option, + amount: Uint128, + memo: Option, + msg: Option, +) -> StdResult { + let mut messages = vec![]; + let denom = CoinInfo::load(deps.storage)?.symbol; + + try_send_impl( + deps.storage, + &mut messages, + &info.sender, + None, + &recipient, + recipient_code_hash, + amount, + memo, + msg, + denom, + &env.block, + )?; + + Ok(Response::new() + .set_data(to_binary(&ExecuteAnswer::Send { status: Success })?) + .add_submessages(messages)) +} + +pub fn try_batch_send( + deps: DepsMut, + env: Env, + info: MessageInfo, + actions: Vec, +) -> StdResult { + let mut messages = vec![]; + let sender = info.sender; + let denom = CoinInfo::load(deps.storage)?.symbol; + + for action in actions { + try_send_impl( + deps.storage, + &mut messages, + &sender, + None, + &deps.api.addr_validate(action.recipient.as_str())?, + action.recipient_code_hash, + action.amount, + action.memo, + action.msg, + denom.clone(), + &env.block, + )?; + } + + Ok(Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchSend { status: Success })?) + .add_submessages(messages)) +} diff --git a/contracts/snip20/src/lib.rs b/contracts/snip20/src/lib.rs new file mode 100644 index 0000000..dfefd9b --- /dev/null +++ b/contracts/snip20/src/lib.rs @@ -0,0 +1,6 @@ +pub mod contract; +pub mod handle; +pub mod query; + +#[cfg(test)] +mod tests; \ No newline at end of file diff --git a/contracts/snip20/src/query.rs b/contracts/snip20/src/query.rs new file mode 100644 index 0000000..07b845e --- /dev/null +++ b/contracts/snip20/src/query.rs @@ -0,0 +1,143 @@ +use shade_protocol::c_std::{Uint128, Deps}; +use shade_protocol::c_std::{Addr, StdResult}; +use shade_protocol::{ + contract_interfaces::snip20::{ + manager::{ + Allowance, + Balance, + CoinInfo, + Config, + ContractStatusLevel, + Minters, + TotalSupply, + }, + transaction_history::{RichTx, Tx}, + QueryAnswer, + }, + utils::storage::plus::{ItemStorage, MapStorage}, +}; + +pub fn token_info( + deps: Deps, +) -> StdResult { + let info = CoinInfo::load(deps.storage)?; + + let total_supply = match Config::public_total_supply(deps.storage)? { + true => Some(TotalSupply::load(deps.storage)?.0), + false => None, + }; + + Ok(QueryAnswer::TokenInfo { + name: info.name, + symbol: info.symbol, + decimals: info.decimals, + total_supply, + }) +} + +pub fn token_config( + deps: Deps, +) -> StdResult { + Ok(QueryAnswer::TokenConfig { + // TODO: show the other addrd config items + public_total_supply: Config::public_total_supply(deps.storage)?, + deposit_enabled: Config::deposit_enabled(deps.storage)?, + redeem_enabled: Config::redeem_enabled(deps.storage)?, + mint_enabled: Config::mint_enabled(deps.storage)?, + burn_enabled: Config::burn_enabled(deps.storage)?, + transfer_enabled: Config::transfer_enabled(deps.storage)?, + }) +} + +pub fn contract_status( + deps: Deps, +) -> StdResult { + Ok(QueryAnswer::ContractStatus { + status: ContractStatusLevel::load(deps.storage)?, + }) +} + +pub fn exchange_rate( + deps: Deps, +) -> StdResult { + let decimals = CoinInfo::load(deps.storage)?.decimals; + if Config::deposit_enabled(deps.storage)? || Config::redeem_enabled(deps.storage)? { + let rate: Uint128; + let denom: String; + // if token has more decimals than SCRT, you get magnitudes of SCRT per token + if decimals >= 6 { + rate = Uint128::new(10u128.pow(decimals as u32 - 6)); + denom = "SCRT".to_string(); + // if token has less decimals, you get magnitudes token for SCRT + } else { + rate = Uint128::new(10u128.pow(6 - decimals as u32)); + denom = CoinInfo::load(deps.storage)?.symbol; + } + return Ok(QueryAnswer::ExchangeRate { rate, denom }); + } + Ok(QueryAnswer::ExchangeRate { + rate: Uint128::new(0), + denom: String::new(), + }) +} + +pub fn minters(deps: Deps) -> StdResult { + Ok(QueryAnswer::Minters { + minters: Minters::load(deps.storage)?.0, + }) +} + +pub fn allowance( + deps: Deps, + owner: Addr, + spender: Addr, +) -> StdResult { + let allowance = Allowance::may_load( + deps.storage, + (owner.clone(), spender.clone()) + )?.unwrap_or_default(); + + //panic!("allowance {}", allowance.amount); + + Ok(QueryAnswer::Allowance { + spender, + owner, + allowance: allowance.amount, + expiration: allowance.expiration, + }) +} + +pub fn balance( + deps: Deps, + account: Addr, +) -> StdResult { + Ok(QueryAnswer::Balance { + amount: Balance::may_load(deps.storage, account)?.unwrap_or(Balance(Uint128::zero())).0, + }) +} + +pub fn transfer_history( + deps: Deps, + account: Addr, + page: u32, + page_size: u32, +) -> StdResult { + let transfer = Tx::get(deps.storage, &account, page, page_size)?; + Ok(QueryAnswer::TransferHistory { + txs: transfer.0, + total: Some(transfer.1), + }) +} + +pub fn transaction_history( + deps: Deps, + account: Addr, + page: u32, + page_size: u32, +) -> StdResult { + let transfer = RichTx::get(deps.storage, &account, page, page_size)?; + Ok(QueryAnswer::TransactionHistory { + txs: transfer.0, + total: Some(transfer.1), + }) +} diff --git a/contracts/snip20/src/tests/handle/allowance.rs b/contracts/snip20/src/tests/handle/allowance.rs new file mode 100644 index 0000000..23503ba --- /dev/null +++ b/contracts/snip20/src/tests/handle/allowance.rs @@ -0,0 +1,267 @@ +use shade_protocol::c_std::{Addr, Timestamp}; +use shade_protocol::utils::{ExecuteCallback, InstantiateCallback, Query, MultiTestable}; +use shade_protocol::c_std::Uint128; +use shade_protocol::contract_interfaces::snip20::{ExecuteMsg, InitialBalance, QueryAnswer, QueryMsg}; +use crate::tests::init_snip20_with_config; + +#[test] +fn increase_allowance() { + let (mut chain, snip) = init_snip20_with_config(Some(vec![ + InitialBalance{ + address: "sam".into(), + amount: (Uint128::new(5000)) + }, + InitialBalance { + address: "esmail".into(), + amount: Uint128::new(1) + }, + ]), None).unwrap(); + + chain.update_block(|block| block.time = Timestamp::from_seconds(0)); + + assert!(ExecuteMsg::IncreaseAllowance { + spender: "esmail".into(), + amount: Uint128::new(1000), + expiration: Some(1_000_000_000), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); + + let answer: QueryAnswer = QueryMsg::Allowance { + owner: "sam".into(), + spender: "esmail".into(), + key: "password".into() + }.test_query(&snip, &chain).unwrap(); + + match answer { + QueryAnswer::Allowance { spender, owner, allowance, expiration} => { + assert_eq!(allowance, Uint128::new(1000)); + }, + _ => assert!(false) + } + + assert!(ExecuteMsg::IncreaseAllowance { + spender: "esmail".into(), + amount: Uint128::new(1000), + expiration: Some(1_000_000_000), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); + + let answer: QueryAnswer = QueryMsg::Allowance { + owner: "sam".into(), + spender: "esmail".into(), + key: "password".into() + }.test_query(&snip, &chain).unwrap(); + + match answer { + QueryAnswer::Allowance { spender, owner, allowance, expiration} => { + assert_eq!(allowance, Uint128::new(2000)); + }, + _ => assert!(false) + } +} + +#[test] +fn decrease_allowance() { + let (mut chain, snip) = init_snip20_with_config(Some(vec![ + InitialBalance{ + address: "sam".into(), + amount: (Uint128::new(5000)) + }, + InitialBalance { + address: "esmail".into(), + amount: Uint128::new(1) + }, + ]), None).unwrap(); + + chain.update_block(|block| block.time = Timestamp::from_seconds(10000)); + + assert!(ExecuteMsg::IncreaseAllowance { + spender: "esmail".into(), + amount: Uint128::new(1000), + expiration: Some(1_000_000_000), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); + + assert!(ExecuteMsg::DecreaseAllowance { + spender: "esmail".into(), + amount: Uint128::new(600), + expiration: Some(1_000_000_000), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); + + let answer: QueryAnswer = QueryMsg::Allowance { + owner: "sam".into(), + spender: "esmail".into(), + key: "password".into() + }.test_query(&snip, &chain).unwrap(); + + match answer { + QueryAnswer::Allowance { spender, owner, allowance, expiration} => { + assert_eq!(allowance, Uint128::new(400)); + }, + _ => assert!(false) + } +} + +#[test] +fn transfer_from() { + let (mut chain, snip) = init_snip20_with_config(Some(vec![ + InitialBalance{ + address: "sam".into(), + amount: (Uint128::new(5000)) + }, + InitialBalance { + address: "esmail".into(), + amount: Uint128::new(1) + }, + ]), None).unwrap(); + + chain.update_block(|block| block.time = Timestamp::from_seconds(0)); + + // Insufficient allowance + assert!(ExecuteMsg::TransferFrom { + owner: "sam".into(), + recipient: "eliot".into(), + amount: Uint128::new(100), + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); + + assert!(ExecuteMsg::IncreaseAllowance { + spender: "esmail".into(), + amount: Uint128::new(1000), + expiration: Some(1_000_000_000), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); + + // Transfer more than allowed amount + assert!(ExecuteMsg::TransferFrom { + owner: "sam".into(), + recipient: "eliot".into(), + amount: Uint128::new(1100), + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); + + chain.update_block(|block| block.time = Timestamp::from_seconds(1_000_000_010)); + + + // Transfer expired + assert!(ExecuteMsg::TransferFrom { + owner: "sam".into(), + recipient: "eliot".into(), + amount: Uint128::new(900), + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); + + assert!(ExecuteMsg::IncreaseAllowance { + spender: "esmail".into(), + amount: Uint128::new(1000), + expiration: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); + + assert!(ExecuteMsg::TransferFrom { + owner: "sam".into(), + recipient: "eliot".into(), + amount: Uint128::new(900), + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_ok()); + + // Check that allowance gets spent + assert!(ExecuteMsg::TransferFrom { + owner: "sam".into(), + recipient: "eliot".into(), + amount: Uint128::new(200), + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); +} + +#[test] +fn send_from() { + let (mut chain, snip) = init_snip20_with_config(Some(vec![ + InitialBalance{ + address: "sam".into(), + amount: (Uint128::new(5000)) + }, + InitialBalance { + address: "esmail".into(), + amount: Uint128::new(1) + }, + ]), None).unwrap(); + + chain.update_block(|block| block.time = Timestamp::from_seconds(0)); + + // Insufficient allowance + assert!(ExecuteMsg::SendFrom { + owner: "sam".into(), + recipient: "eliot".into(), + recipient_code_hash: None, + amount: Uint128::new(100), + msg: None, + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); + + assert!(ExecuteMsg::IncreaseAllowance { + spender: "esmail".into(), + amount: Uint128::new(1000), + expiration: Some(1_000_000_000), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); + + // Transfer more than allowed amount + assert!(ExecuteMsg::SendFrom { + owner: "sam".into(), + recipient: "eliot".into(), + recipient_code_hash: None, + amount: Uint128::new(1100), + msg: None, + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); + + chain.update_block(|block| block.time = Timestamp::from_seconds(1_000_000_010)); + + // Transfer expired + assert!(ExecuteMsg::SendFrom { + owner: "sam".into(), + recipient: "eliot".into(), + recipient_code_hash: None, + amount: Uint128::new(900), + msg: None, + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); + + assert!(ExecuteMsg::IncreaseAllowance { + spender: "esmail".into(), + amount: Uint128::new(1000), + expiration: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); + + assert!(ExecuteMsg::SendFrom { + owner: "sam".into(), + recipient: "eliot".into(), + recipient_code_hash: None, + amount: Uint128::new(900), + msg: None, + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_ok()); + + // Check that allowance gets spent + assert!(ExecuteMsg::SendFrom { + owner: "sam".into(), + recipient: "eliot".into(), + recipient_code_hash: None, + amount: Uint128::new(200), + msg: None, + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); +} diff --git a/contracts/snip20/src/tests/handle/burn.rs b/contracts/snip20/src/tests/handle/burn.rs new file mode 100644 index 0000000..f411f0b --- /dev/null +++ b/contracts/snip20/src/tests/handle/burn.rs @@ -0,0 +1,219 @@ +use shade_protocol::c_std::{Addr, Timestamp}; +use shade_protocol::utils::{ExecuteCallback, Query, MultiTestable}; +use shade_protocol::c_std::Uint128; +use shade_protocol::contract_interfaces::snip20::{ExecuteMsg, InitConfig, InitialBalance}; +use shade_protocol::contract_interfaces::snip20::batch::BurnFromAction; +use shade_protocol::contract_interfaces::snip20::manager::{Balance, TotalSupply}; +use shade_protocol::utils::storage::plus::{ItemStorage, MapStorage}; +use crate::tests::init_snip20_with_config; + +#[test] +fn burn() { + let (mut chain, snip) = init_snip20_with_config(Some(vec![ + InitialBalance{ + address: "finger".into(), + amount: (Uint128::new(5000)) + }, + ]), Some(InitConfig { + public_total_supply: None, + enable_deposit: None, + enable_redeem: None, + enable_mint: None, + enable_burn: Some(true), + enable_transfer: None + })).unwrap(); + + chain.update_block(|block| block.time = Timestamp::from_seconds(0)); + + // Insufficient tokens + assert!(ExecuteMsg::Burn { + amount: Uint128::new(8000), + padding: None, + memo: None + }.test_exec(&snip, &mut chain, Addr::unchecked("finger"), &[]).is_err()); + + // Burn some + assert!(ExecuteMsg::Burn { + amount: Uint128::new(4000), + padding: None, + memo: None + }.test_exec(&snip, &mut chain, Addr::unchecked("finger"), &[]).is_ok()); + + // Check that tokens were spend + chain.deps(&snip.address, |storage| { + assert_eq!(Balance::load( + storage, + Addr::unchecked("finger")).unwrap().0, Uint128::new(1000) + ); + assert_eq!(TotalSupply::load(storage).unwrap().0, Uint128::new(1000) + ); + }); + +} + +#[test] +fn burn_from() { + let (mut chain, snip) = init_snip20_with_config(Some(vec![ + InitialBalance{ + address: "sam".into(), + amount: (Uint128::new(5000)) + }, + InitialBalance { + address: "esmail".into(), + amount: Uint128::new(1) + }, + ]), Some(InitConfig { + public_total_supply: None, + enable_deposit: None, + enable_redeem: None, + enable_mint: None, + enable_burn: Some(true), + enable_transfer: None + })).unwrap(); + + chain.update_block(|block| block.time = Timestamp::from_seconds(0)); + + // Insufficient allowance + assert!(ExecuteMsg::BurnFrom { + owner: "sam".into(), + amount: Uint128::new(1000), + padding: None, + memo: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); + + assert!(ExecuteMsg::IncreaseAllowance { + spender: "esmail".into(), + amount: Uint128::new(700), + expiration: Some(1_000_000_000), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); + + // Transfer more than allowed amount + assert!(ExecuteMsg::BurnFrom { + owner: "sam".into(), + amount: Uint128::new(1000), + padding: None, + memo: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); + + chain.update_block(|block| block.time = Timestamp::from_seconds(1_000_000_010)); + // Transfer expired + assert!(ExecuteMsg::BurnFrom { + owner: "sam".into(), + amount: Uint128::new(1000), + padding: None, + memo: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); + + assert!(ExecuteMsg::IncreaseAllowance { + spender: "esmail".into(), + amount: Uint128::new(1000), + expiration: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); + + assert!(ExecuteMsg::BurnFrom { + owner: "sam".into(), + amount: Uint128::new(800), + padding: None, + memo: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_ok()); + + // Check that allowance gets spent + assert!(ExecuteMsg::BurnFrom { + owner: "sam".into(), + amount: Uint128::new(300), + padding: None, + memo: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); +} + +#[test] +fn batch_burn_from() { + let (mut chain, snip) = init_snip20_with_config(Some(vec![ + InitialBalance{ + address: "eliot".into(), + amount: (Uint128::new(5000)) + }, + InitialBalance{ + address: "alderson".into(), + amount: (Uint128::new(5000)) + }, + InitialBalance{ + address: "sam".into(), + amount: (Uint128::new(5000)) + }, + InitialBalance { + address: "esmail".into(), + amount: Uint128::new(1) + }, + ]), Some(InitConfig { + public_total_supply: None, + enable_deposit: None, + enable_redeem: None, + enable_mint: None, + enable_burn: Some(true), + enable_transfer: None + })).unwrap(); + + chain.update_block(|block| block.time = Timestamp::from_seconds(0)); + + let granters = vec!["eliot", "alderson", "sam"]; + + let batch: Vec<_> = granters.iter().map(|name| { + BurnFromAction { + owner: (*name).to_string(), + amount: Uint128::new(800), + memo: None + } + }).collect(); + + // Insufficient allowance + assert!(ExecuteMsg::BatchBurnFrom { + actions: batch.clone(), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); + + for granter in granters.iter() { + assert!(ExecuteMsg::IncreaseAllowance { + spender: "esmail".into(), + amount: Uint128::new(700), + expiration: Some(1_000_000_000), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked(*granter), &[]).is_ok()); + } + + // Transfer more than allowed amount + assert!(ExecuteMsg::BatchBurnFrom { + actions: batch.clone(), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); + + chain.update_block(|block| block.time = Timestamp::from_seconds(1_000_000_010)); + + // Transfer expired + assert!(ExecuteMsg::BatchBurnFrom { + actions: batch.clone(), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); + + for granter in granters.iter() { + assert!(ExecuteMsg::IncreaseAllowance { + spender: "esmail".into(), + amount: Uint128::new(1000), + expiration: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked(*granter), &[]).is_ok()); + } + + assert!(ExecuteMsg::BatchBurnFrom { + actions: batch.clone(), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_ok()); + + // Check that allowance gets spent + assert!(ExecuteMsg::BatchBurnFrom { + actions: batch.clone(), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); +} \ No newline at end of file diff --git a/contracts/snip20/src/tests/handle/mint.rs b/contracts/snip20/src/tests/handle/mint.rs new file mode 100644 index 0000000..a282d32 --- /dev/null +++ b/contracts/snip20/src/tests/handle/mint.rs @@ -0,0 +1,152 @@ +use shade_protocol::c_std::Addr; +use shade_protocol::utils::{ExecuteCallback, Query, MultiTestable}; +use shade_protocol::c_std::Uint128; +use shade_protocol::contract_interfaces::snip20::{ExecuteMsg, InitConfig}; +use shade_protocol::contract_interfaces::snip20::manager::{Balance, Minters, TotalSupply}; +use shade_protocol::utils::storage::plus::{ItemStorage, MapStorage}; +use crate::tests::init_snip20_with_config; + +#[test] +fn mint() { + let (mut chain, snip) = init_snip20_with_config(None, Some(InitConfig { + public_total_supply: None, + enable_deposit: None, + enable_redeem: None, + enable_mint: Some(true), + enable_burn: None, + enable_transfer: None + })).unwrap(); + + assert!(ExecuteMsg::Mint { + recipient: "jimmy".into(), + amount: Uint128::new(1000), + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_err()); + + assert!(ExecuteMsg::AddMinters { + minters: vec!["admin".into()], + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); + + assert!(ExecuteMsg::Mint { + recipient: "jimmy".into(), + amount: Uint128::new(1500), + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); + + chain.deps(&snip.address, |storage| { + assert_eq!(Balance::load( + storage, + Addr::unchecked("jimmy")).unwrap().0, Uint128::new(1500) + ); + assert_eq!(TotalSupply::load(storage).unwrap().0, Uint128::new(1500) + ); + }).unwrap(); +} + +#[test] +fn set_minters() { + let (mut chain, snip) = init_snip20_with_config(None, Some(InitConfig { + public_total_supply: None, + enable_deposit: None, + enable_redeem: None, + enable_mint: Some(true), + enable_burn: None, + enable_transfer: None + })).unwrap(); + + assert!(ExecuteMsg::SetMinters { + minters: vec!["admin".into()], + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("notadmin"), &[]).is_err()); + + assert!(ExecuteMsg::SetMinters { + minters: vec!["admin".into()], + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); + + chain.deps(&snip.address.clone(), |storage| { + assert_eq!(Minters::load(storage).unwrap().0, vec![Addr::unchecked("admin")]); + }).unwrap(); + + assert!(ExecuteMsg::SetMinters { + minters: vec!["other_address".into(), "some_other".into()], + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); + + chain.deps(&snip.address, |storage| { + assert_eq!(Minters::load(storage).unwrap().0, + vec![Addr::unchecked("other_address"), Addr::unchecked("some_other")]); + }).unwrap(); +} + +#[test] +fn add_minters() { + let (mut chain, snip) = init_snip20_with_config(None, Some(InitConfig { + public_total_supply: None, + enable_deposit: None, + enable_redeem: None, + enable_mint: Some(true), + enable_burn: None, + enable_transfer: None + })).unwrap(); + + assert!(ExecuteMsg::AddMinters { + minters: vec!["admin".into()], + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("notadmin"), &[]).is_err()); + + assert!(ExecuteMsg::AddMinters { + minters: vec!["admin".into()], + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); + + chain.deps(&snip.address.clone(), |storage| { + assert_eq!(Minters::load(storage).unwrap().0, vec![Addr::unchecked("admin")]); + }).unwrap(); + + assert!(ExecuteMsg::AddMinters { + minters: vec!["other_address".into(), "some_other".into()], + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); + + chain.deps(&snip.address, |storage| { + assert_eq!(Minters::load(storage).unwrap().0, + vec![ + Addr::unchecked("admin"), + Addr::unchecked("other_address"), + Addr::unchecked("some_other") + ]); + }).unwrap(); +} + +#[test] +fn remove_minters() { + let (mut chain, snip) = init_snip20_with_config(None, Some(InitConfig { + public_total_supply: None, + enable_deposit: None, + enable_redeem: None, + enable_mint: Some(true), + enable_burn: None, + enable_transfer: None + })).unwrap(); + + assert!(ExecuteMsg::AddMinters { + minters: vec!["other_address".into(), "some_other".into()], + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); + + assert!(ExecuteMsg::RemoveMinters { + minters: vec!["other_address".into()], + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); + + chain.deps(&snip.address, |storage| { + assert_eq!(Minters::load(storage).unwrap().0, + vec![ + Addr::unchecked("some_other") + ]); + }).unwrap(); +} \ No newline at end of file diff --git a/contracts/snip20/src/tests/handle/mod.rs b/contracts/snip20/src/tests/handle/mod.rs new file mode 100644 index 0000000..9e9a259 --- /dev/null +++ b/contracts/snip20/src/tests/handle/mod.rs @@ -0,0 +1,229 @@ +use shade_protocol::c_std::{Coin, Addr}; +use shade_protocol::c_std::Uint128; +use shade_protocol::utils::{ExecuteCallback}; +use shade_protocol::contract_interfaces::snip20::{ExecuteMsg, InitConfig}; +use shade_protocol::contract_interfaces::snip20::manager::{ContractStatusLevel, HashedKey, Key, ReceiverHash}; +use shade_protocol::utils::storage::plus::MapStorage; +use crate::tests::init_snip20_with_config; + +pub mod transfer; +pub mod wrap; +pub mod mint; +pub mod burn; +pub mod allowance; + +#[test] +fn register_receive() { + let (mut chain, snip) = init_snip20_with_config(None, None).unwrap(); + + assert!(ExecuteMsg::RegisterReceive { + code_hash: "some_hash".into(), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("contract"), &[]).is_ok()); + + chain.deps(&snip.address, |borrowed_chain| { + let hash = ReceiverHash::load(borrowed_chain, Addr::unchecked("contract")).unwrap(); + assert_eq!(hash.0, "some_hash".to_string()); + }).unwrap(); +} + +#[test] +fn create_viewing_key() { + let (mut chain, snip) = init_snip20_with_config(None, None).unwrap(); + + assert!(ExecuteMsg::CreateViewingKey { + entropy: "some_entropy".into(), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); + + chain.deps(&snip.address, |borrowed_chain| { + assert!(HashedKey:: + may_load(borrowed_chain, Addr::unchecked("sam")) + .unwrap().is_some()); + }).unwrap(); +} + +#[test] +fn set_viewing_key() { + let (mut chain, snip) = init_snip20_with_config(None, None).unwrap(); + + assert!(ExecuteMsg::SetViewingKey { + key: "some_key".into(), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); + + chain.deps(&snip.address, |borrowed_chain| { + assert!(Key::verify( + borrowed_chain, + Addr::unchecked("sam"), + "some_key".into() + ).unwrap()); + }).unwrap(); +} + +#[test] +fn change_admin() { + let (mut chain, snip) = init_snip20_with_config(None, None).unwrap(); + + assert!(ExecuteMsg::ChangeAdmin { + address: "newadmin".into(), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("notadmin"), &[]).is_err()); + + assert!(ExecuteMsg::ChangeAdmin { + address: "newadmin".into(), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); + + assert!(ExecuteMsg::ChangeAdmin { + address: "otheradmin".into(), + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_err()); +} + +#[test] +fn set_contract_status() { + let (mut chain, snip) = init_snip20_with_config(None, None).unwrap(); + + assert!(ExecuteMsg::SetContractStatus { + level: ContractStatusLevel::StopAll, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("notadmin"), &[]).is_err()); + + chain.deps(&snip.address.clone(), |storage| { + assert_eq!(ContractStatusLevel::load(storage).unwrap(), ContractStatusLevel::NormalRun); + }).unwrap(); + + assert!(ExecuteMsg::SetContractStatus { + level: ContractStatusLevel::StopAll, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); + + chain.deps(&snip.address, |storage| { + assert_eq!(ContractStatusLevel::load(storage).unwrap(), ContractStatusLevel::StopAll); + }).unwrap(); +} + +#[test] +fn contract_status_stop_all() { + let (mut chain, snip) = init_snip20_with_config(None, Some(InitConfig { + public_total_supply: None, + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: None, + enable_burn: None, + enable_transfer: None + })).unwrap(); + + let scrt_coin = Coin { + denom: "uscrt".into(), + amount: Uint128::new(1000) + }; + + chain.init_modules(|router, _, storage| { + router.bank.init_balance(storage, &Addr::unchecked("bob"), vec![scrt_coin.clone()]).unwrap(); + }); + + // Deposit + assert!(ExecuteMsg::Deposit { + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[scrt_coin]).is_ok()); + + assert!(ExecuteMsg::SetContractStatus { + level: ContractStatusLevel::StopAll, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); + + assert!(ExecuteMsg::Transfer { + recipient: "dylan".into(), + amount: Uint128::new(100), + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_err()); + + assert!(ExecuteMsg::Redeem { + amount: Uint128::new(100), + denom: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_err()); + + assert!(ExecuteMsg::SetContractStatus { + level: ContractStatusLevel::NormalRun, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); + + assert!(ExecuteMsg::Transfer { + recipient: "dylan".into(), + amount: Uint128::new(100), + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_ok()); + + assert!(ExecuteMsg::Redeem { + amount: Uint128::new(100), + denom: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_ok()); +} + +#[test] +fn contract_status_stop_all_but_redeem() { + let (mut chain, snip) = init_snip20_with_config(None, Some(InitConfig { + public_total_supply: None, + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: None, + enable_burn: None, + enable_transfer: None + })).unwrap(); + + let scrt_coin = Coin { + denom: "uscrt".into(), + amount: Uint128::new(1000) + }; + + chain.init_modules(|router, _, storage| { + router.bank.init_balance(storage, &Addr::unchecked("bob"), vec![scrt_coin.clone()]).unwrap(); + }); + + // Deposit + assert!(ExecuteMsg::Deposit { + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[scrt_coin]).is_ok()); + + assert!(ExecuteMsg::SetContractStatus { + level: ContractStatusLevel::StopAllButRedeems, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); + + assert!(ExecuteMsg::Transfer { + recipient: "dylan".into(), + amount: Uint128::new(100), + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_err()); + + assert!(ExecuteMsg::Redeem { + amount: Uint128::new(100), + denom: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_ok()); + + assert!(ExecuteMsg::SetContractStatus { + level: ContractStatusLevel::NormalRun, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); + + assert!(ExecuteMsg::Transfer { + recipient: "dylan".into(), + amount: Uint128::new(100), + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_ok()); + + assert!(ExecuteMsg::Redeem { + amount: Uint128::new(100), + denom: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_ok()); +} \ No newline at end of file diff --git a/contracts/snip20/src/tests/handle/transfer.rs b/contracts/snip20/src/tests/handle/transfer.rs new file mode 100644 index 0000000..13891f8 --- /dev/null +++ b/contracts/snip20/src/tests/handle/transfer.rs @@ -0,0 +1,134 @@ +use shade_protocol::c_std::Addr; +use shade_protocol::utils::{ExecuteCallback, Query}; +use shade_protocol::c_std::Uint128; +use shade_protocol::contract_interfaces::snip20::{ExecuteMsg, InitialBalance, QueryMsg, QueryAnswer}; +use crate::tests::init_snip20_with_config; + +#[test] +fn total_supply_overflow() { + assert!(init_snip20_with_config(Some(vec![ + InitialBalance{ + address: "john".into(), + amount: Uint128::MAX + } + ]), None).is_ok()); + + assert!(init_snip20_with_config(Some(vec![ + InitialBalance{ + address: "john".into(), + amount: (Uint128::MAX - Uint128::new(1)) + }, + InitialBalance { + address: "salchi".into(), + amount: Uint128::new(1) + }, + InitialBalance { + address: "chonn".into(), + amount: Uint128::new(1) + } + ]), None).is_err()); +} + +#[test] +fn transfer() { + let (mut chain, snip) = init_snip20_with_config(Some(vec![ + InitialBalance{ + address: "bob".into(), + amount: (Uint128::new(1000)) + }, + InitialBalance { + address: "dylan".into(), + amount: Uint128::new(1000) + }, + ]), None).unwrap(); + + assert!(ExecuteMsg::Transfer { + recipient: "dylan".into(), + amount: Uint128::new(100), + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_ok()); + + { + let answer: QueryAnswer = QueryMsg::Balance { + address: "bob".into(), + key: "password".into() + }.test_query(&snip, &chain).unwrap(); + + match answer { + QueryAnswer::Balance {amount} => assert_eq!(amount, Uint128::new(900)), + _ => assert!(false) + } + + let answer: QueryAnswer = QueryMsg::Balance { + address: "dylan".into(), + key: "password".into() + }.test_query(&snip, &chain).unwrap(); + + match answer { + QueryAnswer::Balance {amount} => assert_eq!(amount, Uint128::new(1100)), + _ => assert!(false) + } + } + + assert!(ExecuteMsg::Transfer { + recipient: "dylan".into(), + amount: Uint128::new(1000), + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_err()); +} + +#[test] +fn send() { + let (mut chain, snip) = init_snip20_with_config(Some(vec![ + InitialBalance{ + address: "bob".into(), + amount: (Uint128::new(1000)) + }, + InitialBalance { + address: "dylan".into(), + amount: Uint128::new(1000) + }, + ]), None).unwrap(); + + assert!(ExecuteMsg::Send { + recipient: "dylan".into(), + amount: Uint128::new(100), + recipient_code_hash: None, + memo: None, + padding: None, + msg: None + }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_ok()); + + { + let answer: QueryAnswer = QueryMsg::Balance { + address: "bob".into(), + key: "password".into() + }.test_query(&snip, &chain).unwrap(); + + match answer { + QueryAnswer::Balance {amount} => assert_eq!(amount, Uint128::new(900)), + _ => assert!(false) + } + + let answer: QueryAnswer = QueryMsg::Balance { + address: "dylan".into(), + key: "password".into() + }.test_query(&snip, &chain).unwrap(); + + match answer { + QueryAnswer::Balance {amount} => assert_eq!(amount, Uint128::new(1100)), + _ => assert!(false) + } + } + + assert!(ExecuteMsg::Send { + recipient: "dylan".into(), + amount: Uint128::new(1000), + recipient_code_hash: None, + memo: None, + padding: None, + msg: None + }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_err()); +} \ No newline at end of file diff --git a/contracts/snip20/src/tests/handle/wrap.rs b/contracts/snip20/src/tests/handle/wrap.rs new file mode 100644 index 0000000..2318909 --- /dev/null +++ b/contracts/snip20/src/tests/handle/wrap.rs @@ -0,0 +1,107 @@ +use shade_protocol::c_std::{Coin, Addr}; +use shade_protocol::utils::{ExecuteCallback, Query, MultiTestable}; +use shade_protocol::c_std::Uint128; +use shade_protocol::contract_interfaces::snip20::{ExecuteMsg, InitConfig}; +use shade_protocol::contract_interfaces::snip20::manager::{Balance, TotalSupply}; +use shade_protocol::utils::storage::plus::{ItemStorage, MapStorage}; +use crate::tests::init_snip20_with_config; + +#[test] +fn deposit() { + let (mut chain, snip20) = init_snip20_with_config(None, Some(InitConfig{ + public_total_supply: None, + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: None, + enable_burn: None, + enable_transfer: None + })).unwrap(); + + let scrt_coin = Coin { + denom: "uscrt".into(), + amount: Uint128::new(1000) + }; + + let not_coin = Coin { + denom: "token".into(), + amount: Uint128::new(1000) + }; + + // chain.add_funds(Addr::unchecked("marco"), vec![ + // scrt_coin.clone(), not_coin.clone()]); + chain.init_modules(|router, _, storage| { + router.bank.init_balance(storage, &Addr::unchecked("marco"), vec![scrt_coin.clone(), not_coin.clone()]).unwrap(); + }); + + // Deposit + assert!(ExecuteMsg::Deposit { + padding: None + }.test_exec(&snip20, &mut chain, Addr::unchecked("marco"), &vec![not_coin]).is_err()); + + assert!(ExecuteMsg::Deposit { + padding: None + }.test_exec(&snip20, &mut chain, Addr::unchecked("marco"), &vec![scrt_coin]).is_ok()); + + // Check that internal states were updated accordingly + chain.deps(&snip20.address, |storage| { + assert_eq!(Balance::load( + storage, + Addr::unchecked("marco")).unwrap().0, Uint128::new(1000) + ); + assert_eq!(TotalSupply::load(storage).unwrap().0, Uint128::new(1000) + ); + }).unwrap(); +} + +#[test] +fn redeem() { + let (mut chain, snip20) = init_snip20_with_config(None, Some(InitConfig{ + public_total_supply: None, + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: None, + enable_burn: None, + enable_transfer: None + })).unwrap(); + + let scrt_coin = Coin { + denom: "uscrt".into(), + amount: Uint128::new(1000) + }; + + chain.init_modules(|router, _, storage| { + router.bank.init_balance(storage, &Addr::unchecked("marco"), vec![scrt_coin.clone()]).unwrap(); + }); + + + // Deposit + assert!(ExecuteMsg::Deposit { + padding: None + }.test_exec(&snip20, &mut chain, Addr::unchecked("marco"), &vec![scrt_coin]).is_ok()); + + // Redeem + assert!(ExecuteMsg::Redeem { + amount: Uint128::new(10000), + denom: None, + padding: None + }.test_exec(&snip20, &mut chain, Addr::unchecked("marco"), &[]).is_err()); + + assert!(ExecuteMsg::Redeem { + amount: Uint128::new(500), + denom: None, + padding: None + }.test_exec(&snip20, &mut chain, Addr::unchecked("marco"), &[]).is_ok()); + + // Check that internal states were updated accordingly + chain.deps(&snip20.address, |storage| { + assert_eq!(Balance::load( + storage, + Addr::unchecked("marco")).unwrap().0, Uint128::new(500) + ); + assert_eq!(TotalSupply::load(storage).unwrap().0, Uint128::new(500) + ); + }).unwrap(); + + let balance = chain.wrap().query_balance("marco", "uscrt").unwrap(); + assert_eq!(balance.amount, Uint128::new(500)); +} \ No newline at end of file diff --git a/contracts/snip20/src/tests/mod.rs b/contracts/snip20/src/tests/mod.rs new file mode 100644 index 0000000..cb02326 --- /dev/null +++ b/contracts/snip20/src/tests/mod.rs @@ -0,0 +1,116 @@ +pub mod handle; +pub mod query; + +use shade_multi_test::multi::{admin::Admin, query_auth::QueryAuth, snip20::Snip20}; +use shade_protocol::{ + admin, + c_std::{Addr, Binary, ContractInfo, StdResult}, + contract_interfaces::{ + query_auth, + snip20, + snip20::{InitConfig, InitialBalance}, + }, + multi_test::{App, AppResponse, Executor}, + utils::{ExecuteCallback, InstantiateCallback, MultiTestable, Query}, + AnyResult, + Contract, +}; + +pub fn init_snip20_with_auth( + initial_balances: Option>, + config: Option, + auth: bool, +) -> AnyResult<(App, ContractInfo, Option)> { + let mut chain = App::default(); + + let query_auth_addr: Option; + let query_auth_contract: Option; + + if auth { + let stored_code = chain.store_code(Admin::default().contract()); + let admin = chain + .instantiate_contract( + stored_code, + Addr::unchecked("admin"), + &admin::InstantiateMsg { super_admin: None }, + &[], + "admin", + None, + ) + .unwrap(); + + let auth = query_auth::InstantiateMsg { + admin_auth: Contract { + address: admin.address.clone(), + code_hash: admin.code_hash.clone(), + }, + prng_seed: Binary::from("random".as_bytes()), + } + .test_init( + QueryAuth::default(), + &mut chain, + Addr::unchecked("admin"), + "query_auth", + &[], + ) + .unwrap(); + + query_auth_contract = Some(auth.clone()); + + query_auth_addr = Some(Contract { + address: auth.address, + code_hash: auth.code_hash, + }) + } else { + query_auth_addr = None; + query_auth_contract = None; + } + + let snip = snip20::InstantiateMsg { + name: "Token".into(), + admin: None, + symbol: "TKN".into(), + decimals: 8, + initial_balances: initial_balances.clone(), + prng_seed: Binary::from("random".as_bytes()), + config, + query_auth: query_auth_addr, + } + .test_init( + Snip20::default(), + &mut chain, + Addr::unchecked("admin"), + "snip20", + &[], + )?; + + if let Some(balances) = initial_balances { + for balance in balances.iter() { + create_vk(&mut chain, &snip, balance.address.as_str(), None)?; + } + } + + Ok((chain, snip, query_auth_contract)) +} + +pub fn init_snip20_with_config( + initial_balances: Option>, + config: Option, +) -> AnyResult<(App, ContractInfo)> { + let (chain, snip20, _) = init_snip20_with_auth(initial_balances, config, false)?; + + Ok((chain, snip20)) +} + +pub fn create_vk( + chain: &mut App, + snip: &ContractInfo, + addr: &str, + key: Option, +) -> AnyResult { + snip20::ExecuteMsg::SetViewingKey { + key: key.unwrap_or("password".into()), + padding: None, + } + .test_exec(snip, chain, Addr::unchecked(addr), &[]) +} diff --git a/contracts/snip20/src/tests/query/mod.rs b/contracts/snip20/src/tests/query/mod.rs new file mode 100644 index 0000000..c65978d --- /dev/null +++ b/contracts/snip20/src/tests/query/mod.rs @@ -0,0 +1,2 @@ +pub mod user; +pub mod public; \ No newline at end of file diff --git a/contracts/snip20/src/tests/query/public.rs b/contracts/snip20/src/tests/query/public.rs new file mode 100644 index 0000000..14bd2ac --- /dev/null +++ b/contracts/snip20/src/tests/query/public.rs @@ -0,0 +1,73 @@ +use shade_protocol::contract_interfaces::snip20::{InitConfig, QueryAnswer, QueryMsg}; +use shade_protocol::utils::{ExecuteCallback, InstantiateCallback, Query, MultiTestable}; +use crate::tests::init_snip20_with_config; + +#[test] +fn token_info() { + let (mut chain, snip) = init_snip20_with_config(None, None).unwrap(); + let answer: QueryAnswer = QueryMsg::TokenInfo { }.test_query(&snip, &chain).unwrap(); + + match answer { + QueryAnswer::TokenInfo { name, symbol, decimals, total_supply} => { + assert_eq!(name, "Token"); + assert_eq!(symbol, "TKN"); + assert_eq!(decimals, 8); + assert_eq!(total_supply, None); + }, + _ => assert!(false) + } +} + +#[test] +fn token_config() { + let (mut chain, snip) = init_snip20_with_config(None, None).unwrap(); + let answer: QueryAnswer = QueryMsg::TokenConfig { }.test_query(&snip, &chain).unwrap(); + + match answer { + QueryAnswer::TokenConfig { + public_total_supply, + deposit_enabled, + redeem_enabled, + mint_enabled, + burn_enabled, + transfer_enabled + } => { + assert_eq!(public_total_supply, false); + assert_eq!(deposit_enabled, false); + assert_eq!(redeem_enabled, false); + assert_eq!(mint_enabled, false); + assert_eq!(burn_enabled, false); + }, + _ => assert!(false) + } + + let (mut chain, snip) = init_snip20_with_config(None, Some(InitConfig{ + public_total_supply: Some(true), + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: None, + enable_burn: None, + enable_transfer: None + })).unwrap(); + let answer: QueryAnswer = QueryMsg::TokenConfig { }.test_query(&snip, &chain).unwrap(); + + match answer { + QueryAnswer::TokenConfig { + public_total_supply, + deposit_enabled, + redeem_enabled, + mint_enabled, + burn_enabled, + transfer_enabled + } => { + assert_eq!(public_total_supply, true); + assert_eq!(deposit_enabled, true); + assert_eq!(redeem_enabled, true); + assert_eq!(mint_enabled, false); + assert_eq!(burn_enabled, false); + }, + _ => assert!(false) + } +} + +// TODO: add exchange rate after IBC is added \ No newline at end of file diff --git a/contracts/snip20/src/tests/query/user.rs b/contracts/snip20/src/tests/query/user.rs new file mode 100644 index 0000000..ffdf784 --- /dev/null +++ b/contracts/snip20/src/tests/query/user.rs @@ -0,0 +1,208 @@ +use shade_protocol::c_std::{Coin, Addr, Uint128}; +use shade_protocol::contract_interfaces::snip20::{ExecuteMsg, InitialBalance, QueryAnswer, QueryMsg}; +use shade_protocol::contract_interfaces::snip20::transaction_history::{RichTx, TxAction}; +use shade_protocol::query_auth; +use shade_protocol::utils::{ExecuteCallback, InstantiateCallback, Query, MultiTestable}; +use crate::tests::{create_vk, init_snip20_with_auth, init_snip20_with_config}; + +#[test] +fn allowance_vk() { + let (mut chain, snip) = init_snip20_with_config(None, None).unwrap(); + + let saul = Addr::unchecked("saul"); + let goodman = Addr::unchecked("goodman"); + + create_vk(&mut chain, &snip, "saul", None).unwrap(); + + ExecuteMsg::IncreaseAllowance { + spender: goodman.clone().into_string(), + amount: Uint128::new(100), + expiration: None, + padding: None + }.test_exec(&snip, &mut chain, saul.clone(), &[]).unwrap(); + + let answer: QueryAnswer = QueryMsg::Allowance { + owner: saul.clone().into(), + spender: goodman.clone().into(), + key: "password".into() + }.test_query(&snip, &chain).unwrap(); + + match answer { + QueryAnswer::Allowance { spender, owner, allowance, expiration} => { + assert_eq!(owner, saul); + assert_eq!(spender, goodman); + assert_eq!(allowance, Uint128::new(100)); + assert_eq!(expiration, None); + }, + _ => assert!(false) + } +} + +#[test] +fn allowance_auth_vk() { + let (mut chain, snip, auth) = init_snip20_with_auth(None, None, true).unwrap(); + + let saul = Addr::unchecked("saul"); + let goodman = Addr::unchecked("goodman"); + + query_auth::ExecuteMsg::SetViewingKey { + key: "password".into(), + padding: None, + }.test_exec(&auth.unwrap(), &mut chain, saul.clone(), &[]).unwrap(); + + ExecuteMsg::IncreaseAllowance { + spender: goodman.clone().into_string(), + amount: Uint128::new(100), + expiration: None, + padding: None + }.test_exec(&snip, &mut chain, saul.clone(), &[]).unwrap(); + + let answer: QueryAnswer = QueryMsg::Allowance { + owner: saul.clone().into(), + spender: goodman.clone().into(), + key: "password".into() + }.test_query(&snip, &chain).unwrap(); + + match answer { + QueryAnswer::Allowance { spender, owner, allowance, expiration} => { + assert_eq!(owner, saul); + assert_eq!(spender, goodman); + assert_eq!(allowance, Uint128::new(100)); + assert_eq!(expiration, None); + }, + _ => assert!(false) + } +} + +#[test] +fn balance_vk() { + let (mut chain, snip) = init_snip20_with_config(Some(vec![InitialBalance { + address: "robinson".into(), + amount: Uint128::new(1500) + }]), None).unwrap(); + + let answer: QueryAnswer = QueryMsg::Balance { + address: "robinson".into(), + key: "password".into() + }.test_query(&snip, &chain).unwrap(); + + match answer { + QueryAnswer::Balance { amount } => { + assert_eq!(amount, Uint128::new(1500)); + }, + _ => assert!(false) + } +} + +#[test] +fn transaction_history() { + let setsuna = Addr::unchecked("setsuna"); + let stratos = Addr::unchecked("stratos"); + let smirnoff = Addr::unchecked("smirnoff"); + let felt = Addr::unchecked("felt"); + let tieria = Addr::unchecked("tieria"); + + let (mut chain, snip) = init_snip20_with_config(Some(vec![InitialBalance { + address: setsuna.clone().into_string(), + amount: Uint128::new(1500) + }]), None).unwrap(); + + ExecuteMsg::Transfer { + recipient: stratos.clone().into_string(), + amount: Uint128::new(200), + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("setsuna"), &[]).unwrap(); + + ExecuteMsg::Send { + recipient: smirnoff.clone().into_string(), + recipient_code_hash: None, + amount: Uint128::new(140), + msg: None, + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("setsuna"), &[]).unwrap(); + + ExecuteMsg::Transfer { + recipient: felt.clone().into_string(), + amount: Uint128::new(300), + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("setsuna"), &[]).unwrap(); + + ExecuteMsg::Transfer { + recipient: tieria.clone().into_string(), + amount: Uint128::new(540), + memo: None, + padding: None + }.test_exec(&snip, &mut chain, Addr::unchecked("setsuna"), &[]).unwrap(); + + let answer: QueryAnswer = QueryMsg::TransactionHistory { + address: setsuna.clone().into(), + key: "password".into(), + page: None, + page_size: 10 + }.test_query(&snip, &chain).unwrap(); + + match answer { + QueryAnswer::TransactionHistory { txs, .. } => { + assert_eq!(txs.len(), 5); + + assert_eq!(txs[0].id, 1); + assert_eq!(txs[0].action, TxAction::Mint { + minter: Addr::unchecked("admin"), + recipient: setsuna.clone() + }); + assert_eq!(txs[0].coins, Coin { + denom: "TKN".into(), + amount: Uint128::new(1500) + }); + + assert_eq!(txs[1].id, 2); + assert_eq!(txs[1].action, TxAction::Transfer { + from: setsuna.clone(), + sender: setsuna.clone(), + recipient: stratos.clone() + }); + assert_eq!(txs[1].coins, Coin { + denom: "TKN".into(), + amount: Uint128::new(200) + }); + + assert_eq!(txs[2].id, 3); + assert_eq!(txs[2].action, TxAction::Transfer { + from: setsuna.clone(), + sender: setsuna.clone(), + recipient: smirnoff.clone() + }); + assert_eq!(txs[2].coins, Coin { + denom: "TKN".into(), + amount: Uint128::new(140) + }); + + assert_eq!(txs[3].id, 4); + assert_eq!(txs[3].action, TxAction::Transfer { + from: setsuna.clone(), + sender: setsuna.clone(), + recipient: felt.clone() + }); + assert_eq!(txs[3].coins, Coin { + denom: "TKN".into(), + amount: Uint128::new(300) + }); + + assert_eq!(txs[4].id, 5); + assert_eq!(txs[4].action, TxAction::Transfer { + from: setsuna.clone(), + sender: setsuna.clone(), + recipient: tieria.clone() + }); + assert_eq!(txs[4].coins, Coin { + denom: "TKN".into(), + amount: Uint128::new(540) + }); + + }, + _ => assert!(false) + } +} \ No newline at end of file diff --git a/contracts/snip20_derivative/.cargo/config b/contracts/snip20_derivative/.cargo/config new file mode 100644 index 0000000..bbe1fc9 --- /dev/null +++ b/contracts/snip20_derivative/.cargo/config @@ -0,0 +1,8 @@ +[alias] +# Temporarily removed the backtraces feature from the unit-test run due to compilation errors in +# the cosmwasm-std package: +# cosmwasm-std = { git = "https://github.com/scrtlabs/cosmwasm", branch = "secret" } +# unit-test = "test --lib --features backtraces" +unit-test = "test --lib" +integration-test = "test --test integration" +schema = "run --example schema" diff --git a/contracts/snip20_derivative/.circleci/config.yml b/contracts/snip20_derivative/.circleci/config.yml new file mode 100644 index 0000000..161e927 --- /dev/null +++ b/contracts/snip20_derivative/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.46 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: make compile-optimized + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/snip20_derivative/.editorconfig b/contracts/snip20_derivative/.editorconfig new file mode 100644 index 0000000..3d36f20 --- /dev/null +++ b/contracts/snip20_derivative/.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/snip20_derivative/.github/workflows/cicd.yaml b/contracts/snip20_derivative/.github/workflows/cicd.yaml new file mode 100644 index 0000000..a7907e6 --- /dev/null +++ b/contracts/snip20_derivative/.github/workflows/cicd.yaml @@ -0,0 +1,26 @@ +on: [pull_request] + +name: Basic CICD + +jobs: + TestAndCompileContract: + runs-on: ubuntu-22.04 + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: wasm32-unknown-unknown + override: true + + - name: Run unit test + run: cargo test --locked + shell: bash + + # - name: Build contracts + # run: make compile-optimized-reproducible + # shell: bash diff --git a/contracts/snip20_derivative/.images/engineering-diagram.png b/contracts/snip20_derivative/.images/engineering-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..50f1b07eb4261b241f08f462517d7d90641cd119 GIT binary patch literal 63431 zcmd?QcQl+|^fxL6NdzfG5Rs7RB_SfBr70mIdL5!gA3=C#)D$Hl2vH(h^e_z3nHhoz zF-T`$WT%3>wj=hTVZEs$HT+pSm}_XGlA zV`C#YIM~+Kc3@yYQBkqBwpLG1Z)RquqoZSUbMwcKA8TuCxw*Mdo;)!zG11r8kB^UE zTwLtw=}AgT8Xq6Gv$KnhjqUC2O-@dR!{Non#h#v?-@kvKnwm;WOLKE`d;R)#XlQ6z zSy@|KTWV^mqoZR*MMYj--piLS`}+E#qoYShN56jkIy*b-iCEiW%eMMXiO(8$P0OH0e<=4KcSW^Qg?SXh{!pKoSn z=I-wP=FOX;qN1FfoRpN5nwpx3h=^COUcGqn!otGB-rl~YrN!FXy1cyH%gc*QCfnH9 z`1<;O`SK+zE6c&b0gXn#efu^sF)=(m9DzVMJ3Bi$IhmT8y12MJefkuMMEd*t0{{>h z7>LK?3knJX0s_dZt7GF6pNcDb`v>RdNhuk*s@e}Ph{|2N_2Bu-@TQjbM~@ywyh}29 z8WB6sfGSy@>L2?>dd zi~IKNTYrE5r%#_S7>u#8ac5_ztE+2RSeUoB_q%uRCaf2W!728ct18{0dP&Wp4E}TY zv9f_H71imu-QRt8-Cjh34{4v=Ra2myJV?VrL)V&A=na|-)!jQcwPF2pTdz{b6@7*d zb3LX;9$Z+}`E=Qfr%xu^$nLC8UADPooE^d{&$e$SN<#QmNvHT+alQ*ByHAFWA}q1{D1l9X>83q z9B*9hJCsjebMLD#MsPFt4zyCXmNK6%hXft%o}-td^h}z^I|+0;Uh~8*mQK)uLli?U z@h}P}*-Hc@7oK-6y8j^#GwU~!A@C&x9Tzr4Kk&o>9Q5$s8^BR898a!!;MvNpC8A;! zKzFhB!IVMTLUhpM_Jyu{4vD8w37Q$&5ywOc zyn2WcZSrDNio$ogCG-2VtCqP-mgTf%;ilT+tuYiyG-A1aiAv@ett$Z;UyGumYKx)E zXW3y%R!RP>PZ{jShotiHZctlhURt|yG!$#MAcE(vJ`L%hv zH_HW;r~;g1PFNljEz&6<5delTYkaaDi zMnyp?(ZE9_N&`tKziyQYa4gNGM5aGTf1SdAq*U)Z46!NVUmuBJej|yt$8HvBoatZn z=3y45jXo?Au+5iI{Z{YxjyNsV>p`b(OhK@)kf+jm9*^B)OAh`h`HsJ<#d>Fx# zH7Mz&PIB^2`T;7%nvPbcy3!=-jhoM)jW6i&#b?+=6t)odqY$3kYOJJwmxY3vpj@LD zsw7SOPr?@W!sQs|>G8~6E-tu}aejn1Huh$d*?MI+qvYQr^~_7zF7j0PSo!d%0hL+E0eFFRyCHxOk(2O5$`0$&GpKG736|$ z!2(<*7e)7eZnm_G9kJMwCbKB`K~6h!8lv&><>f99{VT=FhFY*o$;o;Jo!aQ+>iZ#M z*aGD;G4=b)FT|Fz4>k3u{kwcZpB~sy`%MZW!M%5$X5n~%YXQh zFbV`BYnW_dr87xZxiHD7fk=mCy8tJDZcT#Sp~#cVnmNS)_7<7RL?Tc!vJglpN-ROJ zG-pqYs?=$O>^;KdilS;|r$K?bn!J61lWiH&q5Vm&pQj_QF|-j9T2 z#3cop;!EONDKM?kc2zT8J!0^eJiBD2dP*VTVsg#v6vJ4qMlT8cwH2(~K*57G6Q*>w zR~VtU>EDdcI)=Wl*IQGm7V(lzH0%_c)mLt}KZPnFMt-bHo#kCZ5o)yW!?`NM2nm#t zCB;P*nY{s0VA6gco2E%^EWpC+Plo+s!Xxx^=F3SaAQ z^QGB(2B69++$^T#1s@+Vfv?$ptm23DoIyevwgQj7)C<_YJw=ZmsGd*WN&GwwbNe7K z0P8tTJh~-$^h#;qw$>CK8dY=g{hQa1KKLRQko?BqO~2z}L_fdz7`-ihd*|}XK|JJH z3NEgcM85@4hlZ_}uJ@nw)dfeQg|-7s&Q_ebQL;L8y5fZFGeEEGe37)i=(%gcD^BEz zb4me#+6XaH`jp!9{taG_%m5&!oJX$7^S0jp_@LlqITKh9(=lSGf81>e6ON;3)X`!t zdI;^~>(HYsPRw|TgXiq8&XIdeyybp5rxO9Y~I;E=+MX-&e4 zz^k0=^&aBFOJW*A%_{JBI%hw)o%6(aVD4m^;m>A9x|}`hC3V2N-TUd^N#se2cjtRM z`=%hg=~ohUJoSD&R(m*oHAuMu!H&ju)V*)?w}mtiGntkwV3Pj8GiuU%22dF(Nx%XA z;&X2Tu#z5nZ14TL7erc7FLW+i$?NXRE$&0vOT=D{u#`@U@IFckID@A?g|@#AYV@TK zzFC#CUu{Px`a%&f-{@oKG?n3%5Z_=J&z78OJna<%2wPWoD-{aZtfszcPg7Y4=j4@n zg@s!1hEI`&bWsqKm;XTs?3y9D5>BylT5;SmTphn21FI{vLCc zC@WKD16<^>FcPh+!64u3_}W^0o6BGDA0;_ zUGxlOI{Shh36f(^1y%Ma|H4daj6kKRCnxNZew(O*DL?^@tGTu)-T0zcDT0$6F~m7*n0JKE;5d~ga%uLSW?Y;$%t z40;|uRtO2k_z&wPA37Tf&`+K)H98pZEzGRPIJS1S2 z0f97i^~-vkT^x)g>GkwgY3>>CvMSv)9wXN@EhezGJ>_XRh##Zcluunb=LX(98jahQ`3HT-G<_xVC@WYVKI`@ zX0w>Z_dKH$D`}qvmva`O`u$H{>2TKTSQ;NmTK<}!FM-!Nzco`fD?leW5F?Ap2&5+M zr{BACFDqwt;6BA)6fVAplA^ahlkwMY6vftvxcc9WYW@^Dj=PQ)@i%qbw_$`HXh%-2 zm#`HG6{c~7f~I+J?_PY_H!Ch`E}K|7_hx<-Dy5l?YcZ}GMKfP!YovEM>)`P8*e~qpfip(AMRDDWB50$1B#{GqE@Slb!hO z&nWCKM>9o$9IUAK_8|DdoT+yBn=~-zC5X$O|`6$;%#?!A|uS<`bKG20SrjU3- zoOL`s01bxY2rpCWFURQ`Y5A&AH}>y|172GDy99Ki;s60s-1xSqmwiDZ-0p&fg;Zo# z(TpLfF?y=VHOe~n!eO`b?iQ>mp2}KnNApj|ue5&Lnq^HFVJj?95$R34g583)_qDBFP zfWZ(;ydCbf6{$V;0v*<+#si-Fb)WXK+c=^|FgVy8S5$d$=@`@!J|d@nu5-@`t4&UN z!r@C}HJ}^+y!=w7-DTx*CSY~PI5b?qz@jP5z~N!;GLpw+`5-=ux+am##QJ$lhdHUr&`o3=c1l0O>&WG6k^fX&x)kjhMwR^=aQnjNp$^K>P3wy4q3txd-s`NuvCmUvLt0X$Zd4blDd z*xIsnzE=X@w499a%<$YaxqP|T?WJ~A$V!or@2H)3X-44XvHoegLB}M+P3RGAk!;+K zYe6ZOr*U`iZ&=FFj-om3g0={~%Wn;Cn@*JxAUps^mX(fcGUtJw2cY)ID(cZcn#D`t zY(c^CcDZ*+y8RbxG649f;r*OQ;YKxYLbcBHE9~IrL9WHMAICWqeJ+iP-*Xf_@w{2c zyH`GH7>Z%xVkL_)!A-;>J)AaFF?fG=%dr>H5pXGI92w%s#VOjdUn04SA zNrzhORENDDhni$`&wQ`N5>b9*(pTH}$AU;NGk648GjVo*UNuuR`H>Y!sMRjm*aftnx(3E zdhf6LCZ3Wbw#!DUSXAZxG_;XhJ(Mhxs_OI&(QHZY0(mQeS9>4JA$d$9XPcqF@Riai zPX78PDQ(>oZmzfd%b~u-Y)p=}&KZe9M{+ay`E%Dk5Ie2Vs&9Rl0suF4r#JEjpcU*zGd;cJ3e{pkNcA0X=qPp~=+fmpTktH6I zgUJa}!i?WmG7UnFO~!CvPrLFuG9(zX*2pazoNjUUX(g>9jBMCrxdbPIq}t8uOPbse|&S14F9Tz*&8kTws5~3S$%<}0|nx4 zTS!=sY*$&fW<~e#d=o*3-?JbNpZ*9gneEMkG>#}zOm`$hOKQuq?F=6m!~yK2seuS? z!E6jlOxyb?9t@bA#a*8E{9OdCyPsb0?JQX6Dfe%a26H>Ozhz1%`hSev;1U4JXR&SQtS3Wm{>qCW~<;p~8 zFtp8N;~#b=k#-j3w-*gbhTQjdbLCht+T3^a&hWd58-QmzKH}TN~fkJT>)%IeVRj6x0Xwc2AuQ4a) zjVf+jrx(nG*misLbk`B=yPH26CT|&%W*{*@93n=D!zWo#r7EFxTENlY&PR=IiEjwEpT!Z?g+Ztsp0i{4i0j?~_791HY^;{$8SSrGmBG0dCyzO>BH$ zIn!5{qwF;0iT4V}aRoodoIhQF*iXS?z8{6CvZ<_HwJ++bP752Ug?n*lY6ZU3)$4l|C5L(q`XbFAdquBx1Sq1*0OU+i@be2C9(t}OQ( zivS33w@2&VN8^>7*+i_LOk2!+4Ynf9xSfXw0tt8AXv#cF-y&SFPL^hPV@Ho;?%Q6# z3K1#y7v~(6OO$%z?fZRhkzq@L(C9BpxrQt<{1_osq-?>|-8S(+J^`PVs5zv6*Dgx= zOgdRakW+&+oeDG43APytj8du9eU>Q7`TSVyWur4WKKpvD~K9Aqk31{^W`N2rSzi?+9nW&)kss1@wt!L76&7;1`0R%Xo zs(`$3858fd{d3J2BS~en{rmSL5La-qM+6WD5k2q5oEU+$)otT3+%AJ>EBfCOO3FM$ z0n+oI#7Nxgq$io^S-=nONV=d3@Ru^-sOUa9LF~uU?t$zO5M)>FyeObMyf?z>zAj_x za7#ZO!`|nF$&D2)Nw(XP!;pOL4NpDGveUa0s2Eq-CwPKr5tKyHr*a5B0}R~e+2It; zUC2~e%!?xx&L$A5QQ`;ivXb_BoO{6iWhq3I+9q<^ew(4Np?94p95h5Yc^ znMSFL#8GHPkqe3%o6BDZ?xcBCT2vfION$TVTn~_1s6e7?WpZQv?cENP^a^R4&>A#K z?y_7UHa!Je>Cw^$KoTQMe#m!0x(XErBcj4}{{P)q@6(swGc<~`E(k&vu`vPb^2(p^L_z+<{ zrmVEU7DlQt)|Vv6gpC_<;yKzOp4HD?P>51Pk4E)mw~`e!10Y7P7m9!!pw*?Sw?&6Fy%S*Co$Ji0+C~}<=k=v z`(1AK$RdlW@qFY7JT+u)6j$OwOMy15rTADEMo!-QfOl>&H#1pNgi8+dyB$=Lntat8 z#UKn$wqd^5M5agO4e>#bLh4*y`Q-?DqJc>2YxFUBLDHkpySo=P6z}*Elnq4V^hQny z_eX)QpX#wu@6v$-j)qB-qowsQ83#hhGLg*5Tz8UvwQ9>t!^?U~B{9Kh)S_~+zwN-W z>ceI3N;ais7$b8SP+*L4zh!d5_ZGXmyvCLA84f&(YnWhbT!*G7d5qk0JUqn$?-4m* zjANg3A<`RpI&WS;N; z!a@{`AR5HM(@U+h^;mW&UWA5%B&Cq$XJYo)cCuQx^sX;-ra4%iunS?Cw$XvMX z6aV53T6&I}GXQ&Y2lu?Az?Bo6M9t>u*Jf1an1aT30B*=+-P4o6KsKhIhul;?@X>g! zU>c&wSlEtNgG;4JjWY*tg}L7wciS9e_%h+UQQtAf__j(v;6CANa1g}==Gi+d%hW?0 z4De_kCqgY^55s_nHZMQVK$gNp+yAmVskK&LVC+COexi;7bIbR~^Zo?=zi|AF-Hf{~!{zbgk8hr*WeHR+;L8TTJET_>^B7Jo?h{ zoV4!zN9%m)1tL?qE)#EvJFo(^-%o18lN-rmw{klJfG&~al~OrD-h>UuqC_$g&1PwsCIN7BB*uKF-=K5 z>bvmq4xwbeiTDm1foU9H-gQq1*}ko@wGP!?`lwVfs(SN9$`9Il!Ffyyj5MetP28K# zM0!mi&j{@~+1*$m^ukk5M5E5;PGJy4VKA=X4h;rr%+8%|YE}*ns3jm@o+?P0 z(DDVf?0M>WwTupvI!A8)Y^R_5)`H7wcE3jJL&jkozgQU{eHxE4dDC7to;H`Cp4=S& z9rcpu^ryBXa_cnl7WRqcKv+F2h?r z%02%0?%QS7`G4Q;80ELh9eYh%xfv@kZ>?m&Xj7lfK|Z>=|Ihk{Q&t&a%f|>g#`{Qo zzvmbIGe1uqMODjZ=XAQPLGq%i6{jJz`AD_yf zd;2@L=H`c@ZpP3Dy|d)yW)pd%u22t}jeVs1$6-A(paw4@?X*ldO!`S(>09{npHnmb znVNrbqqx{-XQ4f!qNtXAci+yf59R;JmGOnq&YlB*&vPwjIIu=xWn=gv!u_Xi)c=g( z`9j!P8VwfpAr0+p$RDfPju{go!i3;+`o$;XtBIdcS|Cx)2xGYyGKMFH8TN@));u<*d11Y=~8Yu6WuiGJ}LvW4sMl3?I+Z;o-P&iaDQuDR_| z$0$s)`F$$V+`~8;gZL$xUZFAwCNA$tP9`9GzV^bS7KXd?XNL|uMB9Khy7V~AK;|a? z?@LWfM5DT3+HrR)bbW`7Rtg7ZcEJ&ki@j$f*)MF1#|E2m+$(I>nbY$Dt*Y{;!6Y1i z1gB7ft&x=0gylgxg)i0}?7R=2IZbUuv(t+8AAQl=fv4Ykw}w6cagwIVbGcYN;@qnB`+2CxVSwU00uGV#!=2Q`bw@3Y#oy_altQJ$c`Mrv z`}3+w*Q7$y0UbR)Q?a1DQdI(X(`+O*Zo#p`x=eaHw)3O~sQ0#RbaV(6ju1?Lc?eVJ0sO0m9*=Ck#2Jpo zBYF8vZ$Yg^wy zz`$)gyv;tk;Vn@l>1e#z7Zk7j3*663Db0NpgdFS&;WBHQoIWn;&wF9#txl?{3pWJI z!9~&=M3R#gR^&3Nx{U0mm{Fq*N7924mBwLrqV5i8YKY}Wb>hjzq;W>z;**>pm4@X4 zI}?K{clzu~YxT;8G;@1%133#~`?d~AvEangCd&$UkU>7uEBs=*IdUtl$I7%-J$O{T zht#~f=|N4ZL5+cU_;>)kr(!!5G$9##xDS%2s|;<&x0)W=pcLwm;x_9(am}7nI%V(` zZWT%BeFb&V6>hP5Y3wQ4sWL9+V>}DV%VFXk>!R8Oc zU+s<|fh>upNDG>+NnCx-$^^!O=K%5g>r2h3X!6$38a~(?Hf!5B-@s#dT8m0P zPt&HLYgelyjkdzI~HSdR7I2~1Y-hm@!ZJ< z*a?N}akqfA$`)_3ZXUKWu}U~dvYUACi}3hbh+vJ-zTf+ufR{=lTlor35?A64vx-KG~J{SYeIuNT<|)kR(FCskv2L zNfMejB}2zHb!_6+g53y|c#h(P=F*L-O#=fQVR6S9*WFOPpVV>)R(h-9!eQGs zG48sHCgb80S1^Ks_r9H1WKeysESSafP$=zVM8IjCw@_QZh?to0^Ybk_Gdd*Ao&2~Q z3i7oW%WJDnOQ9L-!t}Z;GPo|4jj;v`mn(W9e7-j=G78hTIL5MPmVu8Qh9Mg>BEIKH zhEBeyA8Q>-g7Y7Bq`zIJ{EXqm*G~a^b6ggyiozoLwUa8TaZS!eMw~pJAq1H*q5l{z zm0YctEq=8%mMQ;@<*lpH0TIc0*wY=UPucP5<+bs-6yhx%0di;ZQr9=>(Mz^9U0G=% zR~`LRvy)0!>EOHtI(^0OjPq1B9TMf~&#PjiE}$AY8&RG!RCs@&9!wiS$M?R0H}6tk zO~tWCScQ9Fn-gIo%PEUr;&q-)bC6BM@m8HX%2wf}=y#c{YRy(2xT7PNTk`^yCD5wO z`Z=Vb^o;rV%ugNJroop)^z==>*jc*M#hMC#*O<2^AIn~1c8cQEID`zI5%5(e5x;}Q ztzQPd01f^KEek=1ZsCav1{$4weEnAG^?a}S9<*G&(5G3YNRP2`^>I=SVIxSs)inaH zr{3=MK>32~4kyU&{GNme`=IfKuKQi7xKjz9NLd2ggGixZ>Zihe+ zfje!k%wl&?YI0jHufi0{cpP*82SkbdPmw`u$K}cNKE$M4T#+*nc&X!n`%wqE3ilY z>LYc9C~HGG1~thoNB%Qfkz~RRN3SnY_r`gaLf^o8u1Ne7#!q8BmB!0tdzoXem)3Q8 zmV2`NRXzv?YR=>_xZliZ#TCae{ym4UQ@FUc+Vg`3p4B6Nv~2kr&&(=9gF4qS`J0FN zdjJw)#XbK_Fbr7+kFN9SuekRUH%k0Ite@bG{^)?3@N!=edbTcBWu)&D`>Ualts>!0yj#K3zTkAtRR3lN zrAJ3N1`5RDRPs;VJXeU4vu&Y3){lG0VZT5ZRkd%QvCIV7a&V6JEjEoc4qSnW z@hJsnrVDnS(_J1_{WVYS84-KP%Q?Dh1wOXtnr0hVTvw6G8>!;Djyp%rp8O4aqZg5v zj-5PlFTH3jj_?=XENcZW9iGq}^PzSGc^gU!$~*E(u!`kMBQPbcz+)v?yoCBY?K6ru3RBnaOiDJs?FVK zlhcj2rj)oE7ZpqzNh+Kks`!w`ZJqA#I|B3bVu~gVwH3T2D|VQw0o45|MFEXeFC$(x zVRfJKW2Tq4O*bHAW^1+@o6}~81$-`SZ<}5l ziJWY>AF?H8L$=fn>vuj`H4m{jMDMfzQv>{@c?#p&SmvucxokDyhK>d8Il}t%SE^|C*ICe zG6yrOtR@AW#(CADJV1(k>i!T7*mCq}=?!r5Yy>b?5Yy%48S^d19xaIGjLge2lQvj_ zTKJX%3Nnd>Prji>W#7VP(!LnTEq0r*K=rpv`0%~a!}Ta;^7w517P}u%{T<9eYtN8; zGWmS+DUc6y{`4{ES(VmlSbHi%djN3qguZ?YcTmBTEMLit?fwrdB3gJi+f>c#9b0t> zd`R?cOe3t9!qLe2pllEd;$_5kKYAX=mww*J4UK4I%sv3O;$3yv09ql%W`c+IvclJk zL@P}hUvH9e$%D|ij{M9LU`!bvzBm|cnF)fJ$X8dz1cO*z`DUKP`9}oS#59AotvEynrlU`idf*s-sX^(|INzwDl~A^o6Y7fb@~SY4M{10ILjbfwvip z`Chiv^l_`2arpxaA`0KK-n(4LTuGZQ0BrB8M-)V@XK{aHm51LW77;wO5A#>y^E@1_ zK)s4eYFZR_@X4oWbk1y!U%3FIccrA4VB529AYU#3#j6V`58e$y^pJ^8|t(t~lLu}iRUuvAc)kMvp4xp=Do`aip%KB>1n>^ z`X3F5o|2!Axx92;(vzLy*{zuJar1kNj-zwMvfHWIA`_U=DnJwtGqQGXR6`V>k^Pee z2j0Yc+fE5QUHmN@K=#|Jsd9hv{lmT!0y+2Mq z&M)0q`T`v(kpl)?{gq%2|0HG)<7JQj9mzWQn}jvS3+stIK(I-i@SK;RXCv7NQ^P2a zD|-Ok(M!J~iRD2&b5WP{Dbjkp)azxL;LZ$c;icK^?>csuZ}M0T+_#1o>0{_fXPb01 z&a8z)|D?Ou&;z<87&PG10(5dLvWq= zhJ47OdnbP7y&(Ym3`ZCswLGP91gxB{Pr9-F5ehNIY!u>1=!42yRwh zAc!{rdGOlFJ!NxYJjwls;dgBw))IMxcr-EGOCqvEb;(NaJ2IZG9&FBRE?Z&haj$qF z`*e`C8wIk0NlzA>f<|e5#g1=hCb85dF}hJ#gRK09m@28#cW3(rEu&o7&PW^cbnpT( zfxYhOCJbKNJixsK)yDAa%yt~;;;=GY$Ha?tsPl7nqZE%Fb=z0nxBI?43BNNnrmiBl zHMcx1>xJbaRVpA+p4Uy72u{6Q^XYN(f`CZ<7(};L? zk6?!Fk9^y;ruY2hPffm||1Fg`Ns&=*%kotqi4l^ul}zl?BXO9tc({{>n3IVKs`f|c zo4ynAZapwb)?O4uny0HOZmYS0L^b>pw1(IiK;HBd@(O9(+$0&JC#vq@2f-LI+MI1c zg|FC54_T2K7qFA%R&dq%CSg)&azfuzXPs^`dtGS&^1x3OPmJRzRc$F-KMFy;z!rwh z*0wuZ1CjlSn|TV(?KXfCsf1^NVG>tyGRDIp1~VXqY&24l1p6;}K=zYw3`6}i6(mrs z6!Dn6=eRKW>_9Ed4uRVWPkUPrDxcU3ERJLPh&cAr{`aTbP_HwgOjhNIoSmy8Y8;K4 ztjB3(oLac;(oUXtoijN90$`jps-wHDw=bR+G@Io`;-o;4cYfzbubtq-B@*&_o8(tv zV6g#!LWG^z+E84E1Y5)0!0)S2u9(IKteg0WMIJZZ_C9m_d#^@tx2xBi3isjXu@2~s zHA9RK|JZQgZN0~wG!!W90goP+6NWpN`FngL2UJLkE(yv}p(fY{_lpUy-CTPD0Y)&R zr3VdgVid=iZd^5(@1&yEyg)n>T4-iER$O{DLA;lnbnzE4_GlERoK1e==2_KS1o-U{ zg}XGB6%T&v^_L2t&ZUhsf_i309o-&-3DRdVa2Q3-mXb<;ST0^y6>JCmO|nglK;8?l z*jNP7Uci6LF$Q~{cRA19Hw<8u0}(S@&9-uh?e8A#5sJIlxv2(~5V$jCGq^^v!#u?5^jEeF1PnS(|ctn_I@f zn$<2(xa);n7l^6zso96@Le5rXgZ+Dd1d@Wqct8RDlNL&{Vf+4(ry7+scdDTVMgOx5 zxVIz^9%mKMrom~BT>#7Q1+4Z-0Ja`4^<;2e@u*&ue-hzFtSAW(F z_cS2|qm1GF))z$(h9L+3vv85ja*UA{rpiKJhUPDj7NaHk@cXUT-OnnKl`#|gM?(zw zD8}NUipO7BI#^qdlGqOY(zO&!Z*>r=H4~`iu^{{Ra2|o7>eluX)z2F?x0;(4kk5Yk z{8)b=RlQVhjI_<$VUT+SFLC^r?g|%>#zA&t+9^}P313Y@sQ(eR7ImDw;8S7dLQHiu zKg;q{DYYkDhTMW`8$UJjNH8oc+=GID$YQyUHfEAUQSLRP zoR~!u*IpWjpIs4!6#}JiINty@yQd!v=%_Abaz2?0_eLdoU5}Ew^99U+Qu&~ayIGE) z!lDW#F>8+rOrpGu=QaEKEiO@A1LOe{t=6`=8qFVd2=*+;iWZ+)zr37~zCZrlstW1) z%@6yn8%d3uDca9^nO;TM#LQ}$CAT^lMA&PU2?PF-npKBp&zO8QcH@I@)^aT`g7MVq zDqhEZYrN13^V2b^PLh6{P6eD^odIZ2Tw0T%Kg;Fs-vfC;qXUz&DGq&PDaI#l0ww`2 z_Duwl5kmZ^nYv=}mz1cXyLB!4k09d(%^qeF%ZPiCnsdxGA=g?78rL|quHJHu(sWE? zVVL~%gP@tEAyn%u223vU(t%g452go<`Ye5`%`h9<(KH0i`#I8qJRFk&bg2AluR`o4 zmX-8$eb?!X3CL_#3T!6lxDzW$#1J2qZN-$=PG{8c{h~sKPqc`{UEW(sHw6lh+^z`r z%hVP3WeRw;1m&SSF4iohKG((1@k#2??I3T=rOqwXEA1k_*3=be8?1`kS>J-)6A1^p z9JnIpY8i>XHkjSWCAt#b%Cp+~@HIZ_Jo{AZ`^SI>9^;eB7r|?KokhO9eG7%`T0$Ok z%w~(MlMCG6cB4_7&owJS4;B}os9`CPK5eHEvzBXT9L1a3`z~@O+$Jh;z`M2|<)W9? zLD#Ctj}Z#%=KBQW?hHASv%XrGxe)oqO&lkDdzHJh9C; z`Xu4&Jfc|7%>!SYL)vMfn8>g=$wF318Q~{B*tP4dUOZuY3%5^D`mxa{bF9Ps{cv70 zzkwzvFiYfuloj|!_s1Gk;!$ZfK~K_qvv5*lIWuCF~F;!yPq7V<&IcG@iUsU=~z))I{Cf;SW@KLr*!Yn z_cABu1;ALT)`5-oQeTRwuP#$w*yI)Wk?mUb?!~&8jv)Bk(ejg=$$3BXlhT_R!G5$> zCm$0vES#|@^b)c*;#>-{N0-s*);*zX=W z+4(7Fj6`j*{eDdS6zJG7^*WfX`O!)Bu!$=r%hjom29~(1S=5$&WMZwk^j$K(&IZO5 zfaDSQMWg|~4fO+A5AHuQ9>eivULAU38$}l0w7X{V6+=PUGJVPyvjd+GkJ;H29l-srhQB=FHjH`7>{S0m+%2M9v2OPt&yusKac_1$O$m8YCQti{IW6rK;A1v#MR|Vk5 zuP2ht6CdfOt%u&0y4~~L)nk?)ZJZlD(kG>Mv7np7HygY}QVl4?&7Nx*_9)>dPT%tW zD0c5{gK~iFEi7*v$%mIF-~8EZOZ4wSU_X8zVw>-$2%J>>Zg&{+?t4D^Am|JZOpxUC zF6g1s*yA^n+;iT2zmsP`{>IDLec^}X*vRCL#_2`UeV^u^)BGgh2vnrA1(jc=zGJOq z2CvwaUm>No+_~K2u`ja>oJF#Gm-tjSB+H^|jCiXY{uxmL zkjIXloqMzLqL@+nbkuY7&I)JnC}+Yt-#>%O0J8XY2{r2aVyf&3#+MY%%Zc#cgx}=> zlnM$Z|J|hcZkP#r^!CQcE5@Ha*n2GBBTqe={IJz``U~l=VM@cOj=|sXTLodaIp;_-%gefpfxL2i4QxNDtipF#BarJSqcjz~B`2w?1S210N8fOPsME`P;?`9!=jf0a0v?Sl_>(P(?QkIvy zQ5wFUDi*$%Rt`IWmDc0G_`BC+5w{MXeph2LHTiYe)X843t?M_j`ECYBVPxeX`wa2S zxbE8}&iA&P;XxU>d0vpPZ9b*dOZ9jB=^5Nq{!8*!xG2lvzoP>@D9Z;tZ8$?cmBs%v zR+Pbb;}#8i3?h8uuRKjF#F73@^GhY~n!w z=Q-zDvr8^lCM3YFs6TQjljN=|4hfNuGkSB)J_W45%we!n0|j-HGBQePr`0J<$6bg{ z>N?%>G9BRnFnX)L(EX3j^V5gei_*{y>>PEX@f z{jwK`ZPte`ER&%v^WE;AHl%glRpM@1Yn}}-lHP9}!c$v3^uc#IBUhZJ=!`vB)}>nO z*vntQS(gdg)k3SqrCz?r%P({5c|t#nsc)_plf8xtQ~aCv_xFEJ<#=*z(U1tpViLZ+ zFhnL91V7C>qO(UVu4*gjo#GOl-ALwpI>U>ZrOj?@uqw&4(;_O!vHNG%L;SyaQu=*Ge6A>yhjD7J-S+R=J}5=| z!^RhHzRdV1uOfL(E|@8sM~Iw&J`q+zFIf--vjh|uk$mTz6%I$Xf4Xxjyy*m6D+`xx zn~ZiX%3MBmt0c0VLx8oved9Xb%|KW8lK!}ANUbm_$ne{`|M=hyo9ijp$B_b6_@Nx% z(~qJ6?AGY@$I@>`PmxuhY<;ci+~pMmvXjE`-mnYvK9^JSqJ-X_eooSK%f#-Fe<$#PY&B71P6u>7j!pzhGs0xfVEIyVUvC zbc)}zf%zhOQc%hXrFnHefz`AW{S25HD?%%!F(K5mS?_sZ01 zudBNAp`9o_$-vc~$nsKR0Rs`&Q{^_Hx>#{&#?NEFW@OhqPP-u_slHL7SUdk7>Knf9|{l8d;YCW(xjZLAVF$T5+>Ornr{3JS^4A0jVK1? zEV574=GXjjoJ-ZZ$p|*-6LP1n%{P1E@!0gUZME2GNC#^=K)Q(ve7tPe?8ecLYJJss zQ8Y2UH1U)Pfor1k>+F(9;d^|6s;6C6ke-z4w-<7mtNmYioD`h1rPb^sr}t;w6Ybur zVloh`F_An6KQH}s;1(2Knq$g0;BR$4D>5s`4kOPOR<$d^zyj1@2T|IxX6rEO6g(Q@ zc-Tj#J^OH?mv_Fe_HK7U>i=v$rClWgAR98dEvcq!w9pQt$MNV|1g~oHbm(aahQEb? zQFAH2{?m_7Z=`RPT%o>eDUR)+`<3^qWnGOG=X3~?7$J`5=%uCw%U|IihMn6|OHu6P^ zooM*vnXppKtcLbf1d33`(-XB-&8liFz-@c>6^$S~)*(W7!?j;@Og-kic8Ul|@v?>H zr(ETCG!_cg*+4nVHxjkj6MC{up6(V$WjAV6@P-{C?;I-@yAI}9dyN*UKYM3)yFgI( zqC@$3?^3=>-_;k5?p=4VVNt1(g!uD{1@`b){U$M^IKQjZ{Pcwns#Z_(DMLA98^mFj?Z83Z?YbuX(fwzTx z$gB$1S*O4!RDx}BPK|U&yHL za%+83rT(U6*^9i<`J%f{gO0Ym)_Kj|K|yG>!zG>V&f}6nmxZ--{`LapV0>YqZ?O_os3dZM!4!d_3#ulA?^S;hjng|T|m zX+2X}ADJgsq5J=)+W#lbMpn`I5R)J4vn(rW#8H1qyjEV-`Z~uff~9iC?WH*Q{ny`& ziIfM$-&BqVpC)XIjR`Y-BU%L?TO7*^M9z!+!vO@~L(aWL2UtF>yEG7(=v z{#aDpGQrP^OqDJW^8%1Fq5oO2xDvkkLy~m2ZhHn4=wjsf?DSFhfS>Jey9GV040C;X;gIbJhuVCwk9-v%t=^JkRgvEc=1f$5x3QBt1zTdgEP7n7@HEX_AN&04|1(14f zeK`rX(f_4_komJ?raoKDmdMF_J-t3K=7Zf@!%zn{-sm?wzmBt&i_*##m!$!{%lk#c{DXyundHv ze_PlF%#4Tg2C(pJPpD&_0)dKN>aGFE+M?g}*V4Cq%Gbgm%;Oc6Nwp|iR~J$cNy#&P zXu&G`Yfq9>nbu6f+4#bf36{$KU>8lMbW7}>$%j{CJe{4aIu`J5C*Z&syw9)6y>dE~ z3habFHuR$X+CaR`1-x4w=l!NRNW{d3&i}#QSB6E^{q5Q)hzioB(v6g~C?(x6)PS_K zfG`Xpg3?HLO2Ys{=M12fz)+G>gLE_0fRvsM==1#FbFS-sKb{Zge4UxS*P6A~-oLu< z`_)^3s{%S6n?UCto(|1rMANoZ-cH6KVhR<+_(QgY?bSt74P&%9vO!mNQI7fv%M|K zLCL-$y;`~p0q43Ie-QBehpxMamR95=(jfDyl!wfa`m&}Iziv`XO%lo=)8ezMqv*p?DdSd@~ zynj$((CA3UkBvnuwf#=`>9G7{#nJtsZ8mYuYqx!eHkyZHpyXaDSJPg{XpsD8U0Sag z6h^|4ttpC$PL4ZK`R!*~;)dH&SM+ZT1r3g?;I|76>Y_4SKAwhk#8@ai5Eu=VSKrptuP) zNP%j6C`REUJ~U1}`9p-!ERz$GV8e)A09%b@3nmojS;_g_E0>YR+=-uEyn0GA#-hDJ zuABe}JMDxNIhGccs%o;S%_SC)WU?06K9iFKjg>}fnrZV6&2P75jHfo6)I8`Yaoacr zs+X%fq$bfNtPOf^KPA5+Y(=~%8ZSLx?OE}_i9NQG)UbG|!@r>QNrYm%>ITQr;;J7W zwz1xC1g+H3l^7U^Lu1McpGJ5eFj1c*Xa4+>e!IEpxFz!?7j}V^72RDSD^o2&UvTr4 zWI=W)5oLrZ^$K`~YEf6g88(5&@6;&UgHJA~KYB!*`s5&Bz`1jv7N-3+cm8|hiTQ09 zVI5Iv{f@^obzB`z7w6Sgt0{t_K-A)|Q9aU`H1KsCMwx z5%;Ygk+-{!?+k2&er+mu@C>#p3e*vbO=RC9ku!@M1a9%gwpNn{lG6^^`3;nX0wI90 zVmHhzL%^_ntP_sTW4%RQ`HHQDu8U>`C6xfz_gMZ{gsYZJ%oHB>m*SJ{8-?iM;}!tI zHV2Le;Ws!QI_2oCN~=F;vQUekL^rKY!A?{=gvSgT;cadG4`3OhyRC^mctG)Y2E4Y@ z%LY;T8ZsO1*s|%sbvo7Pv{X)30g1ij77y{lGE(_LerLW1>S4dPNL#l0e*b9ubt0Ck zHLo7cQ}vxE=tEu{*|fgUppi&0E4qc`BAVL2j4~@KQgC zSjAdoO6##Hs8a-RK?P#{j`(zXBQxxV_F*LxvBjJiy@Aa!Jpk^>u)ZK$1Bz?XDxJ7b)N5q*3qr$8^bgX1^=<4J zYB$hRtntk}ntG}h_WsifBBUcaD=9}pvi&3%;&)=p6>W&GLl{-ACmBORn~z=B!t6x& zzu?k*l#v1lW5NadQUgm4;HoJbxi$@Kncb}+p14lb^7Cy)VqJQ7a6;akcFhR@un5r? zVU#u)BP^VGIxPQ0b|3O02&QRyxV2eUI5NB8Kq@X?2qaHfcnHrcW}8z$1sjAPQjARQ zqO8An8R;QIf}N+L5Q`0w9ky~GjdTuC7gG*q3ffl+S|wJVK<7Mf)Ffh1S};7SoG^?{ zmZX@Js;DmLtSvJt9;`(ps$r}R3JZk`SUzTnKIaB;@EE4BWuo|05e|~xScj*y#_{M; zuz~W@3wSid`id(&-44D3CHKs;u;4w!Vba>hoP1PU=g_JOIrfbKioMYyK8x6a;9#X-aO)oz!mA@lEtPek3BVTB7W zaas?)j751*Tn$6^u2tlme0v!NY2)A*+9rGl_)vdY544#%H#$N$-HnNj^T`4;iz7(x z?W_g9M**->QW)DieGC1c*7Ua>3pfNxW&TXpY>?JZLt=s0!M zd3PrT?=2{Q#=VWkp3e+)Z%Cq<|K4jM7fW~bUS0mU73VYM4k`?tHnT)|q z$)*x9SbN0x?!AiKQEXsqY=3)P`{!DKNWdIC@g5wpvMaBK9B9Bn=e&H<_aWEz5WZu- z%sY`mnx@jvHhN$%8f+U(3jpAJyY*R7owF|qIwue#n@yqVMoC8 z64n4A!Pi2A27^6GtT6*`JsgqDdc201O|O@3q_*Zj_{rcjNxmMem?|>6+A!IGhjtO< zrxmfH>8JtxC31>^C&UZ`Pcq1RE2K{H5Wc8~by5sH9Ci9}5o+IP!N_&6BUP?&v=}2V zhhK{iD{Is=UYGq+z30l5qIM~-cW<4fp=(w(#EVTG>!3BBx2!`{+OZHxl6kbs%D(&( zN-m^7%WiP07t=f^#i(&S2jp<)u$#XDTxJ$80KG)8lutG~+m(j!7;`QwD3mP@d_fo^ z%ypY~Kd_PBQfWgm-;N$Y)b$%jgwUIKHwMNg*htksTXxN;q`EdB6BNcXVM0Hc6FLhn zw6s%7s@84&K z38vLpX#0@4_=@G_ZOR3ZSKTb-I|Y`jbsW*xOFBz@`{_fLe+CaA6pZ+o3(=o&324B9 ze3@N!H(z_9Zjnajb9!BWgn^OR%|KP+>6JOciXNB4sSn>$gS~5)(gl%t z)jaZ;f&Pd6$yIiH?>zvh9=@m5x-v#DBQ>VD5Fc|yd&k9$j=k#Y(wBjo3|t{D(WP7! z>Q7&|4{U6~c@jYGk-GItx{RS~J8RsUhV5_UhiBJ0mmGdD))(2KbFRVaCX1tkm4-BX zS|8C0bhs#Zq-s@N>ZozL2{L)Hla6I~`(@oH?sRteQqWbLh5+%msV2y2ZvW`GwIA)J zJj4Zce1<-<>|%)AEU>HOt?(R1m^7`Pgqn=0Pu_8Jwk`z&sgMR5K=;!RK_tXWTbhKn zi!t)3;S?{b_bxnPqb{O>4(V@BSCtRL7Y!P=%sqIs7?%t_4DLP1tF~}_SGfo8O&UR0 zHA`9jUMa@5xEM7R%ZD$7 z1dwb2R1AQgsOrFVQx0+qc8DBQvr)tKdWghTaAT;?eR5vN!hw1kCoRPak_dCdzL5tP z@oWEK*pl?qkI9*721x-i9UVq8y&<^}D`a&Zr(U;?Y=P}s?M0Y!1`YSeZX%?&PcurM z0aQZO1k@}0a4K_lq9mud2&L9`&9GTJcM!*^W;L;m8Hi1ZTs{dMyBjosMT1qyV)$`J$&84&iA3SB!Kx`M`EoD<|v08?oC;szOk!8 zF}~Kt7YpGKP+^%C#^|LfDF_R1WSSiou!@IbYZ>)#svkAd#Ot*VlrgE$X3D9H8gXJ@{H z+y(4w8cv8Taux~2>(Y-$Rv=1 z4OGBMJBrUa*e=MmXpotMvw@O7JRz!noT2#C{nMR^{Y@v6Ji_vdj8G077*})BeA-Rd zmRFHZjXK+K%!JUARlsQjzuK0QQo}d`J^q>eAC?C#adqq>1@q$Ll5=%i5O^~mG%fbaQ&73u<*l?zS6P|N zL<7l$M(zh_=jaZ^!K9%CA=5rjGfQfOGoP06#oo^reI+QkD71^BF7L(1$jnm8z}=&G z9VKQ(HIW6d_FX^G`{J@y+rw=)&B-&ESjqLsmo(o68pWQH^%ZJtugg4&EC#6b0$SF_ z$LL@Phn4~HWJtCnt2%;P6pZ*C`jK%Y^ z!j5QdfIgnc0NPP)yQ%PfZ(xLx*zYk*x~9wrsTD9Qe{AB-jVZrzGR=*7UGO6bw;pZ& z1+_s$K{uoF7|NxvWZym?mp-S^cVvtoql5uZCdjV10YoAKs)5=N5J>fT8FTCtL&syM55lH>6E zDLIoVZLbRpu9@bOEIzyyP6DL>T0WxO-Q|Ae?k+c_@$e_^YO3UlUP3qBlB9)H3b=(` z{DLdv0Bq-^qjWLtHf!WUJw9e&MTWb9FfTFaq0x|{?&FAC&gwA1`-_D-+M0qv2J#Qf zL+0rBi0N|IO7r=;@_pf=#A>9_s8y$*hN_4Wbt41YKGr6YWeiRZnfia$}D0d56H|#xf=Hn4^Jf z!lI~rk=o&gWxoM$o*>N}wygFdAQ`q7A(y9$DB$A%Vy4PW%)UXep&RMD zik>z7VrVt8KV?J81eD>@ zH{)jvr_2mkmGm=!6NH~{*j3BH#Wdm0GJ2W?vx@V7F_y!ET2vm}PaYq574^e+yI_4myw%W1au7F3Ea?{l&b zu>(=vMnL@2MK`R*QpLzMFpeNfDtHqjBalT|XxNuB3r>&gP_5303Bw)NbPe#g(&CtQ z(r14!Ua|ou&|NI2@9G_5bIE4q?2QQ?jDy?{hL~U9k%yPprwPM~D?uM$XWm#Z3Wc1u z3*67!@$h05{vLM&jD=GSa8L#zjvxj1Ct*mGCOA9QOC@G4M2WlajXre!2G&x;Yc9h{ z6!#SR_8V4LyozBm^`Gsq-Q8RW_t&-yvFwy;&Y+E&Rn8CKmX2#cBPz8u4aSvZ+zjc^ zJ#Br`EO`TnFgkuR)F;)Lm;7cqSt2jm5E+CK_AIdz1}lB_5A+JSG8EO#>uhu3jE_6t zkeg;O4XPPAw;&SPlgr26B+-~wKjqN1~$WCU3?y}?##!SE1<<1aQCR&|03 z*f_*J-aU+RE^z^JufSD^gL$ED9m-Ur9$#-ixxW@+ShM@WnBElq`(TynbbffGX=>#s zpck$Wu*119NS-~gchY373&v?fYibdkF-196kvIEsR^ z_tom^eH2z-MddRIav^NNcKixAVswQJ71lcnccaiQZ?4;tj|6ZFr8W}C(($^dSH^99 z`lSx#21EqS6&(1flv0CFDdGmzJW%}60EzNf#A$7MEc)m)oYL?bVPD;usQWDMtgSaP%o zF6l5emCO06-Ep?VE0wSdh%rLT(k zzAj|W1-wrJi#iU#b)Kbq3JK$;ScR{aa$?Kh%dE&l{V*s-{nY*)lf@V1k6k{nEAKtl z@3Z8o=#k}swRTumGk?wH58e2{Pm}ivVK*fRSDIaRN`1GoK-=;uF#v7B`o`c|_s1^w zFg+rnE}e%0>RRi5!Z?SrmsK>MFPat^x|t9RMp4b0yqWct)Wobha&aL*A*-7tn;tH@4VcH6l8it%WNVeg6Q)x)n4 z#vA!8=h5+$&7S4)Dk_fxWW#e{KlN}MZe@+0oyzPey6?MmDeXFf>Z#owmEk8I*30#w z@Z1Zq0w$2E2ImaZdm(`}zCLBc^otY+ljieIfXRrwd9A9)@IgIiR^i1MVB;_l&KZdk zP{XPjL*$+R^m4m~YZwzwT6}sekHQ@;RAo{#kRCoOyf2r_M=zZVV^Q?^++C++QHguv z;k29-TJmRjY5$Nj1&KPsQHbzESOT4o_sr-z(p~btbL(xfoulVqQG_mHkvj?c?mq_oiVfo9O>g-3vPH*(&3=9|%YSd)JvSv6YVp+jpHD>Y!|kV--}xYS zc7}VsuU(9o1t7%$6@Kg`<*&Ue4+Ad5K&}JAU7SYbdiXzLCehoNKe{eNfOOWUR|kt5 z9;BZI4daXo3@0}Je)k#S{KLP0bF<;yOLc=OqV!EbSm}?Ni^x@Ykjf#tKlW1ItjSV1 z>0k8`-X9AMzjb|B98E|40X$srdGWmW-Dt2051!n+Q>gjqnW)#rs+=ovt)CqJUG;Mj zu&WpfK-HV(ub>w{#aXHNT)7PRm=Dm{aLa(yH)lN-m0#f?HJr|k0Sr0l#AMy@&g9tL~`7{}j%GSS($0PTvju&{h@AD%3Kb?4`YyowQo z?@50%4t8@;dOIWwoO|cTlq6mG zcIPyI`bE$3&{)6a8sD9J#oL_s#U&jLAU4p*ioM27&V4H2 zjZtamR3wzcb{^z}NE5BG@w?M{%)+sp)bUzhmF(2gP|?C4Z#L8EuXov*pQx@I1kSoZ z?Ry7n_v}gXhF(wY1|RHg-j_B>jVR1?JB?ZB0}AnHYXP-8z!BRwBgIq?PmCfI1@>fk z)(uqCZ8$}l-fJ53bBe7i%q^tcs$f8^#5E|aw8Gz2*TpE$>td!97c+NE9P{a|{Y%PU z5)kkjk89d*3m!;OJL#q|g++BxthVs#YxY;Uf9pf2h--+BV%!khXX;%VapJsy44Dt) zqkkpjpAzdn)*g16*8wH$;xu9q9nHBEA1UcK;sm;u&MIo3tG^GQjAc>7y)F_*x{F0C znSFKYHMQoB8zHhTw!R-_1uoDTzRh{9o3~+Nu^>P4W_}LD)M_#!?=+VkyR%u*M|?tkEfKhtEJt25@dLQqu+HjyM^3_ zbIl$#97DVZzBB<@$rCw&-@0b?#{->iJBgV;cL#s-_uc%n3B|P@g=A3=xEyMsh$cwI zky1M}+j}iArj^_pBR|aJJ}6%^3s#Ax9Fa`h!OkUWjMFu*Mr^T&5yyY~xe99&>h{cS zF&!cMT|{hKXhw^96diTrRCLt$)j|HbEIh@>Ew~GGDfUbq%&~8(^msy{W(l-uuzz4L z8Pr&v6q84Fdl;zWnhz#x*Art_omzyc*LLi?bh>o2(VG2~6Y_4&bf9|IFGFDii08Tz z*-f`G1JJNn zCuZ}A-R`mb#eP|wmk!r+danN$H2XNm;`xSOJ1N@zI4>G}m7Tqw>vxh9H6PVv@Y)6~KKgT)%20 z*U%KHiJqB)J8IE_h4_RFvG6#TN>|Z}tbIYf3~Qg;y`}^*OF~(+>SLu;3#n*3fIDGU zQ!e|R3y>wh5E6uc3;lu+u&?6(Hi}T|g8qLR&1&LPerE`iKSQ)Vp0Ao3O z)OVO~fMD}5CzZCN6m}8j* zmjZI^e_mt|`{K~rl{K)82u%4VryH~4Zy@r&+Yg2A-9i!}f}GA3DKYCw^|j%Ha+o9D&| zo4c6=#@6_HF~2a8Q%|T!bd@UKg4SV;!-O#7Yni~xF=jJ^up-MmA!WaTjjNM2oNIm(=4qRwS0f%gd@ zrVnUy-MBdOJyZ>Ahd5%cFZcfHIgY+sTm}yUNuYDBNBFl33-!ljoLlSv`L6$+g*uZ; zyHJiCeRb|%{9hl)R1hO$OlGgpk^pXe<2fzftOjnU!kWsEVZbj=IG~as6G6ZR0Ds+- z8RO5*RGlB=oNFYFd^_c{hH;w!I9kKu{0k{tDL5^!F-LVL`s&j;WJ}hCM{`jggmKc6 zpq-e{h_fyDs){DA>C&8}g*I|)ZRo}C+De8d?G%?)m{dTrc5>}VEIb4vEOH*~!BL1k zNHtk~5)w-ACSo^4u*)F&nAN)0Osr#0lDN)+GA461r*=;37^{SEEX`XK0+8E;_= zqVZ=1EkEXGn=Rms$N-3||EL|N4Z4FSZ(iL-MJuBS2~Ngorg(LULcU8O_oRYF*&8Tf z{!%v2iw8qDKXG0c^m#%Xjc9BC@b;^DvdMhjUR;>zVBwG*6qFk$9We}DEpSR8HZP|V z$(##L8MO9jWKKJ5V8BvrfNM0ZX(?5Nk!&{yl^TdNfMcA*{l80{LF+FDqg+E4fX7d}V9=o<{|iJ8hTAr}vbWPIc0vz7Qi zB-LDIe@#8ZcXOn3XSp(&kS#k-m$Ue$Ns+cbCr&LGnabHwfJbODvYD$@a)rk!Cjafz zFWzH+QDl(&-=0y$qzg}xLz6;wxCA&&|4O0(}v05IjOiGM}Osw*cBjSF3pv=l0 zaT7=yAI$^034OExTzyv!vpTP(@t36L%?ADPaV9-EB3RMP>(Vg;g$036bS2dA=q`7O zSTZ|A_eMedD9N+F$MJC(t#}mdz^xTxNEjwrcY4fZiPVyvizlTXJh^{ZSr7|}&0+Z& zhfW=r{XPJowvsl$+d_F|`fnBa?mZjWhcoD_-M)Ul1t5RhPvSPsQ;le^?n00Qk=U z0$vZAU?r%4pPI#MP~f|+&Bd7$|;Q`A)VEi_Gpx73W73fnh9;g(zYD3iZrlQ;g zDp%b`wO_o+hH$xx%E~ujVC%@Y-w@LUMYXvFqam z$fm^l0p{&*f9AdEhT2r|tY@Q9+&P7|t3UBdX21$z6%JTxK)`ss%xuyIp&kBFij(t9><2n*Npwm4e38PBYYbe)aw0ZrR%@$}0bA6ows8Tz!YRVkm=FB_yau^}SCUGeJ1W$a($7W(yQ zqW9rZaPwLSHR!HjeAbjeHysb8jTNv&KwJW-2MPp}LR6^y;F$PAIlspBD3|$Y!XcG9 zj-rv|0m=en9h>28Qnf6;s_AY05IGrps79DU6@A;p^j?>7K zc?r1Z(NXC4X$0seNbm;S&{koTXA&_4?bD6eavH}*L7d}i>gjKvzj1*iC# z&Xh`ezk*H``S*$ocLcDk*Zww*0;~c+;ri3`3NXw72kcKXG$6=3BYmBTS_Af);+$i3 z(KPKW$OAO6E;^R|9qGKe+CNkKe_|kBJq+3dfA7aaA^s@QMf16{sI>s%Fvb2%+;>vIYeqdxP7qa=AU% zy{zg+8!(~hk5}LwVIJ;z2b(n8_Px&2BAbUB=gQN7zxxeQ^TCzt0Cj~GK4n0;mDYz) zqm7}gb?vGPc59IFDDos9ret-Ip8Dn(JG4e(S(#frst@GZ*3mnSuax{_c_=4RczED! z_fm_;$S>SIK$i?j@ZeMW5RaCEEMq@bN3!vyL}ig#!UG9O4ZAh;1?4+* zrP>v5Bp5$r?~&p(sz!IE&jt`LiavZ45c<_{1FYs0!GqEZ%slDn%AWDtkiN`vx9G7l zRQWJJMT%%4Xil)Pn`Yi%y<*k3_27N0BOyWKDr?9CV|rG^tKs&YHt2krD_iuoC-WM$ zEHw(2!g2|~{hzHpw>zNK&inm(;?p-@5XpGGOW-EkmyE_T5-Jh}lZ>E+Jy!GjTY5-E z4J?mp!GJkNoe8$Z9HhlC5^(PZQBw6A57%YK%_z<`6?gI+C+~+S0w`WG% zyRKbZC;MlI%pn-{&O|;=7gVkxQ7Mmoj>EM((x1bW7Gls!VA!j|C)}yAJqdTLBEqeWN*}nTf0VpEVDFm0|RD2;HIMv$T9})g2MfvrL~EzH4%ATjMZ&EYwmcSv`R)E z&s7vnZpRt9rjIAS*eHp!w%gp|+{lct-r;Pk=yG(OK)_f=t@kkl$BONUE8+AkE$Gk` zuCOoVpExDVx{C)8kt$vl(27rho+<#bxKmfm9GzNU@1y4qc{B%pzTD+DdhmPow+9S$ zWM)dB`u9E9V;T+VYthfRgGXuri!ftC*n=7r5c#*pSd_kY zJotQKdo=ra>&uv|{A_{;h*(;->Gu zxH&{tBs?%>aKAf7nD07kq-3tfHtTKJBhw6f59L$#oLwTVZ7%}QLG4SCZG&&M0Q3Cs z`me#$f0F8*jJ=h{8kMjvVv8uS!bUpO=pH$%pvQ^HU7GHBl+N)Xr-wH3ZGbr7{6{{xh2fvZN(=nI@Xad+$Fg@ZHQhC9gB@xs#7fqYv8kJ zTfmcZ%)0IN@1b)U#dOZ504)SP+ik^MJ5dP+N4|_RI&m!tg+@`!2Dy};q|eu3RYe9Q z>{6^R)=kYc4|#W_AMwOX#lS1t%BalNxn*Sbv`hr5=_1=8S{5Plw@BdKoUpj>A^Exy z^3q)98=Az!eX^QQW2(xiBDr6qLc=(PZYLN9)E9LX{IoxHym(!o#ieWT>FJbVgh}erJB_~BXY}kpa!Xn zx=k-t_sO|%F0|TyfK9(FU5*31(sNa8c-fELOA3FPN25Ah(02@fX=^s@KSe(i{!LNy zlX%j+1a$8&^4Ju3Pw$ojZ1r>L^e1zo#(8cAca~-ruRT4u%klvI&yN$9z@sHXpT>pa zKOE0*leng6EAK(JC$46G`Z-}RNQ-E>gDLKA`P6T=8g-(87sL_XE^+}3jMrjukPc{b9 zO4*-L@C`Jy8G`G8_7KF&%X3}wZa$vWDb0v@f#9a^qli%ljz(nBrAu#eaN=eqD%dZhzCd<-gSA9lHX*%Z+^Wlc8r<0$HiRdnW=LsfQt!B zFHDwpJVp=CTrW1Y#AvUI!5}PK?8#BZ32OQ!g-7;&5SETgEpgi}bKX(_25O{T7%oQ| zVqjHiV2DS+4Me3+$nZu-P7%d(U0vM8Q;OHWM6wS>D-G?fy~Locchehxi3iUP10pBs zPZ7N0Cn~=inSb?VJm}$p#JT&x#f^x(f2~(cQ`hUgye)cs@VgE;;hn>U@1I)jd{w|W z0IZbnX#6-=f7oFH3qI!%B4MfLX7vmhe#Jy3c1+E%Q{v96Fc$%M6AzqqsEV-ZN-p`}ZxjrWAn%ZoLT09{oDO#)7R9O{soHK7M@6gE!*ic1{rt3{ zG@9>otT8f`o;NaZcPjlwwokIx2kbm*MBzZ|H3>UtiUCQQhBc_q(`1YD)s|+3`Qd7( z+49l5Z(H-jKvfE?{PX9!Wt-7g|Eb>|yCuulHoCQQ1Qo&}2$O)u@$v{OjGpl`x|FU@ zHQGixcguI;QEQ(=v^?IR5UaICJDerHi|*Cjg-l%3EeLP)?7^>I6ymc)z%RA}(-{WOfeW=w zClIcP9*o~ML_oz@NJ~M zj7DK%3{}ri&durta6myVFF;bExw=5_6K8VpNRqnQ{isHtvg#>ZUrV`=|cPE zW-U@pG~C~!ALx2hPc|>~&gKYWT1TZx)S;%h;cD?heCXMd%^f@3;VAV^YbZyTbbATL79K9utc+0|N7->S2PdyZTiy?ftUjQR^|KC>gE zNtKObFqh|f!XZ}jWKUVTa_iLE3x=Zz#@~A28Pe+39=Hs_8d;g1 z)DQxRPxr$II#FCY>v%N!SjL^p01Lm3A6p4y{$}$bLG4f&<}C8@^ocosA&ZB#H=T!9 zM2?u!&@bc)N|piJwc-~kKe%JBCE%Fg4=J>K4_&w_aoANh&IPeD6Q>2v!Id+g`$S1XybF?69k8>RU}pv9n#~iXxYX6SxuelV0$_}T}B5{=m(nO z#ECS|^1$e=p&DP!*mZMSaFW*Wr}T2d%PJ&C#*E|dbZOPw!?d0Thtg0mhIbCN$iAZf zF8x_UA2!&dAzt)=HMc9splkibID#{;>Sby1Y8GwVRceUqj2I>XbsWdlnfT~EHX z3FtZEn`R$0Gho`CrpUq#VZ>&l!6Z-^O(TOR1F2tGRkwPgh^kc~PWT6>7gaRw**iF^ zL54dkBRc_li~jPifaA{gZ4d6*%+%IDj=+m`$w01k&DvG<mTydrKy{SmLy+~> z;FQp?RFrMGtgeb>`kEVAp*K~kH|O7mQZ{6C4Wyh<(1-WsmT{AH_sU92zx$ei!zI9k zgu5gx9GR@MBN6+&>a-y#3Bz}39$jA43UPP_!&^2slz0~;DdP30K{MC6_%YM<8YHR$ zM(A?4_?}67f~4Pv9mG4zPQaXnSr7F>35Dt3xQ|yeSsN^BFI4umM33jV7=yRl9!8RW z-uHyDB~Ck$iOeM|4U2~Xsw|h#Vn90OYy)fOrwn8=SXX1X?9|hk^vy}<*@*8%jhm4B z@vspZo0Pr^=A;8w_dyCd!?bR-fd60_@cO*=asofDoi6w5qS^J)JT`O%TqO{y-- z*Su+Y13NKo2hP^G@t2Db^HcA_BweDHB0IFebcsxA{6Fe%RB9Jd(}q=YqpL!;^`dDCbyY8OGPxj4v%D+4_dcev{#y+{*@awnzc@}|SCrf608R#vsMSmNy3*AxAp`ga#;h;O zNLx!1&KlCXea`4QFgXHORChKQ;(Yi}#oA7L+n!t-C7SV+m|s}X_Kh$D$SgK4lMl0c>c{Z%v0zvHvGZGMzHMf0vXhg>$;B(-|7$n5}+-tqgJI3gxLmc zWf`LO2q8OSwO5-*EV_j=;M@aVbz7Qx=vdZTfvP~k4Z*=$6N>B99yyRiPP!~6Egbc_ zy?*O-E@*r(E-Dc@nfpi=Pv|tDc|)@YH;Mqp^wp6&?TDOOEVK7z6Gb08B!Iw$ovU?3 zGZjY(vLoAG%~mr^hj>-_T_Q!*Q)uYH*9jYY+>6Jm=Pl5*@w3M8i(Va!U;ROBp>Ac5 zD(@(u8Ad)-V8h?A76sF=nt=V*&b;zU$Wy+GPh&rYeuc|pRAGp>j9Q^*$yfm$3$6w# zI;(wJN?$TbGzF^fQMLedOb3pg@(FF%8%!>2Ra}L6tBHu7Lqr%%!iF(A$)7%J&be&v z0nSC2xr%hi^cuzK&Gyz;7(e6BWEv{b7EWb1^~%SLyMs=2N^UQdKf6&78~lNF@I*UiImn%0fBte5UR0j zU*#UAr*Diks*r;Zi)>Z29>wwNw+-aHdB;~VY!ues_mL^t?*TV~-LNR*PC_ghX&Za{{3Th8I6 z6a1%5`x*LK4~QhLpEacew|us|cGi3vVAuoA>?m$LrTcsFnT6RR_c^n-e{7Ra-_3Kk zoV6o#RYQ)~ z0mNeFKl1?kArjBlLhY8VRpL;TYmL>P5%;hEnK6)zK9C9neH)kz523V4UYVH+^8GUj zApeZ(cD)37V}F6Td^5=czzV_sjwXimIJCZed{M7y@YJ$a_(M!9_LOeq^Rw4n*UtZk7a;NHei(~*X=Di9Kp!d84Mu+6emal2WkRphre{mz#nGY=5z8(Re%4v7C+-`hNL6PS54J5~I{@aCT- zE-QTxiGFM_pOW{jo+r~Oe5rVI5Ab_!6jbvg#^;b49WM5k3PKf^{@KDXKWe1HK&Vr> zF>@@lexO$`m>?n8C@y;IUCato%97(mka6#ZEOA*NboB+#v9Dvge5YU^a~3m-aKg?0 z#n5N=h>h;q1WmmzjYu|G=rg^8Sxryk4*y2`_wWBXIbWNn*B8Ob3WuE|i$Ge;WSfmq zrQM0wE;#x6brb!hF4`+AgPL9XwvlM8=1fpvPJ^a>buyY52i)OgCrq{BYA!U(Txp8_ zkAJx}4?jg%F%l;XqZb+}Bj2{D-pHjLccD!#v(R_u+?T1YwCU2VedrgJ{=Pdp=W!SF zI;9Tx=}UTu(T(AGa6omW7qyyOFTtKbgrs@FN`Q&;=3YbS;qJi>Ki)enNEE&u1TYc16>Bh z!{?6SB;FSQ4+1;~A{2jeP!O)9GfOqZ1+)}?O%TlpFtaazm5;(Z)OaGe)N|t{h=CS! zBc~}#wMrVLPD@x3XwIQf!&TG37P>A-WNK+{|J6zTh}tclG~n@RPNUSH_(rjAjB%DTO*tz-O78tT>+f~ zezHUplv*%Jo0S^jmM-?1C6cMczi}tvUoZ8=`>jpysV@i#pE`Ywga ziGQe=#%t-H;0<7EVsa_lU0;6_l>PU*r- z(SVpk^H3hm_@WmN&;!O=)VWs!{mVQlbCxnrIkrQ z^K9T<1mT;Ua%q6k4@CRuK#&O4PG5`Xi9@hGINPzghGi$hhs5ULvHyLsj>NTN>(=3i z>`6G?!|NHf>o`wC&%&?vn5*cPO~2v1@v0ad(#t9v#iO3zI5%#h!IT&{mvPw|3;FHy z2;fD_6AbKAN8iv!-$zrBtrJH)H!j-{7j|%4myjKwKg`xb#0M89g$|8U+g}AS%jdDH zwV%jx&;S9S!-0$-4N?@c7)9SE~CuCe88W=d^*~bJMgU&^14m;A7k5)q5 z2#xpd*ULU>rgaVN+s8UWu^S!wa^<&T^I5;>qwjx40592VXlnckqz(Ih@y?dQ5>s&R zsUUVi_@z}5PA4>!mm)zK|DM%)$JqD~(ad)imn$8}xA4T^h0)InEK1eZ6AcQcChq~=*!;fBMC9`UPmqw-m{Py4*6JNp_i zfXM+On=IpMbOR90iO={i(zy$a79R_JOpSiOq(0n;DPZV`lJ$UOXyfC}XRpG7gjH># z>T)3W6PeY}9|>3W12x+x2O0>h7N&~y0z-xG4H$UTl(FkA=LcEVKKCOZ}-EJpTKOu@NOJW=C+>V4zT8Hq%APfM6>)-p63%Lz!w}|DqL1 zi*<1{gorvdnM4LVPx&ezP%~wkF(CQVK>|$NrA5$e(pb? zEp8}0mTghw3L`|Rsy8a~j!73HlLx=M7O3Qozh$6p{77n;)hZ(wlmwE|=<_TvsJlik zwk^RBx+h_VyyFBKPh?><%wLdTd7-2IPB`^`I^#!=H-wRIeSy}xhpl1WW9Kf=i>p8% z&p&yY0eMq|lNHdr2=8s2g70CEw&8F|a=b&T)9p98BqJkkPTB73F$_l^qiSuh$5pfq z&GBkR!MsN~Lb91`*)_M2v*5aNc}^fdkTsU5Ue!Fz^e7~U@Q!}B?f7}B=X)6-6n5sN zzXFmzUBj1c{QHAI=o}-7G`TnqkI}AI-9!+WmY8#$wa#~%i1iMjl)-2iPBXY1KjTE( zqPhBX<{Ef*ZEAa-{F2;>n;9+Ck>Q-)>HR^7VvKb;Ouf@Qo-l} zd2_AydT&s#N@k+e>Wl`E6Y|jI1Hr9HbYXOYTn%iiEpNO4jD*GJRBHS)06KT$teN(d zob#|l9cOSrTm$MvQ+cJhfz5aLbL61gkc|3P=15v|&UF=%a&nz@J-0on7rIe&6Cqb| zH9H$aoEnG^Vcj%aG1`0za^p^NJlO7z{qk3lZ(Dg!<3iryL>9d(UYi5@Z?QJ~{AC=H zsFYN5_n7n&hT``5uyq4F@RaGfXktpVR^QtJ8{3eNG;EeDd~;Ku^q#ZF+%?bb6f>8z z{$-yHBENrK$Z4j`+Ca2FhT;Gkkoi*Mtm^b!pz2~x!m83V*DhelW1EARMo zDX51e*6oE}%N^x$Zo{jIY%mDy-f6|d(tnIK|Mp6YH<3PT0Y&9V1okO^x=KGJ6Mq!> z<<8&HILQbCwfX>VFwoZdOc|B{=#308I)R`Hkm|io2c!$={vKL98L7a$r8EFH_vhP} zfQ>WXK;tkyA8HuT{j zqsB`f&KQy^wJC%9H-J{+uQA^REFLq4{i?$KU+leSR8!j(nl zAcx1K-3iWS;P)GLAMwG%{K9dp=Mv>_?-2gtLNd#LIj%Y5fSBL8uTyt^ zn-K8V*gpZy|NjI3;}8FTGKs8<5H=UeHpYNH@itVi$o7T5@EEz&uN@=VikAGt)tEP( zSw^)408ikGdF0uc)`R#X;RPZ9Z zp_@=ojRd^=ZDVNRjRlPI?A4NGvLz*??&``m z9{eP*q798k&-~ePZ@~~2rybT%gCLK@4TMTeS7Wb~XE2-FrrK0xBdH0(U68JRRF>or zZ-#|CK^$Rk{nJbo&gUH?!{3vQgdI;$4B0$~-~lKKAmczjt^ARHs*) zkcZ?fdXI!P}n(3%6Ul3(keiASOs#@K;sMI`Q`sqas@e zS6#H{azq_JZG+VlPMyYx8;ddLjTS6kob-O$b|JN;g8g z!B}zcT_BxLKF}np+75&igE;Yj`~Dh1}7zTFWj;WJu%2{J`uC#8QF zaCBsxqUTg72TUd1@SjPR^AJauyNmACdSK5Ce)-Em~)#R1IdNkGDTTN!IMUs0F#vYHD1`$2t zn)~~!#*8AbHsGp@oIKh*9}Hr8vs7S30!mMK1~{h&L>*xGNfv^hVchN@MlrdCBXtWd z$!?rn5&{%g=#(zfNbPPn{}>hg3{}}v4SAa zHgTXEBpMj96eTkyy=`5Ocb0rPVCr`WE@!Np zJZ@@XiA|Ol|_3C;!tM{~Nsvd!@SI#Om}Jg#v=^q?t8-T7e!fLeKBK?n0`% z{h6(;LLMVc&>&LR^ME)_w3zhsT!Ls6E?)H^cIUk}tRJI!}&v(8u@Hc8;K(CvSGZ6i(8&R%UH)_B~yFO!Ks@rq+=LR;|V`J ztq2Pxv4ZWD9+jXc;=4^X5}9yma2g^8=cvr(*L2$saC$IQ0e3QY>vu=;3z6cokLvTVTT zL=Sw$8gR*@KLE<+!&vW~Heqp0uMv5=#ky2%0;elrKHu#~!;=pdr61EqJuL^1#Ba2} zipy{)*y2@Ftvkb9_lOyCKOL&emmxv(H}gc(Q)HuB?~N~Mbc+gwTMRh6Kp%YvT3R3A zSt+#YSU_u1AX&(hR9XWf=JyaT)fQBGl~f?M8*4&$ODUpoe&NfGx<(<|$fD$5lj}DL z*lO2uTbPdd6gsuN8D1%cB*cy#R6W|*JqXV)IVYZ1{?{Ca zQe_n~+Sqzi`;Kvc3;Hh7`*w^x0N?~gcj4z?txS-)(qP&6*W@=DGZ zZ?E58#hAJs<vDw(g-9~_E$X08EIK^OK z;^J2l{97T`(Oqyx^wQP_-_~$;{HgeDp2aj}syV%f3Hf%q^Dgi6ePbj0HXm-4EHQlK znipM%)rVe!rj~pck+ANOQM-WT%6lTE`;7m_sUk>iVTXR3#2tPjURIy=z`~E`2$j3N zNDS7^i&+RiCt3{JB*#_WH%0FUupl_dfLb5=1XC!2I!7o@m=XMGa$7 z%E4?T!o6wtD!lS+cB-qu<4Ho;2Wu>o zz2x8H3UIGXJ04crb1lnVm#&b%7W!_FjUnF3b~oA8dA<64#}cY@CXt7m+$_?fcOhdj zxYhYO>TMFIvDT+(RoiTuf{4j9_H~yKr=W+k)m@t7mrFNj-xM>a%4BoUYtO^j-W1L; zeer+-2qp=_#m`JO41&a_n0M}AtTvjqV-woT0yJq$ z&9?X+Y?h%o8($Z7j#yOTBFV2cBY6nAWw z-X{olXOCGJ0slrktB$0#TLD(NB}A8ozJ4uN_!xQs#V&I0z$=3I$o{X>Gr^QC;(GkBN;8TyDlh^5c;``Il>hbx zec5-AuVJ<{H~jTX#~(GMA0!bCtlDA=n;8HPG#CS%+_D>q2QS*fb1PiD6e_OK_VtL_ zHGK2;GR_K2Eh9A&!Qf7M2#OVDb)o?XU}$edNF=`+vX@8S{sb&J>TR7Vk`n7I*?zSI z@qJC;8Dr^nFl#;~@g5+Jxrx_KJi;E7p={CaKuC|wJ3#p)7Bc+>5!gzmXtV+{H$SKE zEkWj;b6s6L=m}K|M2{gLyboo7#)-CeTzLp&9m8E&MyQo5kt6*jUDQmm~l2`+nf*Qsl9*_(sLMBC9y z)+gTYQQ>%2gl77;5gMbbQnsV_^tgRlj^1?+BB&~Ey}VTB>AgU#IXsW!x2O2M|3HS% zoy<_JXxHnddPh%PBUQk_vEwV@dNU9f&2UW!&G0?Uhbgw_`KvY~_|(J_q*~)~VWzqa zvSS|2`J@rixONz`cvWl}f8x+0XYp#NW4xR@`WCoRrZ5?^yFSK4ft|6E@ib2^8T2^K zm##we&MMsB>I z?fDF99cqB{o(H&!Y|vOkMkga+K^TIop*o4>o^qR? z^2Mr+v*AI0603HWhZd#LpTL5v|2#xjrimo@A}gr%LJO8}7TrFD@_>YbEELBZ6ZA|C zysf}`8#R@sdR6of7Gz-X)9O@Z?mFliXf7$F1fV%ChWOLqD74bX@^CKL?G>Haj*0O^ z{_LEpxD|QNr`kKI5TyAN`hnmCfpYXZ(Fjvug)LPy-w#cy8?49Ih)-~+9L^0;xSEis z8%qj$Di~akvr55(4dn1Hiz@~S6=G7o_SWhbNd({BsbFZwn0$>X|6~83Ok$uy4^olh zxT$1I{u{c zaCW0C;s+K3xpO@dhF@3vJ~xRl60p~V@PBy=v34*x5)JX#vxF28LPQl5zcjB5+Ps}3 zh@mTUDva<)#|rf2YH`Lwj}|bcLr^+q9DrZ;md{OyYX&#E4d6UxpiEGuC_A*-2&&a{KlfXlX?#BUTImUp7*Qf$Ny)0j;E6@!9)c zy*>CTRS@7VR3Lgt4u{v;oT=!eU=o-hTZDWKY@Yj5&o*wIK2haezMbE2JT1s&9gAb5 ziytJ1HR?G7ts3K^_tq6D8yenM=bod0Ssi?P#NhtXwku-;KDBXbrDrHx@gji(>qE-& z-I?g#uT0c!uaL%@JWqw! zcpP^Jdu^}DP8ET*$f0Hd*upaMufshltNlTZ4?@Lz`0(KxeEkRFr%{kZ zD-)79o4i{jy}pmnQ*GG%3@kS9BlRsw^$R9OE(Lz;)#u#^_MO_??*%G8gY&`YuJeVu zsb{;U^qVhDQtGIO;GTQS=f}{Iy9*SlwsibjR^B!}r#}Lr6j&{ojs%9E@a6apIM!F) zutSphwNG_L^(e>o-Wp^iOhabh1M@I6d4Zv1@9YAhJE+=2hst|z=c5K!YT}y1=S_XE zWJ7e#E|uK=6d6p+Uufz?lAnjKH-kI=aQ=Yo`MJiXy|&IXW&q4ApaSC>1Ve8+9Dh$p zya#Dkj6%$N?R!E8d8v`g&9w+WD`u*O0<+K7lUfiex9mpf{L|3Yjh7ljkLVkEM^WER z!K0b-cq7LGOb`6=J8K_?WbcShIDU_!gtcrv1vj0$$`Yidd{#-}i(~CwNNe?Eb>pJJ zj6{+Fg(%IhU9F3 zuy?dlv166_ukJeCeH-iJoF;7@fitrJl@NRYx)rV4vBT8n;3rvfrSB>tI3U%U`ypbqcNJR^j2CpN)U%P7*x#(T zv)L;WoEBmiTsa9r8JQY=xVTy%__vFTpP7K2tX>l2Ktl~~OKXYy0y*N?@Ro8VP?Z&B z+vHi4KL}_$sll#VD5nq|m>hP)Vu2i?W}4JK_aZ;gSnKHBTvfgd!Cr14;+mEMSEJDq z#(?n|hhQ#}u483%@8a!gObisP#sGOYned{6OMSwq7{5Zd2$G+pGRp@bbnzqB_OC3B z+5BHQ9Rd-z3zR;(YVuufR`7EwA(Q}xacxgb70;p~w&7|8%IqDa+9&vhb-ir&O6}wG zjei4AgW$PGtlv8ulkrI;ybJ1Ua7@0tzA}sHSZSjH<_WraT4-x%Y=Hiwh6{RF>f(px z7i5c94u?A4aCxG0I`c{dZJB#yHnMoBdbjLykz^ff>zzvJbs6Gvi%9CL9(vhDql(ni za=#BDacrpD_EZbqTRqgy29I+@?X963$GdNP4bNL!-Xb|-v|;+GFzW>fpJ!;QF58@W zX_A!d{j}iRbD`qy>KTY^K!riHMg_lqB8oiItC}Ifbc8>41I34f##%Xsv>9qGj~C}B zF~>WM&z|c6DQQI!o`Tnz#;6R(qK%~P@hBfi-#Mk51CqH<2QK~$@zCzRE}PjN&0&0d zm42~W5xTk!|LkrLpo9)X90aCk6w`>^c6q08?GAqYGjQ2l z9P9lZNB@oY$tAs>PK;qUMMi@B8E`jugEiNSP5hn*IL+$;RY@v?Yw!dbsWi9|J|=(b z<2TEX)IHT!P8HfnI>%A6;!L#ln=ES-oi-L?B;mIQ$k*K51J6A0<7LJqJb7G3#k<&; z9(EDxf-N;TKGj4#N!8w|m+d`@)hym>8re1?VwhjNW^JJzfQ@h;JYb{YwwQQ{>}BC1 z5o&~}?scfdOMdlr7W@QoF6phompZ!;;{)E+g<^Y~5W0k+Uj>^f^LlnPMM;?kU^r`K z1(WHXF1wz*X`Zo6wnWotwTwN>KuX5C@;4yLUAW!RE(^1maG-3;e0%{TgLv|oV6|7e zwj8(+cN`iwCs5iaF!Os8Dl)C>5!w7;adye&WPG1C%x74s*1Gt#bKwJga^~yfmX44ySRnQm4wfLkx(?lZ`wzdK?K9^NP-&_m}Q1CJ(jhw z<#zik{C*7*a-&j3^YL+X69Ot}e!CzCv+ak$r8!(^sjv7*3xY|%hbr7*cJ(?$`**Ak zq)a*oGI+<5-GNz)dDIMs_mknC%sMZRs*CRLwi*zxZ+EsJhRIA~W1m&Fm%Ly?MTkKfP1I_>djD=qUQMeqe#j%(O*?d>`(}nAu`n2z3cn%{R2ol2TqigtA z?|^B@!^?K`9zVLaPzcvUc+lsT62@jb_oJ(`@!&Nl(1PF%_SMc=zJ1Eix$x-*vKyoW ztgm>l#bm>@E-IeF9mJIKd#b?dANN?hPB1m{%u{OFBRv6cf=_P1-J;aPjF_}NuU}BS zygJe~(&_gI5@sPdt#!ikytO3yRMLkIeOV?4a6y(A84E7E6FM*=T-SwoQrHg{?$7_` zH5F(D>FO*W&xPt2oouXf)uD6|!*9Gl=dL61%f9P0j<^W`?qP%~ci2wj*QiU!X$R;# z1U(=i1$thkwM(Ss!1~b-LoEz(Pn*xmBm-X-0J5uqqfGW}D{$@2d9NU3G z9Phi%>yk)oOpyShb5OfeEu;m}Kwm`ZEx_7|DUYT4({>YL_>*-Cn!I#|>=wB0PFe_q zo=Be1^{oHX8W^EEY;-+IXlzr)M1vo=*VxpnW87d6h(n7}o&58OMg&1(V(S69C))x% zGj9wVO1OgwFq=Qm5M-FVOZ>wh`d0oX1;YpMe*o&~!9`IN1Kk_Yt*LtRaK>^$6?iPD zg@KkzBW*q6b(qp5g6cN(V5TF$2s8q8jnRXw*FlK|eFsmO!68NDc?#xZ_SP3+I4Pf9 z>2h+ZWw}7(e|``26x>U%umggq^0j~Qva%faX$o`M{0!lP5T7kUd+flaapqR4udwv` zZ(qmXpOyEl?8lpB$b_>#_&?sTB*chg_FOQ$9pczDYb2N|&i(^I*A23CI-9g(xpP+W z-%mL2PJCPnu?jf0r7#XMpe!?A-?m%or*f0`@=g6|3?lCQJ3KUEz7Y?2O?;`?%F$e! z0U>t&_tF3nZ!WUE8uJUhrvu<5kOb}DVYh)^Ml5qySuTMJvWfX^w*B4IS0cW}Q;<$J z|IJ=sufXKx|BhFSu#6w$CADqVN1ggVhNVTwcOV5H@QEd;{Q%XHQ%J;g$Q0zEEGx(~ zjCz6gsSW^%%>L~(Xh@T}C5R4ncY7tllEwc$p1p+0UA%XTW9q==*D?Vlqys%Lf0j8u z8;~I2dcxUCEx_D>|Nfh48**y4qwO(B=lbvPK)0EHArn4zT{3=s77W~+{_i|TVng?U zf&6lQ*6$#U?CBS~ zgY$?l1%wFG(#@lAQ~#vYb({a~3weT1S<8;+h^C} z6^z+cY$SjZCR;+ir~lr#rwCzDM}-gID(ZM`!o{&=v2=jDW0Af5vB>jok4R}N8t1nl zCcwZ2kHY>|73t6PTrt6$oTd36Oj)&U)#VzUYtL(_f7C8Y+#DQ^4wMe!Vh>;7bIgo; zyI3$>(dFl-dhnVc4!KeHwCZNYDNuIJBC%pGL0WshEn1t!V*;@tCt%%9A0S2M0D-$-`1n7OB*9j&(fMKg@$C zDxm{nLXKj~=MxY2w%U!!3ckqtqIbg5zp=os2j5kX^>&+N-0cH{@*e!)Vz}kHu|0$< zxK~zrd+a*_TXvFQ+whKR9U0UmYI5ZIiy*!MzDJy5oc(>zxMX4=+}#VJ`OK5TXY6Wk zfc5gr>nv${CwpkLrAo3Omr^z}Z9b^Tk=5_`whJrti!;MwpN=3@sV9Z2uFd{E+=D4K zg39an3yVQVckD)rkZv}H*N&reQhieSq&RHot#IxHYfjhf04e|FSZ!u?4?(R*-&f8^UURVC;V`cF!aDa9s{3!WkKUG?G5Sk0?ctNw;S9_uV}jyT)5K-~_TG z#+8oSORSY+&-NGs zjp^ z=%T8xp2VjXgdkZYq!k2sF!e+m=9mka1$cMk{gxrn6!`Y6zoDs*8}yIs+p z>tepnr-?3v;w(7iira6gd#_kV@K8sTMK7$JZ;Yp6>e)|sJjn~nI*6~L5wM95)wfkn zx`S1={Qfl-w*He2so%w{w`2xFz%`{;Gs11kO%Q6^HZdTp)TU?wSkH)?Yv+RFow_{| zT6JYxUa4+?EE^P?fXL>lhg!!S`vMRq^mJKpXRy?7Oq156&Oh89!@3?v+0;7>r7jfS zjdr_SG~;A^M@-(@_E!_%OW%UldKb0v4W3Z^+o2lTE@A01F7578gD_t+jpa+IvF7*m zD;T32O{8$Oa4Dft`L~!-lNMzuIr& zzISK7#ri*pTP)xx--6HYrX!!O+|nukT)lJe8BUH*_mb*@Sc8LEK@W22_iX}p{%stf zt^hXm~@y)S~)-2dL4nj82cCJDou_hLT#S~?9yOunV?5PQbTsQttiBPwrg>;FD{ z9efyME!~ Joz5+=jWPyHBqdWh{_`siQ%@*8eCBcL}w-JBclIHuie6nDYJY*bZ8( z=YHaQRqMkHMe>7 zpB2~c=^{abZ*FaQAE-!FDAelv+~J>Xad&DGeo}aSncHhYFm=J}C&#M6&j5;-1SsR{ zE3C7SApvsOZQZOW1*?O6ugz#HL7(^6K-8~i+m)fkI+HI)v6eSQ+bRE1f6AbCc~O*N zv>Z{YAtBKK|K1};LXDNs+T)MFbE@naNMVm$1+%_h5=hQ~Sa!|y(ZWWDws$@1fmXmD z%&iL=#gbmQ1DDqDZ;sVK3*yAv-EZvrVvCQyhJU&jsqPWWI2qBr{5?hLtJ$f|)Kk=&oemyZkor{9g z8*S|;7gHMJaD33(C`oLnP4>$bRuE0ac)Ov3+xiU~*XFOhFZb;ZG{tU3R_s0!+Zt8% zQnd%-4r%qmdafnvg*JtKX%E`J|FSk{;*M!lPTwkrSF8q$l=rDsjLZck?Y0YXtJ1pH zG_z~Ilst(@?dV5xsjT>S(=<8mMus(zV_dfrKub<;npWmx)YLBZGV7s(SZnb-Tn=HoSr-QcM{v@qu{s^Q(H2TdIX z;#B%tZ(gcj&6PuhBr&^Xk%mBwK-4_G6w{|=x!eXLnBEM`ME z51FLrfXH2RK+vON)xg1qPZh1DkUqWH>oAfJyrA9 z3YT1jQV`#jPsT5yD#GR^To)@3q*ZFKDX6rP90w4Nx;%GLbb^XswA!;aPmR3^tYJxe z>i;v}%9!^Whm?s*(#P7jy%FA5Cl_nZulIMGNLFAZKiWv&U3Vd znnsT=PUmi(Xym_2@X_o#7kN6zCj(nMI*+lh@E}zh)#aVW2;xFB{sfX5X zv+LS}oE`1&U$R;`c=Y@q2lu?JRf}E1#cr+su%j^cY+Lk)no=Xe{_#ySAI(<&z0j2twG&ch|3JMU@5*mA zj@U3atmJ3Y;M5Qbdl@ovrxBZ(V3q+fja}_7d;am~;}C@%jz~=UWdhWd%r(7RVp%D( z!!;zZj`d&S)4Vm+h zJTI-DFHttKQ9mr*WA##Xx|t0wV(U+Ib|5;!Q8`-oWQFp<#CR~W3QAyd;_YCrM%jz1 z(QI$U!gIHHTS0SI25-0{sYt;Cmkx3hurKHX&JMxhWb}~p!b$x%}kT_b+-%E z!5B1X5TccKD|H;{L7a$6fzrhjR~$j~Pr9Z}bgUql3YK?*kkC+%rDnPHglwFEC5}cg z8ebxGCwEJDx&voG5DEv^Dqxb+&R~*#`ej@;5FX&udNPh1j9SXJ=OR7re}|WOf)X*F zZrk`$X<67{RD3mP%2a#ZUD{*4ObB}iI^Sf}Cm8pvl;U)PW}@+8BA)0yb@hqXlmom! z`)A&35(Yk6PC{E0PXL}zhq#By=Bb>*wjdnN@tZNNj^@8Q9_ivg!$?jfI0ogp`|=T; zLn3WlaN5zSg&&$|5nfTG$m7{{e0k!%j#UwKK`Oucx-t=Wvch~(ImEd z^$EOyLhnXTl>5U8I{kMM3|mTnospYxLs0RLwn;PI(W&ojo>2WzP5B*rhwAEUvDHB% zt;`33C8kg0)td;XhfD5jQ?qMyh5Irbk;@6x13zpR+Cde6_h55TB1s7B>KN$77B=xa zOu7I_%t4wC%*}T;?MP1h`7(C$9(OusQSwu$A%y#C+r~Opde+B9Ubd+``=brYCgSY# zd~=oV{>L_l0hU&iD$mepPjZ0^wS!)eWL>F;6C4IsOP%E3fV*7&fK4jQIQj-K%&huo zS&PlTiKx?+8Z7YW^-tlol2D!_voKJj6B6>p6t$G&eo0PblOH`NLaUtS46R3WgvLIn z!HY=Sx=S2Wxls>mL?!;Z1b*0g1HX)4Bw3H+WIXow9JUK)FwQtaGxmv(c?iw{dwI2g zlWEQFzLQweYSla>-moTYxG`+vbRUt}0N^f!U3$u4+SNJ(1M7Ew6d)1lvBnaNSm7-9 z%FWuDCVyS1VNZM9S!gZ{-rRLh{}bKgllew69vzQ;boa7Dn6om~zX(J(EQc#Z&qq+^ zf$ZOYtqVFee{C#e(aZ8{l@vTuGaai-6pF9YLh9wobQj; zh6(FuJ$FX#!S?b}1yb88OWE#lccH)TFSowXebif2r}#i%%F?-NPvq0yks09n$d+Jc z`i&|l=qptmUBDSa_q%EjnsXEcH>}RZQoHcs=pyu)mlv-V&im`;JWz{RVC_pE?AZJ1 z8tRfg8FVciX+69>D!0Y{;;)q&PqPDmZL!iESYX#B-Kihe!vbG|OiK@cC{Pq{SbV=0 zoeV|Of4Z7!P9wl)-7To@@R~-XM3HUKFa@3`j{FjvGWTt^?pu#2+UJ+dLsr#LT8vlIUuQkh#-;00sD-hO zDFjFomNRr@*>Sg6iQS3PA5G_9s7jc$j>#mBKm~cW?)m8_mmcy+YY8Y$6lKkPE8(QJ zCvvFLK{~OB4do%<_gpVlq%~dt@{6+{ zpTX`?e>%e{k&n0{8^LwP!fR;T?v`}cnLKOuJ6|-k-m6D-j3ZYu4>nBO^w@vGcN1v| z+Sg!qxUpF?+lev{+7??vc4)dXw=0Vfk<^>ZM!~Be{hpVicLQ!7iVB0Ny<}2w>+z{p z@(#3N`2-K2%#4lpG8ORrT4z(+A;W~s8_~W=V(Hb<4|>|ZCEw3}n-CBwF-S$#i*J0}>Qpg8qg&}E2z7@PX*v?CbIfeLBO zylOV@!Tr=jcetgGaXcztQb~9mDBRWN#Etc5$|qzG*jUp=QsLq?x#f9bq!_vF>{v}T z-#~8#1c3Uw^^5kN?RG`z*ZW}`BbV+&ut~uSzVfKXD+I%|8kHg6a`9aMddVz1Q6P_sN)czbQ7i3NpENOb#^;OFH%2y7TMAMvgJQ)jV8MjR` z>o;48f2&;_XiHkRJ9zdUD5CgBX3*Z!)eec4T{qHCs++v5K46TQ5WGSC-Btsm*gkGJ&k`8S* z&*|@G5;eHRS{tPs7lZw7mrCT@d|Djy03F-B5CfFQX3305)i_gF_f7Q@X)dXlbG(G(G0XU7aud! zp;cT4ZwHqq^$N$$|m_20{zb0Q@C z5))i^(^RSb;Dac|yVnS!z%R>IN!m%&zGmP`?;u8CN_L(ibp4oM70GRp00%kpy_q2pMv4I@l9C>SSNo=%iL9FH2 z$v1Ds#qxs5!h3e$QpJS>Mp16nXqe@4?{L$68Lz zrX3d6hM>Syy3=O!5IunG?7O``4aq^04~~Cb=c^|+9dD}|uvGUXp~3c1)BpMRdw}K? z))fT)IfeBfKSkgEv;A3gGePp#IO`9h|Ys%)APvaPG8PG%yn@f6W92TtJcr zyiDpoQ8|<%S6kNI3RPa+Xj!6TpRFSQ7&*~%CCcEt^=OCVpHm{TYYXM_pty+Q}N9b~_mk6(V0A&+P zn8)6nW%$#77WCs$1-ULGiW~+I7*)h;S`k`FbCy$0(z0tc;)?;lKkb+`XR&-I~x~nJityvDkPH zDNnQx-N7^OgtOJ`1F=W`9MbM>j>{%% zE&JW?gofy%$b`?`RM&W|W{O|0+S+6bZo`1`;@S(#E@r_-cE~X)3i9Pn(goWFB?hc; z)lW%D55e;1HrgIp+wi@j)~48K=+(B}o*`IOdkDZHx~Uac}+R#WZyXq$* z(H(ZQAUx47s>zhkpoe~zIvjj%yVNEl>&jp}M5M+61sb%ftoE21Xn zRu7h!2eUOMU<2{Iy*z~1bt!>CnqMDsZpF-;TBn@HJg{q;pP5kbaDb^ABm0UJh-s0l z&AEJiMrv8|70N2>cDam?ImmxyU}>zq8#3D^9_7>r51o$is(#_!<~ob9MTSnoF}1h; z7Uks^65)5T!AO|<;f6^gRuP%VI7rR5T+6r@SS|l)>4{4JW3g4|tceJQ8`D{D-WIS6 zD>n=-i)z_tow*)h7iAjsK($;U-<=_V(|8)wQjcubfrVuO*Qe*=wJ@C9-?;G!4k)4 zQ^!#xv~p*Ce@Jh`E;F|mssrgP|5=kZTup?JF&gyW^gX$4=j2OTw_1qD2Cl)!!82yf zndIw+D3W0GL1jwv5aDY(8^*40$RKHoJ#HD>tKe>J-;a&o9=Ok=N8q^nk@BhJDg`Yf zQtu>u4C8%&5Oy{SYM4#>Znp|t*RjXzu=JibKBdo$Q9wtTi_7bi#kTCyFe%bBF3mBU z=(fpKH(y`i5fwH9I1OU0f4h zxI_Tn5ZUU>N-V|2rj~`S-M-l8Wfr&>-VBA=!;f513lYF(M&vz+2KMFv9od$nswW#Z zJFwxy~vu|!@^i(-I(EGbk$ZoqV$dMbDHfC22VlQa*cb2<#?_B zQq+5IdlaH?9(u(0i?G@_jaMQ_{Vk)_WS^tQD35Ryd7s#VFIh+E-br@Mk9PaOW+O3! zaVQ$G;6?IdF%uxyALw10-?tu=mg*3F#RR}rGPiHLQ zn~f*Rp)Fco2*D>u=z8z<;IfBCzcA5T1%=iU)-Fo!-M+XA0C!#ovo}e^yB*0aSBe#t zU@?qQzF5Sx9*ORv%7E5hd>RyufDLY)sVy*y-fJ1C_g!&2+S`45c)!>(QX!+H-?{@4 zxWz&;{oX6H$A`OZF7~xegI8FPXv5(eiboUhrxyOkl=;sT;Z%nPZ#T0#fg#(1@R|dw&>b>0!6q% zm#x48fOov8jaG~JdRAL_+|j}ti+H}N9#a&Y{hWB&O)_-ya@kUsTv`lpIuPlKUVg?} z+W#KLDEl&(H&-N=MH$GQb=P6!&qOJ?yhoyH?H~-zuibe$8 z>`12fgI$(ct8<%p4ne37Yy#GDlyyrn>vu~oapX$q>>|*|%NZQ0#0E?luY!Vch zjLu($444WFzRi=?8$C!Kx33r-dn4O-_zlM#c};p~PjazO=cu z0hd%h+XdK0qLVM}--n{kCVsY~ZzM}`Io>&Mx|6x5>{tGS{EPjr+=X{|1}iKu(VX!6 zGS|uNHz?KB;=hwdz=K}@T*Oifh{~1MnaswgD-}6<&~6g#)|K+qkOD$hp0UN}s56{B z$45WXsgTuv>7@t9iayR3>r>ZWpD4;FP^`D!Zk4DQP)hmhScCelF*&dhum?|ff2D6@ zT*cy~>w+n7%71qjaUE34hANu!jp_~hVHL}Pf#E?-TS7o|d}{u~-A*4Ew5qH6U^v6P z-ETHnZ+@udYT0&T+({vdO%r7yqiD&0bcjemq^iJ3>%nze!OaCSN4HJ6*B(d0iUIRO z#f*cw_Ovs}K9GdN@lJSBo~@yvl2HN4t#L_ zr%&b06)u{qbnLw*r$``0T(MwT%5MQE^q}c}|9{g0X&Q^Rf71D^FB<+wH3a_|CQ4_u z)Mj+%UyXA-oa^&2&WtKU!wWTT-(EXOX0xN>w7`0pn=Fl&=Wrq*!(VPZ!DLrtQQLRRI4imI zUfK`~GfN^n9QOTBKb>wydHVWp^xYttu3JU7nxvTR=Jk#p-Xk&Zgo+gpSqVDn|l#_3&UaEXF0S0pZUe zXE^4ccRQ_ul{H+CzaXAtj7xR-tU{RF>dHgn7RIe)(_tWaqI2%{!uUa1hQ_|74Sj7L zt1G7?S$WryV8%46>#I^kU}`6vKDr{}GfJu>zGA=bbMm*E{xOjRpHV|ycSqYN2(Oko zwEIHw{go(0Kg`JGEVANOe4wp0R!-kvEBbz)0p;nNAf}(^F>wOm8e; z%Y)Gj7R-buUxYUz?kRGPOq?5$Pp&hhS>YzQ%@vWD!Y9v6)Ycx&+8^c|oCFQ!zvuJF zwvybF5^Bs}MLmSI{lCh)(x|4AZ5>dgaiSFz5P@zKPzFK5Afw`dGJ^^T$Rq++nM8(= zFsV4S1JJ0*BoP}M86+SOKoSN~1VN09(Zn1D1R6qefJ{J4xRubi-@R|G`+mOl-n#jd z_`+WV_rRb{AKmh~Sq5n)S2(WIz&x@0%`cC)9LZybJGe8-Gmn z+gKxfHb8)~s%5ig-7R1E4;m(S5<=3uG!Y#70W%7!@BGIqv%C%f*`c4~7wIGy&krdU zsbR|sx-Ro!xpcm4DyK8eVgHQm+_xNoQ6pu>Qu_ zYj|Ti2{kihWv8O72q>KbF{b;HzR?CJ?qQ~X+cR~Fpcge9SiTzfGVt!wEZ75V(Q0YC z&@0p-EN^my1f8i%^r=*@s|2|;lyBe-l*^0d?^JxS*-rxrDE!*qAE0jCtOV3c5 zp-rV0^mK>oIGPtQX}SSmK1g;5GD#cQh3ZjMwEI{k|ki zAS-rGdYqQDs8g!kALmw-%Y@59l#~%Kcq{gduZdH;7L;91j~JFuMDrs-WB^WhDk3U> z?Zgut=K=E_Hs>(ap*b;ke?CO0ILCK?$rih`X@+Tpfw(uD7@u-894TpDo3vm~#rYr9 zod%lncaLk}v-M#TahS@wSGo^0LsN=*MM1CtGg{$5MMVhPCrDpr^h^vqw;f%50?1ba z-q^jK>Fk+jx}&GKX|t)3t+DD!oc$gSGdhdPZG)=ImaPW-DBD`Y=!uynJ8~bzj+WVLJOv+XpF$l(MrZv-JfGU z=ZBNr=MNrvRIeP3eZ(YAv=tn;X)cQyDr2Q_X)~kWy`qWBn_l!=W*HcZo%^D9UzxF~ zlrO>gXmuUhs&dr}VOF_y=9g%cSxizifV(pa<#-g6yoap_P^UD*tYUaA& zX60dMgC*&Na>I1y#-K02i6Iz!J$ZV(B-G=}xA>x-)0Fw@C5fBdRo z$a*y0`vxmgcu$ZC4*y)iKZ@S5m_|;i69%`4PA4gs^l#mH_PvYUXkWYr`H;)d7<*NE zO|M`7-cEB<&7)fHWJ?Y@7w?p57Z7$?o=8dFX(Nx{nHM!&CKzn8?2(IO3w;&`#sn_a z+c!brv|Y4TdCoS-3h>(t5MV6AeX)t!91phsC+Ggflr@Lj+{t_P9mTef|BZ#Ax8{is)Tpl9$;Hsh{_s zl;s^Fw(`@@PndzF^7h%UH0!UvxvC&}q)M3Jn#Y+WJYI3%n&U6q_t6b8(^q{rxI2@-Ca8Q1X<3T^wfx1kli8pj%9QhgP*V1PEJ| zNR=k88$b#{dx1zwu>Q7EFIGs~cJX2s{}L(?qnWFQ_5zq#Je$nf0&_&o9SRfHs0S5y(4J?X@`82iw1nobQ2&;gD4gX6LswhHEnA$4G z6`|fhV|aFg+NM^nP1~uY<2k%eysV_Ac(n`k1=tu8z;tCks1Xejq00=r|a&40E$l z{%j{H%!4eKD$|}p(l(>nI*mU z@>5F@@FW`A?htjHJPDofY&3g6r7HBj4&V~rT%qckN<7V%mVU)LNDy2EiuW&=a{hJD zxk6@q5seW*ofUrfUHZWL6GR4GQnKwBh%~6046`3e9t`S<;cXT7OvH}8YH<1b*a>>( z!&&CEik=mLwL~W(k!kttzn?QzAP~0hO8bY8Ck%#3K&yC zhxhd5MjYzU@4ot{#;N-@zEg4mSC$wV;7OzI0&upGQM+TYWkt`AaLBnWp6T|r#5#SX z`!C?2z$G1j>t0FUeEH&Qh1rMJE(jZFaH^u#pl%IZN;BaM1BnK`#5zZLu)?r z$KL~|&{Z$-~#Rqk`4k?|a`qJn5>o)w}(!*A^VK zwI;1*E>c77&8s6skrs^cndtR%ejvPum7}H1b)N8z(~>1?&UX)Pusmj^)4L0I{APD- z-IfySKD!gNZ@aF{UddkDSU%QXYow>)h?FUay%z3pvPfacWBPojP2+q+aa?N?4%J-G zR+q7IKdkIPtG?ygaMAj{QmrH118v$@v*B=;mlxS8cP(E~Z1-aBd$x5B)^hzKI1DX% z!Gwi>Ux^yx)}*vA#xa4@vgT4>LIT8S5hg5fu55fUNN6AH$sD>54W+r)X>~XLScyyD zW{Mr$D~&(h+^p^pl>JO)(72A0F57=?c$IQrTFrGRTP=NPtL|jgy;dqiD2zO-T6fkb z)N)87vi$wwakTamPO?hld2V*k^qFvS?`|tDv)iTk*g9I0$0>MPXYo9Bo6Kvu^T>=b_L7!J=_GsY-Nj+D*&zO8=I;Uuqu5*d*i!k|5ynmId4dq;lS zvMWyIIrD9O#NF+Qs2wf`u$6g!pImuPFy;^%;Fo_>K)e|b{Nq+9i4Ze{&0jWe>`Bhr zI`NiR5w%cpFmed*P3QVn_BzAE#7UY*Q%UFpnWEY@WrF`uB=1Gq@<)b{!|M8Ohsv7w z3{>ArOZ23m7y*wXh`k=>x)1JEXQihoUE4ggwfAOtBAG0}a)N%vb#V1(nXe9fk)}(w z^+u!lOkNaksko2mlL!$9cXNv7z$`#8U&2_6xout^&M4dxd)^S?9b8PJB3t+e2 zvXulPr2E(O4o888#!#F_ZKbp@%0)Wm(y?~`py^>5D)odT;r;8L&9Lc8j_c7PPX?b_YngI1&mXn+hIwBZk|%*(Yk&I3ERs`?PDEoC6Ebg=hK2+zEU9<$Eycxy`ONF zs#?0Uhto5ID{R@9UU8&k`xkBGhoqjhb0cp^i&>!24cAbNxQm^VqQ6Q+3u7?A1-$)A zD+Bfb!eI^UyaBYrDZ^S0t`?XDmo(B)PuKj>|4kNWm|)Ho6+@2YRM^UYuP3URMAByh0PlP7nM;3pMhL{ZuVaw=Xo4`7@v6m( zsYbG{7Zp@$HX0VXn)#KTV|LQU7SCfuwLn-iePt51_@}fVT~r^o^6b4Ms^bB0%6BWX zX#W?O^w3kB4U>S1TjtDB=9|2*{_nw+k|Nrrg#YH%fC5VXG3BeMDo&nd0E-oaCe9#zMUpA<`OY>AsK_DKqk;C|FjQl?U zIU6=6qMPeuzV-l{eLdiB&V;{~1M3^{f9rE%Zdu~dI{6nJXIzIt$uwe*oQgdg7#nOJ zbTJrwAq))+k$MJ3dIlyZ4Uy&sCgywh>KGW98yM7KlA8X7AR_AQxe)ZfC+N_6y#^5M j`s)p`=OTh*Vgn;C{Ck?ahU1?^^0GVPd>D5y;L6_tD?L~p literal 0 HcmV?d00001 diff --git a/contracts/snip20_derivative/Cargo.toml b/contracts/snip20_derivative/Cargo.toml new file mode 100644 index 0000000..23566a9 --- /dev/null +++ b/contracts/snip20_derivative/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "snip20_derivative" +version = "1.0.0" +authors = ["David Rodriguez "] +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] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] + +[dependencies] +cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0", default-features = false } +cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.0.0" } +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ + "admin", + "query_auth", + "snip20", + "storage_plus", +] } +secret-toolkit = { version = "0.10.0", features = [ + "permit", + "viewing-key", +] } +secret-toolkit-crypto = { version = "0.10.0", features = ["rand", "hash"]} + +schemars = "0.8.11" +serde = { version = "1.0.114", default-features = false, features = ["derive"] } +base64 = "0.13.0" + +[dev-dependencies] +cosmwasm-schema = { version = "1.1.8" } diff --git a/contracts/snip20_derivative/Developing.md b/contracts/snip20_derivative/Developing.md new file mode 100644 index 0000000..45baccd --- /dev/null +++ b/contracts/snip20_derivative/Developing.md @@ -0,0 +1,153 @@ +# 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 +``` + +### Using macos? + +You'll need to install LLVM using Homebrew: +```sh +brew install llvm +echo 'export PATH="/usr/local/opt/llvm/bin:$PATH"' >> ~/.profile +echo 'export CC=/usr/local/opt/llvm/bin/clang' >> ~/.profile +echo 'export AR=/usr/local/opt/llvm/bin/llvm-ar' >> ~/.profile +source ~/.profile +``` + +## 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/snip20_derivative/Importing.md b/contracts/snip20_derivative/Importing.md new file mode 100644 index 0000000..e367b65 --- /dev/null +++ b/contracts/snip20_derivative/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/snip20_derivative/LICENSE b/contracts/snip20_derivative/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/contracts/snip20_derivative/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/snip20_derivative/Makefile b/contracts/snip20_derivative/Makefile new file mode 100644 index 0000000..c630b22 --- /dev/null +++ b/contracts/snip20_derivative/Makefile @@ -0,0 +1,89 @@ +SECRETCLI = docker exec -it secretdev /usr/bin/secretcli + +.PHONY: all +all: clippy test + +.PHONY: check +check: + cargo check + +.PHONY: check-receiver +check-receiver: + $(MAKE) -C tests/example-receiver check + +.PHONY: clippy +clippy: + cargo clippy + +.PHONY: clippy-receiver +clippy-receiver: + $(MAKE) -C tests/example-receiver clippy + +.PHONY: test +test: unit-test unit-test-receiver integration-test + +.PHONY: unit-test +unit-test: + RUST_BACKTRACE=1 cargo test + +.PHONY: unit-test-nocapture +unit-test-nocapture: + RUST_BACKTRACE=1 cargo test -- --nocapture + +.PHONY: unit-test-receiver +unit-test-receiver: + $(MAKE) -C tests/example-receiver unit-test + +.PHONY: integration-test +integration-test: compile-optimized compile-optimized-receiver + if tests/integration.sh; then echo -n '\a'; else echo -n '\a'; sleep 0.125; echo -n '\a'; fi + +compile-optimized-receiver: + $(MAKE) -C tests/example-receiver compile-optimized + +.PHONY: list-code +list-code: + $(SECRETCLI) query compute list-code + +.PHONY: compile _compile +compile: _compile contract.wasm.gz +_compile: + cargo build --target wasm32-unknown-unknown --locked + cp ./target/wasm32-unknown-unknown/debug/*.wasm ./contract.wasm + +.PHONY: compile-optimized _compile-optimized +compile-optimized: _compile-optimized contract.wasm.gz +_compile-optimized: + RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown + @# The following line is not necessary, may work only on linux (extra size optimization) + wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm -o ./contract.wasm + +.PHONY: compile-optimized-reproducible +compile-optimized-reproducible: + docker run --rm -v "$$(pwd)":/contract \ + --mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + enigmampc/secret-contract-optimizer:1.0.9 + +contract.wasm.gz: contract.wasm + cat ./contract.wasm | gzip -9 > ./contract.wasm.gz + +contract.wasm: + cp ./target/wasm32-unknown-unknown/release/snip20_reference_impl.wasm ./contract.wasm + +.PHONY: start-server +start-server: # CTRL+C to stop + docker run -it --rm \ + -p 9091:9091 -p 26657:26657 -p 26656:26656 -p 1317:1317 -p 5000:5000 \ + -v $$(pwd):/root/code \ + --name secretdev ghcr.io/scrtlabs/localsecret:v1.6.0-alpha.4 + +.PHONY: schema +schema: + cargo run --example schema + +.PHONY: clean +clean: + cargo clean + rm -f ./contract.wasm ./contract.wasm.gz + $(MAKE) -C tests/example-receiver clean diff --git a/contracts/snip20_derivative/NOTICE b/contracts/snip20_derivative/NOTICE new file mode 100644 index 0000000..f18150b --- /dev/null +++ b/contracts/snip20_derivative/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/snip20_derivative/Publishing.md b/contracts/snip20_derivative/Publishing.md new file mode 100644 index 0000000..35f5212 --- /dev/null +++ b/contracts/snip20_derivative/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/snip20_derivative/README.md b/contracts/snip20_derivative/README.md new file mode 100644 index 0000000..44dd8cf --- /dev/null +++ b/contracts/snip20_derivative/README.md @@ -0,0 +1,901 @@ +# Derivative minter contract + +This contract enables users to send SHD (or any SNIP-20) and receive a staking derivative token that can later be sent to the contract to unbond the sent amount's value in SHD (SNIP-20). + +## Index + +#### [Engineering Design Diagram](#design) + +#### [How to deploy](#deploy) + +#### [Instantiation message](#init) + +**Messages** + +- [Stake](#Stake) +- [Unbond](#Unbond) +- [TransferStaked](#TransferStaked) +- [Claim](#Claim) +- [CompoundRewards](#CompoundRewards) +- [UpdateFees](#UpdateFees) +- [PanicUnbond](#PanicUnbond) +- [PanicWithdraw](#PanicWithdraw) +- [SetContractStatus](#SetContractStatus) + +**Queries** + +- [Holdings](#Holdings) +- [StakingInfo](#StakingInfo) +- [Unbondings](#Unbondings) +- [FeeInfo](#FeeInfo) +- [ContractStatus](#ContractStatus) + + + +## Engineering Design Diagram + +![Engineering Design Diagram](./.images/engineering-diagram.png) + + + +## How to deploy + +### Requirements + +- [SecretCLI installed and configured](https://docs.scrt.network/secret-network-documentation/development/tools-and-libraries/secret-cli) +- Account with funds (~6 scrt) +- Derivative (SNIP-20) deployed + +#### Steps + +1. Make sure you have the docker demon turned on. +2. Open project in a terminal. +3. Compile and optimized contract. In the root folder run this command: + +```shell +make compile-optimized-reproducible +``` + +4. Store contract on chain. + +```shell +secretcli tx compute store contract.wasm.gz --from -y --gas 3000000 | jq +``` + +5. Query contract code id + +```shell +CODE_ID=$(secretcli q compute list-code | jq '.[-1].code_id') +``` + +6. Instantiate a new contract + +```shell +TX_HASH=$(secretcli tx compute instantiate ${CODE_ID} '' --from -y --gas 3000000 --label $(openssl rand -base64 12 | tr -d /=+ | cut -c -16) | jq '.txhash' | tr -d '"') && echo ${TX_HASH} +``` + +7. Query contract's address + +```shell +ADDRESS=$(secretcli q compute tx ${TX_HASH} | jq '.output_logs[0].attributes[0].value' | tr -d '"') && echo ${ADDRESS} +``` + +8. Set staking derivative as minter of derivative +```shell +secretcli tx compute execute '{"set_minters":{"minters":["'${ADDRESS}'"]}}' --from -y | jq +``` + +9. Whitelist staking derivative in staking contract +``` +secretcli tx compute execute '{"add_transfer_whitelist":{"user":"'${ADDRESS}'"}}' +``` + +#### Troubleshooting + +- Query transaction's status + +```shell +secretcli q compute tx ${TX_HASH} | jq +``` + + + +## Init Message + +```ts +import { Binary } from "cosmwasm-stargate"; + +interface ContractInfo { + address: string; + code_hash: string; + entropy?: string | null; +} + +interface Fee { + collector: string; + rate: number; + decimal_places: number; +} + +interface FeeInfo { + staking: Fee; + unbonding: Fee; +} + +interface InstantiateMsg { + prng_seed: Binary; + staking: ContractInfo; + query_auth: ContractInfo; + derivative: ContractInfo; + token: ContractInfo; + admin: ContractInfo; + fees: FeeInfo; +} +``` + +```json +{ + "prng_seed": "base64-encoded binary", + "staking": { + "address": "secret1abcdefghjklmnopqrstuvwxyz", + "code_hash": "string", + "entropy": "string or null" + }, + "query_auth": { + "address": "secret1abcdefghjklmnopqrstuvwxyz", + "code_hash": "string", + "entropy": "string or null" + }, + "derivative": { + "address": "secret1abcdefghjklmnopqrstuvwxyz", + "code_hash": "string", + "entropy": "string or null" + }, + "token": { + "address": "secret1abcdefghjklmnopqrstuvwxyz", + "code_hash": "string", + "entropy": "string or null" + }, + "admin": { + "address": "secret1abcdefghjklmnopqrstuvwxyz", + "code_hash": "string", + "entropy": "string or null" + }, + "fees": { + "staking": { + "collector": "secret1abcdefghjklmnopqrstuvwxyz", + "rate": 0, + "decimal_places": 0 + }, + "unbonding": { + "collector": "secret1abcdefghjklmnopqrstuvwxyz", + "rate": 0, + "decimal_places": 0 + } + } +} +``` + +## Messages + +### Stake + +Calculates the equivalent amount of derivative per token sent. +Triggered by the receiver interface when sending SHD tokens. + +🌐 Anyone can use this feature. + +**Request** + +```typescript +interface ExecuteSendMsg { + recipient: string; + amount: string; + msg: string; // '{"stake":{}}' Base64 encoded + padding?: string; +} + +interface ExecuteStakeMsg { + send: ExecuteSendMsg; +} +``` + +```json +{ + "send": { + "recipient": "secret1b1b1b1bb1b1b1b1b1b1", + "amount": "100000000", + "msg": "eyJzdGFrZSI6e319", + "padding": "random string" + } +} +``` + +**Response** + +```typescript +interface StakeResponse { + shd_staked: string; + tokens_returned: string; +} +interface StakeMsgResponse { + stake: StakeResponse; +} +``` + +```json +{ + "stake": { + "shd_staked": "50000000", + "tokens_returned": "50000000" + } +} +``` + +**Errors** + +| Message | Cause | How to solve it | +| ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ----------------------------------------- | +| Sender is not SHD contract | The token sent is not the same as indicated at contract's instantiation | Send the appropriate token | +| No SHD was sent for staking | You send 0 tokens to the contract (if that's possible) | Send more than 0 | +| The amount of SHD deposited is not enough to receive any of the derivative token at the current price | The price is high causing that the amount sent is not enough to buy 1 derivative. | Send more SHD to the contract than before | + +### Unbond + +Calculates the equivalent amount of SHD per derivative sent. +Triggered by Receiver interface when sending derivative tokens. + +🌐 Anyone can use this feature. + +**Request** + +```typescript +interface ExecuteSendMsg { + recipient: string; + amount: string; + msg: string; // '{"unbond":{}}' Base64 encoded + padding?: string; +} + +interface ExecuteUnbondMsg { + send: ExecuteSendMsg; +} +``` + +```json +{ + "send": { + "recipient": "secret1b1b1b1bb1b1b1b1b1b1", + "amount": "100000000", + "msg": "eyJ1bmJvbmQiOnt9fQ==", + "padding": "random string" + } +} +``` + +**Response** + +```typescript +interface UnbondResponse { + tokens_redeemed: string; + shd_to_be_received: string; + estimated_time_of_maturity: string; +} +interface UnbondMsgResponse { + unbond: UnbondResponse; +} +``` + +```json +{ + "unbond": { + "tokens_redeemed": "50000000", + "shd_to_be_received": "50000000", + "estimated_time_of_maturity": "50000000" + } +} +``` + +**Errors** + +| Message | Cause | How to solve it | +| ---------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------- | +| Sender is not derivative (SNIP20) contract | The token sent is not the same as indicated at contract's instantiation | Send the appropriate tokens | +| 0 amount sent to unbond | You send 0 tokens to the contract (if that's possible) | Send more than 0 | +| Redeeming derivative tokens would be worth less than 1 SHD | The price is high causing that the amount sent is not enough to buy 1 SHD. | Send more derivatives to the contract than before | + +### TransferStaked + +Calculates the equivalent amount of SHD per derivative sent. Then sends this SHD as staked position to the sender. +Triggered by Receiver interface when sending derivative tokens. + +🌐 Anyone can use this feature. + +**Request** + +```typescript +interface ExecuteSendMsg { + recipient: string; + amount: string; + msg: string; // '{"transfer_staked":{"receiver":"opt_receiver_address"}}' Base64 encoded + padding?: string; +} + +interface ExecuteUnbondMsg { + send: ExecuteSendMsg; +} +``` + +```json +{ + "send": { + "recipient": "secret1b1b1b1bb1b1b1b1b1b1", + "amount": "100000000", + "msg": "eyJ0cmFuc2Zlcl9zdGFrZWQiOnsicmVjZWl2ZXIiOiJzZWNyZXQxcjZ5OXBxdXkwc3l4a2tndXJodXBnN2tzY3NuMDd6Mmo3ZGo3NyJ9fQ==", + "padding": "random string" + } +} +``` + +**Response** + +```typescript +interface TransferStakedResponse { + tokens_returned: string; + amount_sent: string; +} +interface TransferStakedMsgResponse { + transfer_staked: TransferStakedResponse; +} +``` + +```json +{ + "transfer_staked": { + "amount_sent": "50000000", + "tokens_returned": "50000000" + } +} +``` + +**Errors** + +| Message | Cause | How to solve it | +| ---------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------- | +| Sender is not derivative (SNIP20) contract | The token sent is not the same as indicated at contract's instantiation | Send the appropriate tokens | +| 0 amount sent to unbond | You send 0 tokens to the contract (if that's possible) | Send more than 0 | +| Redeeming derivative tokens would be worth less than 1 SHD | The price is high causing that the amount sent is not enough to buy 1 SHD. | Send more derivatives to the contract than before | + +### Claim + +This message claims user's mature unbondings in case there is any. + +🌐 Anyone can use this feature. + +**Request** + +```typescript +interface ExecuteClaimMsg { + claim: {}; +} +``` + +```json +{ + "claim": {} +} +``` + +**Response** + +```typescript +interface ClaimMsgResponse { + claim: { + amount_claimed: string; + }; +} +``` + +```json +{ + "claim": { + "amount_claimed": "200000000" + } +} +``` + +**Errors** + +| Message | Cause | How to solve it | +| ----------------------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| No mature unbondings to claim | None of your unbondings in progress are matured or you haven't unbonded any SHD | Query your unbondings to see when they will matured or unbond some SHD | + +### CompoundRewards + +Claims SHD rewards generated and re-stake them. Claims non-SHD rewards and sends them to fee's collector. + +🌐 Anyone can use this feature. + +**Request** + +```typescript +interface ExecuteCompoundRewardsMsg { + compound_rewards: {}; +} +``` + +```json +{ + "compound_rewards": {} +} +``` + +**Response** + +```typescript +import ResponseStatus from "shade-protocol"; + +interface CompoundRewardsMsgResponse { + compound_rewards: { + status: ResponseStatus; + }; +} +``` + +```json +{ + "compound_rewards": { + "status": "success" + } +} +``` + +**Errors** + +There are no errors triggered by this contract but the `staking contract`. + +### UpdateFees + +Updates fee's collector, percentage or decimal places. + +👥 Only admin(s) can use this feature. + +**Request** + +```typescript +interface Fee { + rate: number; + decimal_places: number; +} + +interface UpdateFeesMsg { + staking?: Fee; + unbonding?: Fee; + collector?: string; +} + +interface ExecuteUpdateFeesMsg { + update_fees: UpdateFeesMsg; +} +``` + +```json +{ + "update_fees": { + "collector": "secretb1b1b1b1b1b1b1b1b1b1b1b1", + "staking": { + "rate": 50000, + "decimal_places": 5 + }, + "unbonding": { + "rate": 50000, + "decimal_places": 5 + } + } +} +``` + +**Response** + +```typescript +import ResponseStatus from "shade-protocol"; + +interface Fee { + rate: number; + decimal_places: number; +} + +interface FeeInfo { + staking: Fee; + unbonding: Fee; + collector: string; +} + +interface UpdateFeesMsgResponse { + update_fees: { + status: ResponseStatus; + fee: FeeInfo; + }; +} +``` + +```json +{ + "update_fees": { + "status": "success", + "fee": { + "collector": "secretb1b1b1b1b1b1b1b1b1b1b1b1", + "staking": { + "rate": 50000, + "decimal_places": 5 + }, + "unbonding": { + "rate": 50000, + "decimal_places": 5 + } + } + } +} +``` + +**Errors** + +| Message | Cause | How to solve it | +| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------- | +| [Unauthorize admin](https://github.com/securesecrets/shade/blob/basic-staking/packages/shade_protocol/src/contract_interfaces/admin/errors.rs#L51-L56) | Sender is not part of the admins list | Use an admin account to perform this action. | + +### PanicUnbond + +Unbonds X amount staked from staking contract. + +👥 Only admin(s) can use this feature. + +**Request** + +```typescript +interface ExecutePanicUnbondMsg { + panic_unbond: { + amount: string; + }; +} +``` + +```json +{ + "panic_unbond": { + "amount": "100000000" + } +} +``` + +**Response** + +_Default response_ + +**Errors** + +| Message | Cause | How to solve it | +| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------- | +| [Unauthorize admin](https://github.com/securesecrets/shade/blob/basic-staking/packages/shade_protocol/src/contract_interfaces/admin/errors.rs#L51-L56) | Sender is not part of the admins list | Use an admin account to perform this action. | + +### PanicWithdraw + +Withdraws all rewards, matured unbondings and SHD balance. +This funds will be sent to `super admin`. + +👥 Only admin(s) can use this feature. + +**Request** + +```typescript +interface ExecutePanicWithdrawMsg { + panic_withdraw: {}; +} +``` + +```json +{ + "panic_withdraw": {} +} +``` + +**Response** + +_Default response_ + +**Errors** + +| Message | Cause | How to solve it | +| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------- | +| [Unauthorize admin](https://github.com/securesecrets/shade/blob/basic-staking/packages/shade_protocol/src/contract_interfaces/admin/errors.rs#L51-L56) | Sender is not part of the admins list | Use an admin account to perform this action. | + +### SetContractStatus + +Sets contract status. + +👥 Only admin(s) can use this feature. + +**Request** + +```typescript +enum ContractStatusLevel { + NormalRun, + Panicked + StopAll, +} + +interface ExecuteSetContractStatusMsg { + set_contract_status: { + level: ContractStatusLevel; + padding?: string; + }; +} +``` + +```json +{ + "set_contract_status": { + "level": "stop_all", + "padding": "random string" + } +} +``` + +**Response** + +```typescript +import ResponseStatus from "shade-protocol"; + +interface ContractStatusMsgResponse { + set_contract_status: { + status: ResponseStatus; + }; +} +``` + +```json +{ + "set_contract_status": { + "status": "success" + } +} +``` + +**Errors** + +| Message | Cause | How to solve it | +| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------- | +| [Unauthorize admin](https://github.com/securesecrets/shade/blob/basic-staking/packages/shade_protocol/src/contract_interfaces/admin/errors.rs#L51-L56) | Sender is not part of the admins list | Use an admin account to perform this action. | + +## Queries + +### Holdings + +Queries user's claimable total amount and unbonding total amount. + +**Request** + +```typescript +interface HoldingsQuery { + holdings: { + address: string; + viewing_key: string; + }; +} +``` + +```json +{ + "holdings": { + "address": "secret1b1b1b1b1b1b1b1b1", + "viewing_key": "password" + } +} +``` + +**Response** + +```typescript +interface HoldingsQueryResponse { + holdings: { + derivative_claimable: string; + derivative_unbonding: string; + }; +} +``` + +```json +{ + "holdings": { + "derivative_claimable": "100000000", + "derivative_unbonding": "200000000" + } +} +``` + +### StakingInfo + +Queries contract's balances and price information. + +**Request** + +```typescript +interface StakingInfoQuery { + staking_info: {}; +} +``` + +```json +{ + "staking_info": {} +} +``` + +**Response** + +```typescript +interface StakingInfoQueryResponse { + staking_info: { + unbonding_time: string; + bonded_shd: string; + available_shd: string; + rewards: string; + total_derivative_token_supply: string; + price: string; + }; +} +``` + +```json +{ + "staking_info": { + "unbonding_time": "36000", + "bonded_shd": "100000000", + "available_shd": "0", + "rewards": "320400", + "total_derivative_token_supply": "100000000", + "price": "102000000" + } +} +``` + +### Unbondings + +Queries user's unbondings in progress. + +**Request** + +```typescript +interface UnbondingsQuery { + unbondings: { + address: string; + viewing_key: string; + }; +} +``` + +```json +{ + "unbondings": { + "address": "secret1b1b1b1b1b1b1b1b1", + "viewing_key": "password" + } +} +``` + +**Response** + +```typescript +import Unbonding from "shade-protocol"; +interface UnbondingsQueryResponse { + unbondings: { + unbonds: Unbonding[]; + }; +} +``` + +```json +{ + "unbondings": { + "unbonds": [ + { + "id": "1", + "amount": "400000000", + "complete": "39478094578404" + } + ] + } +} +``` + +### FeeInfo + +Queries staking and unbonding fees configuration. + +**Request** + +```typescript +interface FeeInfoQuery { + fee_info: {}; +} +``` + +```json +{ + "fee_info": {} +} +``` + +**Response** + +```typescript +interface Fee { + rate: number; + decimal_places: number; +} + +interface FeeInfoQueryResponse { + fee_info: { + collector: string; + staking: Fee; + unbonding: Fee; + }; +} +``` + +```json +{ + "fee_info": { + "collector": "secret1b1b1b1b1b1b1b1b1", + "staking": { + "rate": 100, + "decimal_places": 3 + }, + "unbonding": { + "rate": 100, + "decimal_places": 3 + } + } +} +``` + +### ContractStatus + +Queries contracts status. + +**Request** + +```typescript +interface ContractStatusQuery { + contract_status: {}; +} +``` + +```json +{ + "contract_status": {} +} +``` + +**Response** + +```typescript +interface ContractStatusLevel { + NormalRun; + StopAll; +} + +interface ContractStatusQueryResponse { + contract_status: { + status: ContractStatusLevel; + }; +} +``` + +```json +{ + "contract_status": { + "status": "stop_all" + } +} +``` diff --git a/contracts/headstash-contract/examples/schema.rs b/contracts/snip20_derivative/examples/schema.rs similarity index 59% rename from contracts/headstash-contract/examples/schema.rs rename to contracts/snip20_derivative/examples/schema.rs index 87cea6e..cba5853 100644 --- a/contracts/headstash-contract/examples/schema.rs +++ b/contracts/snip20_derivative/examples/schema.rs @@ -2,9 +2,9 @@ use std::env::current_dir; use std::fs::create_dir_all; use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; -use headstash_contract::msg::{ - ConfigResponse, ExecuteMsg, InstantiateMsg, IsClaimedResponse, - MerkleRootResponse, QueryMsg, + +use derivative_snip_20_contract::msg::{ + ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, }; fn main() { @@ -15,8 +15,7 @@ fn main() { export_schema(&schema_for!(InstantiateMsg), &out_dir); export_schema(&schema_for!(ExecuteMsg), &out_dir); + export_schema(&schema_for!(ExecuteAnswer), &out_dir); export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(MerkleRootResponse), &out_dir); - export_schema(&schema_for!(IsClaimedResponse), &out_dir); - export_schema(&schema_for!(ConfigResponse), &out_dir); + export_schema(&schema_for!(QueryAnswer), &out_dir); } diff --git a/contracts/snip20_derivative/src/contract.rs b/contracts/snip20_derivative/src/contract.rs new file mode 100644 index 0000000..43b1cfb --- /dev/null +++ b/contracts/snip20_derivative/src/contract.rs @@ -0,0 +1,2660 @@ +use std::ops::Add; + +use crate::{ + msg::{ + status_level_to_u8, Config, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, + InProcessUnbonding, InstantiateMsg, PanicUnbond, QueryAnswer, QueryMsg, QueryWithPermit, + ReceiverMsg, ResponseStatus::Success, + }, + staking_interface::{transfer_staked_msg, Reward, Rewards, Token}, + state::{ContractsVksStore, REWARDED_TOKENS_LIST}, +}; + +#[allow(unused_imports)] +use crate::staking_interface::{ + balance_query as staking_balance_query, claim_rewards_msg, compound_msg, config_query, + rewards_query, unbond_msg, withdraw_msg, Action, RawContract, StakingConfig, UnbondResponse, + Unbonding, WithdrawResponse, +}; + +use crate::state::{ + UnbondingIdsStore, UnbondingStore, CONFIG, CONTRACT_STATUS, PANIC_UNBONDS, + PANIC_UNBOND_REPLY_ID, PANIC_WITHDRAW_REPLY_ID, PENDING_UNBONDING, RESPONSE_BLOCK_SIZE, + UNBOND_REPLY_ID, +}; +/// This contract implements SNIP-20 standard: +/// https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md +use cosmwasm_std::{ + entry_point, from_binary, to_binary, Addr, Binary, CosmosMsg, CustomQuery, Deps, DepsMut, Env, + MessageInfo, QuerierWrapper, Reply, Response, StdError, StdResult, Storage, SubMsg, + SubMsgResult, Uint128, Uint256, +}; + +#[allow(unused_imports)] +use secret_toolkit::{ + snip20::{ + balance_query, burn_msg, mint_msg, register_receive_msg, send_msg, set_viewing_key_msg, + token_info_query, TokenInfo, + }, + utils::{pad_handle_result, pad_query_result}, +}; + +use secret_toolkit_crypto::{sha_256, ContractPrng}; +use serde::de::DeserializeOwned; +use shade_protocol::query_auth::QueryPermit; + +use crate::msg::{Fee, FeeInfo}; +#[allow(unused_imports)] +use shade_protocol::{ + admin::{ + helpers::{validate_admin, AdminPermissions}, + ConfigResponse, QueryMsg as AdminQueryMsg, + }, + query_auth::helpers::{authenticate_permit, authenticate_vk}, + utils::Query, + Contract, +}; + +#[entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let token_info = get_token_info( + deps.querier, + RESPONSE_BLOCK_SIZE, + msg.token.code_hash.clone(), + msg.token.address.to_string(), + false, + )?; + let derivative_info = get_token_info( + deps.querier, + RESPONSE_BLOCK_SIZE, + msg.derivative.code_hash.clone(), + msg.derivative.address.to_string(), + true, + )?; + + if token_info.decimals != derivative_info.decimals { + return Err(StdError::generic_err( + "Derivative and token contracts should have the same amount of decimals", + )); + } + // Generate viewing key for staking contract + let entropy: String = msg + .staking + .entropy + .clone() + .unwrap_or_else(|| msg.prng_seed.to_string()); + let (staking_contract_vk, new_seed) = + new_viewing_key(&info.sender, &env, &msg.prng_seed.0, entropy.as_ref()); + + // Generate viewing key for SHD contract + let entropy: String = msg + .token + .entropy + .clone() + .unwrap_or_else(|| msg.prng_seed.to_string()); + let (token_contract_vk, _new_seed) = + new_viewing_key(&info.sender, &env, &new_seed, entropy.as_ref()); + + CONFIG.save( + deps.storage, + &Config { + prng_seed: msg.prng_seed, + staking_contract_vk: staking_contract_vk.clone(), + token_contract_vk: token_contract_vk.clone(), + query_auth: msg.query_auth.clone(), + token: msg.token.clone(), + derivative: msg.derivative.clone(), + staking: msg.staking, + fees: msg.fees, + contract_address: env.contract.address.clone(), + admin: msg.admin, + }, + )?; + CONTRACT_STATUS.save(deps.storage, &ContractStatusLevel::NormalRun)?; + + let msgs: Vec = vec![ + // Register receive Derivative contract needed for Unbond functionality + register_receive_msg( + env.contract.code_hash.clone(), + msg.derivative.entropy.clone(), + RESPONSE_BLOCK_SIZE, + msg.derivative.code_hash.clone(), + msg.derivative.address.to_string(), + )?, + // Register receive SHD contract + register_receive_msg( + env.contract.code_hash, + msg.token.entropy.clone(), + RESPONSE_BLOCK_SIZE, + msg.token.code_hash.clone(), + msg.token.address.to_string(), + )?, + // Set viewing key for SHD + set_viewing_key_msg( + token_contract_vk, + msg.token.entropy, + RESPONSE_BLOCK_SIZE, + msg.token.code_hash, + msg.token.address.to_string(), + )?, + // Set viewing key for staking contract + set_viewing_key_msg( + staking_contract_vk, + msg.query_auth.entropy, + RESPONSE_BLOCK_SIZE, + msg.query_auth.code_hash, + msg.query_auth.address.to_string(), + )?, + ]; + + Ok(Response::default().add_messages(msgs)) +} + +#[entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + let response = match msg { + // Messages always available + ExecuteMsg::SetContractStatus { level, .. } => { + set_contract_status(deps, info, level, ContractStatusLevel::StopAll) + } + // Messages available during panic mode + ExecuteMsg::Claim {} => try_claim(deps, env, info, ContractStatusLevel::Panicked), + ExecuteMsg::PanicUnbond { amount } => { + try_panic_unbond(env, deps, info, amount, ContractStatusLevel::Panicked) + } + ExecuteMsg::PanicWithdraw {} => { + try_panic_withdraw(deps, env, info, ContractStatusLevel::Panicked) + } + ExecuteMsg::UpdateFees { + staking, + unbonding, + collector, + } => update_fees( + deps, + info, + staking, + unbonding, + collector, + ContractStatusLevel::Panicked, + ), + + // Messages available when status is normal + ExecuteMsg::Receive { + sender: _, + from, + amount, + msg, + } => receive(deps, env, info, from, amount, msg), + ExecuteMsg::CompoundRewards {} => { + try_compound_rewards(deps, ContractStatusLevel::NormalRun) + } + }; + + pad_handle_result(response, RESPONSE_BLOCK_SIZE) +} + +#[entry_point] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + pad_query_result( + match msg { + QueryMsg::StakingInfo {} => query_staking_info(&deps, &env), + QueryMsg::FeeInfo {} => query_fee_info(&deps), + QueryMsg::ContractStatus {} => query_contract_status(deps.storage), + QueryMsg::WithPermit { permit } => permit_queries(deps, &env, permit), + _ => viewing_keys_queries(deps, &env, msg), + }, + RESPONSE_BLOCK_SIZE, + ) +} + +#[entry_point] +pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> StdResult { + match (msg.id, msg.result) { + (UNBOND_REPLY_ID, SubMsgResult::Ok(s)) => match s.data { + Some(x) => { + let result: UnbondResponse = from_binary(&x)?; + // Unbonding stored in try_unbond function + // Because of here you can't access the sender of the TX this was stored previously + let pending_unbonding = PENDING_UNBONDING.may_load(deps.storage)?; + + if let Some(unbonding_processing) = pending_unbonding { + // Set properly id for this unbonding + let unbond = Unbonding { + id: result.unbond.id, + amount: unbonding_processing.amount, + complete: unbonding_processing.complete, + }; + UnbondingStore::save(deps.storage, result.unbond.id.clone().u128(), &unbond)?; + + // Add unbonding id to user's unbondings IDs + let mut users_unbondings_ids = + UnbondingIdsStore::load(deps.storage, &unbonding_processing.owner); + users_unbondings_ids.push(result.unbond.id.u128()); + UnbondingIdsStore::save( + deps.storage, + &unbonding_processing.owner, + users_unbondings_ids, + )?; + + Ok(Response::default()) + } else { + Err(StdError::generic_err( + "Unexpected error: pending unbond storage is empty.", + )) + } + } + None => Err(StdError::generic_err("Unknown reply id")), + }, + + (PANIC_WITHDRAW_REPLY_ID, SubMsgResult::Ok(s)) => match s.data { + Some(x) => { + let config = CONFIG.load(deps.storage)?; + let result: WithdrawResponse = from_binary(&x)?; + let withdrawn = result.withdraw.withdrawn; + let addr = get_super_admin(&deps.querier, &config)?; + + Ok(Response::default().add_message(send_msg( + addr.to_string(), + withdrawn, + None, + Some("Panic withdraw {} tokens".to_string()), + config.token.entropy, + RESPONSE_BLOCK_SIZE, + config.token.code_hash, + config.token.address.to_string(), + )?)) + } + None => Err(StdError::generic_err("Unknown reply id")), + }, + + (PANIC_UNBOND_REPLY_ID, SubMsgResult::Ok(s)) => match s.data { + Some(x) => { + let result: UnbondResponse = from_binary(&x)?; + let mut panic_unbonds = PANIC_UNBONDS.may_load(deps.storage)?.unwrap_or_default(); + let last = panic_unbonds.pop(); + + // Validate there is at least 1 element is storage to update + // should never happen but you never know + if let Some(mut last_unbond) = last { + // Update latest panic unbond id + last_unbond.id = result.unbond.id; + panic_unbonds.push(last_unbond); + //Save list of panic unbonds in storage + PANIC_UNBONDS.save(deps.storage, &panic_unbonds)?; + } + Ok(Response::default()) + } + None => Err(StdError::generic_err("Unknown reply id")), + }, + _ => Err(StdError::generic_err("Unknown reply id")), + } +} +/************ HANDLES ************/ +/// It takes a list of unbonding ids, and if they are mature, it removes them from storage and sends the +/// tokens to the user +/// +/// Arguments: +/// +/// * `deps`: DepsMut - This is the dependency struct that contains all the dependencies that the +/// handler needs. +/// * `env`: Env - This is the environment that the transaction is being executed in. It contains +/// information about the block, the transaction, and the message. +/// * `info`: MessageInfo - contains the sender of the message, the sent amount, and the sent memo +/// +/// Returns: +/// +/// StdResult +fn try_claim( + deps: DepsMut, + env: Env, + info: MessageInfo, + priority: ContractStatusLevel, +) -> StdResult { + check_status(deps.storage, priority)?; + let sender = info.sender; + let time = Uint128::from(env.block.time.seconds()); + let user_unbondings_ids = UnbondingIdsStore::load(deps.storage, &sender); + let config = CONFIG.load(deps.storage)?; + let mut to_claim_ids: Vec = vec![]; + let mut amount_claimed = Uint128::zero(); + + for id in user_unbondings_ids.iter() { + let opt_unbonding = UnbondingStore::may_load(deps.storage, *id); + if let Some(unbonding) = opt_unbonding { + // Handle mature unbondings + if time >= unbonding.complete { + to_claim_ids.push(unbonding.id.u128()); + amount_claimed += unbonding.amount; + + // Remove unbonding from storage + UnbondingStore::remove(deps.storage, *id)?; + } + } + } + if to_claim_ids.is_empty() { + return Err(StdError::generic_err("No mature unbondings to claim")); + } + let (fee, deposit) = get_fee(amount_claimed, &config.fees.unbonding)?; + + let users_new_pending_unbondings: Vec = user_unbondings_ids + .into_iter() + .filter(|id| !to_claim_ids.contains(id)) + .collect(); + UnbondingIdsStore::save(deps.storage, &sender, users_new_pending_unbondings)?; + + let to_claim_ids_uint128: Vec = to_claim_ids.into_iter().map(Uint128::from).collect(); + + let config: Config = CONFIG.load(deps.storage)?; + let messages: Vec = vec![ + withdraw_msg( + config.staking.code_hash, + config.staking.address.to_string(), + Some(to_claim_ids_uint128), + )?, + send_msg( + config.fees.collector.to_string(), + fee, + None, + Some(base64::encode(&"Payment of fee for unbonding SHD")), + config.token.entropy.clone(), + RESPONSE_BLOCK_SIZE, + config.token.code_hash.clone(), + config.token.address.to_string(), + )?, + send_msg( + sender.to_string(), + deposit, + None, + Some(format!("Claiming {} SHD tokens", { deposit })), + config.token.entropy, + RESPONSE_BLOCK_SIZE, + config.token.code_hash, + config.token.address.to_string(), + )?, + ]; + + Ok(Response::default() + .add_messages(messages) + .set_data(to_binary(&ExecuteAnswer::Claim { + amount_claimed: deposit, + })?)) +} + +/// It creates a `compound_msg` and returns it as a `Response` +/// +/// Arguments: +/// +/// * `deps`: DepsMut - This is the dependencies object that contains the storage, querier, and other +/// useful things. +/// +/// Returns: +/// +/// StdResult +fn try_compound_rewards(deps: DepsMut, priority: ContractStatusLevel) -> StdResult { + check_status(deps.storage, priority)?; + let config = CONFIG.load(deps.storage)?; + let staked = get_staked_shd(deps.querier, &config.contract_address, &config)?; + let rewards = query_rewards(deps.querier, &config.contract_address, &config)?; + + let mut messages: Vec = vec![]; + if staked > 0 { + messages.push(compound_msg( + config.staking.code_hash, + config.staking.address.to_string(), + )?); + } + let response = Response::default(); + let rewarded_tokens_list = REWARDED_TOKENS_LIST + .may_load(deps.storage)? + .unwrap_or_default(); + for addr in rewarded_tokens_list.into_iter() { + let token = ContractsVksStore::may_load(deps.storage, &addr); + if let Some(t) = token { + let balance = balance_query( + deps.querier, + config.contract_address.to_string(), + t.viewing_key, + RESPONSE_BLOCK_SIZE, + t.code_hash.clone(), + t.address.to_string(), + )?; + + let item = rewards + .rewards + .iter() + .find(|r| r.token.address == t.address); + + let amount = if let Some(reward) = item { + balance.amount.add(reward.amount) + } else { + balance.amount + }; + + if amount > Uint128::zero() { + messages.push(send_msg( + config.fees.collector.to_string(), + amount, + None, + Some(format!( + "Sending {} rewards to ShadeDAO", + t.address.to_string() + )), + None, + RESPONSE_BLOCK_SIZE, + t.code_hash.clone(), + t.address.to_string(), + )?); + } + } + } + + Ok(response.add_messages(messages)) +} + +/// It checks if the sender is an admin, and if so, it sends a message to the staking contract to unbond +/// the given amount +/// +/// Arguments: +/// +/// * `deps`: DepsMut - This is the dependencies object that contains the storage, querier, and other +/// useful objects. +/// * `info`: MessageInfo - this is a struct that contains the sender, sent_funds, and sent_funds_count. +/// * `amount`: The amount of tokens to unbond. +/// +/// Returns: +/// +/// StdResult. +fn try_panic_unbond( + env: Env, + deps: DepsMut, + info: MessageInfo, + amount: Uint128, + priority: ContractStatusLevel, +) -> StdResult { + check_status(deps.storage, priority)?; + let config = CONFIG.load(deps.storage)?; + check_if_admin( + &deps.querier, + AdminPermissions::DerivativeAdmin, + info.sender.to_string(), + &config.admin, + )?; + // Store panic unbond + let staking_config = get_staking_contract_config(deps.querier, &config)?; + let complete: Uint128 = + Uint128::from(env.block.time.seconds()).checked_add(staking_config.unbond_period)?; + let mut panic_unbonds: Vec = + PANIC_UNBONDS.may_load(deps.storage)?.unwrap_or_default(); + panic_unbonds.push(PanicUnbond { + id: Uint128::zero(), + amount, + complete, + }); + PANIC_UNBONDS.save(deps.storage, &panic_unbonds)?; + + let msg = unbond_msg( + amount, + config.staking.code_hash, + config.staking.address.to_string(), + Some(false), + )?; + Ok(Response::default().add_submessage(SubMsg::reply_always(msg, PANIC_UNBOND_REPLY_ID))) +} + +/// It sends a message to the staking contract to claim rewards, then sends a message to the staking contract +/// to withdraw the rewards and then sends available SHD balance to the super admin address +/// +/// Arguments: +/// +/// * `deps`: DepsMut, +/// * `env`: The environment of the contract. +/// * `info`: MessageInfo - contains the sender, sent_funds, and sent_funds_attachment +/// * `ids`: Option> +/// +/// Returns: +/// +/// StdResult. +fn try_panic_withdraw( + deps: DepsMut, + env: Env, + info: MessageInfo, + priority: ContractStatusLevel, +) -> StdResult { + check_status(deps.storage, priority)?; + let config = CONFIG.load(deps.storage)?; + check_if_admin( + &deps.querier, + AdminPermissions::DerivativeAdmin, + info.sender.to_string(), + &config.admin, + )?; + let addr = get_super_admin(&deps.querier, &config)?; + let rewards = get_rewards(deps.querier, &env.contract.address, &config)?; + let balance = get_available_shd(deps.querier, &env.contract.address, &config)?; + let amount = Uint128::from(rewards + balance); + let mut response = Response::default().add_messages(vec![ + claim_rewards_msg( + config.staking.code_hash.clone(), + config.staking.address.to_string(), + )?, + send_msg( + addr.to_string(), + amount, + None, + Some("Panic withdraw {} tokens".to_string()), + config.token.entropy, + RESPONSE_BLOCK_SIZE, + config.token.code_hash, + config.token.address.to_string(), + )?, + ]); + let panic_unbonds = PANIC_UNBONDS.may_load(deps.storage)?; + if let Some(unbonds) = panic_unbonds { + let time = Uint128::from(env.block.time.seconds()); + let mut to_withdraw: Vec = vec![]; + let mut pending_unbonds: Vec = vec![]; + + for u in unbonds.into_iter() { + if time >= u.complete { + to_withdraw.push(u.id); + } else { + pending_unbonds.push(u); + } + } + + PANIC_UNBONDS.save(deps.storage, &pending_unbonds)?; + + response = response.add_submessage(SubMsg::reply_on_success( + withdraw_msg( + config.staking.code_hash, + config.staking.address.to_string(), + Some(to_withdraw.clone()), + )?, + PANIC_WITHDRAW_REPLY_ID, + )); + } + + Ok(response) +} + +/// `update_fees` updates the fees for staking and unbonding +/// +/// Arguments: +/// +/// * `deps`: DepsMut - This is the dependency object that contains the storage, querier, and logger. +/// * `info`: MessageInfo - this is the information about the message that was sent to the contract. +/// * `staking`: The fee for staking. +/// * `unbonding`: Option +/// +/// Returns: +/// +/// StdResult. +fn update_fees( + deps: DepsMut, + info: MessageInfo, + staking: Option, + unbonding: Option, + collector: Option, + priority: ContractStatusLevel, +) -> StdResult { + check_status(deps.storage, priority)?; + let mut config = CONFIG.load(deps.storage)?; + check_if_admin( + &deps.querier, + AdminPermissions::DerivativeAdmin, + info.sender.to_string(), + &config.admin, + )?; + let fees: FeeInfo = FeeInfo { + staking: staking.unwrap_or(config.fees.staking), + unbonding: unbonding.unwrap_or(config.fees.unbonding), + collector: collector.unwrap_or(config.fees.collector), + }; + config.fees = fees.clone(); + CONFIG.save(deps.storage, &config)?; + + Ok( + Response::default().set_data(to_binary(&ExecuteAnswer::UpdateFees { + status: Success, + fee: fees, + })?), + ) +} + +/// If the message is a `Stake` message, call `try_stake`, if it's an `Unbond` message, call +/// `try_unbond`, otherwise return an error +/// +/// Arguments: +/// +/// * `deps`: This is a struct that contains all the dependencies that the contract needs to run. +/// * `env`: The environment of the transaction. +/// * `info`: MessageInfo - contains information about the message that was sent to the contract +/// * `from`: The address of the sender +/// * `amount`: The amount of tokens sent to the contract. +/// +/// Returns: +/// +/// Response::default() +fn receive( + deps: DepsMut, + env: Env, + info: MessageInfo, + from: Addr, + amount: Uint256, + msg: Option, +) -> StdResult { + if let Some(x) = msg { + match from_binary(&x)? { + ReceiverMsg::Stake {} => try_stake( + deps, + env, + info, + from, + amount, + ContractStatusLevel::NormalRun, + ), + ReceiverMsg::Unbond {} => try_unbond( + deps, + env, + info, + from, + amount, + ContractStatusLevel::NormalRun, + ), + ReceiverMsg::TransferStaked { receiver } => try_transfer_staked( + deps, + env, + info, + from, + amount, + receiver, + ContractStatusLevel::NormalRun, + ), + #[allow(unreachable_patterns)] + _ => Err(StdError::generic_err(format!( + "Invalid msg provided, expected {} , {} or {}", + to_binary(&ReceiverMsg::Stake {})?, + to_binary(&ReceiverMsg::Unbond {})?, + to_binary(&ReceiverMsg::TransferStaked { receiver: None })? + ))), + } + } else { + Ok(Response::default()) + } +} + +/// `try_stake` takes a deposit of SHD and returns the equivalent mint of the derivative token +/// +/// Arguments: +/// +/// * `deps`: DepsMut, +/// * `env`: The environment of the contract. +/// * `info`: MessageInfo - contains information about the message that was sent to the contract +/// * `from`: The address of the staker +/// * `amt`: The amount of SHD to stake. +/// +/// Returns: +/// +/// The amount of tokens that were minted for the staking transaction. +fn try_stake( + deps: DepsMut, + env: Env, + info: MessageInfo, + from: Addr, + amt: Uint256, + priority: ContractStatusLevel, +) -> StdResult { + check_status(deps.storage, priority)?; + let config = CONFIG.load(deps.storage)?; + let amount = Uint128::try_from(amt)?; + if info.sender != config.token.address { + return Err(StdError::generic_err("Sender is not SHD contract")); + } + + if amount == Uint128::zero() { + return Err(StdError::generic_err("No SHD was sent for staking")); + } + + let rewards = query_rewards(deps.querier, &config.contract_address, &config)?; + + let mut non_shd_rewards: Vec = vec![]; + let mut shd_rewards: Option = None; + + for r in rewards.rewards.into_iter() { + if r.token.address == config.token.address { + shd_rewards = Some(r); + } else { + non_shd_rewards.push(r) + } + } + let rewards_amount = if let Some(r) = shd_rewards { + r.amount.u128() + } else { + 0_128 + }; + + let available = get_available_shd(deps.querier, &config.contract_address, &config)?; + let (fee, deposit) = get_fee(amount, &config.fees.staking)?; + + // get available SHD + available rewards + let claiming = available + rewards_amount; + + // get staked SHD + let bonded = get_staked_shd(deps.querier, &env.contract.address, &config)?; + let starting_pool = (claiming + bonded).saturating_sub(deposit.u128() + fee.u128()); + + let token_info = get_token_info( + deps.querier, + RESPONSE_BLOCK_SIZE, + config.derivative.code_hash.clone(), + config.derivative.address.to_string(), + true, + )?; + let total_supply = token_info.total_supply.unwrap_or(Uint128::zero()); + // mint appropriate amount + let mint = if starting_pool == 0 || total_supply.is_zero() { + deposit + } else { + // unwrap is ok because multiplying 2 u128 ints can not overflow a u256 + let numer = Uint256::from(deposit) + .checked_mul(Uint256::from(total_supply)) + .unwrap(); + // unwrap is ok because starting pool can not be zero + Uint128::try_from(numer.checked_div(Uint256::from(starting_pool)).unwrap())? + }; + if mint == Uint128::zero() { + return Err(StdError::generic_err("The amount of SHD deposited is not enough to receive any of the derivative token at the current price")); + } + // Sync rewarded tokens + let mut messages = sync_rewarded_tokens(&env, deps, info, &non_shd_rewards, &config)?; + + // Mint derivatives in exchange + messages.push(mint_msg( + from.to_string(), + mint, + Some(format!( + "Minted {} u_{} to stake {} SHD", + mint, token_info.symbol, deposit + )), + config.derivative.entropy.clone(), + RESPONSE_BLOCK_SIZE, + config.derivative.code_hash.clone(), + config.derivative.address.to_string(), + )?); + + // send fee to collector + messages.push(send_msg( + config.fees.collector.to_string(), + fee, + None, + Some(base64::encode(format!( + "Payment of fee for staking SHD using contract {}", + env.contract.address.clone() + ))), + config.token.entropy.clone(), + RESPONSE_BLOCK_SIZE, + config.token.code_hash.clone(), + config.token.address.to_string(), + )?); + + // Stake available SHD + if deposit > Uint128::zero() { + messages.push(generate_stake_msg(deposit, Some(true), &config)?); + } + + Ok(Response::new() + .add_attribute("derivative_returned", mint) + .set_data(to_binary(&ExecuteAnswer::Stake { + shd_staked: deposit, + tokens_returned: mint, + })?) + .add_messages(messages)) +} + +fn try_transfer_staked( + deps: DepsMut, + env: Env, + info: MessageInfo, + from: Addr, + amt: Uint256, + receiver: Option, + priority: ContractStatusLevel, +) -> StdResult { + check_status(deps.storage, priority)?; + let config = CONFIG.load(deps.storage)?; + let amount = Uint128::try_from(amt)?; + + let derivative_token_info = get_token_info( + deps.querier, + RESPONSE_BLOCK_SIZE, + config.derivative.code_hash.clone(), + config.derivative.address.to_string(), + true, + )?; + + if info.sender != config.derivative.address { + return Err(StdError::generic_err( + "Sender is not derivative (SNIP20) contract", + )); + } + + if amount == Uint128::zero() { + return Err(StdError::generic_err("0 amount sent to unbond")); + } + + let (_, rewards, delegatable) = get_delegatable(deps.querier, &env.contract.address, &config)?; + let staked = get_staked_shd(deps.querier, &env.contract.address, &config)?; + let pool = delegatable + staked; + // unwrap is ok because multiplying 2 u128 ints can not overflow a u256 + let number = Uint256::from(amount) + .checked_mul(Uint256::from(pool)) + .unwrap(); + // unwrap is ok because derivative token supply could not have been 0 if we were able + // to burn + let unbond_amount = Uint128::try_from( + number + .checked_div(Uint256::from(derivative_token_info.total_supply.unwrap())) + .unwrap(), + )?; + // calculate the amount going to the user and fee's collector + let (fee, deposit) = get_fee(unbond_amount, &config.fees.unbonding)?; + + if deposit.is_zero() { + return Err(StdError::generic_err(format!( + "Redeeming {} derivative tokens would be worth less than 1 SHD", + amount + ))); + } + let recipient: String = receiver.unwrap_or(from).to_string(); + Ok(Response::default() + .add_messages([ + // Claim rewards + claim_rewards_msg( + config.staking.code_hash.clone(), + config.staking.address.to_string(), + )?, + // Re-stake rewards + generate_stake_msg( + Uint128::from(rewards).saturating_sub(fee), + Some(true), + &config, + )?, + // Burn derivatives sent + burn_msg( + amount, + Some(format!( + "Burn {} derivatives to receive {} SHD", + amount, deposit + )), + config.derivative.entropy, + RESPONSE_BLOCK_SIZE, + config.derivative.code_hash.clone(), + config.derivative.address.to_string(), + )?, + //Sends fee collector + send_msg( + config.fees.collector.to_string(), + fee, + None, + Some(base64::encode(format!( + "Payment of fee for transfer staked SHD using contract {}", + env.contract.address + ))), + config.token.entropy.clone(), + RESPONSE_BLOCK_SIZE, + config.token.code_hash.clone(), + config.token.address.to_string(), + )?, + // Transfer staked + transfer_staked_msg( + config.staking.code_hash, + config.staking.address.to_string(), + deposit, + recipient, + Some(true), + )?, + ]) + .set_data(to_binary(&ExecuteAnswer::TransferStaked { + tokens_returned: deposit, + amount_sent: amount, + })?)) +} + +/// `try_unbond` is called when a user sends a derivative token to the contract. The contract then +/// calculates the amount of SHD that the user will receive and unbonds it from the staking contract +/// this amount will be maturing in the unbondings for X amount of time +/// +/// Arguments: +/// +/// * `deps`: DepsMut, +/// * `env`: The environment in which the contract is running. +/// * `info`: MessageInfo - this is the information about the message that was sent to the contract. +/// * `from`: The address of the user who is unbonding +/// * `amt`: The amount of derivative tokens to be redeemed. +/// +/// Returns: +/// +/// The amount of SHD that will be received when the unbonding period is over. +fn try_unbond( + deps: DepsMut, + env: Env, + info: MessageInfo, + from: Addr, + amt: Uint256, + priority: ContractStatusLevel, +) -> StdResult { + check_status(deps.storage, priority)?; + let mut response = Response::new(); + let config = CONFIG.load(deps.storage)?; + let derivative_token_info = get_token_info( + deps.querier, + RESPONSE_BLOCK_SIZE, + config.derivative.code_hash.clone(), + config.derivative.address.to_string(), + true, + )?; + let staking_info = get_staking_contract_config(deps.querier, &config)?; + let amount = Uint128::try_from(amt)?; + if info.sender != config.derivative.address { + return Err(StdError::generic_err( + "Sender is not derivative (SNIP20) contract", + )); + } + + if amount == Uint128::zero() { + return Err(StdError::generic_err("0 amount sent to unbond")); + } + + let (_, _, delegatable) = get_delegatable(deps.querier, &env.contract.address, &config)?; + + let staked = get_staked_shd(deps.querier, &env.contract.address, &config)?; + let pool = delegatable + staked; + // unwrap is ok because multiplying 2 u128 ints can not overflow a u256 + let number = Uint256::from(amount) + .checked_mul(Uint256::from(pool)) + .unwrap(); + // unwrap is ok because derivative token supply could not have been 0 if we were able + // to burn + let unbond_amount = Uint128::try_from( + number + .checked_div(Uint256::from(derivative_token_info.total_supply.unwrap())) + .unwrap(), + )?; + // calculate the amount going to the user + let (_, shd_to_be_received) = get_fee(unbond_amount, &config.fees.unbonding)?; + + if shd_to_be_received.is_zero() { + return Err(StdError::generic_err(format!( + "Redeeming {} derivative tokens would be worth less than 1 SHD", + amount + ))); + } + + // Store unbonding temporarily + // This unbonding is used in unbond sub-message reply handler + // Due to that in reply handler you can't access sender information + // and this is required to store user's unbondings + let unbonding = InProcessUnbonding { + id: Uint128::zero(), + amount: shd_to_be_received, + owner: from, + complete: Uint128::from(env.block.time.seconds()) + .checked_add(staking_info.unbond_period)?, + }; + PENDING_UNBONDING.save(deps.storage, &unbonding)?; + CONFIG.save(deps.storage, &config)?; + + response = response.add_submessage(SubMsg::reply_always( + unbond_msg( + shd_to_be_received, + config.staking.code_hash.clone(), + config.staking.address.to_string(), + Some(true), + )?, + UNBOND_REPLY_ID, + )); + + Ok(response + .add_attribute("unbonded_amount", shd_to_be_received) + .add_message(burn_msg( + amount, + Some(format!( + "Burn {} derivatives to receive {} SHD", + amount, shd_to_be_received + )), + config.derivative.entropy, + RESPONSE_BLOCK_SIZE, + config.derivative.code_hash.clone(), + config.derivative.address.to_string(), + )?) + .set_data(to_binary(&ExecuteAnswer::Unbond { + shd_to_be_received, + tokens_redeemed: amount, + estimated_time_of_maturity: Uint128::from(env.block.time.seconds()) + .checked_add(staking_info.unbond_period)?, + })?)) +} + +/// It queries the token's balance of the contract address +/// +/// Arguments: +/// +/// * `querier`: The querier object that will be used to query the blockchain. +/// * `contract_addr`: The address of the contract that we want to query. +/// * `config`: The configuration object that contains the token contract address, token contract +/// verifier key, and token code hash. +/// +/// Returns: +/// +/// The available SHD balance of the contract. +#[cfg(not(test))] +#[allow(dead_code)] +fn get_available_shd( + querier: QuerierWrapper, + contract_addr: &Addr, + config: &Config, +) -> StdResult { + let balance = balance_query( + querier, + contract_addr.to_string(), + config.token_contract_vk.clone(), + RESPONSE_BLOCK_SIZE, + config.token.code_hash.to_string(), + config.token.address.to_string(), + )?; + + let available = balance.amount; + Ok(available.u128()) +} +#[cfg(test)] +fn get_available_shd( + _: QuerierWrapper, + _: &Addr, + _: &Config, +) -> StdResult { + Ok(100000000_u128) +} + +/// It queries the staking contract to get the amount of staked SHD for the given contract address +/// +/// Arguments: +/// +/// * `querier`: The querier object that will be used to query the blockchain. +/// * `contract_addr`: The address of the contract that is being queried. +/// * `config`: The configuration of the contract. +/// +/// Returns: +/// +/// The balance of the staking contract. +#[cfg(not(test))] +fn get_staked_shd( + querier: QuerierWrapper, + contract_addr: &Addr, + config: &Config, +) -> StdResult { + let balance = staking_balance_query( + contract_addr.to_string(), + config.staking_contract_vk.clone(), + querier, + config.staking.code_hash.to_string(), + config.staking.address.to_string(), + )?; + + Ok(balance.amount.u128()) +} +#[cfg(test)] +fn get_staked_shd(_: QuerierWrapper, _: &Addr, _: &Config) -> StdResult { + Ok(300000000) +} + +/// It queries the rewards generated for this contract address. +/// Filters out to the token contract rewards and returns them as u128 +/// +/// Arguments: +/// +/// * `querier`: The querier object that will be used to query the blockchain. +/// * `contract_addr`: The address of the contract that we want to query. +/// * `config`: The configuration file that contains the contract addresses and other parameters. +/// +/// Returns: +/// +/// The rewards for the staking contract. +#[cfg(not(test))] +fn get_rewards( + querier: QuerierWrapper, + contract_addr: &Addr, + config: &Config, +) -> StdResult { + let rewards = query_rewards(querier, contract_addr, config)?; + let item = rewards + .rewards + .iter() + .find(|r| r.token.address == config.token.address); + + if let Some(reward) = item { + Ok(reward.amount.u128()) + } else { + Ok(0) + } +} +#[cfg(test)] +#[allow(dead_code)] +// Allow warn code because mock queries make warnings to show up +fn get_rewards(_: QuerierWrapper, _: &Addr, _: &Config) -> StdResult { + Ok(100000000) +} + +#[cfg(not(test))] +#[allow(dead_code)] +// Allow warn code because mock queries make warnings to show up +fn query_rewards( + querier: QuerierWrapper, + contract_addr: &Addr, + config: &Config, +) -> StdResult { + rewards_query( + contract_addr.to_string(), + config.staking_contract_vk.clone(), + querier, + config.staking.code_hash.to_string(), + config.staking.address.to_string(), + ) +} + +#[cfg(test)] +#[allow(dead_code)] +// Allow warn code because mock queries make warnings to show up +fn query_rewards(_: QuerierWrapper, _: &Addr, _: &Config) -> StdResult { + use crate::staking_interface::RewardToken; + + Ok(Rewards { + rewards: vec![Reward { + token: RewardToken { + address: Addr::unchecked("shade_contract_info_address"), + code_hash: String::from("shade_contract_info_code_hash"), + }, + amount: Uint128::from(100000000_u128), + }], + }) +} + +#[cfg(test)] +#[allow(dead_code)] +// Allow warn code because mock queries make warnings to show up +fn get_staking_contract_config( + _: QuerierWrapper, + _: &Config, +) -> StdResult { + Ok(StakingConfig { + admin_auth: RawContract { + address: String::from("mock_address"), + code_hash: String::from("mock_code_hash"), + }, + query_auth: RawContract { + address: String::from("mock_address"), + code_hash: String::from("mock_code_hash"), + }, + unbond_period: Uint128::from(300_u32), + max_user_pools: Uint128::from(5_u32), + reward_cancel_threshold: Uint128::from(0_u32), + }) +} + +/// It queries the staking contract for its configuration +/// +/// Arguments: +/// +/// * `querier`: The querier object that will be used to query the contract. +/// * `staking_info`: The staking contract information. +/// +/// Returns: +/// +/// StakingConfig +#[cfg(not(test))] +fn get_staking_contract_config( + querier: QuerierWrapper, + staking_info: &Config, +) -> StdResult { + config_query( + querier, + staking_info.staking.code_hash.clone(), + staking_info.staking.address.to_string(), + ) +} + +/// It gets the available and rewards balances, and returns the sum of the two +/// +/// Arguments: +/// +/// * `querier`: The querier object that will be used to query the contract. +/// * `contract_addr`: The address of the contract that you want to query. +/// * `config`: The configuration of the contract. +/// +/// Returns: +/// +/// a tuple of three values: +/// - The first value is the amount of available SHD +/// - The second value is the amount of rewards +/// - The third value is the sum of the first two values +#[cfg(not(test))] +fn get_delegatable( + querier: QuerierWrapper, + contract_addr: &Addr, + config: &Config, +) -> StdResult<(u128, u128, u128)> { + let rewards = get_rewards(querier, contract_addr, config)?; + + let available = get_available_shd(querier, contract_addr, config)?; + Ok((available, rewards, rewards + available)) +} + +#[cfg(test)] +fn get_delegatable( + _: QuerierWrapper, + _: &Addr, + _: &Config, +) -> StdResult<(u128, u128, u128)> { + Ok((100000000, 50000000, 100000000 + 50000000)) +} + +#[cfg(test)] +fn get_super_admin(_: &QuerierWrapper, _: &Config) -> StdResult { + Ok(Addr::unchecked("super_admin")) +} +/// It queries the `admin` contract for the `super_admin` address +/// +/// Arguments: +/// +/// * `querier`: The querier object that will be used to query the blockchain. +/// * `config`: The configuration of the current contract. +/// +/// Returns: +/// +/// The address of the super admin. +#[cfg(not(test))] +fn get_super_admin(querier: &QuerierWrapper, config: &Config) -> StdResult { + let response: StdResult = + AdminQueryMsg::GetConfig {}.query(querier, &config.admin); + + match response { + Ok(resp) => Ok(resp.super_admin), + Err(err) => Err(err), + } +} + +/// It takes an amount and a fee config, and returns the fee and the remainder +/// +/// Arguments: +/// +/// * `amount`: The amount of tokens to be transferred +/// * `fee_config`: The fee configuration for the transaction. +/// +/// Returns: +/// +/// A tuple of two Uint128 values. +pub fn get_fee(amount: Uint128, fee_config: &Fee) -> StdResult<(Uint128, Uint128)> { + // first unwrap is ok because multiplying a u128 by a u32 can not overflow a u256 + // second unwrap is ok because we know we aren't dividing by zero + let _fee = Uint256::from(amount) + .checked_mul(Uint256::from(fee_config.rate)) + .unwrap() + .checked_div(Uint256::from(10_u32.pow(fee_config.decimal_places as u32))) + .unwrap(); + let fee = Uint128::try_from(_fee)?; + let remainder = amount.saturating_sub(fee); + Ok((fee, remainder)) +} +/// It queries the token contract for the token info, and +/// if the total supply is not public, it returns an error +/// +/// Arguments: +/// +/// * `querier`: The querier object that will be used to query the blockchain. +/// * `block_size`: The number of blocks to look back for the token's price. +/// * `callback_code_hash`: The code hash of the contract that will be called when the derivative token +/// is redeemed. +/// * `contract_addr`: The address of the contract that holds the token. +/// +/// Returns: +/// +/// A TokenInfo struct +#[cfg(not(test))] +fn get_token_info( + querier: QuerierWrapper, + block_size: usize, + callback_code_hash: String, + contract_addr: String, + check_public_supply: bool, +) -> StdResult { + let token_info = token_info_query(querier, block_size, callback_code_hash, contract_addr)?; + if check_public_supply && token_info.total_supply.is_none() { + return Err(StdError::generic_err( + "Token supply must be public on derivative token", + )); + } + + Ok(token_info) +} + +#[cfg(test)] +fn get_token_info( + _querier: QuerierWrapper, + _block_size: usize, + _callback_code_hash: String, + _contract_addr: String, + _check_public_supply: bool, +) -> StdResult { + Ok(TokenInfo { + name: String::from("STKD-SHD"), + symbol: String::from("STKDSHD"), + decimals: 6, + total_supply: Some(Uint128::from(2000_u128)), + }) +} +/// It checks if the user is an admin, and if so, it returns `Ok(())` +/// +/// Arguments: +/// +/// * `querier`: The querier object that can be used to query the state of the blockchain. +/// * `permission`: The permission you want to check for. +/// * `user`: The user to check if they are an admin. +/// * `admin_auth`: The contract that holds the admin permissions. +/// +/// Returns: +/// +/// A StdResult<()> +#[cfg(not(test))] +fn check_if_admin( + querier: &QuerierWrapper, + permission: AdminPermissions, + user: String, + admin_auth: &Contract, +) -> StdResult<()> { + validate_admin(querier, permission, user, admin_auth) +} + +#[cfg(test)] +fn check_if_admin( + _: &QuerierWrapper, + _: AdminPermissions, + user: String, + _: &Contract, +) -> StdResult<()> { + if user != String::from("admin") { + return Err(StdError::generic_err( + "This is an admin command. Admin commands can only be run from admin address", + )); + } + + Ok(()) +} + +/// It takes an amount of SHD to stake, a boolean indicating whether or not to compound the stake, and a +/// configuration object, and returns a CosmosMsg object that can be used to send a transaction to the +/// staking contract +/// +/// Arguments: +/// +/// * `amount`: The amount of SHD to stake +/// * `compound`: Whether to compound the interest or not. +/// * `config`: The configuration file that contains the staking contract address, token address, token +/// code hash, and entropy. +/// +/// Returns: +/// +/// A CosmosMsg +fn generate_stake_msg( + amount: Uint128, + compound: Option, + config: &Config, +) -> StdResult { + let memo = + Some(to_binary(&format!("Staking {} SHD into staking contract", amount))?.to_base64()); + let msg = Some(to_binary(&Action::Stake { compound })?); + send_msg( + config.staking.address.to_string(), + amount, + msg, + memo, + config.token.entropy.clone(), + RESPONSE_BLOCK_SIZE, + config.token.code_hash.clone(), + config.token.address.to_string(), + ) +} + +/// It checks if the sender is an admin, and if so, it sets the contract status to the value passed in +/// +/// Arguments: +/// +/// * `deps`: DepsMut - This is the set of dependencies that the contract needs to run. +/// * `info`: MessageInfo - contains the sender, sent_funds, and sent_funds_count +/// * `status_level`: ContractStatusLevel +/// +/// Returns: +/// +/// The response is being returned. +fn set_contract_status( + deps: DepsMut, + info: MessageInfo, + status_level: ContractStatusLevel, + priority: ContractStatusLevel, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + check_status(deps.storage, priority)?; + check_if_admin( + &deps.querier, + AdminPermissions::DerivativeAdmin, + info.sender.to_string(), + &config.admin, + )?; + + CONTRACT_STATUS.save(deps.storage, &status_level)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::SetContractStatus { + status: Success, + })?), + ) +} + +// Copied from secret-toolkit-viewing-key-0.7.0 +pub fn new_viewing_key( + sender: &Addr, + env: &Env, + seed: &[u8], + entropy: &[u8], +) -> (String, [u8; 32]) { + pub const VIEWING_KEY_PREFIX: &str = "api_key_"; + // 16 here represents the lengths in bytes of the block height and time. + let entropy_len = 16 + sender.to_string().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(sender.as_bytes()); + rng_entropy.extend_from_slice(entropy); + + let mut rng = ContractPrng::new(seed, &rng_entropy); + + let rand_slice = rng.rand_bytes(); + + let key = sha_256(&rand_slice); + + let viewing_key = VIEWING_KEY_PREFIX.to_string() + &base64::encode(key); + (viewing_key, rand_slice) +} + +pub fn sync_rewarded_tokens( + env: &Env, + deps: DepsMut, + info: MessageInfo, + rewarded_tokens: &Vec, + config: &Config, +) -> StdResult> { + let mut messages = vec![]; + let mut no_registered_tokens: Vec = vec![]; + + for r in rewarded_tokens.into_iter() { + let is_registered = ContractsVksStore::may_load(deps.storage, &r.token.address); + + if is_registered.is_none() { + // This contract isn't registered. + no_registered_tokens.push(r.token.address.clone()); + // Generated a vk for this token and store it + let (new_vk, _) = new_viewing_key( + &info.sender, + &env, + &config.prng_seed, + config.staking.entropy.clone().unwrap_or_default().as_ref(), + ); + + let token: Token = Token { + address: r.token.address.clone(), + code_hash: r.token.code_hash.clone(), + viewing_key: new_vk.clone(), + }; + ContractsVksStore::save(deps.storage, &r.token.address, &token)?; + + messages.push(set_viewing_key_msg( + new_vk, + None, + RESPONSE_BLOCK_SIZE, + r.token.code_hash.clone(), + r.token.address.to_string(), + )?) + } + } + + if no_registered_tokens.len() > 0 { + let registered_tokens = REWARDED_TOKENS_LIST + .may_load(deps.storage)? + .unwrap_or_default(); + let new_rewarded_tokens = [registered_tokens, no_registered_tokens].concat(); + + REWARDED_TOKENS_LIST.save(deps.storage, &new_rewarded_tokens)?; + } + + Ok(messages) +} + +/// If the contract admin has disabled the contract, then this function will return an error +/// +/// Arguments: +/// +/// * `storage`: The storage object that is passed to the contract. +/// * `priority`: The priority of the action being performed. +/// +/// Returns: +/// +/// Ok is messages is allowed. +fn check_status(storage: &dyn Storage, priority: ContractStatusLevel) -> StdResult<()> { + let contract_status = CONTRACT_STATUS.load(storage)?; + + if status_level_to_u8(priority) < status_level_to_u8(contract_status) { + return Err(StdError::generic_err( + "The contract admin has temporarily disabled this action", + )); + } + Ok(()) +} +/************ QUERIES ************/ + +fn permit_queries(deps: Deps, env: &Env, permit: QueryPermit) -> Result { + // Validate permit content + let config = CONFIG.load(deps.storage)?; + let (addr, query) = validate_permit::(&deps.querier, &config, permit)?; + + // Permit validated! We can now execute the query. + match query { + QueryWithPermit::Unbondings {} => query_unbondings(&deps, addr), + QueryWithPermit::Holdings {} => query_holdings(&deps, env, addr), + #[allow(unreachable_patterns)] + _ => Err(StdError::generic_err("Invalid query message")), + } +} + +pub fn validate_permit( + querier: &QuerierWrapper, + config: &Config, + permit: QueryPermit, +) -> StdResult<(Addr, T)> { + let authenticator = Contract { + address: config.query_auth.address.clone(), + code_hash: config.query_auth.code_hash.clone(), + }; + let response = authenticate_permit::(permit, querier, authenticator)?; + + if response.revoked { + return Err(StdError::generic_err("Permit was revoked")); + } + + Ok((response.sender, response.data)) +} +#[cfg(not(test))] +pub fn validate_viewing_key( + querier: &QuerierWrapper, + config: &Config, + address: Addr, + key: String, +) -> StdResult<()> { + let authenticator = Contract { + address: config.query_auth.address.clone(), + code_hash: config.query_auth.code_hash.clone(), + }; + let is_valid = authenticate_vk(address, key, querier, &authenticator)?; + + if !is_valid { + return Err(StdError::generic_err("Invalid viewing key")); + } + + Ok(()) +} +#[cfg(test)] +pub fn validate_viewing_key( + _querier: &QuerierWrapper, + _config: &Config, + _address: Addr, + key: String, +) -> StdResult<()> { + if key != "password".to_string() { + return Err(StdError::generic_err("Invalid viewing key")); + } + + Ok(()) +} + +pub fn viewing_keys_queries(deps: Deps, env: &Env, msg: QueryMsg) -> StdResult { + let (addresses, key) = msg.get_validation_params(deps.api)?; + let config = CONFIG.load(deps.storage)?; + for address in addresses { + validate_viewing_key(&deps.querier, &config, address, key)?; + + return match msg { + QueryMsg::Unbondings { address, .. } => query_unbondings(&deps, address), + QueryMsg::Holdings { address, .. } => query_holdings(&deps, env, address), + _ => Err(StdError::generic_err( + "This query type does not require authentication", + )), + }; + } + + to_binary(&QueryAnswer::ViewingKeyError { + msg: "Wrong viewing key for this address or viewing key not set".to_string(), + }) +} + +/// It loads all the unbonding ids for the given address, then for each unbonding id, it loads the +/// unbonding, and if the unbonding is complete, it adds the amount to the claimable amount, otherwise +/// it adds the amount to the unbonding amount +/// +/// Arguments: +/// +/// * `deps`: &Deps - this is the dependencies object that contains the storage, querier, and logger. +/// * `env`: The environment of the current transaction. +/// * `addr`: The address of the account to query +/// +/// Returns: +/// +/// The holdings of the address. +fn query_holdings(deps: &Deps, env: &Env, addr: Addr) -> StdResult { + let mut derivative_claimable = Uint128::zero(); + let mut derivative_unbonding = Uint128::zero(); + + let time = Uint128::from(env.block.time.seconds()); + + let unbondings_ids = UnbondingIdsStore::load(deps.storage, &addr); + + for id in unbondings_ids.into_iter() { + let opt_unbonding = UnbondingStore::may_load(deps.storage, id); + if let Some(unbonding) = opt_unbonding { + if time >= unbonding.complete { + derivative_claimable += unbonding.amount; + } else { + derivative_unbonding += unbonding.amount; + } + } + } + to_binary(&QueryAnswer::Holdings { + derivative_claimable, + derivative_unbonding, + }) +} + +/// It loads all unbonding ids for a given address, then loads all unbonding structs for those ids, and +/// returns the result +/// +/// Arguments: +/// +/// * `deps`: &Deps - this is the dependencies object that contains the storage, querier, and logger. +/// * `addr`: The address of the user whose unbondings we want to query. +/// +/// Returns: +/// +/// A vector of unbonding structs. +fn query_unbondings(deps: &Deps, addr: Addr) -> StdResult { + let user_unbonds_ids = UnbondingIdsStore::load(deps.storage, &addr); + let mut unbonds: Vec = vec![]; + + for id in user_unbonds_ids.into_iter() { + let opt_unbonding = UnbondingStore::may_load(deps.storage, id); + + if let Some(unbond) = opt_unbonding { + unbonds.push(unbond) + } + } + + to_binary(&QueryAnswer::Unbondings { unbonds }) +} + +/// It loads the contract status from the storage and returns it as a query answer +/// +/// Arguments: +/// +/// * `storage`: &dyn Storage - this is the storage that the contract will use to store and retrieve +/// data. +/// +/// Returns: +/// +/// A QueryAnswer::ContractStatus +fn query_contract_status(storage: &dyn Storage) -> StdResult { + let contract_status = CONTRACT_STATUS.load(storage)?; + + to_binary(&QueryAnswer::ContractStatus { + status: contract_status, + }) +} + +/// It queries the staking contract for the amount of SHD bonded, the amount of SHD available, the +/// amount of SHD in rewards, the total supply of the derivative token, and the price of the derivative +/// token +/// +/// Arguments: +/// +/// * `deps`: &Deps - this is a struct that contains all the dependencies that the contract needs to +/// run. +/// * `env`: The environment of the contract. +/// +/// Returns: +/// +/// The query_staking_info function returns the staking information of the contract. +fn query_staking_info(deps: &Deps, env: &Env) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + let derivative_info = get_token_info( + deps.querier, + RESPONSE_BLOCK_SIZE, + config.derivative.code_hash.clone(), + config.derivative.address.to_string(), + true, + )?; + let bonded = get_staked_shd(deps.querier, &env.contract.address, &config)?; + let rewards = get_rewards(deps.querier, &env.contract.address, &config)?; + let available = get_available_shd(deps.querier, &env.contract.address, &config)?; + + let total_supply = derivative_info.total_supply.unwrap_or(Uint128::zero()); + + let pool = bonded + rewards + available; + let price = if total_supply == Uint128::zero() || pool == 0 { + Uint128::from(10_u128.pow(derivative_info.decimals as u32)) + } else { + // unwrap is ok because multiplying a u128 by 1 mill can not overflow u256 + let number = Uint256::from(pool) + .checked_mul(Uint256::from(10_u128.pow(derivative_info.decimals as u32))) + .unwrap(); + // unwrap is ok because we already checked if the total supply is 0 + Uint128::try_from(number.checked_div(Uint256::from(total_supply)).unwrap())? + }; + + let staking_contract_config = get_staking_contract_config(deps.querier, &config)?; + + to_binary(&QueryAnswer::StakingInfo { + unbonding_time: staking_contract_config.unbond_period, + bonded_shd: Uint128::from(bonded), + available_shd: Uint128::from(available), + rewards: Uint128::from(rewards), + total_derivative_token_supply: total_supply, + price, + }) +} + +/// It loads the fee configuration from the storage, and returns it as a binary +/// +/// Arguments: +/// +/// * `deps`: &Deps +/// +/// Returns: +/// +/// The fee information for staking and unbonding. +fn query_fee_info(deps: &Deps) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + to_binary(&QueryAnswer::FeeInfo { + staking: config.fees.staking, + unbonding: config.fees.unbonding, + collector: config.fees.collector, + }) +} + +#[cfg(test)] +mod tests { + use std::any::Any; + + use cosmwasm_std::testing::*; + use cosmwasm_std::{from_binary, OwnedDeps, QueryResponse}; + use shade_protocol::Contract; + + use crate::msg::{ContractInfo as CustomContractInfo, Fee, FeeInfo}; + + use super::*; + + fn init_helper() -> ( + StdResult, + OwnedDeps, + ) { + let mut deps = mock_dependencies_with_balance(&[]); + let env = mock_env(); + let info = mock_info("instantiator", &[]); + + let init_msg = InstantiateMsg { + prng_seed: Binary::from("lolz fun yay".as_bytes()), + derivative: CustomContractInfo { + address: Addr::unchecked("derivative_snip20_info_address"), + code_hash: String::from("derivative_snip20_info_codehash"), + entropy: Some(String::from("4359o74nd8dnkjerjrh")), + }, + staking: CustomContractInfo { + address: Addr::unchecked("staking_contract_info_address"), + code_hash: String::from("staking_contract_info_code_hash"), + entropy: Some(String::from("4359o74nd8dnkjerjrh")), + }, + query_auth: CustomContractInfo { + address: Addr::unchecked("authentication_contract_info_address"), + code_hash: String::from("authentication_contract_info_code_hash"), + entropy: Some(String::from("ljkdsfgh9548605874easfnd")), + }, + token: CustomContractInfo { + address: Addr::unchecked("shade_contract_info_address"), + code_hash: String::from("shade_contract_info_code_hash"), + entropy: Some(String::from("5sa4d6aweg473g87766h7712")), + }, + admin: Contract { + address: Addr::unchecked("shade_contract_info_address"), + code_hash: String::from("shade_contract_info_code_hash"), + }, + fees: FeeInfo { + staking: Fee { + rate: 5, + decimal_places: 2_u8, + }, + unbonding: Fee { + rate: 5, + decimal_places: 2_u8, + }, + collector: Addr::unchecked("collector_address"), + }, + }; + + (instantiate(deps.as_mut(), env, info, init_msg), deps) + } + fn extract_error_msg(error: StdResult) -> String { + match error { + Ok(response) => { + let bin_err = (&response as &dyn Any) + .downcast_ref::() + .expect("An error was expected, but no error could be extracted"); + match from_binary(bin_err).unwrap() { + QueryAnswer::ViewingKeyError { msg } => msg, + _ => panic!("Unexpected query answer"), + } + } + Err(err) => match err { + StdError::GenericErr { msg, .. } => msg, + _ => panic!("Unexpected result from init"), + }, + } + } + + #[test] + fn test_init_sanity() { + let (init_result, deps) = init_helper(); + let env = mock_env(); + let info = mock_info("instantiator", &[]); + let prnd = Binary::from("lolz fun yay".as_bytes()); + let staking = CustomContractInfo { + address: Addr::unchecked("staking_contract_info_address"), + code_hash: String::from("staking_contract_info_code_hash"), + entropy: Some(String::from("4359o74nd8dnkjerjrh")), + }; + + let authentication_contract = CustomContractInfo { + address: Addr::unchecked("authentication_contract_info_address"), + code_hash: String::from("authentication_contract_info_code_hash"), + entropy: Some(String::from("ljkdsfgh9548605874easfnd")), + }; + let token = CustomContractInfo { + address: Addr::unchecked("shade_contract_info_address"), + code_hash: String::from("shade_contract_info_code_hash"), + entropy: Some(String::from("5sa4d6aweg473g87766h7712")), + }; + let derivative = CustomContractInfo { + address: Addr::unchecked("derivative_snip20_info_address"), + code_hash: String::from("derivative_snip20_info_codehash"), + entropy: Some(String::from("4359o74nd8dnkjerjrh")), + }; + + // Generate viewing key for staking contract + let entropy: String = staking.entropy.clone().unwrap(); + let (staking_contract_vk, new_seed) = + new_viewing_key(&info.sender.clone(), &env, &prnd.0, entropy.as_ref()); + + // Generate viewing key for SHD contract + let entropy: String = token.entropy.clone().unwrap(); + let (token_contract_vk, _new_seed) = + new_viewing_key(&info.sender.clone(), &env, &new_seed, entropy.as_ref()); + + let msgs: Vec = vec![ + // Register receive Derivative contract + register_receive_msg( + env.contract.code_hash.clone(), + derivative.entropy, + RESPONSE_BLOCK_SIZE, + derivative.code_hash, + derivative.address.to_string(), + ) + .unwrap(), + // Register receive SHD contract + register_receive_msg( + env.contract.code_hash, + token.entropy.clone(), + RESPONSE_BLOCK_SIZE, + token.code_hash.clone(), + token.address.to_string(), + ) + .unwrap(), + // Set viewing key for SHD + set_viewing_key_msg( + token_contract_vk, + token.entropy, + RESPONSE_BLOCK_SIZE, + token.code_hash, + token.address.to_string(), + ) + .unwrap(), + // Set viewing key for staking contract + set_viewing_key_msg( + staking_contract_vk, + authentication_contract.entropy, + RESPONSE_BLOCK_SIZE, + authentication_contract.code_hash, + authentication_contract.address.to_string(), + ) + .unwrap(), + ]; + assert_eq!(init_result.unwrap(), Response::default().add_messages(msgs)); + + assert_eq!( + CONTRACT_STATUS.load(&deps.storage).unwrap(), + ContractStatusLevel::NormalRun + ); + } + + #[test] + fn test_panic_messages_when_contract_panicked() { + let (init_result, mut deps) = init_helper(); + let info = mock_info("admin", &[]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::SetContractStatus { + level: ContractStatusLevel::Panicked, + padding: None, + }; + let handle_result = execute(deps.as_mut(), mock_env(), info.clone(), handle_msg); + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::PanicWithdraw {}; + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + } + + #[test] + fn test_handle_set_contract_status() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::SetContractStatus { + level: ContractStatusLevel::StopAll, + padding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let contract_status = CONTRACT_STATUS.load(&deps.storage).unwrap(); + assert!(matches!( + contract_status, + ContractStatusLevel::StopAll { .. } + )); + } + #[test] + fn test_receive_msg_sender_is_not_shd_contract() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked(""), + from: Addr::unchecked(""), + amount: Uint256::from(100000000 as u32), + msg: Some(to_binary(&ReceiverMsg::Stake {}).unwrap()), + }; + let info = mock_info("giannis", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(handle_result.is_err()); + let error = extract_error_msg(handle_result); + + assert_eq!(error, "Sender is not SHD contract"); + } + + #[test] + fn test_receive_stake_msg_successfully() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked(""), + from: Addr::unchecked(""), + amount: Uint256::from(100000000 as u32), + msg: Some(to_binary(&ReceiverMsg::Stake {}).unwrap()), + }; + let info = mock_info("shade_contract_info_address", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + } + + #[test] + fn test_receive_unbond_msg_sender_is_not_derivative_contract() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked(""), + from: Addr::unchecked(""), + amount: Uint256::from(100000000 as u32), + msg: Some(to_binary(&ReceiverMsg::Unbond {}).unwrap()), + }; + let info = mock_info("giannis", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(handle_result.is_err()); + let error = extract_error_msg(handle_result); + + assert_eq!(error, "Sender is not derivative (SNIP20) contract"); + } + + #[test] + fn test_receive_unbond_msg_successfully() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked(""), + from: Addr::unchecked(""), + amount: Uint256::from(100000000 as u32), + msg: Some(to_binary(&ReceiverMsg::Unbond {}).unwrap()), + }; + let info = mock_info("derivative_snip20_info_address", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + } + + #[test] + fn test_receive_transfer_staked_msg_successfully() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked(""), + from: Addr::unchecked(""), + amount: Uint256::from(100000000 as u32), + msg: Some(to_binary(&ReceiverMsg::TransferStaked { receiver: None }).unwrap()), + }; + let info = mock_info("derivative_snip20_info_address", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + } + + #[test] + fn test_receive_transfer_staked_msg_sender_is_not_derivative_contract() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked(""), + from: Addr::unchecked(""), + amount: Uint256::from(100000000 as u32), + msg: Some(to_binary(&ReceiverMsg::TransferStaked { receiver: None }).unwrap()), + }; + let info = mock_info("giannis", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(handle_result.is_err()); + let error = extract_error_msg(handle_result); + + assert_eq!(error, "Sender is not derivative (SNIP20) contract"); + } + #[test] + fn test_unbonding_query_not_unbonds() { + let (init_result, deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + // Query unbondings + let query_msg = QueryMsg::Unbondings { + address: Addr::unchecked("david"), + viewing_key: String::from("password"), + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let unbonds = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::Unbondings { unbonds } => unbonds, + other => panic!("Unexpected: {:?}", other), + }; + + assert_eq!(unbonds, vec![]); + } + + #[test] + fn test_holdings_query_not_funds() { + let (init_result, deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + // Query unbondings + let query_msg = QueryMsg::Holdings { + address: Addr::unchecked("david"), + viewing_key: String::from("password"), + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let (derivative_claimable, derivative_unbonding) = + match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::Holdings { + derivative_claimable, + derivative_unbonding, + } => (derivative_claimable, derivative_unbonding), + other => panic!("Unexpected: {:?}", other), + }; + + assert_eq!(derivative_claimable, Uint128::zero()); + assert_eq!(derivative_unbonding, Uint128::zero()); + } + + #[test] + fn test_sanity_unbonding_processing_storage() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + let env = mock_env(); + // Unbond from bob account + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked(""), + from: Addr::unchecked("bob"), + amount: Uint256::from(100000000 as u32), + msg: Some(to_binary(&ReceiverMsg::Unbond {}).unwrap()), + }; + let info = mock_info("derivative_snip20_info_address", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let unbonding_processing = PENDING_UNBONDING.load(&deps.storage).unwrap(); + + assert_eq!( + unbonding_processing, + InProcessUnbonding { + id: Uint128::zero(), + owner: Addr::unchecked("bob"), + amount: Uint128::from(21375000000000_u128), + complete: Uint128::from(env.block.time.seconds()) + .checked_add(Uint128::from(300_u32)) + .unwrap(), + } + ) + } + + #[test] + fn test_update_fees_should_fail_no_admin_sender() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::UpdateFees { + staking: None, + unbonding: None, + collector: None, + }; + let info = mock_info("not_admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(handle_result.is_err()); + let error = extract_error_msg(handle_result); + + assert_eq!( + error, + "This is an admin command. Admin commands can only be run from admin address" + ); + } + + #[test] + fn test_update_fees_successfully_sender_is_admin_no_new_config_provided() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + let config_before_tx = CONFIG.load(&deps.storage).unwrap(); + let handle_msg = ExecuteMsg::UpdateFees { + staking: None, + unbonding: None, + collector: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + let config_after_tx = CONFIG.load(&deps.storage).unwrap(); + + assert_eq!(config_before_tx.fees, config_after_tx.fees); + } + + #[test] + fn test_update_fees_successfully_sender_is_admin() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + let config_before_tx = CONFIG.load(&deps.storage).unwrap(); + let handle_msg = ExecuteMsg::UpdateFees { + staking: Some(Fee { + rate: 5_u32, + decimal_places: 2_u8, + }), + collector: Some(Addr::unchecked("new_collector")), + unbonding: None, + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + let config_after_tx = CONFIG.load(&deps.storage).unwrap(); + + assert_ne!(config_before_tx.fees, config_after_tx.fees); + + let answer: ExecuteAnswer = from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); + let fee_info_returned = match answer { + ExecuteAnswer::UpdateFees { fee, status: _ } => fee, + _ => panic!("NOPE"), + }; + let fees = CONFIG.load(&deps.storage).unwrap().fees; + + assert_eq!(fee_info_returned, fees) + } + + #[test] + fn test_staking_returned_tokens() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::Receive { + sender: Addr::unchecked(""), + from: Addr::unchecked("bob"), + amount: Uint256::from(300000000 as u32), + msg: Some(to_binary(&ReceiverMsg::Stake {}).unwrap()), + }; + let info = mock_info("shade_contract_info_address", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + let expected_tokens_return = Uint128::from(2850_u128); + let (_, tokens_returned) = match from_binary(&handle_result.unwrap().data.unwrap()).unwrap() + { + ExecuteAnswer::Stake { + shd_staked, + tokens_returned, + } => (shd_staked, tokens_returned), + other => panic!("Unexpected: {:?}", other), + }; + assert_eq!(tokens_returned, expected_tokens_return) + } + + #[test] + fn test_staking_info_query() { + let (init_result, deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + let query_msg = QueryMsg::StakingInfo {}; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let ( + unbonding_time, + bonded_shd, + available_shd, + rewards, + total_derivative_token_supply, + price, + ) = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::StakingInfo { + unbonding_time, + bonded_shd, + available_shd, + rewards, + total_derivative_token_supply, + price, + } => ( + unbonding_time, + bonded_shd, + available_shd, + rewards, + total_derivative_token_supply, + price, + ), + other => panic!("Unexpected: {:?}", other), + }; + + assert_eq!(unbonding_time, Uint128::from(300_u32)); + assert_eq!(bonded_shd, Uint128::from(300000000_u128)); + assert_eq!(available_shd, Uint128::from(100000000_u128)); + assert_eq!(rewards, Uint128::from(100000000_u128)); + assert_eq!(total_derivative_token_supply, Uint128::from(2000_u128)); + assert_eq!(price, Uint128::from(250000000000_u128)); + } + + #[test] + fn test_fee_info() { + let (init_result, deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + let query_msg = QueryMsg::FeeInfo {}; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let (staking, unbonding, collector) = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::FeeInfo { + staking, + unbonding, + collector, + } => (staking, unbonding, collector), + other => panic!("Unexpected: {:?}", other), + }; + + assert_eq!( + staking, + Fee { + rate: 5, + decimal_places: 2_u8, + } + ); + assert_eq!( + unbonding, + Fee { + rate: 5, + decimal_places: 2_u8, + } + ); + + assert_eq!(collector, Addr::unchecked("collector_address")); + } + #[test] + fn test_handle_claim_not_mature_unbonds() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::Claim {}; + let info = mock_info("x", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + assert!(handle_result.is_err()); + let error = extract_error_msg(handle_result); + + assert_eq!(error, "No mature unbondings to claim"); + } + + #[test] + fn test_handle_compound_rewards_msg() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::CompoundRewards {}; + let info = mock_info("x", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let config = CONFIG.load(&deps.storage).unwrap(); + + let msgs = vec![ + compound_msg(config.staking.code_hash, config.staking.address.to_string()).unwrap(), + ]; + + assert_eq!( + handle_result.unwrap(), + Response::default().add_messages(msgs) + ); + } + + #[test] + fn test_handle_panic_unbond_not_admin_user() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + let handle_msg = ExecuteMsg::PanicUnbond { + amount: Uint128::from(100000000_u128), + }; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(handle_result.is_err()); + let error = extract_error_msg(handle_result); + + assert_eq!( + error, + "This is an admin command. Admin commands can only be run from admin address" + ); + } + + #[test] + fn test_handle_panic_unbond_msg() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::PanicUnbond { + amount: Uint128::from(100000000_u128), + }; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let config = CONFIG.load(&deps.storage).unwrap(); + + let msg = SubMsg::reply_always( + unbond_msg( + Uint128::from(100000000_u128), + config.staking.code_hash, + config.staking.address.to_string(), + Some(false), + ) + .unwrap(), + PANIC_UNBOND_REPLY_ID, + ); + + assert_eq!( + handle_result.unwrap(), + Response::default().add_submessage(msg) + ); + } + + #[test] + fn test_handle_panic_withdraw_not_admin_user() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + let handle_msg = ExecuteMsg::PanicWithdraw {}; + let info = mock_info("bob", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + + assert!(handle_result.is_err()); + let error = extract_error_msg(handle_result); + + assert_eq!( + error, + "This is an admin command. Admin commands can only be run from admin address" + ); + } + + #[test] + fn test_handle_panic_withdraw_msg() { + let (init_result, mut deps) = init_helper(); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + + let handle_msg = ExecuteMsg::PanicWithdraw {}; + let info = mock_info("admin", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + assert!( + handle_result.is_ok(), + "handle() failed: {}", + handle_result.err().unwrap() + ); + + let config = CONFIG.load(&deps.storage).unwrap(); + let rewards = 100000000_u128; + let balance = 100000000_u128; + let amount = Uint128::from(rewards + balance); + assert_eq!( + handle_result.unwrap(), + Response::default().add_messages(vec![ + claim_rewards_msg( + config.staking.code_hash.clone(), + config.staking.address.to_string(), + ) + .unwrap(), + send_msg( + Addr::unchecked("super_admin").to_string(), + amount, + None, + Some("Panic withdraw {} tokens".to_string()), + config.token.entropy, + RESPONSE_BLOCK_SIZE, + config.token.code_hash, + config.token.address.to_string(), + ) + .unwrap(), + ]) + ); + } +} diff --git a/contracts/snip20_derivative/src/lib.rs b/contracts/snip20_derivative/src/lib.rs new file mode 100644 index 0000000..2d972fe --- /dev/null +++ b/contracts/snip20_derivative/src/lib.rs @@ -0,0 +1,4 @@ +pub mod contract; +pub mod msg; +pub mod staking_interface; +pub mod state; diff --git a/contracts/snip20_derivative/src/msg.rs b/contracts/snip20_derivative/src/msg.rs new file mode 100644 index 0000000..6dc84b0 --- /dev/null +++ b/contracts/snip20_derivative/src/msg.rs @@ -0,0 +1,277 @@ +#![allow(clippy::field_reassign_with_default)] // This is triggered in `#[derive(JsonSchema)]` +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128, Uint256}; +use shade_protocol::{query_auth::QueryPermit, Contract}; + +use crate::staking_interface::Unbonding; + +#[derive(Serialize, Debug, Deserialize, Clone, JsonSchema)] +#[cfg_attr(test, derive(Eq, PartialEq))] +pub struct Config { + pub prng_seed:Binary, + // Staking contract (SHADE-CUSTOM) information + pub staking: ContractInfo, + pub staking_contract_vk: String, + // Staking authentication contract (SHADE-CUSTOM) information + pub query_auth: ContractInfo, + // SHD (SNIP-20) information + pub token: ContractInfo, + pub token_contract_vk: String, + // Derivative SNIP-20 + pub derivative: ContractInfo, + // Fee collector and rate information + pub fees: FeeInfo, + pub contract_address: Addr, + pub admin: Contract, +} + +#[cfg_attr(test, derive(Eq, PartialEq))] +#[derive(Serialize, Deserialize, Clone, JsonSchema, Debug)] +pub struct Fee { + pub rate: u32, + pub decimal_places: u8, +} + +#[cfg_attr(test, derive(Eq, PartialEq))] +#[derive(Serialize, Deserialize, Clone, JsonSchema, Debug)] +pub struct FeeInfo { + pub staking: Fee, + pub unbonding: Fee, + pub collector: Addr, +} + +#[cfg_attr(test, derive(Eq, PartialEq))] +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] +pub struct ContractInfo { + pub address: Addr, + #[serde(default)] + pub code_hash: String, + // Optional entropy use to any transaction required to execute in this contract + pub entropy: Option, +} + +#[derive(Serialize, Deserialize, JsonSchema)] +pub struct InstantiateMsg { + pub prng_seed: Binary, + pub staking: ContractInfo, + pub query_auth: ContractInfo, + pub derivative: ContractInfo, + pub token: ContractInfo, + pub admin: Contract, + pub fees: FeeInfo, +} + +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + Claim {}, + CompoundRewards {}, + UpdateFees { + staking: Option, + unbonding: Option, + collector: Option, + }, + PanicUnbond { + amount: Uint128, + }, + PanicWithdraw {}, + //Receiver interface + Receive { + sender: Addr, + from: Addr, + amount: Uint256, + #[serde(default)] + msg: Option, + }, + SetContractStatus { + level: ContractStatusLevel, + padding: Option, + }, +} + +#[derive(Serialize, Deserialize, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteAnswer { + TransferStaked { + amount_sent: Uint128, + tokens_returned: Uint128, + }, + Claim { + amount_claimed: Uint128, + }, + CompoundRewards { + status: ResponseStatus, + }, + Stake { + /// amount of SHD staked + shd_staked: Uint128, + /// amount of derivative token minted + tokens_returned: Uint128, + }, + /// redeem derivative tokens to unbond SCRT + Unbond { + /// amount of derivative tokens redeemed + tokens_redeemed: Uint128, + /// amount of shd to be unbonded + shd_to_be_received: Uint128, + /// estimated time of maturity + estimated_time_of_maturity: Uint128, + }, + CreateViewingKey { + key: String, + }, + SetViewingKey { + status: ResponseStatus, + }, + ChangeAdmin { + status: ResponseStatus, + }, + SetContractStatus { + status: ResponseStatus, + }, + UpdateFees { + status: ResponseStatus, + fee: FeeInfo, + }, + // Permit + RevokePermit { + status: ResponseStatus, + }, +} + +#[derive(Serialize, Deserialize, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ReceiverMsg { + Stake {}, + Unbond {}, + TransferStaked { receiver: Option }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +#[cfg_attr(test, derive(PartialEq))] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + Holdings { address: Addr, viewing_key: String }, + StakingInfo {}, + FeeInfo {}, + ContractStatus {}, + Unbondings { address: Addr, viewing_key: String }, + WithPermit { permit: QueryPermit }, +} + +impl QueryMsg { + pub fn get_validation_params(&self, api: &dyn Api) -> StdResult<(Vec, String)> { + match self { + Self::Unbondings { + address, + viewing_key, + } => { + let address = api.addr_validate(address.as_str())?; + Ok((vec![address], viewing_key.clone())) + } + Self::Holdings { + address, + viewing_key, + } => { + let address = api.addr_validate(address.as_str())?; + Ok((vec![address], viewing_key.clone())) + } + _ => panic!("This query type does not require authentication"), + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +#[cfg_attr(test, derive(Eq, PartialEq))] +#[serde(rename_all = "snake_case")] +pub enum QueryWithPermit { + Unbondings {}, + Holdings {}, +} + +#[derive(Serialize, Deserialize, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum QueryAnswer { + Holdings { + derivative_claimable: Uint128, + derivative_unbonding: Uint128, + }, + Unbondings { + unbonds: Vec, + }, + StakingInfo { + /// unbonding time + unbonding_time: Uint128, + /// amount of bonded SHD + bonded_shd: Uint128, + /// amount of available SHD not reserved for mature unbondings + available_shd: Uint128, + /// unclaimed staking rewards + rewards: Uint128, + /// total supply of derivative token + total_derivative_token_supply: Uint128, + /// price of derivative token in SHD to 6 decimals + price: Uint128, + }, + FeeInfo { + staking: Fee, + unbonding: Fee, + collector: Addr, + }, + ContractStatus { + status: ContractStatusLevel, + }, + ViewingKeyError { + msg: String, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct InProcessUnbonding { + pub id: Uint128, + pub owner: Addr, + pub amount: Uint128, + pub complete: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct PanicUnbond { + pub id: Uint128, + pub amount: Uint128, + pub complete: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, JsonSchema, Debug)] +#[cfg_attr(test, derive(Eq, PartialEq))] +#[serde(rename_all = "snake_case")] +pub enum ResponseStatus { + Success, + Failure, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ContractStatusLevel { + NormalRun, + Panicked, + StopAll, +} + +pub fn status_level_to_u8(status_level: ContractStatusLevel) -> u8 { + match status_level { + ContractStatusLevel::NormalRun => 0, + ContractStatusLevel::Panicked => 1, + ContractStatusLevel::StopAll => 2, + } +} + +pub fn u8_to_status_level(status_level: u8) -> StdResult { + match status_level { + 0 => Ok(ContractStatusLevel::NormalRun), + 1 => Ok(ContractStatusLevel::Panicked), + 2 => Ok(ContractStatusLevel::StopAll), + _ => Err(StdError::generic_err("Invalid state level")), + } +} diff --git a/contracts/snip20_derivative/src/staking_interface.rs b/contracts/snip20_derivative/src/staking_interface.rs new file mode 100644 index 0000000..3f58878 --- /dev/null +++ b/contracts/snip20_derivative/src/staking_interface.rs @@ -0,0 +1,388 @@ +use crate::{msg::ResponseStatus, state::RESPONSE_BLOCK_SIZE}; +use core::fmt; +use cosmwasm_std::{ + to_binary, Addr, CosmosMsg, CustomQuery, QuerierWrapper, QueryRequest, StdError, StdResult, + Uint128, WasmMsg, WasmQuery, +}; +use schemars::JsonSchema; +use secret_toolkit::utils::space_pad; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +// HANDLES +#[derive(Serialize, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum Action { + // Messages allowed when sending SHD to Staking contract + // Deposit rewards to be distributed + Stake { compound: Option }, +} + +// Staking contract handles +#[derive(Serialize, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum StakingMsg { + // Claims rewards generated + Claim {}, + // Unbond X amount + Unbond { + amount: Uint128, + compound: Option, + }, + // Claims mature unbondings + Withdraw { + ids: Option>, + }, + // Claims available rewards and re-stake them + Compound {}, + TransferStake { + amount: Uint128, + recipient: String, + compound: Option, + }, +} + +impl StakingMsg { + /// Returns a StdResult used to execute a contract function + /// + /// # Arguments + /// + /// * `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, code_hash: String, contract_addr: String) -> StdResult { + let mut msg = to_binary(self)?; + space_pad(&mut msg.0, RESPONSE_BLOCK_SIZE); + let funds = Vec::new(); + let execute = WasmMsg::Execute { + contract_addr, + code_hash, + msg, + funds, + }; + Ok(execute.into()) + } +} + +pub fn transfer_staked_msg( + callback_code_hash: String, + contract_addr: String, + amount: Uint128, + recipient: String, + compound: Option, +) -> StdResult { + StakingMsg::TransferStake { + amount, + recipient, + compound, + } + .to_cosmos_msg(callback_code_hash, contract_addr) +} +/// Returns a StdResult used to execute Claim +/// Claims all rewards generated on the Staking Contract +/// # Arguments +/// * `callback_code_hash` - String holding the code hash of the contract being called +/// * `contract_addr` - address of the contract being called +pub fn claim_rewards_msg( + callback_code_hash: String, + contract_addr: String, +) -> StdResult { + StakingMsg::Claim {}.to_cosmos_msg(callback_code_hash, contract_addr) +} + +/// Returns a StdResult used to execute Unbond +/// Unbonds X amount; This can later be claim with a "withdraw" message +/// # Arguments +/// * `amount` - amount of SHD to unbond +/// * `callback_code_hash` - String holding the code hash of the contract being called +/// * `contract_addr` - address of the contract being called +pub fn unbond_msg( + amount: Uint128, + callback_code_hash: String, + contract_addr: String, + compound: Option, +) -> StdResult { + StakingMsg::Unbond { amount, compound }.to_cosmos_msg(callback_code_hash, contract_addr) +} + +/// Returns a StdResult used to execute Withdraw +/// Claims any mature unbondings +/// # Arguments +/// * `callback_code_hash` - String holding the code hash of the contract being called +/// * `contract_addr` - address of the contract being called +pub fn withdraw_msg( + callback_code_hash: String, + contract_addr: String, + ids: Option>, +) -> StdResult { + StakingMsg::Withdraw { ids }.to_cosmos_msg(callback_code_hash, contract_addr) +} + +/// Returns a StdResult used to execute Compound +/// Claims available rewards and re-stake them +/// # Arguments +/// * `callback_code_hash` - String holding the code hash of the contract being called +/// * `contract_addr` - address of the contract being called +pub fn compound_msg(callback_code_hash: String, contract_addr: String) -> StdResult { + StakingMsg::Compound {}.to_cosmos_msg(callback_code_hash, contract_addr) +} + +// QUERIES + +#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct RawContract { + pub address: String, + pub code_hash: String, +} + +#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct StakingConfig { + pub admin_auth: RawContract, + pub query_auth: RawContract, + pub unbond_period: Uint128, + pub max_user_pools: Uint128, + pub reward_cancel_threshold: Uint128, +} + +#[derive(Serialize, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct ViewingKey { + key: String, + address: String, +} +#[derive(Serialize, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct Auth { + viewing_key: ViewingKey, + // Removed since contract's don't support permits + // to communicate between them + // Permit(QueryPermit), +} + +#[derive(Serialize, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum StakingQuery { + Config {}, + Staked { + auth: Auth, + }, + Rewards { + auth: Auth, + }, + Unbonding { + auth: Auth, + ids: Option>, + }, +} + +impl fmt::Display for StakingQuery { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + StakingQuery::Config {} => write!(f, "Config"), + StakingQuery::Staked { .. } => write!(f, "Staked"), + StakingQuery::Rewards { .. } => write!(f, "Rewards"), + StakingQuery::Unbonding { .. } => write!(f, "Unbonding"), + } + } +} + +impl StakingQuery { + /// Returns a StdResult, where T is the "Response" type that wraps the query answer + /// + /// # Arguments + /// + /// * `querier` - a reference to the Querier dependency of the querying contract + /// * `callback_code_hash` - String holding the code hash of the contract being queried + /// * `contract_addr` - address of the contract being queried + pub fn query( + &self, + querier: QuerierWrapper, + code_hash: String, + contract_addr: String, + ) -> StdResult { + let mut msg = to_binary(self)?; + space_pad(&mut msg.0, RESPONSE_BLOCK_SIZE); + querier + .query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr, + code_hash, + msg, + })) + .map_err(|err| { + StdError::generic_err(format!("Error performing {} query: {}", self, err)) + }) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, PartialEq, Eq)] +pub struct Unbonding { + pub id: Uint128, + pub amount: Uint128, + pub complete: Uint128, +} +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub struct RewardToken { + pub address: Addr, + pub code_hash: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub struct Token { + pub address: Addr, + pub code_hash: String, + pub viewing_key: String +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub struct Reward { + pub token: RewardToken, + pub amount: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum QueryResponse { + Config { config: StakingConfig }, + Staked { amount: Uint128 }, + Rewards { rewards: Vec }, + Unbonding { unbondings: Vec }, + ViewingKeyError { msg: String }, +} +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Staked { + pub amount: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Rewards { + pub rewards: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Unbondings { + pub unbondings: Vec, +} + +/// Returns a StdResult from performing Balance query +/// +/// # Arguments +/// * `address` - the address whose balance should be displayed +/// * `key` - String holding the authentication key needed to view the balance +/// * `querier` - a reference to the Querier dependency of the querying contract +/// * `callback_code_hash` - String holding the code hash of the contract being queried +/// * `contract_addr` - address of the contract being queried +pub fn balance_query( + address: String, + key: String, + querier: QuerierWrapper, + callback_code_hash: String, + contract_addr: String, +) -> StdResult { + let auth = Auth { + viewing_key: ViewingKey { address, key }, + }; + let answer: QueryResponse = + StakingQuery::Staked { auth }.query(querier, callback_code_hash, contract_addr)?; + match answer { + QueryResponse::Staked { amount } => Ok(Staked { amount }), + QueryResponse::ViewingKeyError { .. } => Err(StdError::generic_err("unauthorized")), + _ => Err(StdError::generic_err("Invalid Balance query response")), + } +} + +/// Returns a StdResult from performing rewards query +/// +/// # Arguments +/// * `address` - the address whose balance should be displayed +/// * `key` - String holding the authentication key needed to view the balance +/// * `querier` - a reference to the Querier dependency of the querying contract +/// * `callback_code_hash` - String holding the code hash of the contract being queried +/// * `contract_addr` - address of the contract being queried +pub fn rewards_query( + address: String, + key: String, + querier: QuerierWrapper, + callback_code_hash: String, + contract_addr: String, +) -> StdResult { + let auth = Auth { + viewing_key: ViewingKey { address, key }, + }; + let answer: QueryResponse = + StakingQuery::Rewards { auth }.query(querier, callback_code_hash, contract_addr)?; + match answer { + QueryResponse::Rewards { rewards } => Ok(Rewards { rewards }), + QueryResponse::ViewingKeyError { .. } => Err(StdError::generic_err("unauthorized")), + _ => Err(StdError::generic_err("Invalid Rewards query response")), + } +} + +/// Returns a StdResult from performing rewards query +/// +/// # Arguments +/// * `address` - the address whose balance should be displayed +/// * `key` - String holding the authentication key needed to view the balance +/// * `querier` - a reference to the Querier dependency of the querying contract +/// * `callback_code_hash` - String holding the code hash of the contract being queried +/// * `contract_addr` - address of the contract being queried +pub fn unbondings_query( + address: String, + key: String, + ids: Option>, + querier: QuerierWrapper, + callback_code_hash: String, + contract_addr: String, +) -> StdResult { + let auth = Auth { + viewing_key: ViewingKey { address, key }, + }; + let answer: QueryResponse = + StakingQuery::Unbonding { auth, ids }.query(querier, callback_code_hash, contract_addr)?; + match answer { + QueryResponse::Unbonding { unbondings } => Ok(Unbondings { unbondings }), + QueryResponse::ViewingKeyError { .. } => Err(StdError::generic_err("unauthorized")), + _ => Err(StdError::generic_err("Invalid Unbonding query response")), + } +} + +pub fn config_query( + querier: QuerierWrapper, + callback_code_hash: String, + contract_addr: String, +) -> StdResult { + let answer: QueryResponse = + StakingQuery::Config {}.query(querier, callback_code_hash, contract_addr)?; + match answer { + QueryResponse::Config { config } => Ok(config), + _ => Err(StdError::generic_err("Invalid Rewards query response")), + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub struct Unbond { + pub id: Uint128, + pub status: ResponseStatus, +} +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub struct UnbondResponse { + pub unbond: Unbond, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub struct Withdraw { + pub withdrawn: Uint128, + pub status: ResponseStatus, +} +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub struct WithdrawResponse { + pub withdraw: Withdraw, +} diff --git a/contracts/snip20_derivative/src/state.rs b/contracts/snip20_derivative/src/state.rs new file mode 100644 index 0000000..5df951f --- /dev/null +++ b/contracts/snip20_derivative/src/state.rs @@ -0,0 +1,69 @@ +use cosmwasm_std::{Addr, StdResult, Storage}; +use secret_toolkit::storage::Item; +use secret_toolkit::{serialization::Json, storage::Keymap}; + +use crate::msg::{Config, ContractStatusLevel, InProcessUnbonding, PanicUnbond}; +use crate::staking_interface::{Unbonding, Token}; + +pub const KEY_CONFIG: &[u8] = b"config"; +pub const KEY_STAKING_INFO: &[u8] = b"staking_info"; +pub const KEY_PANIC_UNBONDS: &[u8] = b"panic_unbonds_ids"; +pub const KEY_PENDING_UNBONDING: &[u8] = b"last_unbonding_id"; +pub const KEY_CONTRACT_STATUS: &[u8] = b"contract_status"; +pub const PREFIX_UNBONDINGS_IDS: &[u8] = b"unbondings_ids"; +pub const PREFIX_UNBONDINGS: &[u8] = b"unbondings"; +pub const RESPONSE_BLOCK_SIZE: usize = 256; +pub const UNBOND_REPLY_ID: u64 = 1_u64; +pub const PANIC_WITHDRAW_REPLY_ID: u64 = 2_u64; +pub const PANIC_UNBOND_REPLY_ID: u64 = 3_u64; +// Circle back rewards storage keys +pub const KEY_REWARDED_TOKENS_LIST: &[u8] = b"rewarded_tokens"; +pub const PREFIX_CONTRACTS_VKS: &[u8] = b"contracts_vks"; +pub static REWARDED_TOKENS_LIST: Item> = Item::new(KEY_REWARDED_TOKENS_LIST); +pub static CONTRACTS_VKS: Keymap = Keymap::new(PREFIX_CONTRACTS_VKS); + +pub static CONTRACT_STATUS: Item = Item::new(KEY_CONTRACT_STATUS); +pub static CONFIG: Item = Item::new(KEY_CONFIG); +pub static PANIC_UNBONDS: Item> = Item::new(KEY_PANIC_UNBONDS); +pub static PENDING_UNBONDING: Item = Item::new(KEY_PENDING_UNBONDING); +pub static UNBONDINGS_IDS: Item> = Item::new(PREFIX_UNBONDINGS_IDS); +pub static UNBONDING: Keymap = Keymap::new(PREFIX_UNBONDINGS); + +pub struct ContractsVksStore {} +impl ContractsVksStore { + pub fn may_load(store: &dyn Storage, contract_addr: &Addr) -> Option { + CONTRACTS_VKS.get(store, &contract_addr) + } + + pub fn save(store: &mut dyn Storage, contract_addr: &Addr, token: &Token) -> StdResult<()> { + CONTRACTS_VKS.insert(store, &contract_addr, token) + } +} + +pub struct UnbondingIdsStore {} +impl UnbondingIdsStore { + pub fn load(store: &dyn Storage, account: &Addr) -> Vec { + let unbondings_ids = UNBONDINGS_IDS.add_suffix(account.as_str().as_bytes()); + unbondings_ids.load(store).unwrap_or_default() + } + + pub fn save(store: &mut dyn Storage, account: &Addr, ids: Vec) -> StdResult<()> { + let unbondings_ids = UNBONDINGS_IDS.add_suffix(account.as_str().as_bytes()); + unbondings_ids.save(store, &ids) + } +} + +pub struct UnbondingStore {} +impl UnbondingStore { + pub fn may_load(store: &dyn Storage, id: u128) -> Option { + UNBONDING.get(store, &id) + } + + pub fn save(store: &mut dyn Storage, id: u128, unbond: &Unbonding) -> StdResult<()> { + UNBONDING.insert(store, &id, unbond) + } + + pub fn remove(store: &mut dyn Storage, id: u128) -> StdResult<()> { + UNBONDING.remove(store, &id) + } +} diff --git a/contracts/snip20_derivative/tests/integration.rs b/contracts/snip20_derivative/tests/integration.rs new file mode 100644 index 0000000..35fb6b8 --- /dev/null +++ b/contracts/snip20_derivative/tests/integration.rs @@ -0,0 +1,3 @@ +#[test] +#[ignore] +fn empty_test() {} diff --git a/contracts/snip20_derivative/tests/integration.sh b/contracts/snip20_derivative/tests/integration.sh new file mode 100755 index 0000000..f546133 --- /dev/null +++ b/contracts/snip20_derivative/tests/integration.sh @@ -0,0 +1,1699 @@ +#!/bin/bash + +set -eu +set -o pipefail # If anything in a pipeline fails, the pipe's exit status is a failure +#set -x # Print all commands for debugging + +declare -a KEY=(a b c d) + +declare -A FROM=( + [a]='-y --from a' + [b]='-y --from b' + [c]='-y --from c' + [d]='-y --from d' +) + +# This means we don't need to configure the cli since it uses the preconfigured cli in the docker. +# We define this as a function rather than as an alias because it has more flexible expansion behavior. +# In particular, it's not possible to dynamically expand aliases, but `tx_of` dynamically executes whatever +# we specify in its arguments. +function secretcli() { + docker exec secretdev /usr/bin/secretd "$@" +} + +# Just like `echo`, but prints to stderr +function log() { + echo "$@" >&2 +} + +# suppress all output to stdout for the command described in the arguments +function quiet() { + "$@" >/dev/null +} + +# suppress all output to stdout and stderr for the command described in the arguments +function silent() { + "$@" >/dev/null 2>&1 +} + +# Pad the string in the first argument to 256 bytes, using spaces +function pad_space() { + printf '%-256s' "$1" +} + +function assert_eq() { + set -e + local left="$1" + local right="$2" + local message + + if [[ "$(echo $left | xargs)" != "$(echo $right | xargs)" ]]; then + if [ -z ${3+x} ]; then + local lineno="${BASH_LINENO[0]}" + message="assertion failed on line $lineno - both sides differ. left: ${left@Q}, right: ${right@Q}" + else + message="$3" + fi + log "$message" + return 1 + fi + + return 0 +} + +function assert_ne() { + set -e + local left="$1" + local right="$2" + local message + + if [[ "$left" == "$right" ]]; then + if [ -z ${3+x} ]; then + local lineno="${BASH_LINENO[0]}" + message="assertion failed on line $lineno - both sides are equal. left: ${left@Q}, right: ${right@Q}" + else + message="$3" + fi + + log "$message" + return 1 + fi + + return 0 +} + +# what the fuck is this? +function assert_contains() { + set -e + local str="$1" + local substr="$2" + local message + + if [[ "$str" == "$substr" ]]; then + if [ -z ${3+x} ]; then + local lineno="${BASH_LINENO[0]}" + message="assertion failed on line $lineno - str doesn't contain substr. str: ${str@Q}, substr: ${substr@Q}" + else + message="$3" + fi + log "$message" + return 1 + fi + + return 0 +} + +declare -A ADDRESS=( + [a]="$(secretcli keys show --address a)" + [b]="$(secretcli keys show --address b)" + [c]="$(secretcli keys show --address c)" + [d]="$(secretcli keys show --address d)" +) + +declare -A VK=([a]='' [b]='' [c]='' [d]='') + +# Generate a label for a contract with a given code id +# This just adds "contract_" before the code id. +function label_by_id() { + local id="$1" + echo "contract_$id" +} + +# Keep polling the blockchain until the tx completes. +# The first argument is the tx hash. +# The second argument is a message that will be logged after every failed attempt. +# The tx information will be returned. +function wait_for_tx() { + local tx_hash="$1" + local message="$2" + + local result + + log "waiting on tx: $tx_hash" + # secretcli will only print to stdout when it succeeds + until result="$(secretcli query tx "$tx_hash" 2>/dev/null)"; do + log "$message" + sleep 1 + done + + # log out-of-gas events + if quiet jq -e '.raw_log | startswith("execute contract failed: Out of gas: ") or startswith("out of gas:")' <<<"$result"; then + log "$(jq -r '.raw_log' <<<"$result")" + fi + + echo "$result" +} + +# This is a wrapper around `wait_for_tx` that also decrypts the response, +# and returns a nonzero status code if the tx failed +function wait_for_compute_tx() { + local tx_hash="$1" + local message="$2" + local return_value=0 + local result + local decrypted + + result="$(wait_for_tx "$tx_hash" "$message")" + # log "$result" + if quiet jq -e '.logs == null' <<<"$result"; then + return_value=1 + fi + decrypted="$(secretcli query compute tx "$tx_hash")" || return + log "$decrypted" + echo "$decrypted" + + return "$return_value" +} + +# If the tx failed, return a nonzero status code. +# The decrypted error or message will be echoed +function check_tx() { + local tx_hash="$1" + local result + local return_value=0 + + result="$(secretcli query tx "$tx_hash")" + if quiet jq -e '.logs == null' <<<"$result"; then + return_value=1 + fi + decrypted="$(secretcli query compute tx "$tx_hash")" || return + log "$decrypted" + echo "$decrypted" + + return "$return_value" +} + +# Extract the tx_hash from the output of the command +function tx_of() { + "$@" | jq -r '.txhash' +} + +# Extract the output_data_as_string from the output of the command +function data_of() { + local result="$("$@" | jq -ec '.answers[0].output_data_as_string' | sed 's/\\//g' | sed 's/ //g' | sed 's/^"\(.*\)"$/\1/')" + echo "$result" +} + +function extract_exec_error() { + set -e + local search_pattern + local error_msg + + error_msg="$(jq -r '.output_error' <<<"$1")" + search_pattern=${error_msg#*"$2"} + echo $search_pattern +} + +# Send a compute transaction and return the tx hash. +# All arguments to this function are passed directly to `secretcli tx compute execute`. +function compute_execute() { + tx_of secretcli tx compute execute "$@" +} + +# Send a query to the contract. +# All arguments to this function are passed directly to `secretcli query compute query`. +function compute_query() { + secretcli query compute query "$@" +} + +function upload_code() { + set -e + local directory="$1" + local tx_hash + local code_id + + tx_hash="$(tx_of secretcli tx compute store "code/$directory/contract.wasm.gz" ${FROM[a]} --gas 10000000)" + code_id="$( + wait_for_tx "$tx_hash" 'waiting for contract upload' | + jq -r '.logs[0].events[0].attributes[] | select(.key == "code_id") | .value' + )" + + log "uploaded contract #$code_id" + + echo "$code_id" +} + +function instantiate() { + set -e + local code_id="$1" + local init_msg="$2" + + log 'sending init message:' + log "${init_msg@Q}" + + local tx_hash + tx_hash="$(tx_of secretcli tx compute instantiate "$code_id" "$init_msg" --label "$(label_by_id "$code_id")" ${FROM[a]} --gas 10000000)" + wait_for_tx "$tx_hash" 'waiting for init to complete' +} + +# This function uploads and instantiates a contract, and returns the new contract's address +function create_contract() { + set -e + local dir="$1" + local init_msg="$2" + + local code_id + code_id="$(upload_code "$dir")" + + local init_result + init_result="$(instantiate "$code_id" "$init_msg")" + + if quiet jq -e '.logs == null' <<<"$init_result"; then + log "$(secretcli query compute tx "$(jq -r '.txhash' <<<"$init_result")")" + return 1 + fi + + jq -r '.logs[0].events[0].attributes[] | select(.key == "contract_address") | .value' <<<"$init_result" +} + +function deposit() { + set -e + local contract_addr="$1" + local key="$2" + local amount="$3" + + local deposit_message='{"deposit":{"padding":":::::::::::::::::"}}' + local tx_hash + local deposit_response + tx_hash="$(compute_execute "$contract_addr" "$deposit_message" --amount "${amount}uscrt" ${FROM[$key]} --gas 250000)" + deposit_response="$(wait_for_compute_tx "$tx_hash" "waiting for deposit to \"$key\" to process" | jq -ec '.answers[0].output_data_as_string' | sed 's/\\//g' | sed 's/ //g' | sed 's/^"\(.*\)"$/\1/')" + assert_eq "$deposit_response" "$(pad_space '{"deposit":{"status":"success"}}' | sed 's/ //g')" + log "deposited ${amount}uscrt to \"$key\" successfully" + echo "$tx_hash" +} + +function mint() { + set -e + local contract_addr="$1" + local key="$2" + local recipient="$3" + local amount="$4" + + local mint_message='{"mint":{"recipient":"'"$recipient"'","amount":"'"$amount"'","padding":":::::::::::::::::"}}' + local tx_hash + local deposit_response + tx_hash="$(compute_execute "$contract_addr" "$mint_message" ${FROM[$key]} --gas 251000)" + echo "$tx_hash" + deposit_response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for mint to \"$recipient\" to process")" + assert_eq "$deposit_response" "$(pad_space '{"mint":{"status":"success"}}' | sed 's/ //g')" + log "minted ${amount}uscrt for \"$recipient\" successfully" +} + +function burn() { + set -e + local contract_addr="$1" + local key="$2" + local amount="$3" + + local burn_message='{"burn":{"amount":"'"$amount"'"}}' + local tx_hash + local burn_response + tx_hash="$(compute_execute "$contract_addr" "$burn_message" ${FROM[$key]} --gas 250000)" + echo "$tx_hash" + burn_response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for burn for \"$key\" to process")" + log "$burn_response" + assert_eq "$burn_response" "$(pad_space '{"burn":{"status":"success"}}' | sed 's/ //g')" + log "burned ${amount}uscrt for \"$key\" successfully" +} + +function get_balance() { + set -e + local contract_addr="$1" + local key="$2" + + log "querying balance for \"$key\"" + local balance_query='{"balance":{"address":"'"${ADDRESS[$key]}"'","key":"'"${VK[$key]}"'"}}' + local balance_response + balance_response="$(compute_query "$contract_addr" "$balance_query")" + log "balance response was: $balance_response" + jq -r '.balance.amount' <<<"$balance_response" +} + +# Redeem some SCRT from an account +# As you can see, verifying this is happening correctly requires a lot of code +# so I separated it to its own function, because it's used several times. +function redeem() { + set -e + local contract_addr="$1" + local key="$2" + local amount="$3" + + local redeem_message + local tx_hash + local redeem_tx + local transfer_attributes + local redeem_response + + log "redeeming \"$key\"" + redeem_message='{"redeem":{"amount":"'"$amount"'","denom":"uscrt"}}' + tx_hash="$(compute_execute "$contract_addr" "$redeem_message" ${FROM[$key]} --gas 150000)" + redeem_tx="$(wait_for_tx "$tx_hash" "waiting for redeem from \"$key\" to process")" + transfer_attributes="$(jq -r '.logs[0].events[] | select(.type == "transfer") | .attributes' <<<"$redeem_tx")" + assert_eq "$(jq -r '.[] | select(.key == "recipient") | .value' <<<"$transfer_attributes")" "${ADDRESS[$key]}" + assert_eq "$(jq -r '.[] | select(.key == "amount") | .value' <<<"$transfer_attributes")" "${amount}uscrt" + log "redeem response for \"$key\" returned ${amount}uscrt" + + redeem_response="$(data_of check_tx "$tx_hash")" + assert_eq "$redeem_response" "$(pad_space '{"redeem":{"status":"success"}}' | sed 's/ //g')" + log "redeemed ${amount} from \"$key\" successfully" + echo "$tx_hash" +} + +function get_token_info() { + set -e + local contract_addr="$1" + + local token_info_query='{"token_info":{}}' + compute_query "$contract_addr" "$token_info_query" +} + +function increase_allowance() { + set -e + local contract_addr="$1" + local owner_key="$2" + local spender_key="$3" + local amount="$4" + + local owner_address="${ADDRESS[$owner_key]}" + local spender_address="${ADDRESS[$spender_key]}" + local allowance_message='{"increase_allowance":{"spender":"'"$spender_address"'","amount":"'"$amount"'"}}' + local allowance_response + + tx_hash="$(compute_execute "$contract_addr" "$allowance_message" ${FROM[$owner_key]} --gas 150000)" + allowance_response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for the increase of \"$spender_key\"'s allowance to \"$owner_key\"'s funds to process")" + assert_eq "$(jq -r '.increase_allowance.spender' <<<"$allowance_response")" "$spender_address" + assert_eq "$(jq -r '.increase_allowance.owner' <<<"$allowance_response")" "$owner_address" + jq -r '.increase_allowance.allowance' <<<"$allowance_response" + log "Increased allowance given to \"$spender_key\" from \"$owner_key\" by ${amount}uscrt successfully" +} + +function decrease_allowance() { + set -e + local contract_addr="$1" + local owner_key="$2" + local spender_key="$3" + local amount="$4" + + local owner_address="${ADDRESS[$owner_key]}" + local spender_address="${ADDRESS[$spender_key]}" + local allowance_message='{"decrease_allowance":{"spender":"'"$spender_address"'","amount":"'"$amount"'"}}' + local allowance_response + + tx_hash="$(compute_execute "$contract_addr" "$allowance_message" ${FROM[$owner_key]} --gas 150000)" + allowance_response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for the decrease of \"$spender_key\"'s allowance to \"$owner_key\"'s funds to process")" + assert_eq "$(jq -r '.decrease_allowance.spender' <<<"$allowance_response")" "$spender_address" + assert_eq "$(jq -r '.decrease_allowance.owner' <<<"$allowance_response")" "$owner_address" + jq -r '.decrease_allowance.allowance' <<<"$allowance_response" + log "Decreased allowance given to \"$spender_key\" from \"$owner_key\" by ${amount}uscrt successfully" +} + +function get_allowance() { + set -e + local contract_addr="$1" + local owner_key="$2" + local spender_key="$3" + + log "querying allowance given to \"$spender_key\" by \"$owner_key\"" + local owner_address="${ADDRESS[$owner_key]}" + local spender_address="${ADDRESS[$spender_key]}" + local allowance_query='{"allowance":{"spender":"'"$spender_address"'","owner":"'"$owner_address"'","key":"'"${VK[$owner_key]}"'"}}' + local allowance_response + allowance_response="$(compute_query "$contract_addr" "$allowance_query")" + log "allowance response was: $allowance_response" + assert_eq "$(jq -r '.allowance.spender' <<<"$allowance_response")" "$spender_address" + assert_eq "$(jq -r '.allowance.owner' <<<"$allowance_response")" "$owner_address" + jq -r '.allowance.allowance' <<<"$allowance_response" +} + +# This function is the same as above, but it also checks the expiration +function check_allowance() { + set -e + local contract_addr="$1" + local owner_key="$2" + local spender_key="$3" + local expiration="$4" + + log "querying allowance given to \"$spender_key\" by \"$owner_key\"" + local owner_address="${ADDRESS[$owner_key]}" + local spender_address="${ADDRESS[$spender_key]}" + local allowance_query='{"allowance":{"spender":"'"$spender_address"'","owner":"'"$owner_address"'","key":"'"${VK[$owner_key]}"'"}}' + local allowance_response + allowance_response="$(compute_query "$contract_addr" "$allowance_query")" + log "allowance response was: $allowance_response" + assert_eq "$(jq -r '.allowance.spender' <<<"$allowance_response")" "$spender_address" + assert_eq "$(jq -r '.allowance.owner' <<<"$allowance_response")" "$owner_address" + assert_eq "$(jq -r '.allowance.expiration' <<<"$allowance_response")" "$expiration" + jq -r '.allowance.allowance' <<<"$allowance_response" +} + +function log_test_header() { + log " ########### Starting ${FUNCNAME[1]} ###############################################################################################################################################" +} + +function extract_viewing_key_from_result() { + set -e + local tx_hash="$1" + local key="$2" + local viewing_key + + viewing_key_response="$(wait_for_compute_tx "$tx_hash" "waiting for viewing key for \"$key\" to be created")" + viewing_key="$(jq -ec '.answers[0].output_data_as_string' <<<"$viewing_key_response" | cut -d'\' -f 6 | cut -c2-)" + + log "viewing key for \"$key\" set to ${viewing_key}" + echo "$viewing_key" +} + +function test_viewing_key() { + set -e + local contract_addr="$1" + + log_test_header + + # common variables + local result + local tx_hash + + # query balance. Should fail. + local wrong_key + wrong_key="$(xxd -ps <<<'wrong-key')" + local balance_query + local expected_error='{"viewing_key_error":{"msg":"Wrong viewing key for this address or viewing key not set"}}' + for key in "${KEY[@]}"; do + log "querying balance for \"$key\" with wrong viewing key" + balance_query='{"balance":{"address":"'"${ADDRESS[$key]}"'","key":"'"$wrong_key"'"}}' + result="$(compute_query "$contract_addr" "$balance_query")" + assert_eq "$result" "$expected_error" + done + + # Create viewing keys + local create_viewing_key_message='{"create_viewing_key":{"entropy":"MyPassword123"}}' + local viewing_key_response + for key in "${KEY[@]}"; do + log "creating viewing key for \"$key\"" + tx_hash="$(compute_execute "$contract_addr" "$create_viewing_key_message" ${FROM[$key]} --gas 1400000)" + VK[$key]="$(extract_viewing_key_from_result "$tx_hash" "$key")" + + + if [[ "${VK[$key]}" =~ ^api_key_ ]]; then + log "viewing key \"$key\" seems valid" + else + log 'viewing key is invalid' + return 1 + fi + done + + # Check that all viewing keys are different despite using the same entropy + assert_ne "${VK[a]}" "${VK[b]}" + assert_ne "${VK[b]}" "${VK[c]}" + assert_ne "${VK[c]}" "${VK[d]}" + + # query balance. Should succeed. + local balance_query + for key in "${KEY[@]}"; do + balance_query='{"balance":{"address":"'"${ADDRESS[$key]}"'","key":"'"${VK[$key]}"'"}}' + log "querying balance for \"$key\" with correct viewing key" + result="$(compute_query "$contract_addr" "$balance_query")" + if ! silent jq -e '.balance.amount | tonumber' <<<"$result"; then + log "Balance query returned unexpected response: ${result@Q}" + return 1 + fi + done + + # Change viewing keys + local vk2_a + + log 'creating new viewing key for "a"' + tx_hash="$(compute_execute "$contract_addr" "$create_viewing_key_message" ${FROM[a]} --gas 1400000)" + vk2_a="$(extract_viewing_key_from_result "$tx_hash" "$key")" + assert_ne "${VK[a]}" "$vk2_a" + + # query balance with old keys. Should fail. + log 'querying balance for "a" with old viewing key' + local balance_query_a='{"balance":{"address":"'"${ADDRESS[a]}"'","key":"'"${VK[a]}"'"}}' + result="$(compute_query "$contract_addr" "$balance_query_a")" + assert_eq "$result" "$expected_error" + + # query balance with new keys. Should succeed. + log 'querying balance for "a" with new viewing key' + balance_query_a='{"balance":{"address":"'"${ADDRESS[a]}"'","key":"'"$vk2_a"'"}}' + result="$(compute_query "$contract_addr" "$balance_query_a")" + if ! silent jq -e '.balance.amount | tonumber' <<<"$result"; then + log "Balance query returned unexpected response: ${result@Q}" + return 1 + fi + + # Set the vk for "a" to the original vk + log 'setting the viewing key for "a" back to the first one' + local set_viewing_key_message='{"set_viewing_key":{"key":"'"${VK[a]}"'"}}' + tx_hash="$(compute_execute "$contract_addr" "$set_viewing_key_message" ${FROM[a]} --gas 1400000)" + viewing_key_response="$(wait_for_compute_tx "$tx_hash" "waiting for viewing key for "a" to be set")" + viewing_key_response="$(jq -ec '.answers[0].output_data_as_string' <<<"$viewing_key_response" | cut -d'\' -f 6 | cut -c2-)" + assert_eq "$viewing_key_response" "success" + + # try to use the new key - should fail + log 'querying balance for "a" with new viewing key' + balance_query_a='{"balance":{"address":"'"${ADDRESS[a]}"'","key":"'"$vk2_a"'"}}' + result="$(compute_query "$contract_addr" "$balance_query_a")" + assert_eq "$result" "$expected_error" + + # try to use the old key - should succeed + log 'querying balance for "a" with old viewing key' + balance_query_a='{"balance":{"address":"'"${ADDRESS[a]}"'","key":"'"${VK[a]}"'"}}' + result="$(compute_query "$contract_addr" "$balance_query_a")" + if ! silent jq -e '.balance.amount | tonumber' <<<"$result"; then + log "Balance query returned unexpected response: ${result@Q}" + return 1 + fi +} + +function test_permit() { + set -e + local contract_addr="$1" + + log_test_header + + # common variables + local result + local tx_hash + + # fail due to token not in permit + secretcli keys delete banana -yf || true + secretcli keys add banana + local wrong_contract=$(secretcli keys show -a banana) + + local permit + permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$wrong_contract"'"],"permissions":["balance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit_query + local expected_error="Error: query result: Generic error: Permit doesn't apply to token \"$contract_addr\", allowed tokens: [\"$wrong_contract\"]" + for key in "${KEY[@]}"; do + log "permit querying balance for \"$key\" with wrong permit for that contract" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'") + permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$wrong_contract"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_error" + done + + # fail due to revoked permit + local permit + permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"to_be_revoked","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit_query + local expected_error + for key in "${KEY[@]}"; do + log "permit querying balance for \"$key\" with a revoked permit" + tx_hash="$(compute_execute "$contract_addr" '{"revoke_permit":{"permit_name":"to_be_revoked"}}' ${FROM[$key]} --gas 250000)" + wait_for_compute_tx "$tx_hash" "waiting for revoke_permit from \"$key\" to process" + + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'") + permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"to_be_revoked","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' + expected_error="Error: query result: Generic error: Permit \"to_be_revoked\" was revoked by account \"${ADDRESS[$key]}" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_error" + done + + # fail due to params not matching params that were signed on + local permit + permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit_query + local expected_error + for key in "${KEY[@]}"; do + log "permit querying balance for \"$key\" with params not matching permit" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'") + permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"not_blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' + expected_error="Error: query result: Generic error: Failed to verify signatures for the given permit" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_error" + done + + # fail balance query due to no balance permission + local permit_conf + permit_conf='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit + local permit_query + local expected_error + for key in "${KEY[@]}"; do + log "permit querying balance for \"$key\" without the right permission" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'") + permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}' + expected_error="Error: query result: Generic error: No permission to query balance, got permissions [History]" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_error" + done + + # fail history query due to no history permission + local permit_conf + permit_conf='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit + local permit_query + local expected_error + for key in "${KEY[@]}"; do + log "permit querying history for \"$key\" without the right permission" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'") + + permit_query='{"with_permit":{"query":{"transfer_history":{"page_size":10}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' + expected_error="Error: query result: Generic error: No permission to query history, got permissions [Balance]" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_error" + + permit_query='{"with_permit":{"query":{"transaction_history":{"page_size":10}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' + expected_error="Error: query result: Generic error: No permission to query history, got permissions [Balance]" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_error" + done + + # fail allowance query due to no allowance permission + local permit_conf + permit_conf='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit + local permit_query + local expected_error + for key in "${KEY[@]}"; do + log "permit querying allowance for \"$key\" without the right permission" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'") + permit_query='{"with_permit":{"query":{"allowance":{"owner":"'"${ADDRESS[$key]}"'","spender":"'"${ADDRESS[$key]}"'"}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}' + expected_error="Error: query result: Generic error: No permission to query allowance, got permissions [History]" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_error" + done + + # fail allowance query due to no permit signer not owner or spender + local permit + wrong_permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["allowance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit_query + local expected_error + log "permit querying allowance without signer being the owner or spender" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$wrong_permit"') --from a") + permit_query='{"with_permit":{"query":{"allowance":{"owner":"'"$wrong_contract"'","spender":"'"$wrong_contract"'"}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["allowance"]},"signature":'"$permit"'}}}' + expected_error="Error: query result: Generic error: Cannot query allowance. Requires permit for either owner \"$wrong_contract\" or spender \"$wrong_contract\", got permit for \"${ADDRESS[a]}" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_error" + + # succeed balance query + local permit + local good_permit + good_permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit_query + local expected_output + for key in "${KEY[@]}"; do + log "permit querying balance for \"$key\"" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'") + permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' + expected_output="{\"balance\":{\"amount\":\"0\"}}" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_output" + done + + # succeed history queries + local permit + local good_permit + good_permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit_query + local expected_output + for key in "${KEY[@]}"; do + log "permit querying history for \"$key\"" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'") + + permit_query='{"with_permit":{"query":{"transfer_history":{"page_size":10}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}' + expected_output="{\"transfer_history\":{\"txs\":[],\"total\":0}}" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_output" + + permit_query='{"with_permit":{"query":{"transaction_history":{"page_size":10}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}' + expected_output="{\"transaction_history\":{\"txs\":[],\"total\":0}}" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_output" + done + + # succeed allowance query + local permit + local good_permit + good_permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["allowance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' + local permit_query + local expected_output + for key in "${KEY[@]}"; do + log "permit querying history for \"$key\"" + permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'") + + permit_query='{"with_permit":{"query":{"allowance":{"owner":"'"${ADDRESS[$key]}"'","spender":"'"${ADDRESS[$key]}"'"}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["allowance"]},"signature":'"$permit"'}}}' + expected_output="{\"allowance\":{\"spender\":\"${ADDRESS[$key]}\",\"owner\":\"${ADDRESS[$key]}\",\"allowance\":\"0\",\"expiration\":null}}" + result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" + assert_eq "$result" "$expected_output" + done +} + +function test_deposit() { + set -e + local contract_addr="$1" + + log_test_header + + local tx_hash + + local -A deposits=([a]=1000000 [b]=2000000 [c]=3000000 [d]=4000000) + local tx_hash + local native_tx + local timestamp + local block_height + for key in "${KEY[@]}"; do + tx_hash="$(deposit "$contract_addr" "$key" "${deposits[$key]}")" + native_tx="$(secretcli q tx "$tx_hash")" + + timestamp="$(unix_time_of_tx "$native_tx")" + block_height="$(jq -r '.height' <<<"$native_tx")" + quiet check_latest_tx_history_deposit "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" "${deposits[$key]}" "$timestamp" "$block_height" + done + + # Query the balances of the accounts and make sure they have the right balances. + for key in "${KEY[@]}"; do + assert_eq "$(get_balance "$contract_addr" "$key")" "${deposits[$key]}" + done + + # Try to overdraft + local redeem_message + local overdraft + local redeem_response + for key in "${KEY[@]}"; do + overdraft="$((deposits[$key] + 1))" + redeem_message='{"redeem":{"amount":"'"$overdraft"'","denom":"uscrt"}}' + tx_hash="$(compute_execute "$contract_addr" "$redeem_message" ${FROM[$key]} --gas 150000)" + # Notice the `!` before the command - it is EXPECTED to fail. + ! redeem_response="$(wait_for_compute_tx "$tx_hash" "waiting for overdraft from \"$key\" to process")" + log "trying to overdraft from \"$key\" was rejected" + assert_eq \ + "$(extract_exec_error "$redeem_response" "error: ")" \ + "insufficient funds to redeem: balance=${deposits[$key]}, required=$overdraft" + done + + # Withdraw Everything + local tx_hash + local native_tx + local timestamp + local block_height + for key in "${KEY[@]}"; do + tx_hash="$(redeem "$contract_addr" "$key" "${deposits[$key]}")" + native_tx="$(secretcli q tx "$tx_hash")" + timestamp="$(unix_time_of_tx "$native_tx")" + block_height="$(jq -r '.height' <<<"$native_tx")" + quiet check_latest_tx_history_redeem "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" "${deposits[$key]}" "$timestamp" "$block_height" + done + + # Check the balances again. They should all be empty + for key in "${KEY[@]}"; do + assert_eq "$(get_balance "$contract_addr" "$key")" 0 + done +} + +function unix_time_of_tx() { + set -e + local tx="$1" + + date -d "$(jq -r '.timestamp' <<<"$tx")" '+%s' +} + +function get_transfer_history() { + set -e + local contract_addr="$1" + local account="$2" + local viewing_key="$3" + local page_size="$4" + local page="$5" + + local transfer_history_query + local transfer_history_response + transfer_history_query='{"transfer_history":{"address":"'"$account"'","key":"'"$viewing_key"'","page_size":'"$page_size"',"page":'"$page"'}}' + transfer_history_response="$(compute_query "$contract_addr" "$transfer_history_query")" + log "$transfer_history_response" + # There's no good way of tracking the exact expected value, + # so we just check that the `total` field is a number + quiet jq -e '.transfer_history.total | numbers' <<<"$transfer_history_response" + jq -r '.transfer_history.txs' <<<"$transfer_history_response" +} + +# This function checks that the latest tx history for the account matches +# the expected parameters. +# The id of the tx is printed out. +function check_latest_transfer_history() { + set -e + local contract_addr="$1" + local account="$2" + local viewing_key="$3" + local sender="$4" + local from="$5" + local receiver="$6" + local amount="$7" + local block_time="$8" + local block_height="$9" + + local txs + local tx + + txs="$(get_transfer_history "$contract_addr" "$account" "$viewing_key" 1 0)" + silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response + tx="$(jq -r '.[0]' <<<"$txs")" + assert_eq "$(jq -r '.sender' <<<"$tx")" "$sender" + assert_eq "$(jq -r '.from' <<<"$tx")" "$from" + assert_eq "$(jq -r '.receiver' <<<"$tx")" "$receiver" + assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" + assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' + assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" + assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" + + jq -r '.id' <<<"$tx" +} + +function get_transaction_history() { + set -e + local contract_addr="$1" + local account="$2" + local viewing_key="$3" + local page_size="$4" + local page="$5" + + local transaction_history_query + local transaction_history_response + transaction_history_query='{"transaction_history":{"address":"'"$account"'","key":"'"$viewing_key"'","page_size":'"$page_size"',"page":'"$page"'}}' + transaction_history_response="$(compute_query "$contract_addr" "$transaction_history_query")" + log "$transaction_history_response" + # There's no good way of tracking the exact expected value, + # so we just check that the `total` field is a number + quiet jq -e '.transaction_history.total | numbers' <<<"$transaction_history_response" + jq -r '.transaction_history.txs' <<<"$transaction_history_response" +} + +function check_latest_tx_history_transfer() { + set -e + local contract_addr="$1" + local account="$2" + local viewing_key="$3" + local sender="$4" + local from="$5" + local recipient="$6" + local amount="$7" + local block_time="$8" + local block_height="$9" + + local txs + local tx + + txs="$(get_transaction_history "$contract_addr" "$account" "$viewing_key" 1 0)" + silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response + tx="$(jq -r '.[0]' <<<"$txs")" + assert_eq "$(jq -r '.action.transfer.sender' <<<"$tx")" "$sender" + assert_eq "$(jq -r '.action.transfer.from' <<<"$tx")" "$from" + assert_eq "$(jq -r '.action.transfer.recipient' <<<"$tx")" "$recipient" + assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" + assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' + assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" + assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" + + jq -r '.id' <<<"$tx" +} + +function check_latest_tx_history_mint() { + set -e + local contract_addr="$1" + local account="$2" + local viewing_key="$3" + local minter="$4" + local recipient="$5" + local amount="$6" + local block_time="$7" + local block_height="$8" + + local txs + local tx + + txs="$(get_transaction_history "$contract_addr" "$account" "$viewing_key" 1 0)" + silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response + tx="$(jq -r '.[0]' <<<"$txs")" + assert_eq "$(jq -r '.action.mint.minter' <<<"$tx")" "$minter" + assert_eq "$(jq -r '.action.mint.recipient' <<<"$tx")" "$recipient" + assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" + assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' + assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" + assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" + + jq -r '.id' <<<"$tx" +} + +function check_latest_tx_history_burn() { + set -e + local contract_addr="$1" + local account="$2" + local viewing_key="$3" + local burner="$4" + local owner="$5" + local amount="$6" + local block_time="$7" + local block_height="$8" + + local txs + local tx + + txs="$(get_transaction_history "$contract_addr" "$account" "$viewing_key" 1 0)" + silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response + tx="$(jq -r '.[0]' <<<"$txs")" + assert_eq "$(jq -r '.action.burn.burner' <<<"$tx")" "$burner" + assert_eq "$(jq -r '.action.burn.owner' <<<"$tx")" "$owner" + assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" + assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' + assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" + assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" + + jq -r '.id' <<<"$tx" +} + +function check_latest_tx_history_deposit() { + set -e + local contract_addr="$1" + local account="$2" + local viewing_key="$3" + local amount="$4" + local block_time="$5" + local block_height="$6" + + local txs + local tx + + txs="$(get_transaction_history "$contract_addr" "$account" "$viewing_key" 1 0)" + silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response + tx="$(jq -r '.[0]' <<<"$txs")" + quiet jq -e '.action.deposit | objects' <<<"$tx" + assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" + assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'uscrt' + assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" + assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" + + jq -r '.id' <<<"$tx" +} + +function check_latest_tx_history_redeem() { + set -e + local contract_addr="$1" + local account="$2" + local viewing_key="$3" + local amount="$4" + local block_time="$5" + local block_height="$6" + + local txs + local tx + + txs="$(get_transaction_history "$contract_addr" "$account" "$viewing_key" 1 0)" + silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response + tx="$(jq -r '.[0]' <<<"$txs")" + quiet jq -e '.action.redeem | objects' <<<"$tx" + assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" + assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' + assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" + assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" + + jq -r '.id' <<<"$tx" +} + +function test_transfer() { + set -e + local contract_addr="$1" + + log_test_header + + local tx_hash + + # Check "a" and "b" don't have any funds + assert_eq "$(get_balance "$contract_addr" 'a')" 0 + assert_eq "$(get_balance "$contract_addr" 'b')" 0 + + # Deposit to "a" + quiet deposit "$contract_addr" 'a' 1000000 + + # Try to transfer more than "a" has + log 'transferring funds from "a" to "b", but more than "a" has' + local transfer_message='{"transfer":{"recipient":"'"${ADDRESS[b]}"'","amount":"1000001"}}' + local transfer_response + tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[a]} --gas 150000)" + # Notice the `!` before the command - it is EXPECTED to fail. + ! transfer_response="$(wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "b" to process')" + log "trying to overdraft from \"a\" to transfer to \"b\" was rejected" + assert_eq "$(extract_exec_error "$transfer_response" "error: ")" "insufficient funds: balance=1000000, required=1000001" + + # Check both a and b, that their last transaction is not for 1000001 uscrt + local txs + for key in a b; do + log "querying the transfer history of \"$key\"" + txs="$(get_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" 1 0)" + silent jq -e 'length <= 1' <<<"$txs" # just make sure we're not getting a weird response + if silent jq -e 'length == 1' <<<"$txs"; then + assert_ne "$(jq -r '.[0].coins.amount' <<<"$txs")" 1000001 + fi + done + + # Transfer from "a" to "b" + log 'transferring funds from "a" to "b"' + local transfer_message='{"transfer":{"recipient":"'"${ADDRESS[b]}"'","amount":"400000"}}' + local transfer_response + tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[a]} --gas 200000)" + transfer_response="$(data_of wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "b" to process')" + assert_eq "$transfer_response" "$(pad_space '{"transfer":{"status":"success"}}' | sed 's/ //g')" + + local native_tx + native_tx="$(secretcli q tx "$tx_hash")" + local timestamp + timestamp="$(unix_time_of_tx "$native_tx")" + local block_height + block_height="$(jq -r '.height' <<<"$native_tx")" + + # Check for both "a" and "b" that they recorded the transfer + local -A tx_ids + local tx_id + for key in a b; do + log "querying the transfer history of \"$key\"" + tx_ids[$key]="$( + check_latest_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ + "${ADDRESS[a]}" "${ADDRESS[a]}" "${ADDRESS[b]}" 400000 "$timestamp" "$block_height" + )" + tx_id="$( + check_latest_tx_history_transfer "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ + "${ADDRESS[a]}" "${ADDRESS[a]}" "${ADDRESS[b]}" 400000 "$timestamp" "$block_height" + )" + assert_eq "$tx_id" "${tx_ids[$key]}" + done + + assert_eq "${tx_ids[a]}" "${tx_ids[b]}" + log 'The transfer was recorded correctly in the transaction history' + + # Check that "a" has fewer funds + assert_eq "$(get_balance "$contract_addr" 'a')" 600000 + + # Check that "b" has the funds that "a" deposited + assert_eq "$(get_balance "$contract_addr" 'b')" 400000 + + # Redeem both accounts + redeem "$contract_addr" a 600000 + redeem "$contract_addr" b 400000 + # Send the funds back + quiet secretcli tx bank send b "${ADDRESS[a]}" 400000uscrt -y -b block +} + +RECEIVER_ADDRESS='' + +function create_receiver_contract() { + set -e + local init_msg + + if [[ "$RECEIVER_ADDRESS" != '' ]]; then + log 'Receiver contract already exists' + echo "$RECEIVER_ADDRESS" + return 0 + fi + + init_msg='{"count":0}' + RECEIVER_ADDRESS="$(create_contract 'tests/example-receiver' "$init_msg")" + + log "uploaded receiver contract to $RECEIVER_ADDRESS" + echo "$RECEIVER_ADDRESS" +} + +# This function exists so that we can reset the state as much as possible between different tests +function redeem_receiver() { + set -e + local receiver_addr="$1" + local snip20_addr="$2" + local to_addr="$3" + local amount="$4" + + local tx_hash + local redeem_tx + local transfer_attributes + + log 'fetching snip20 hash' + local snip20_hash + snip20_hash="$(secretcli query compute contract-hash "$snip20_addr")" + + local redeem_message='{"redeem":{"addr":"'"$snip20_addr"'","hash":"'"${snip20_hash:2}"'","to":"'"$to_addr"'","amount":"'"$amount"'","denom":"uscrt"}}' + tx_hash="$(compute_execute "$receiver_addr" "$redeem_message" ${FROM[a]} --gas 300000)" + redeem_tx="$(wait_for_tx "$tx_hash" "waiting for redeem from receiver at \"$receiver_addr\" to process")" + # log "$redeem_tx" + transfer_attributes="$(jq -r '.logs[0].events[] | select(.type == "transfer") | .attributes' <<<"$redeem_tx")" + assert_eq "$(jq -r '.[] | select(.key == "recipient") | .value' <<<"$transfer_attributes")" "$receiver_addr"$'\n'"$to_addr" + assert_eq "$(jq -r '.[] | select(.key == "amount") | .value' <<<"$transfer_attributes")" "${amount}uscrt"$'\n'"${amount}uscrt" + log "redeem response for \"$receiver_addr\" returned ${amount}uscrt" +} + +function register_receiver() { + set -e + local receiver_addr="$1" + local snip20_addr="$2" + + local tx_hash + + log 'fetching snip20 hash' + local snip20_hash + snip20_hash="$(secretcli query compute contract-hash "$snip20_addr")" + + log 'registering with snip20' + local register_message='{"register":{"reg_addr":"'"$snip20_addr"'","reg_hash":"'"${snip20_hash:2}"'"}}' + tx_hash="$(compute_execute "$receiver_addr" "$register_message" ${FROM[a]} --gas 300000)" + + # we throw away the output since we know it's empty + local register_tx + register_tx="$(wait_for_compute_tx "$tx_hash" 'Waiting for receiver registration')" + + assert_eq \ + "$(jq -r '.output_logs[] | select(.type == "wasm") | .attributes[] | select(.key == "register_status ") | .value' <<<"$register_tx")" \ + 'success' + log 'receiver registered successfully' +} + +function test_send() { + set -e + local contract_addr="$1" + local skip_register_receiver="$2" + + log_test_header + + local receiver_addr + receiver_addr="$(create_receiver_contract)" + local receiver_hash + receiver_hash="$(secretcli q compute contract-hash $receiver_addr | sed 's/^0x//')" + + if [ "$skip_register_receiver" != "skip-register" ]; then + register_receiver "$receiver_addr" "$contract_addr" + fi + + local tx_hash + + # Check "a" and "b" don't have any funds + assert_eq "$(get_balance "$contract_addr" 'a')" 0 + assert_eq "$(get_balance "$contract_addr" 'b')" 0 + + # Deposit to "a" + quiet deposit "$contract_addr" 'a' 1000000 + + # Try to send more than "a" has + log 'sending funds from "a" to "b", but more than "a" has' + local send_message + send_message='{"send":{"recipient":"'"${ADDRESS[b]}"'","amount":"1000001"}}' + local send_response + tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[a]} --gas 150000)" + # Notice the `!` before the command - it is EXPECTED to fail. + ! send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to "b" to process')" + log "trying to overdraft from \"a\" to send to \"b\" was rejected" + + assert_eq "$(extract_exec_error "$send_response" "error: ")" "insufficient funds: balance=1000000, required=1000001" + + # Check both a and b, that their last transaction is not for 1000001 uscrt + local txs + for key in a b; do + log "querying the transfer history of \"$key\"" + txs="$(get_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" 1 0)" + silent jq -e 'length <= 1' <<<"$txs" # just make sure we're not getting a weird response + if silent jq -e 'length == 1' <<<"$txs"; then + assert_ne "$(jq -r '.[0].coins.amount' <<<"$txs")" 1000001 + fi + done + + # Query receiver state before Send + local receiver_state + local receiver_state_query='{"get_count":{}}' + receiver_state="$(compute_query "$receiver_addr" "$receiver_state_query")" + local original_count + original_count="$(jq -r '.count' <<<"$receiver_state")" + + # Send from "a" to the receiver with message to the Receiver + log 'sending funds from "a" to the Receiver, with message to the Receiver' + local receiver_msg='{"increment":{}}' + receiver_msg="$(base64 <<<"$receiver_msg")" + + if [ "$skip_register_receiver" = "skip-register" ]; then + send_message='{"send":{"recipient":"'"$receiver_addr"'","recipient_code_hash":"'"$receiver_hash"'","amount":"400000","msg":"'$receiver_msg'"}}' + else + send_message='{"send":{"recipient":"'"$receiver_addr"'","amount":"400000","msg":"'$receiver_msg'"}}' + fi + + local send_response + + tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[a]} --gas 300000)" + send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to the Receiver to process')" + assert_eq \ + "$(jq -r '.output_logs[0].attributes[] | select(.key == "count") | .value' <<<"$send_response")" \ + "$((original_count + 1))" + log 'received send response' + + local native_tx + native_tx="$(secretcli q tx "$tx_hash")" + local timestamp + timestamp="$(unix_time_of_tx "$native_tx")" + local block_height + block_height="$(jq -r '.height' <<<"$native_tx")" + + # Check that the receiver got the message + log 'checking whether state was updated in the receiver' + receiver_state_query='{"get_count":{}}' + receiver_state="$(compute_query "$receiver_addr" "$receiver_state_query")" + local new_count + new_count="$(jq -r '.count' <<<"$receiver_state")" + assert_eq "$((original_count + 1))" "$new_count" + log 'receiver contract received the message' + + # Check that "a" recorded the transfer + log 'querying the transfer history of "a"' + local tx_id1 + local tx_id2 + tx_id1="$(check_latest_transfer_history "$contract_addr" "${ADDRESS[a]}" "${VK[a]}" \ + "${ADDRESS[a]}" "${ADDRESS[a]}" "$receiver_addr" 400000 "$timestamp" "$block_height")" + tx_id2="$(check_latest_tx_history_transfer "$contract_addr" "${ADDRESS[a]}" "${VK[a]}" \ + "${ADDRESS[a]}" "${ADDRESS[a]}" "$receiver_addr" 400000 "$timestamp" "$block_height")" + assert_eq "$tx_id1" "$tx_id2" + + # Check that "a" has fewer funds + assert_eq "$(get_balance "$contract_addr" 'a')" 600000 + + # Test that send callback failure also denies the transfer + log 'sending funds from "a" to the Receiver, with a "Fail" message to the Receiver' + receiver_msg='{"fail":{}}' + receiver_msg="$(base64 <<<"$receiver_msg")" + + if [ "$skip_register_receiver" = "skip-register" ]; then + send_message='{"send":{"recipient":"'"$receiver_addr"'","recipient_code_hash":"'"$receiver_hash"'","amount":"400000","msg":"'$receiver_msg'"}}' + else + send_message='{"send":{"recipient":"'"$receiver_addr"'","amount":"400000","msg":"'$receiver_msg'"}}' + fi + + tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[a]} --gas 300000)" + # Notice the `!` before the command - it is EXPECTED to fail. + ! send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to the Receiver to process')" + assert_eq "$(extract_exec_error "$send_response" "error: ")" 'intentional failure' # This comes from the receiver contract + + # Check that "a" does not have fewer funds + assert_eq "$(get_balance "$contract_addr" 'a')" 600000 # This is the same balance as before + + log 'a failure in the callback caused the transfer to roll back, as expected' + + # redeem both accounts + redeem "$contract_addr" 'a' 600000 + redeem_receiver "$receiver_addr" "$contract_addr" "${ADDRESS[a]}" 400000 +} + +function set_minters() { + # set -e + local minters + + if (( $# == 0 )); then + minters='[]' + else + minters='["' + + for minter in "${@:1:$(($# - 1))}"; do + minters="${minters}${minter}"'","' + done + + minters="${minters}${*: -1}"'"]' + fi + + log "$minters" + + local set_minters_message='{"set_minters":{"minters":'"$minters"'}}' + local tx_hash + local response + tx_hash="$(compute_execute "$contract_addr" "$set_minters_message" ${FROM[a]} --gas 150000)" + response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for minter set to update")" + assert_eq "$response" "$(pad_space '{"set_minters":{"status":"success"}}' | sed 's/ //g')" + log "set the minters to these addresses: $*" +} + +# This test also tests TokenInfo +function test_burn() { + set -e + local contract_addr="$1" + + log_test_header + + local token_info_response + local burn_response + local tx_hash + + set_minters "${ADDRESS[b]}" + + # minting from the wrong account should fail + set +e; tx_hash="$(mint "$contract_addr" 'a' "${ADDRESS[a]}" 1000000)" + local _res="$?"; set -e; + if (( _res != 0 )); then + assert_eq "$(extract_exec_error "$(secretcli query compute tx "$tx_hash")" "error: ")" 'Minting is allowed to minter accounts only' + log 'minting from the wrong account failed as expected' + else + log 'minting was allowed from a non-minter address!' + return 1 + fi + + # Mint to a using b + local native_tx + local timestamp + local block_height + tx_hash="$(mint "$contract_addr" 'b' "${ADDRESS[a]}" 1000000)" + native_tx="$(secretcli q tx "$tx_hash")" + timestamp="$(unix_time_of_tx "$native_tx")" + block_height="$(jq -r '.height' <<<"$native_tx")" + check_latest_tx_history_mint "$contract_addr" "${ADDRESS[a]}" "${VK[a]}" \ + "${ADDRESS[b]}" "${ADDRESS[a]}" 1000000 "$timestamp" "$block_height" + + # Check total supply + token_info_response="$(get_token_info "$contract_addr")" + log 'token info response was' "$token_info_response" + assert_eq "$(jq -r '.token_info.total_supply' <<<"$token_info_response")" 1000000 + + # Try to over-burn + local burn_message='{"burn":{"amount":"10000000"}}' # 110% + local burn_response + tx_hash="$(compute_execute "$contract_addr" "$burn_message" ${FROM[a]} --gas 150000)" + ! burn_response="$(wait_for_compute_tx "$tx_hash" 'waiting for burn for "a" to process')" + assert_eq "$(extract_exec_error "$burn_response" "error: ")" 'insufficient funds to burn: balance=1000000, required=10000000' + + # Check "a" balance - should not have changes + assert_eq "$(get_balance "$contract_addr" 'a')" 1000000 + + # Check total supply + token_info_response="$(get_token_info "$contract_addr")" + log 'token info response was' "$token_info_response" + assert_eq "$(jq -r '.token_info.total_supply' <<<"$token_info_response")" 1000000 + + # Try to burn + quiet burn "$contract_addr" 'a' 100000 # 10% + + # Check "a" balance + assert_eq "$(get_balance "$contract_addr" 'a')" 900000 + + # Check total supply + token_info_response="$(get_token_info "$contract_addr")" + log 'token info response was' "$token_info_response" + assert_eq "$(jq -r '.token_info.total_supply' <<<"$token_info_response")" 900000 + + # Burn the rest of the balance + tx_hash="$(burn "$contract_addr" 'a' 900000)" + log "the tx_hash is $tx_hash" + native_tx="$(secretcli q tx "$tx_hash")" + timestamp="$(unix_time_of_tx "$native_tx")" + block_height="$(jq -r '.height' <<<"$native_tx")" + check_latest_tx_history_burn "$contract_addr" "${ADDRESS[a]}" "${VK[a]}" \ + "${ADDRESS[a]}" "${ADDRESS[a]}" 900000 "$timestamp" "$block_height" + + token_info_response="$(get_token_info "$contract_addr")" + log 'token info response was' "$token_info_response" + assert_eq "$(jq -r '.token_info.total_supply' <<<"$token_info_response")" 0 +} + +function test_transfer_from() { + set -e + local contract_addr="$1" + + log_test_header + + local tx_hash + + # Check "a", "b", and "c" don't have any funds + assert_eq "$(get_balance "$contract_addr" 'a')" 0 + assert_eq "$(get_balance "$contract_addr" 'b')" 0 + assert_eq "$(get_balance "$contract_addr" 'c')" 0 + + # Check that the allowance given to "b" by "a" is zero + assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 0 + + # Deposit to "a" + quiet deposit "$contract_addr" 'a' 1000000 + + # Make "a" give allowance to "b" + assert_eq "$(increase_allowance "$contract_addr" 'a' 'b' 1000000)" 1000000 + assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 1000000 + + # Try to transfer from "a", using "b" more than "a" has allowed + log 'transferring funds from "a" to "c" using "b", but more than "a" has allowed' + local transfer_message='{"transfer_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"${ADDRESS[c]}"'","amount":"1000001"}}' + local transfer_response + tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[b]} --gas 150000)" + # Notice the `!` before the command - it is EXPECTED to fail. + ! transfer_response="$(wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "c" by "b" to process')" + log "trying to overdraft from \"a\" to transfer to \"c\" using \"b\" was rejected" + assert_eq "$(extract_exec_error "$transfer_response" "error: ")" "insufficient allowance: allowance=1000000, required=1000001" + + # Check both "a", "b", and "c", that their last transaction is not for 1000001 uscrt + local txs + for key in a b c; do + log "querying the transfer history of \"$key\"" + txs="$(get_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" 1 0)" + silent jq -e 'length <= 1' <<<"$txs" # just make sure we're not getting a weird response + if silent jq -e 'length == 1' <<<"$txs"; then + assert_ne "$(jq -r '.[0].coins.amount' <<<"$txs")" 1000001 + fi + done + + # Transfer from "a" to "c" using "b" + log 'transferring funds from "a" to "c" using "b"' + local transfer_message='{"transfer_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"${ADDRESS[c]}"'","amount":"400000"}}' + local transfer_response + tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[b]} --gas 250000)" + transfer_response="$(data_of wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "c" by "b" to process')" + assert_eq "$transfer_response" "$(pad_space '{"transfer_from":{"status":"success"}}' | sed 's/ //g')" + + local native_tx + native_tx="$(secretcli q tx "$tx_hash")" + local timestamp + timestamp="$(unix_time_of_tx "$native_tx")" + local block_height + block_height="$(jq -r '.height' <<<"$native_tx")" + + # Check for both "a", "b", and "c" that they recorded the transfer + local -A tx_ids + local tx_id + for key in a b c; do + log "querying the transfer history of \"$key\"" + tx_ids[$key]="$( + check_latest_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ + "${ADDRESS[b]}" "${ADDRESS[a]}" "${ADDRESS[c]}" 400000 "$timestamp" "$block_height" + )" + tx_id="$( + check_latest_tx_history_transfer "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ + "${ADDRESS[b]}" "${ADDRESS[a]}" "${ADDRESS[c]}" 400000 "$timestamp" "$block_height" + )" + assert_eq "$tx_id" "${tx_ids[$key]}" + done + + assert_eq "${tx_ids[a]}" "${tx_ids[b]}" + assert_eq "${tx_ids[b]}" "${tx_ids[c]}" + log 'The transfer was recorded correctly in the transaction history' + + # Check that "a" has fewer funds + assert_eq "$(get_balance "$contract_addr" 'a')" 600000 + + # Check that "b" has the same funds still, but less allowance + assert_eq "$(get_balance "$contract_addr" 'b')" 0 + assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 600000 + + # Check that "c" has the funds that "b" deposited from "a" + assert_eq "$(get_balance "$contract_addr" 'c')" 400000 + + # Redeem both accounts + redeem "$contract_addr" a 600000 + redeem "$contract_addr" c 400000 + # Reset allowance + assert_eq "$(decrease_allowance "$contract_addr" 'a' 'b' 600000)" 0 + assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 0 + # Send the funds back + quiet secretcli tx bank send c "${ADDRESS[a]}" 400000uscrt -y -b block +} + +function test_send_from() { + set -e + local contract_addr="$1" + local skip_register_receiver="$2" + + log_test_header + + local receiver_addr + receiver_addr="$(create_receiver_contract)" + local receiver_hash + receiver_hash="$(secretcli q compute contract-hash $receiver_addr | sed 's/^0x//')" + + if [ "$skip_register_receiver" != "skip-register" ]; then + register_receiver "$receiver_addr" "$contract_addr" + fi + + local tx_hash + + # Check "a" and "b" don't have any funds + assert_eq "$(get_balance "$contract_addr" 'a')" 0 + assert_eq "$(get_balance "$contract_addr" 'b')" 0 + assert_eq "$(get_balance "$contract_addr" 'c')" 0 + + # Check that the allowance given to "b" by "a" is zero + assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 0 + + # Deposit to "a" + quiet deposit "$contract_addr" 'a' 1000000 + + # Make "a" give allowance to "b" + assert_eq "$(increase_allowance "$contract_addr" 'a' 'b' 1000000)" 1000000 + assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 1000000 + + # TTry to send from "a", using "b" more than "a" has allowed + log 'sending funds from "a" to "c" using "b", but more than "a" has allowed' + local send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"${ADDRESS[c]}"'","amount":"1000001"}}' + local send_response + tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[b]} --gas 150000)" + # Notice the `!` before the command - it is EXPECTED to fail. + ! send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to "c" by "b" to process')" + log "trying to overdraft from \"a\" to send to \"c\" using \"b\" was rejected" + assert_eq "$(extract_exec_error "$send_response" "error: ")" "insufficient allowance: allowance=1000000, required=1000001" + + # Check both a and b, that their last transaction is not for 1000001 uscrt + local txs + for key in a b c; do + log "querying the transfer history of \"$key\"" + txs="$(get_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" 1 0)" + silent jq -e 'length <= 1' <<<"$txs" # just make sure we're not getting a weird response + if silent jq -e 'length == 1' <<<"$txs"; then + assert_ne "$(jq -r '.[0].coins.amount' <<<"$txs")" 1000001 + fi + done + + # Query receiver state before Send + local receiver_state + local receiver_state_query='{"get_count":{}}' + receiver_state="$(compute_query "$receiver_addr" "$receiver_state_query")" + local original_count + original_count="$(jq -r '.count' <<<"$receiver_state")" + + # Send from "a", using "b", to the receiver with message to the Receiver + log 'sending funds from "a", using "b", to the Receiver, with message to the Receiver' + local receiver_msg='{"increment":{}}' + receiver_msg="$(base64 <<<"$receiver_msg")" + + local send_message + if [ "$skip_register_receiver" = "skip-register" ]; then + send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"$receiver_addr"'","recipient_code_hash":"'"$receiver_hash"'","amount":"400000","msg":"'$receiver_msg'"}}' + else + send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"$receiver_addr"'","amount":"400000","msg":"'$receiver_msg'"}}' + fi + + local send_response + tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[b]} --gas 302000)" + send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to the Receiver to process')" + assert_eq \ + "$(jq -r '.output_logs[0].attributes[] | select(.key == "count") | .value' <<<"$send_response")" \ + "$((original_count + 1))" + log 'received send response' + + local native_tx + native_tx="$(secretcli q tx "$tx_hash")" + local timestamp + timestamp="$(unix_time_of_tx "$native_tx")" + local block_height + block_height="$(jq -r '.height' <<<"$native_tx")" + + # Check that the receiver got the message + log 'checking whether state was updated in the receiver' + receiver_state_query='{"get_count":{}}' + receiver_state="$(compute_query "$receiver_addr" "$receiver_state_query")" + local new_count + new_count="$(jq -r '.count' <<<"$receiver_state")" + assert_eq "$((original_count + 1))" "$new_count" + log 'receiver contract received the message' + + # Check that "a" recorded the transfer + local -A tx_ids + local tx_id + for key in a b; do + log "querying the transfer history of \"$key\"" + tx_ids[$key]="$( + check_latest_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ + "${ADDRESS[b]}" "${ADDRESS[a]}" "$receiver_addr" 400000 "$timestamp" "$block_height" + )" + tx_id="$( + check_latest_tx_history_transfer "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ + "${ADDRESS[b]}" "${ADDRESS[a]}" "$receiver_addr" 400000 "$timestamp" "$block_height" + )" + assert_eq "$tx_id" "${tx_ids[$key]}" + done + + assert_eq "${tx_ids[a]}" "${tx_ids[b]}" + log 'The transfer was recorded correctly in the transaction history' + + # Check that "a" has fewer funds + assert_eq "$(get_balance "$contract_addr" 'a')" 600000 + + # Check that "b" has the same funds still, but less allowance + assert_eq "$(get_balance "$contract_addr" 'b')" 0 + assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 600000 + + # Test that send callback failure also denies the transfer + log 'sending funds from "a", using "b", to the Receiver, with a "Fail" message to the Receiver' + receiver_msg='{"fail":{}}' + receiver_msg="$(base64 <<<"$receiver_msg")" + + if [ "$skip_register_receiver" = "skip-register" ]; then + send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"$receiver_addr"'","recipient_code_hash":"'"$receiver_hash"'","amount":"400000","msg":"'$receiver_msg'"}}' + else + send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"$receiver_addr"'","amount":"400000","msg":"'$receiver_msg'"}}' + fi + + tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[b]} --gas 300000)" + # Notice the `!` before the command - it is EXPECTED to fail. + ! send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to the Receiver to process')" + assert_eq "$(extract_exec_error "$send_response" "error: ")" 'intentional failure' # This comes from the receiver contract + + # Check that "a" does not have fewer funds + assert_eq "$(get_balance "$contract_addr" 'a')" 600000 # This is the same balance as before + + log 'a failure in the callback caused the transfer to roll back, as expected' + + # redeem both accounts + redeem "$contract_addr" 'a' 600000 + redeem_receiver "$receiver_addr" "$contract_addr" "${ADDRESS[a]}" 400000 + # Reset allowance + assert_eq "$(decrease_allowance "$contract_addr" 'a' 'b' 600000)" 0 + assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 0 +} + +function main() { + log ' <####> Starting integration tests <####>' + log "secretcli version in the docker image is: $(secretcli version)" + + secretcli tx bank send a $(secretcli keys show -a c) 100000000000uscrt -y -b block > /dev/null + secretcli tx bank send a $(secretcli keys show -a d) 100000000000uscrt -y -b block > /dev/null + + local prng_seed + prng_seed="$(base64 <<<'enigma-rocks')" + local init_msg + init_msg='{"name":"secret-secret","admin":"'"${ADDRESS[a]}"'","symbol":"SSCRT","decimals":6,"initial_balances":[],"prng_seed":"'"$prng_seed"'","config":{"public_total_supply":true,"enable_deposit":true,"enable_redeem":true,"enable_mint":true,"enable_burn":true},"supported_denoms":["uscrt"]}' + contract_addr="$(create_contract '.' "$init_msg")" + + # To make testing faster, check the logs and try to reuse the deployed contract and VKs from previous runs. + # Remember to comment out the contract deployment and `test_viewing_key` if you do. +# local contract_addr='secret18vd8fpwxzck93qlwghaj6arh4p7c5n8978vsyg' +# VK[a]='api_key_U6FcuhP2km6UHtYeFSyaZbggcgMJQAiTMlNWV3X4iXQ=' +# VK[b]='api_key_YoQlmqnOkkEoh81XzFkiZ3z7+ZAJh9kyFXvtaMBhiFU=' +# VK[c]='api_key_/cdkitEbzaHZA41OB6cGcz1XGnQk6LYTAfSBWTOU5aQ=' +# VK[d]='api_key_WQYkuGOco/mSHgtKWG0f7b2UcrSG3s1fIqm1X/wIGDo=' + + log "contract address: $contract_addr" + + wait_for_tx "$(tx_of secretcli tx bank send "${ADDRESS[a]}" "${ADDRESS[b]}" 100000000uscrt -y)" "waiting for send to b" + wait_for_tx "$(tx_of secretcli tx bank send "${ADDRESS[a]}" "${ADDRESS[c]}" 100000000uscrt -y)" "waiting for send to c" + wait_for_tx "$(tx_of secretcli tx bank send "${ADDRESS[a]}" "${ADDRESS[d]}" 100000000uscrt -y)" "waiting for send to d" + + # This first test also sets the `VK[*]` global variables that are used in the other tests + test_viewing_key "$contract_addr" + test_permit "$contract_addr" + test_deposit "$contract_addr" + test_transfer "$contract_addr" + test_send "$contract_addr" register + test_send "$contract_addr" skip-register + test_burn "$contract_addr" + test_transfer_from "$contract_addr" + test_send_from "$contract_addr" register + test_send_from "$contract_addr" skip-register + + log 'Tests completed successfully' + + # If everything else worked, return successful status + return 0 +} + +main "$@" diff --git a/contracts/snip20_migration/Cargo.toml b/contracts/snip20_migration/Cargo.toml new file mode 100644 index 0000000..f653566 --- /dev/null +++ b/contracts/snip20_migration/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "snip20_migration" +version = "0.1.0" +authors = ["jackb7"] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ + "snip20", + "snip20_migration", + "admin", +] } +schemars = "0.7" + +[dev-dependencies] +rstest = "0.15" +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["multi-test", "admin"] } +serde_json = { version = "1.0.67" } +shade-multi-test = { version = "0.1.0", path = "../../packages/multi_test", features = [ "snip20", "snip20_migration", "admin" ] } diff --git a/contracts/snip20_migration/Makefile b/contracts/snip20_migration/Makefile new file mode 100644 index 0000000..5f026e8 --- /dev/null +++ b/contracts/snip20_migration/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 + +# 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 \ + ghcr.io/scrtlabs/localsecret:v1.6.0-rc.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 9091:9091 -p 26657:26657 -p 26656:26656 -p 1317:1317 -p 5000:5000 \ + -v $$(pwd):/root/code \ + --name secretdev ghcr.io/scrtlabs/localsecret:v1.6.0-rc.3 + +# 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/snip20_migration/src/contract.rs b/contracts/snip20_migration/src/contract.rs new file mode 100644 index 0000000..f24c3c7 --- /dev/null +++ b/contracts/snip20_migration/src/contract.rs @@ -0,0 +1,175 @@ +use crate::storage::*; +use shade_protocol::{ + admin::helpers::{validate_admin, AdminPermissions}, + c_std::{ + shd_entry_point, + to_binary, + Addr, + Binary, + CosmosMsg, + Deps, + DepsMut, + Env, + MessageInfo, + Response, + StdError, + StdResult, + Uint128, + }, + contract_interfaces::snip20_migration::{ + Config, + ExecuteMsg, + InstantiateMsg, + QueryAnswer, + QueryMsg, + }, + snip20::helpers::{burn_msg, mint_msg, register_receive}, + snip20_migration::{ExecuteAnswer, RegisteredToken}, + utils::generic_response::ResponseStatus, +}; + +#[shd_entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + CONFIG.save(deps.storage, &Config { admin: msg.admin })?; + + match msg.tokens { + Some(tokens) => Ok(Response::default().add_message(register_tokens(deps, env, tokens)?)), + None => Ok(Response::default()), + } +} + +#[shd_entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg { + ExecuteMsg::UpdateConfig { admin, .. } => { + let mut config = CONFIG.load(deps.storage)?; + validate_admin( + &deps.querier, + AdminPermissions::Snip20MigrationAdmin, + info.sender.to_string(), + &config.admin, + )?; + config.admin = admin.into_valid(deps.api)?; + CONFIG.save(deps.storage, &config)?; + Ok( + Response::default().set_data(to_binary(&ExecuteAnswer::SetConfig { + status: ResponseStatus::Success, + config, + })?), + ) + } + + ExecuteMsg::Receive { from, amount, .. } => { + let from_addr = deps.api.addr_validate(&from)?; + try_burn_and_mint(deps, info, from_addr, amount) + } + ExecuteMsg::RegisterMigrationTokens { + burn_token, + mint_token, + burnable, + .. + } => { + let config = CONFIG.load(deps.storage)?; + validate_admin( + &deps.querier, + AdminPermissions::Snip20MigrationAdmin, + info.sender.to_string(), + &config.admin, + )?; + let tokens = RegisteredToken { + burn_token: burn_token.into_valid(deps.api)?, + mint_token: mint_token.into_valid(deps.api)?, + burnable, + }; + Ok(Response::default().add_message(register_tokens(deps, env, tokens)?)) + } + } +} + +#[shd_entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => to_binary(&QueryAnswer::Config { + config: CONFIG.load(deps.storage)?, + }), + QueryMsg::Metrics { token } => to_binary(&QueryAnswer::Metrics { + amount_minted: match AMOUNT_MINTED + .may_load(deps.storage, deps.api.addr_validate(&token)?)? + { + Some(minted_amount) => { + let mut amount_minted = Uint128::zero(); + // Round to nearest 10,000 + if !minted_amount.lt(&Uint128::new(100_000_000_000)) { + let rounded_minted_amount = + minted_amount.checked_div(Uint128::new(100_000_000_000))?; + amount_minted = + rounded_minted_amount.checked_mul(Uint128::new(100_000_000_000))?; + } + Ok(amount_minted) + } + None => Err(StdError::generic_err("token not found")), + }?, + }), + QueryMsg::RegistrationStatus { token } => to_binary(&QueryAnswer::RegistrationStatus { + status: REGISTERD_TOKENS.load(deps.storage, deps.api.addr_validate(&token)?)?, + }), + } +} + +pub fn try_burn_and_mint( + deps: DepsMut, + info: MessageInfo, + from: Addr, + burn_amount: Uint128, +) -> StdResult { + let mut msgs = vec![]; + + let registered_token = REGISTERD_TOKENS.load(deps.storage, info.sender.clone())?; + + match registered_token.burnable { + Some(burnable) => { + if burnable { + msgs.push(burn_msg( + burn_amount.clone(), + None, + None, + ®istered_token.burn_token, + )?); + } + } + None => {} + } + + msgs.push(mint_msg( + from.clone(), + burn_amount, + None, + None, + ®istered_token.mint_token, + )?); + + let metrics = AMOUNT_MINTED.load(deps.storage, registered_token.mint_token.address.clone())?; + AMOUNT_MINTED.save( + deps.storage, + registered_token.mint_token.clone().address, + &(metrics + burn_amount), + )?; + + Ok(Response::default().add_messages(msgs)) +} + +pub fn register_tokens(deps: DepsMut, env: Env, tokens: RegisteredToken) -> StdResult { + REGISTERD_TOKENS.save(deps.storage, tokens.clone().burn_token.address, &tokens)?; + AMOUNT_MINTED.save( + deps.storage, + tokens.clone().mint_token.address, + &Uint128::zero(), + )?; + let msg = register_receive(env.contract.code_hash, None, &tokens.burn_token)?; + StdResult::Ok(msg) +} diff --git a/contracts/snip20_migration/src/lib.rs b/contracts/snip20_migration/src/lib.rs new file mode 100644 index 0000000..85a5c6a --- /dev/null +++ b/contracts/snip20_migration/src/lib.rs @@ -0,0 +1,5 @@ +pub mod contract; +pub mod storage; + +#[cfg(test)] +pub mod test; diff --git a/contracts/snip20_migration/src/storage.rs b/contracts/snip20_migration/src/storage.rs new file mode 100644 index 0000000..5f72d61 --- /dev/null +++ b/contracts/snip20_migration/src/storage.rs @@ -0,0 +1,10 @@ +use shade_protocol::{ + c_std::{Addr, Uint128}, + contract_interfaces::snip20_migration::Config, + secret_storage_plus::{Item, Map}, + snip20_migration::RegisteredToken, +}; + +pub const CONFIG: Item = Item::new("config"); +pub const REGISTERD_TOKENS: Map<'static, Addr, RegisteredToken> = Map::new("registered_tokens"); +pub const AMOUNT_MINTED: Map<'static, Addr, Uint128> = Map::new("amount_minted"); diff --git a/contracts/snip20_migration/src/test.rs b/contracts/snip20_migration/src/test.rs new file mode 100644 index 0000000..535e2ea --- /dev/null +++ b/contracts/snip20_migration/src/test.rs @@ -0,0 +1,271 @@ +use shade_multi_test::multi::{admin::Admin, snip20::Snip20, snip20_migration::Snip20Migration}; +use shade_protocol::{ + admin, + c_std::{to_binary, Addr, StdResult, Uint128}, + contract_interfaces::snip20_migration, + multi_test::App, + snip20::{self, InitialBalance}, + utils::{ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +#[test] +pub fn migration_test() { + let mut chain = App::default(); + + let admin = Addr::unchecked("admin"); + + let admin_auth = admin::InstantiateMsg { + super_admin: Some(admin.clone().to_string()), + } + .test_init( + Admin::default(), + &mut chain, + admin.clone(), + "admin_auth", + &[], + ) + .unwrap(); + + let token0 = snip20::InstantiateMsg { + name: "token0".into(), + admin: Some(admin.clone().into()), + symbol: "TZERO".into(), + decimals: 6, + initial_balances: Some(vec![InitialBalance { + address: admin.clone().into(), + amount: Uint128::new(1_000_000_000_000_000_000_000), + }]), + prng_seed: to_binary("").ok().unwrap(), + query_auth: None, + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: Some(true), + enable_burn: Some(true), + enable_transfer: Some(true), + }), + } + .test_init(Snip20::default(), &mut chain, admin.clone(), "token0", &[]) + .unwrap(); + + snip20::ExecuteMsg::SetViewingKey { + key: "vk".into(), + padding: None, + } + .test_exec(&token0, &mut chain, admin.clone(), &[]) + .unwrap(); + + let token1 = snip20::InstantiateMsg { + name: "token1".into(), + admin: Some(admin.clone().into()), + symbol: "TONE".into(), + decimals: 6, + initial_balances: None, + prng_seed: to_binary("").ok().unwrap(), + query_auth: None, + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(true), + enable_redeem: Some(true), + enable_mint: Some(true), + enable_burn: Some(false), + enable_transfer: Some(true), + }), + } + .test_init(Snip20::default(), &mut chain, admin.clone(), "token1", &[]) + .unwrap(); + + snip20::ExecuteMsg::SetViewingKey { + key: "vk".into(), + padding: None, + } + .test_exec(&token1, &mut chain, admin.clone(), &[]) + .unwrap(); + + let migration_contract = snip20_migration::InstantiateMsg { + admin: admin_auth.clone().into(), + tokens: None, + } + .test_init( + Snip20Migration::default(), + &mut chain, + admin.clone().into(), + "migration", + &[], + ) + .unwrap(); + + match (snip20_migration::QueryMsg::Config {} + .test_query(&migration_contract, &mut chain) + .unwrap()) + { + snip20_migration::QueryAnswer::Config { config } => { + let expected_config = snip20_migration::Config { + admin: admin_auth.clone().into(), + }; + assert!(config == expected_config, "conifg matches expected"); + } + _ => panic!("config not found"), + } + + match (snip20_migration::QueryMsg::Metrics { + token: "lala".into(), + } + .test_query::(&migration_contract, &mut chain)) + { + Ok(some_val) => panic!("token found when not expected to be found"), + _ => assert!(true, "metrics query errored"), + } + + match (snip20_migration::QueryMsg::RegistrationStatus { + token: "lala".into(), + } + .test_query::(&migration_contract, &mut chain)) + { + Ok(some_val) => panic!("token was found when not expected to be found"), + _ => assert!(true, "registration status query error"), + } + + snip20::ExecuteMsg::AddMinters { + minters: vec![migration_contract.address.clone().into()], + padding: None, + } + .test_exec(&token1, &mut chain, admin.clone().into(), &[]) + .unwrap(); + + snip20_migration::ExecuteMsg::RegisterMigrationTokens { + burn_token: token0.clone().into(), + mint_token: token1.clone().into(), + burnable: Some(true), + padding: None, + } + .test_exec(&migration_contract, &mut chain, admin.clone().into(), &[]) + .unwrap(); + + match (snip20_migration::QueryMsg::RegistrationStatus { + token: token0.address.clone().into(), + } + .test_query(&migration_contract, &mut chain) + .unwrap()) + { + snip20_migration::QueryAnswer::RegistrationStatus { status } => { + assert!( + status.burn_token.clone() == token0.clone().into() + && status.mint_token == token1.clone().into(), + "token is registered" + ); + } + _ => panic!("registration status query error"), + } + + match (snip20_migration::QueryMsg::Metrics { + token: token1.address.clone().into(), + } + .test_query(&migration_contract, &mut chain) + .unwrap()) + { + snip20_migration::QueryAnswer::Metrics { amount_minted } => { + assert!(amount_minted == Uint128::zero(), "metrics is zero"); + } + _ => panic!("metrics query error"), + } + + snip20::ExecuteMsg::Send { + recipient: migration_contract.clone().address.into(), + recipient_code_hash: None, + amount: Uint128::new(1000000), + msg: None, + memo: None, + padding: None, + } + .test_exec(&token0, &mut chain, admin.clone().into(), &[]) + .unwrap(); + + match (snip20::QueryMsg::Balance { + address: admin.clone().into(), + key: "vk".into(), + } + .test_query(&token1, &mut chain) + .unwrap()) + { + snip20::QueryAnswer::Balance { amount } => assert!( + amount == Uint128::new(1000000), + "balance is expected amount" + ), + _ => panic!("wrong query answer"), + } + + match (snip20_migration::QueryMsg::Metrics { + token: token1.address.clone().into(), + } + .test_query(&migration_contract, &mut chain) + .unwrap()) + { + snip20_migration::QueryAnswer::Metrics { amount_minted } => { + assert!( + amount_minted == Uint128::zero(), + "metrics equals the minted amount" + ); + } + _ => panic!("wrong type"), + } + + snip20::ExecuteMsg::Send { + recipient: migration_contract.clone().address.into(), + recipient_code_hash: None, + amount: Uint128::new(1_000_000_000_000_000), + msg: None, + memo: None, + padding: None, + } + .test_exec(&token0, &mut chain, admin.clone().into(), &[]) + .unwrap(); + + match (snip20::QueryMsg::Balance { + address: admin.clone().into(), + key: "vk".into(), + } + .test_query(&token1, &mut chain) + .unwrap()) + { + snip20::QueryAnswer::Balance { amount } => { + assert!( + amount == Uint128::new(1_000_000_001_000_000), + "balance is expected amount" + ) + } + _ => panic!("wrong query answer"), + } + + match (snip20_migration::QueryMsg::Metrics { + token: token1.address.clone().into(), + } + .test_query(&migration_contract, &mut chain) + .unwrap()) + { + snip20_migration::QueryAnswer::Metrics { amount_minted } => { + assert!( + amount_minted == Uint128::new(1_000_000_000_000_000), + "metrics equals the minted amount" + ); + } + _ => panic!("wrong type"), + } + + match (snip20::QueryMsg::TokenInfo {} + .test_query(&token0, &mut chain) + .unwrap()) + { + snip20::QueryAnswer::TokenInfo { total_supply, .. } => match total_supply { + Some(total_supply) => { + assert!( + Uint128::new(999998999999999000000) == total_supply, + "total supply not expected value" + ); + } + None => panic!("no total_supply unexpected"), + }, + _ => panic!("wrong type"), + } +} diff --git a/contracts/snip20_migration/tests/integration.rs b/contracts/snip20_migration/tests/integration.rs new file mode 100644 index 0000000..1ebeecf --- /dev/null +++ b/contracts/snip20_migration/tests/integration.rs @@ -0,0 +1,111 @@ +use shade_multi_test::multi::{ + admin::{init_admin_auth, Admin}, + snip20::Snip20, + snip20_migration::Snip20Migration, +}; +use shade_protocol::{ + c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}, + contract_interfaces::{snip20, snip20_migration}, + multi_test::App, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +#[test] +fn test_admin() { + let mut app = App::default(); + + let admin_user = Addr::unchecked("admin"); + let not_admin = Addr::unchecked("not_admin"); + + let admin_contract = init_admin_auth(&mut app, &admin_user); + + let snip20_migration_contract = snip20_migration::InstantiateMsg { + admin: admin_contract.clone().into(), + tokens: None, + } + .test_init( + Snip20Migration::default(), + &mut app, + admin_user.clone(), + "snip20_migration", + &[], + ) + .unwrap(); + + let token0 = snip20::InstantiateMsg { + name: "burn_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "BURN".into(), + decimals: 6, + initial_balances: Some(vec![snip20::InitialBalance { + amount: Uint128::new(100000000000), + address: admin_user.to_string(), + }]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(true), + enable_burn: Some(true), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + let token1 = snip20::InstantiateMsg { + name: "mint_token".into(), + admin: Some(admin_user.to_string().clone()), + symbol: "MINT".into(), + decimals: 6, + initial_balances: Some(vec![snip20::InitialBalance { + amount: Uint128::new(100000000000), + address: admin_user.to_string(), + }]), + query_auth: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: Some(false), + enable_redeem: Some(false), + enable_mint: Some(true), + enable_burn: Some(true), + enable_transfer: Some(true), + }), + } + .test_init( + Snip20::default(), + &mut app, + admin_user.clone(), + "stake_token", + &[], + ) + .unwrap(); + + let msg_resp = snip20_migration::ExecuteMsg::RegisterMigrationTokens { + burn_token: token0.clone().into(), + mint_token: token1.clone().into(), + burnable: None, + padding: None, + } + .test_exec(&snip20_migration_contract, &mut app, admin_user.clone(), &[ + ]) + .unwrap(); + + let msg_resp = snip20_migration::ExecuteMsg::RegisterMigrationTokens { + burn_token: token0.into(), + mint_token: token1.into(), + burnable: None, + padding: None, + } + .test_exec(&snip20_migration_contract, &mut app, not_admin.clone(), &[]) + .unwrap_err(); +} diff --git a/docker_setup b/docker_setup new file mode 100755 index 0000000..91f1f4f --- /dev/null +++ b/docker_setup @@ -0,0 +1,4 @@ +#!/usr/bin/bash +apt update -y +apt install python3-pip -y +pip3 install click diff --git a/launch/Cargo.toml b/launch/Cargo.toml new file mode 100644 index 0000000..a2bd598 --- /dev/null +++ b/launch/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "launch" +version = "0.1.0" +authors = ["Guy Garcia "] +edition = "2021" + +[[bin]] +name = "shade" +path = "src/shade.rs" + +[[bin]] +name = "airdrop" +path = "src/airdrop.rs" + +[features] +default = [] + +[dependencies] +colored = "2.0.0" +shade-protocol = { version = "0.1.0", path = "../packages/shade_protocol", features = [ + "dex", + "airdrop", + "bonds", + "governance-impl", + "mint", + "mint_router", + "oracles", + "scrt_staking", + "snip20_staking", + "treasury", +] } +secretcli = { version = "0.1.0", path = "../packages/secretcli" } +serde = { version = "1.0.103", default-features = false, features = ["derive"] } +serde_json = { version = "1.0.67" } + +rs_merkle = { git = "https://github.com/FloppyDisck/rs-merkle", branch = "node_export" } +query-authentication = { git = "https://github.com/securesecrets/query-authentication", tag = "v1.3.0" } + +[target.'cfg(not(target_arch="wasm32"))'.dependencies] +rand = { version = "0.8.4" } +chrono = "0.4.19" \ No newline at end of file diff --git a/makefile b/makefile new file mode 100755 index 0000000..c6303b0 --- /dev/null +++ b/makefile @@ -0,0 +1,101 @@ +compiled_dir=compiled +checksum_dir=${compiled_dir}/checksum + +build-release=RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown +# build-debug=RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown --features="debug-print" + +# args (no extensions): wasm_name, contract_dir_name +define opt_and_compress = +wasm-opt -Oz ./target/wasm32-unknown-unknown/release/$(2).wasm -o ./$(1).wasm +echo $(md5sum $(1).wasm | cut -f 1 -d " ") >> ${checksum_dir}/$(1).txt +cat ./$(1).wasm | gzip -n -9 > ${compiled_dir}/$(1).wasm.gz +rm ./$(1).wasm +endef + +CONTRACTS = \ + airdrop treasury treasury_manager scrt_staking \ + snip20 query_auth admin \ + mock_sienna_pair mock_adapter \ + mock_stkd_derivative basic_staking snip20_migration stkd_scrt \ + snip20_derivative + +PACKAGES = shade_protocol contract_harness cosmwasm_math_compat + +release: setup + ${build-release} + @$(MAKE) compress_all + +dao: treasury treasury_manager scrt_staking + +compress_all: setup + @$(MAKE) $(addprefix compress-,$(CONTRACTS)) + +compress-snip20_staking: setup + $(call opt_and_compress,snip20_staking,spip_stkd_0) + +compress-%: setup + $(call opt_and_compress,$*,$*) + +$(CONTRACTS): setup + (${build-release} -p $@) + @$(MAKE) compress-$(@) + +$(PACKAGES): + (cd packages/$@; cargo build) + +snip20: setup + (cd contracts/snip20; ${build-release}) + @$(MAKE) $(addprefix compress-,snip20) + +snip20_staking: setup + (cd contracts/snip20_staking; ${build-release}) + @$(MAKE) $(addprefix compress-,snip20_staking) + +test: + @$(MAKE) $(addprefix test-,$(CONTRACTS)) + +test-%: % + (cargo test -p $*) + +dao-cov: + (cargo llvm-cov --html -p treasury -p treasury_manager; xdg-open target/llvm-cov/html/index.html) + +cov: + (cargo llvm-cov --html; xdg-open target/llvm-cov/html/index.html) + +setup: $(compiled_dir) $(checksum_dir) + +$(compiled_dir) $(checksum_dir): + mkdir $@ + +check: + cargo check + +clippy: + cargo clippy + +clean: + find . -name "Cargo.lock" -delete + rm -rf target + rm -r $(compiled_dir) + +format: + cargo fmt + +# Downloads the docker server +server-download: + docker pull securesecrets/sn-testnet:v0.2 + +# Starts the docker server / private testnet +server-start: + docker run -it --rm \ + -p 26657:26657 -p 26656:26656 -p 1337:1337 \ + -v $$(pwd):/root/code --name shade-testnet securesecrets/sn-testnet:v0.2 + +# Connects to the docker server +server-connect: + docker exec -it shade-testnet /bin/bash + +# Runs integration tests +integration-tests: + cargo test -- --nocapture --test-threads=1 diff --git a/packages/contract_derive/Cargo.toml b/packages/contract_derive/Cargo.toml new file mode 100644 index 0000000..bb13329 --- /dev/null +++ b/packages/contract_derive/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "contract-derive" +version = "0.1.0" +authors = [ + "Guy Garcia ", +] +edition = "2021" + +[dependencies] +syn = { version = "1.0", features = ["full"] } + +[lib] +proc-macro = true + +[dev-dependencies] +shade-protocol = { version = "0.1.0", path = "../shade_protocol" } \ No newline at end of file diff --git a/packages/contract_derive/src/lib.rs b/packages/contract_derive/src/lib.rs new file mode 100644 index 0000000..1d1311f --- /dev/null +++ b/packages/contract_derive/src/lib.rs @@ -0,0 +1,86 @@ +#[macro_use] +extern crate syn; + +use proc_macro::TokenStream; +use std::str::FromStr; + +/// NOTE: This is copied from the original cosmwasm package, +/// this just has a minor change that adds support to the Shade Protocol +/// This attribute macro generates the boilerplate required to call into the +/// contract-specific logic from the entry-points to the Wasm module. +/// +/// It should be added to the contract's init, handle, migrate and query implementations +/// like this: +/// ``` +/// # use shade_protocol::c_std::{ +/// # Storage, Api, Querier, DepsMut, Deps, Env, StdError, MessageInfo, +/// # Response, QueryResponse, shd_entry_point +/// # }; +/// # +/// # type InstantiateMsg = (); +/// # type ExecuteMsg = (); +/// # type QueryMsg = (); +/// +/// #[shd_entry_point] +/// pub fn instantiate( +/// deps: DepsMut, +/// env: Env, +/// info: MessageInfo, +/// msg: InstantiateMsg, +/// ) -> Result { +/// # Ok(Default::default()) +/// } +/// +/// #[shd_entry_point] +/// pub fn handle( +/// deps: DepsMut, +/// env: Env, +/// info: MessageInfo, +/// msg: ExecuteMsg, +/// ) -> Result { +/// # Ok(Default::default()) +/// } +/// +/// #[shd_entry_point] +/// pub fn query( +/// deps: Deps, +/// env: Env, +/// msg: QueryMsg, +/// ) -> Result { +/// # Ok(Default::default()) +/// } +/// ``` +/// +/// where `InstantiateMsg`, `ExecuteMsg`, and `QueryMsg` are contract defined +/// types that implement `DeserializeOwned + JsonSchema`. +#[proc_macro_attribute] +pub fn shd_entry_point(_attr: TokenStream, mut item: TokenStream) -> TokenStream { + let cloned = item.clone(); + let function = parse_macro_input!(cloned as syn::ItemFn); + let name = function.sig.ident.to_string(); + // The first argument is `deps`, the rest is region pointers + let args = function.sig.inputs.len() - 1; + + // E.g. "ptr0: u32, ptr1: u32, ptr2: u32, " + let typed_ptrs = (0..args).fold(String::new(), |acc, i| format!("{}ptr{}: u32, ", acc, i)); + // E.g. "ptr0, ptr1, ptr2, " + let ptrs = (0..args).fold(String::new(), |acc, i| format!("{}ptr{}, ", acc, i)); + + let new_code = format!( + r##" + #[cfg(target_arch = "wasm32")] + mod __wasm_export_{name} {{ // new module to avoid conflict of function name + #[no_mangle] + extern "C" fn {name}({typed_ptrs}) -> u32 {{ + shade_protocol::c_std::do_{name}(&super::{name}, {ptrs}) + }} + }} + "##, + name = name, + typed_ptrs = typed_ptrs, + ptrs = ptrs + ); + let entry = TokenStream::from_str(&new_code).unwrap(); + item.extend(entry); + item +} diff --git a/packages/ethereum_verify/Cargo.toml b/packages/ethereum_verify/Cargo.toml new file mode 100644 index 0000000..4961042 --- /dev/null +++ b/packages/ethereum_verify/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "ethereum-verify" +authors = ["Michael Scotto ", +"A Hardnett "] +description = "Ethereum Cryptographic verification utility functions" +version = "0.1.0" +edition = "2021" +homepage = "https://shadeprotocol.io/" + +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"] + +[dependencies] +sha2 = "0.10.8" +sha3 = "0.10" +hex = "0.4" +cosmwasm-schema = "1.1.5" +cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" } diff --git a/packages/ethereum_verify/README.md b/packages/ethereum_verify/README.md new file mode 100644 index 0000000..812e5b1 --- /dev/null +++ b/packages/ethereum_verify/README.md @@ -0,0 +1 @@ +# Ethereum Verify \ No newline at end of file diff --git a/packages/ethereum_verify/src/decode.rs b/packages/ethereum_verify/src/decode.rs new file mode 100644 index 0000000..0628aac --- /dev/null +++ b/packages/ethereum_verify/src/decode.rs @@ -0,0 +1,47 @@ +use cosmwasm_std::{StdError, StdResult}; +use sha3::{Digest, Keccak256}; + +/// Get the recovery param from the value `v` when no chain ID for replay protection is used. +/// +/// This is needed for chain-agnostig aignatures like signed text. +/// +/// See [EIP-155] for how `v` is composed. +/// +/// [EIP-155]: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md +pub fn get_recovery_param(v: u8) -> StdResult { + match v { + 27 => Ok(0), + 28 => Ok(1), + _ => Err(StdError::generic_err("Values of v other than 27 and 28 not supported. Replay protection (EIP-155) cannot be used here.")) + } +} + +/// Returns a raw 20 byte Ethereum address +pub fn ethereum_address_raw(pubkey: &[u8]) -> StdResult<[u8; 20]> { + let (tag, data) = match pubkey.split_first() { + Some(pair) => pair, + None => return Err(StdError::generic_err("Public key must not be empty")), + }; + if *tag != 0x04 { + return Err(StdError::generic_err("Public key must start with 0x04")); + } + if data.len() != 64 { + return Err(StdError::generic_err("Public key must be 65 bytes long")); + } + + let hash = Keccak256::digest(data); + Ok(hash[hash.len() - 20..].try_into().unwrap()) +} + +pub fn decode_address(input: &str) -> StdResult<[u8; 20]> { + if input.len() != 42 { + return Err(StdError::generic_err( + "Ethereum address must be 42 characters long", + )); + } + if !input.starts_with("0x") { + return Err(StdError::generic_err("Ethereum address must start with 0x")); + } + let data = hex::decode(&input[2..]).map_err(|_| StdError::generic_err("hex decoding error"))?; + Ok(data.try_into().unwrap()) +} diff --git a/packages/ethereum_verify/src/lib.rs b/packages/ethereum_verify/src/lib.rs new file mode 100644 index 0000000..e272e22 --- /dev/null +++ b/packages/ethereum_verify/src/lib.rs @@ -0,0 +1,5 @@ +mod decode; +pub use decode::{decode_address, ethereum_address_raw, get_recovery_param}; + +mod signature_verify; +pub use signature_verify::verify_ethereum_text; diff --git a/packages/ethereum_verify/src/signature_verify.rs b/packages/ethereum_verify/src/signature_verify.rs new file mode 100644 index 0000000..c970a67 --- /dev/null +++ b/packages/ethereum_verify/src/signature_verify.rs @@ -0,0 +1,43 @@ +use cosmwasm_std::{Deps, StdError, StdResult}; +use sha2::Digest; +use sha3::Keccak256; + +use crate::{decode_address, ethereum_address_raw, get_recovery_param}; + +#[allow(dead_code)] +pub const VERSION: &str = "crypto-verify-v2"; + +// TODO CHANGE TO VERIFY +pub fn verify_ethereum_text( + deps: Deps, + message: &str, + signature: &[u8], + signer_address: &str, +) -> StdResult { + let signer_address = decode_address(signer_address)?; + + // Hashing + let mut hasher = Keccak256::new(); + hasher.update(format!("\x19Ethereum Signed Message:\n{}", message.len())); + hasher.update(message); + let hash = hasher.finalize(); + + // Decompose signature + let (v, rs) = match signature.split_last() { + Some(pair) => pair, + None => return Err(StdError::generic_err("Signature must not be empty")), + }; + let recovery = get_recovery_param(*v)?; + + // Verification + let calculated_pubkey = deps.api.secp256k1_recover_pubkey(&hash, rs, recovery)?; + let calculated_address = ethereum_address_raw(&calculated_pubkey)?; + if signer_address != calculated_address { + return Ok(false); + } + let result = deps.api.secp256k1_verify(&hash, rs, &calculated_pubkey); + match result { + Ok(verifies) => Ok(verifies), + Err(err) => Err(err.into()), + } +} diff --git a/packages/multi_derive/Cargo.toml b/packages/multi_derive/Cargo.toml new file mode 100644 index 0000000..00d26e7 --- /dev/null +++ b/packages/multi_derive/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "multi-derive" +version = "0.1.0" +authors = [ + "hoomp " +] +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = [] \ No newline at end of file diff --git a/packages/multi_derive/src/lib.rs b/packages/multi_derive/src/lib.rs new file mode 100644 index 0000000..edec4a8 --- /dev/null +++ b/packages/multi_derive/src/lib.rs @@ -0,0 +1,73 @@ +/// Used for creates a struct that implements the MultiTestable interface. +/// +/// Needs the implementing package to have shade_protocol as a dependency with features. +/// +/// First arg is the struct name that will implement the MultiTestable interface. +/// +/// Second is the name of the package containing the contract module itself. +#[macro_export] +macro_rules! implement_multi { + ($x:ident, $s:ident) => { + use shade_protocol::c_std::{ContractInfo, Empty, Env, Addr}; + use shade_protocol::multi_test::{Contract, ContractWrapper}; + use shade_protocol::utils::callback::MultiTestable; + + pub struct $x { info: ContractInfo } + + impl MultiTestable for $x { + fn contract(&self) -> Box> { + let contract = ContractWrapper::new_with_empty( + $s::contract::execute, + $s::contract::instantiate, + $s::contract::query + ); + Box::new(contract) + } + + fn default() -> Self { + let info = ContractInfo { + address: Addr::unchecked(""), + code_hash: String::default(), + }; + $x { info } + } + } + }; +} + +/// Used for creates a struct that implements the MultiTestable interface **(for contracts that implement the reply method)** +/// +/// Needs the implementing package to have shade_protocol as a dependency with features. +/// +/// First arg is the struct name that will implement the MultiTestable interface. +/// +/// Second is the name of the package containing the contract module itself. +#[macro_export] +macro_rules! implement_multi_with_reply { + ($x:ident, $s:ident) => { + use shade_protocol::c_std::{ContractInfo, Empty, Env, Addr}; + use shade_protocol::multi_test::{Contract, ContractWrapper}; + use shade_protocol::utils::callback::MultiTestable; + + pub struct $x { info: ContractInfo } + + impl MultiTestable for $x { + fn contract(&self) -> Box> { + let contract = ContractWrapper::new_with_empty( + $s::contract::execute, + $s::contract::instantiate, + $s::contract::query + ).with_reply($s::contract::reply); + Box::new(contract) + } + + fn default() -> Self { + let info = ContractInfo { + address: Addr::unchecked(""), + code_hash: String::default(), + }; + $x { info } + } + } + }; +} diff --git a/packages/multi_test/Cargo.toml b/packages/multi_test/Cargo.toml new file mode 100644 index 0000000..af3b535 --- /dev/null +++ b/packages/multi_test/Cargo.toml @@ -0,0 +1,64 @@ +[package] +name = "shade-multi-test" +version = "0.1.0" +authors = [ + "hoomp " +] +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = [] +airdrop = ["dep:airdrop"] +admin = ["dep:admin", "shade-protocol/admin"] +snip20 = ["dep:snip20"] +#liability_mint = ["dep:liability_mint"] +#mint = ["dep:mint"] +#oracle = ["dep:oracle"] +#mock_band= ["dep:mock_band"] +mock_stkd = ["dep:mock_stkd"] +mock_sienna = ["dep:mock_sienna"] +# governance = ["dep:governance"] +# snip20_staking = ["dep:spip_stkd_0"] +# scrt_staking = ["dep:scrt_staking"] +# bonds = ["dep:bonds"] +query_auth = ["dep:query_auth"] +basic_staking = ["dep:basic_staking"] +scrt_staking = ["dep:scrt_staking"] +treasury = ["dep:treasury"] +treasury_manager = ["dep:treasury_manager"] +stkd_scrt = ["dep:stkd_scrt"] +dao = ["mock_adapter", "treasury", "treasury_manager", "snip20"] +# shade-oracles = ["dep:shade-oracles"] +# peg_stability = ["dep:peg_stability"] +snip20_migration = ["dep:snip20_migration"] + +[dependencies] +airdrop = { path = "../../contracts/airdrop", optional = true } +snip20 = { version = "0.1.0", path = "../../contracts/snip20", optional = true } +#liability_mint = { version = "0.1.0", path = "../../contracts/liability_mint", optional = true } +#mint = { version = "0.1.0", path = "../../contracts/mint", optional = true } +#oracle = { version = "0.1.0", path = "../../contracts/oracle", optional = true } +#mock_band = { version = "0.1.0", path = "../../contracts/mock_band", optional = true } +# governance = { version = "0.1.0", path = "../../contracts/governance", optional = true } +basic_staking = { version = "0.1.0", path = "../../contracts/basic_staking", optional = true } +# spip_stkd_0 = { version = "0.1.0", path = "../../contracts/snip20_staking", optional = true } +# bonds = { version = "0.1.0", path = "../../contracts/bonds", optional = true } +query_auth = { version = "0.1.0", path = "../../contracts/query_auth", optional = true } +mock_adapter = { version = "0.1.0", path = "../../contracts/mock/mock_adapter", optional = true } +stkd_scrt = { version = "0.1.0", path = "../../contracts/dao/stkd_scrt", optional = true } +scrt_staking = { version = "0.1.0", path = "../../contracts/dao/scrt_staking", optional = true } +treasury = { version = "0.1.0", path = "../../contracts/dao/treasury", optional = true } +treasury_manager = { version = "0.1.0", path = "../../contracts/dao/treasury_manager", optional = true } +admin = { version = "0.2.0", path = "../../contracts/admin", optional = true } +# peg_stability = { version = "0.1.0", path = "../../contracts/peg_stability", optional = true } +mock_stkd = { version = "0.1.0", package = "mock_stkd_derivative", path = "../../contracts/mock/mock_stkd_derivative", optional = true } +mock_sienna = { version = "0.1.0", package = "mock_sienna_pair", path = "../../contracts/mock/mock_sienna_pair", optional = true } +snip20_migration = { version = "0.1.0", path = "../../contracts/snip20_migration", optional = true } +shade-protocol = { path = "../shade_protocol", features = ["multi-test"] } + +[target.'cfg(not(target_arch="wasm32"))'.dependencies] +shade-protocol = { path = "../shade_protocol", features = ["multi-test"] } +multi-derive = { path = "../multi_derive" } diff --git a/packages/multi_test/src/interfaces/dao.rs b/packages/multi_test/src/interfaces/dao.rs new file mode 100644 index 0000000..0680e5d --- /dev/null +++ b/packages/multi_test/src/interfaces/dao.rs @@ -0,0 +1,490 @@ +use crate::{ + interfaces::{ + snip20, + treasury, + treasury_manager, + utils::{DeployedContracts, SupportedContracts}, + }, + multi::mock_adapter::MockAdapter, +}; +use mock_adapter; +use shade_protocol::{ + c_std::{Addr, StdError, StdResult, Uint128}, + contract_interfaces::dao::{ + adapter, + treasury::AllowanceType, + treasury_manager::AllocationType, + }, + multi_test::App, + utils::{self, asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +pub fn init_dao( + chain: &mut App, + sender: &str, + contracts: &mut DeployedContracts, + treasury_start_bal: Uint128, + snip20_symbol: &str, + allowance_type: Vec, + cycle: Vec, + allowance_amount: Vec, + allowance_tolerance: Vec, + tm_allowance_type: Vec>, + tm_allocation_amount: Vec>, + tm_allocation_tolerance: Vec>, + is_instant_unbond: bool, + do_update: bool, +) -> StdResult<()> { + let num_managers = allowance_amount.len(); + treasury::init(chain, sender, contracts)?; + let mut offset = 0; + snip20::init(chain, sender, contracts, "snip20_1", snip20_symbol, 6, None)?; + treasury::register_asset_exec(chain, sender, contracts, snip20_symbol)?; + snip20::send_exec( + chain, + sender, + contracts, + snip20_symbol, + contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + .address + .to_string(), + treasury_start_bal, + None, + )?; + for i in 0..num_managers { + let num_adapters = tm_allocation_amount[i].len(); + treasury_manager::init(chain, sender, contracts, i)?; + treasury_manager::register_asset_exec( + chain, + "admin", + contracts, + snip20_symbol, + SupportedContracts::TreasuryManager(i), + )?; + treasury::register_manager_exec(chain, sender, contracts, i)?; + treasury::allowance_exec( + chain, + sender, + contracts, + snip20_symbol, + i, + allowance_type[i].clone(), + cycle[i].clone(), + allowance_amount[i].clone(), + allowance_tolerance[i].clone(), + true, + )?; + for j in 0..num_adapters { + let mock_adap_contract = Contract::from( + match (mock_adapter::contract::Config { + owner: contracts + .get(&SupportedContracts::TreasuryManager(i)) + .unwrap() + .clone() + .address, + instant: is_instant_unbond, + token: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone(), + } + .test_init( + MockAdapter::default(), + chain, + Addr::unchecked(sender), + "mock_adapter", + &[], + )) { + Ok(contract_info) => contract_info, + Err(e) => return Err(StdError::generic_err(e.to_string())), + }, + ); + contracts.insert( + SupportedContracts::MockAdapter(j + offset), + mock_adap_contract, + ); + treasury_manager::allocate_exec( + chain, + sender, + contracts, + snip20_symbol, + Some(j.to_string()), + &SupportedContracts::MockAdapter(j + offset), + tm_allowance_type.clone()[i][j].clone(), + tm_allocation_amount[i][j].clone(), + tm_allocation_tolerance[i][j].clone(), + i, + )?; + } + offset += num_adapters + 1; + } + if do_update { + update_dao(chain, sender, contracts, snip20_symbol, num_managers).unwrap(); + } + Ok(()) +} + +pub fn update_dao( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + num_managers: usize, +) -> StdResult<()> { + treasury::update_exec(chain, sender, contracts, snip20_symbol)?; + for i in 0..num_managers { + treasury_manager::update_exec( + chain, + sender, + contracts, + snip20_symbol, + SupportedContracts::TreasuryManager(i), + )?; + } + Ok(()) +} + +pub fn system_balance_reserves( + chain: &App, + contracts: &DeployedContracts, + snip20_symbol: &str, +) -> (Uint128, Vec<(Uint128, Vec)>) { + let mut ret_struct = (Uint128::zero(), vec![]); + ret_struct.0 = treasury::reserves_query(chain, contracts, snip20_symbol.clone()).unwrap(); + let mut i = 0; + let mut j; + let mut offset = 0; + loop { + let mut manager_tuple = (Uint128::zero(), vec![]); + if contracts.get(&SupportedContracts::TreasuryManager(i)) == None { + break; + } else { + manager_tuple.0 = match treasury_manager::reserves_query( + chain, + contracts, + snip20_symbol.clone(), + SupportedContracts::TreasuryManager(i), + SupportedContracts::Treasury, + ) { + Ok(bal) => bal, + Err(_) => { + i += 1; + continue; + } + }; + j = 0; + loop { + if contracts.get(&SupportedContracts::MockAdapter(j + offset)) == None { + offset += j + 1; + break; + } else { + manager_tuple.1.push( + reserves_query( + chain, + contracts, + snip20_symbol.clone(), + SupportedContracts::MockAdapter(j + offset), + ) + .unwrap(), + ); + } + j += 1; + } + } + ret_struct.1.push(manager_tuple); + i += 1; + } + ret_struct +} + +pub fn system_balance_unbondable( + chain: &App, + contracts: &DeployedContracts, + snip20_symbol: &str, +) -> (Uint128, Vec<(Uint128, Vec)>) { + let mut ret_struct = (Uint128::zero(), vec![]); + ret_struct.0 = treasury::reserves_query(chain, contracts, snip20_symbol.clone()).unwrap(); + let mut i = 0; + let mut j; + let mut offset = 0; + loop { + let mut manager_tuple = (Uint128::zero(), vec![]); + if contracts.get(&SupportedContracts::TreasuryManager(i)) == None { + break; + } else { + manager_tuple.0 = match treasury_manager::reserves_query( + chain, + contracts, + snip20_symbol.clone(), + SupportedContracts::TreasuryManager(i), + SupportedContracts::Treasury, + ) { + Ok(bal) => bal, + Err(_) => { + i += 1; + continue; + } + }; + j = 0; + loop { + if contracts.get(&SupportedContracts::MockAdapter(j + offset)) == None { + offset += j + 1; + break; + } else { + manager_tuple.1.push( + unbondable_query( + chain, + contracts, + snip20_symbol.clone(), + SupportedContracts::MockAdapter(j + offset), + ) + .unwrap(), + ); + } + j += 1; + } + } + ret_struct.1.push(manager_tuple); + i += 1; + } + ret_struct +} + +pub fn claimable_query( + chain: &App, + contracts: &DeployedContracts, + snip20_symbol: &str, + adapter_contract: SupportedContracts, +) -> StdResult { + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .address + .to_string(), + }) + .test_query( + &contracts.get(&adapter_contract).unwrap().clone().into(), + &chain, + )? { + adapter::QueryAnswer::Claimable { amount } => Ok(amount), + _ => Err(StdError::generic_err(format!( + "Failed to.test_query adapter claimable", + ))), + } +} + +pub fn unbonding_query( + chain: &App, + contracts: &DeployedContracts, + snip20_symbol: &str, + adapter_contract: SupportedContracts, +) -> StdResult { + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbonding { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .address + .to_string(), + }) + .test_query( + &contracts.get(&adapter_contract).unwrap().clone().into(), + &chain, + )? { + adapter::QueryAnswer::Unbonding { amount } => Ok(amount), + _ => Err(StdError::generic_err( + "Failed to.test_query adapter unbonding", + )), + } +} + +pub fn unbondable_query( + chain: &App, + contracts: &DeployedContracts, + snip20_symbol: &str, + adapter_contract: SupportedContracts, +) -> StdResult { + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbondable { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .address + .to_string(), + }) + .test_query( + &contracts.get(&adapter_contract).unwrap().clone().into(), + &chain, + )? { + adapter::QueryAnswer::Unbondable { amount } => Ok(amount), + _ => Err(StdError::generic_err( + "Failed to.test_query adapter unbondable", + )), + } +} + +pub fn reserves_query( + chain: &App, + contracts: &DeployedContracts, + snip20_symbol: &str, + adapter_contract: SupportedContracts, +) -> StdResult { + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .address + .to_string(), + }) + .test_query( + &contracts.get(&adapter_contract).unwrap().clone().into(), + &chain, + )? { + adapter::QueryAnswer::Reserves { amount } => Ok(amount), + _ => Err(StdError::generic_err( + "Failed to.test_query adapter unbondable", + )), + } +} + +pub fn balance_query( + chain: &App, + contracts: &DeployedContracts, + snip20_symbol: &str, + adapter_contract: SupportedContracts, +) -> StdResult { + match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .address + .to_string(), + }) + .test_query( + &contracts.get(&adapter_contract).unwrap().clone().into(), + &chain, + )? { + adapter::QueryAnswer::Balance { amount } => Ok(amount), + _ => Err(StdError::generic_err( + "Failed to.test_query adapter balance", + )), + } +} + +pub fn claim_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + adapter_contract: SupportedContracts, +) -> StdResult<()> { + let res = adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Claim { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .address + .to_string(), + }) + .test_exec( + &contracts.get(&adapter_contract).unwrap().clone().into(), + chain, + Addr::unchecked(sender), + &[], + ); + match res { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(e.to_string())), + } +} + +pub fn update_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + adapter_contract: SupportedContracts, +) -> StdResult<()> { + match adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .address + .to_string(), + }) + .test_exec( + &contracts.get(&adapter_contract).unwrap().clone().into(), + chain, + Addr::unchecked(sender), + &[], + ) { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(e.to_string())), + } +} + +pub fn unbond_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + amount: Uint128, + adapter_contract: SupportedContracts, +) -> StdResult<()> { + match adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Unbond { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .address + .to_string(), + amount, + }) + .test_exec( + &contracts.get(&adapter_contract).unwrap().clone().into(), + chain, + Addr::unchecked(sender), + &[], + ) { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(e.to_string())), + } +} + +pub fn mock_adapter_sub_tokens( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + amount: Uint128, + adapter_contract: SupportedContracts, +) -> StdResult<()> { + match (mock_adapter::contract::ExecuteMsg::GiveMeMoney { amount }.test_exec( + &contracts.get(&adapter_contract).unwrap().clone().into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(e.to_string())), + } +} + +pub fn mock_adapter_complete_unbonding( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + adapter_contract: SupportedContracts, +) -> StdResult<()> { + match (mock_adapter::contract::ExecuteMsg::CompleteUnbonding {}.test_exec( + &contracts.get(&adapter_contract).unwrap().clone().into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(e.to_string())), + } +} diff --git a/packages/multi_test/src/interfaces/mod.rs b/packages/multi_test/src/interfaces/mod.rs new file mode 100644 index 0000000..085b96f --- /dev/null +++ b/packages/multi_test/src/interfaces/mod.rs @@ -0,0 +1,19 @@ +#[cfg(feature = "dao")] +pub mod dao; +/* +#[cfg(feature = "dao")] +pub mod manager; +#[cfg(feature = "dao")] +pub mod adapter; +*/ +#[cfg(feature = "snip20")] +pub mod snip20; +#[cfg(feature = "treasury")] +pub mod treasury; +#[cfg(feature = "treasury_manager")] +pub mod treasury_manager; + +#[cfg(feature = "scrt_staking")] +pub mod scrt_staking; + +pub mod utils; diff --git a/packages/multi_test/src/interfaces/scrt_staking.rs b/packages/multi_test/src/interfaces/scrt_staking.rs new file mode 100644 index 0000000..6ff8435 --- /dev/null +++ b/packages/multi_test/src/interfaces/scrt_staking.rs @@ -0,0 +1,83 @@ +use crate::{ + interfaces::{ + snip20, + treasury, + treasury_manager, + utils::{DeployedContracts, SupportedContracts}, + }, + multi::{admin::init_admin_auth, scrt_staking::ScrtStaking}, +}; +use shade_protocol::{ + c_std::{Addr, StdError, StdResult}, + contract_interfaces::dao::scrt_staking, + multi_test::App, + utils::{asset::Contract, InstantiateCallback, MultiTestable}, +}; + +pub fn init( + chain: &mut App, + sender: &str, + contracts: &mut DeployedContracts, + validator_bounds: Option, + manager: usize, +) -> StdResult<()> { + let treasury_manager = match contracts.get(&SupportedContracts::TreasuryManager(manager)) { + Some(manager) => manager.clone(), + None => { + treasury_manager::init(chain, sender, contracts, manager)?; + contracts + .get(&SupportedContracts::TreasuryManager(manager)) + .unwrap() + .clone() + } + }; + let _treasury = match contracts.get(&SupportedContracts::Treasury) { + Some(treasury) => treasury.clone(), + None => { + treasury::init(chain, sender, contracts)?; + contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + } + }; + let admin_auth = match contracts.get(&SupportedContracts::AdminAuth) { + Some(admin) => admin.clone(), + None => { + let contract = Contract::from(init_admin_auth(chain, &Addr::unchecked(sender))); + contracts.insert(SupportedContracts::AdminAuth, contract.clone()); + contract + } + }; + let sscrt = match contracts.get(&SupportedContracts::Snip20("SSCRT".to_string())) { + Some(snip20) => snip20.clone(), + None => { + snip20::init(chain, sender, contracts, "secretSCRT", "SSCRT", 6, None)?; + contracts + .get(&SupportedContracts::Snip20("SSCRT".to_string())) + .unwrap() + .clone() + } + }; + let scrt_staking = Contract::from( + match (scrt_staking::InstantiateMsg { + admin_auth: admin_auth.into(), + owner: treasury_manager.address.into(), + sscrt: sscrt.into(), + validator_bounds, + viewing_key: "viewing_key".into(), + } + .test_init( + ScrtStaking::default(), + chain, + Addr::unchecked(sender), + "scrt_staking", + &[], + )) { + Ok(contract_info) => contract_info, + Err(e) => return Err(StdError::generic_err(e.to_string())), + }, + ); + contracts.insert(SupportedContracts::ScrtStaking, scrt_staking); + Ok(()) +} diff --git a/packages/multi_test/src/interfaces/snip20.rs b/packages/multi_test/src/interfaces/snip20.rs new file mode 100644 index 0000000..63bf6bd --- /dev/null +++ b/packages/multi_test/src/interfaces/snip20.rs @@ -0,0 +1,186 @@ +use crate::{ + interfaces::utils::{DeployedContracts, SupportedContracts}, + multi::snip20::Snip20, +}; +use shade_protocol::{ + c_std::{Addr, Binary, Coin, StdError, StdResult, Uint128}, + contract_interfaces::snip20, + multi_test::App, + utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +pub fn init( + chain: &mut App, + sender: &str, + contracts: &mut DeployedContracts, + name: &str, + snip20_symbol: &str, + decimals: u8, + config: Option, +) -> StdResult<()> { + let snip20 = Contract::from( + match (snip20::InstantiateMsg { + name: name.to_string(), + admin: Some(sender.into()), + symbol: snip20_symbol.to_string(), + decimals, + initial_balances: Some(vec![snip20::InitialBalance { + address: sender.into(), + amount: Uint128::from(1_000_000_000 * 10 ^ decimals as u128), + }]), + prng_seed: Binary::default(), + query_auth: None, + config, + } + .test_init( + Snip20::default(), + chain, + Addr::unchecked(sender), + "snip20", + &[], + )) { + Ok(contract_info) => contract_info, + Err(e) => return Err(StdError::generic_err(e.to_string())), + }, + ); + contracts.insert( + SupportedContracts::Snip20(snip20_symbol.to_string()), + snip20, + ); + Ok(()) +} + +pub fn deposit_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + coins: &Vec, +) -> StdResult<()> { + match (snip20::ExecuteMsg::Deposit { padding: None }.test_exec( + &contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + coins, + )) { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(e.to_string())), + } +} + +pub fn set_viewing_key_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + key: String, +) -> StdResult<()> { + match (snip20::ExecuteMsg::SetViewingKey { key, padding: None }.test_exec( + &contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(e.to_string())), + } +} + +pub fn send_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + recipient: String, + amount: Uint128, + msg: Option, +) -> StdResult<()> { + match (snip20::ExecuteMsg::Send { + recipient, + amount, + msg, + memo: None, + padding: None, + recipient_code_hash: None, + } + .test_exec( + &contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(_) => Err(StdError::generic_err("snip20 send failed")), + } +} + +pub fn send_from_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + owner: String, + recipient: String, + amount: Uint128, + msg: Option, +) -> StdResult<()> { + match (snip20::ExecuteMsg::SendFrom { + owner, + recipient, + amount, + msg, + memo: None, + padding: None, + recipient_code_hash: None, + } + .test_exec( + &contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(_) => Err(StdError::generic_err("snip20 send failed")), + } +} + +pub fn balance_query( + chain: &App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + key: String, +) -> StdResult { + let res = snip20::QueryMsg::Balance { + address: sender.to_string(), + key, + } + .test_query( + &contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .into(), + chain, + )?; + match res { + snip20::QueryAnswer::Balance { amount } => Ok(amount), + _ => Err(StdError::generic_err("SetViewingKey failed")), + } +} diff --git a/packages/multi_test/src/interfaces/treasury.rs b/packages/multi_test/src/interfaces/treasury.rs new file mode 100644 index 0000000..fee0a7c --- /dev/null +++ b/packages/multi_test/src/interfaces/treasury.rs @@ -0,0 +1,469 @@ +use crate::{ + interfaces::utils::{DeployedContracts, SupportedContracts}, + multi::{admin::init_admin_auth, treasury::Treasury}, +}; +use shade_protocol::{ + c_std::{Addr, StdError, StdResult, Uint128}, + contract_interfaces::dao::treasury, + multi_test::App, + utils::{ + asset::{Contract, RawContract}, + cycle::Cycle, + storage::plus::period_storage::Period, + ExecuteCallback, + InstantiateCallback, + MultiTestable, + Query, + }, +}; + +pub fn init(chain: &mut App, sender: &str, contracts: &mut DeployedContracts) -> StdResult<()> { + let admin = match contracts.get(&SupportedContracts::AdminAuth) { + Some(admin) => admin.clone(), + None => { + let contract = Contract::from(init_admin_auth(chain, &Addr::unchecked(sender))); + contracts.insert(SupportedContracts::AdminAuth, contract.clone()); + contract + } + }; + let treasury = Contract::from( + match (treasury::InstantiateMsg { + multisig: admin.address.clone().to_string(), + admin_auth: admin.clone().into(), + viewing_key: "viewing_key".to_string(), + } + .test_init( + Treasury::default(), + chain, + Addr::unchecked(sender), + "treasury", + &[], + )) { + Ok(contract_info) => contract_info, + Err(e) => return Err(StdError::generic_err(e.to_string())), + }, + ); + contracts.insert(SupportedContracts::Treasury, treasury); + Ok(()) +} + +pub fn config_query(chain: &App, contracts: &DeployedContracts) -> StdResult { + let res = treasury::QueryMsg::Config {}.test_query( + &contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + .into(), + chain, + )?; + match res { + treasury::QueryAnswer::Config { config } => Ok(config), + _ => Err(StdError::generic_err("query failed")), + } +} + +pub fn allowance_query( + chain: &App, + contracts: &DeployedContracts, + snip20_symbol: &str, + spender: SupportedContracts, +) -> StdResult { + let res = treasury::QueryMsg::Allowance { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .address + .to_string(), + spender: contracts.get(&spender).unwrap().clone().address.to_string(), + } + .test_query( + &contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + .into(), + chain, + )?; + match res { + treasury::QueryAnswer::Allowance { amount } => Ok(amount), + _ => Err(StdError::generic_err("query failed")), + } +} + +pub fn allowances_query( + chain: &App, + contracts: &DeployedContracts, + snip20_symbol: &str, +) -> StdResult> { + let res = treasury::QueryMsg::Allowances { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .address + .to_string(), + } + .test_query( + &contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + .into(), + chain, + )?; + match res { + treasury::QueryAnswer::Allowances { allowances } => Ok(allowances), + _ => Err(StdError::generic_err("query failed")), + } +} + +pub fn assets_query(chain: &App, contracts: &DeployedContracts) -> StdResult> { + let res = treasury::QueryMsg::Assets {}.test_query( + &contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + .into(), + chain, + )?; + match res { + treasury::QueryAnswer::Assets { assets } => Ok(assets), + _ => Err(StdError::generic_err("query failed")), + } +} + +pub fn reserves_query( + chain: &App, + contracts: &DeployedContracts, + snip20_symbol: &str, +) -> StdResult { + let res = treasury::QueryMsg::Reserves { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .address + .to_string(), + } + .test_query( + &contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + .into(), + chain, + )?; + match res { + treasury::QueryAnswer::Reserves { amount } => Ok(amount), + _ => Err(StdError::generic_err("query failed")), + } +} + +pub fn balance_query( + chain: &App, + contracts: &DeployedContracts, + snip20_symbol: &str, +) -> StdResult { + let res = treasury::QueryMsg::Balance { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .address + .to_string(), + } + .test_query( + &contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + .into(), + chain, + )?; + match res { + treasury::QueryAnswer::Balance { amount } => Ok(amount), + _ => Err(StdError::generic_err("query failed")), + } +} + +pub fn run_level_query( + chain: &App, + contracts: &DeployedContracts, +) -> StdResult { + let res = treasury::QueryMsg::RunLevel {}.test_query( + &contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + .into(), + chain, + )?; + match res { + treasury::QueryAnswer::RunLevel { run_level } => Ok(run_level), + _ => Err(StdError::generic_err("query failed")), + } +} + +pub fn metrics_query( + chain: &App, + contracts: &DeployedContracts, + date: Option, + epoch: Option, + period: Period, +) -> StdResult> { + let res = treasury::QueryMsg::Metrics { + date, + epoch, + period, + } + .test_query( + &contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + .into(), + chain, + )?; + match res { + treasury::QueryAnswer::Metrics { metrics } => Ok(metrics), + _ => Err(StdError::generic_err("query failed")), + } +} + +pub fn batch_balance_query( + chain: &App, + contracts: &DeployedContracts, + snip20_symbols: Vec<&str>, +) -> StdResult> { + let assets = { + let mut vec = vec![]; + for symbols in snip20_symbols { + vec.push( + contracts + .get(&SupportedContracts::Snip20(symbols.to_string())) + .unwrap() + .clone() + .address + .to_string(), + ); + } + vec + }; + match (treasury::QueryMsg::BatchBalance { assets }.test_query( + &contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + .into(), + chain, + )) { + Ok(a) => Ok(a), + _ => Err(StdError::generic_err("query failed")), + } +} + +pub fn register_asset_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, +) -> StdResult<()> { + match (treasury::ExecuteMsg::RegisterAsset { + contract: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .into(), + } + .test_exec( + &contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(_) => Err(StdError::generic_err("register wrap failed")), + } +} + +pub fn register_manager_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + manager_id: usize, +) -> StdResult<()> { + match (treasury::ExecuteMsg::RegisterManager { + contract: contracts + .get(&SupportedContracts::TreasuryManager(manager_id)) + .unwrap() + .clone() + .into(), + } + .test_exec( + &contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(_) => Err(StdError::generic_err("register wrap failed")), + } +} + +pub fn register_wrap_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + denom: String, + contract: RawContract, +) -> StdResult<()> { + match (treasury::ExecuteMsg::RegisterWrap { denom, contract }.test_exec( + &contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(_) => Err(StdError::generic_err("register wrap failed")), + } +} + +pub fn allowance_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + manager_id: usize, + allowance_type: treasury::AllowanceType, + cycle: Cycle, + amount: Uint128, + tolerance: Uint128, + refresh_now: bool, +) -> StdResult<()> { + match (treasury::ExecuteMsg::Allowance { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .address + .to_string(), + allowance: treasury::RawAllowance { + spender: contracts + .get(&SupportedContracts::TreasuryManager(manager_id)) + .unwrap() + .clone() + .address + .to_string(), + allowance_type, + cycle, + amount, + tolerance, + }, + refresh_now, + } + .test_exec( + &contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(_) => Err(StdError::generic_err("allowance exec failed")), + } +} + +pub fn set_run_level_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + run_level: treasury::RunLevel, +) -> StdResult<()> { + match (treasury::ExecuteMsg::SetRunLevel { run_level }.test_exec( + &contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => { + return Err(StdError::generic_err(e.to_string())); + } + } +} + +pub fn set_config( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + admin_auth: Option, + multisig: Option, +) -> StdResult<()> { + match (treasury::ExecuteMsg::UpdateConfig { + admin_auth, + multisig, + } + .test_exec( + &contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(e.to_string())), + } +} + +pub fn update_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, +) -> StdResult<()> { + let res = treasury::ExecuteMsg::Update { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .address + .to_string(), + } + .test_exec( + &contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + ); + match res { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(e.to_string())), + } +} diff --git a/packages/multi_test/src/interfaces/treasury_manager.rs b/packages/multi_test/src/interfaces/treasury_manager.rs new file mode 100644 index 0000000..2bf2b91 --- /dev/null +++ b/packages/multi_test/src/interfaces/treasury_manager.rs @@ -0,0 +1,667 @@ +use crate::{ + interfaces::{ + treasury, + utils::{DeployedContracts, SupportedContracts}, + }, + multi::{admin::init_admin_auth, treasury_manager::TreasuryManager}, +}; +use shade_protocol::{ + c_std::{Addr, StdError, StdResult, Uint128}, + contract_interfaces::dao::{manager, treasury_manager}, + multi_test::App, + utils::{ + asset::{Contract, RawContract}, + storage::plus::period_storage::Period, + ExecuteCallback, + InstantiateCallback, + MultiTestable, + Query, + }, +}; + +pub fn init( + chain: &mut App, + sender: &str, + contracts: &mut DeployedContracts, + id: usize, +) -> StdResult<()> { + let treasury = match contracts.get(&SupportedContracts::Treasury) { + Some(treasury) => treasury.clone(), + None => { + treasury::init(chain, sender, contracts)?; + contracts + .get(&SupportedContracts::Treasury) + .unwrap() + .clone() + } + }; + let admin_auth = match contracts.get(&SupportedContracts::AdminAuth) { + Some(admin) => admin.clone(), + None => { + let contract = Contract::from(init_admin_auth(chain, &Addr::unchecked(sender))); + contracts.insert(SupportedContracts::AdminAuth, contract.clone()); + contract + } + }; + let treasury_manager = Contract::from( + match (treasury_manager::InstantiateMsg { + admin_auth: admin_auth.into(), + viewing_key: "viewing_key".to_string(), + treasury: treasury.address.into(), + } + .test_init( + TreasuryManager::default(), + chain, + Addr::unchecked(sender), + "manager", + &[], + )) { + Ok(contract_info) => contract_info, + Err(e) => return Err(StdError::generic_err(e.to_string())), + }, + ); + contracts.insert(SupportedContracts::TreasuryManager(id), treasury_manager); + Ok(()) +} + +pub fn claimable_query( + chain: &App, + contracts: &DeployedContracts, + snip20_symbol: &str, + treasury_manager_contract: SupportedContracts, + holder: SupportedContracts, +) -> StdResult { + match treasury_manager::QueryMsg::Manager(manager::SubQueryMsg::Claimable { + holder: contracts.get(&holder).unwrap().address.to_string(), + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .address + .to_string(), + }) + .test_query( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + &chain, + )? { + manager::QueryAnswer::Claimable { amount } => Ok(amount), + _ => Err(StdError::generic_err( + "Failed to test query treasury_manager claimable", + )), + } +} + +pub fn config_query( + chain: &App, + contracts: &DeployedContracts, + treasury_manager_contract: SupportedContracts, +) -> StdResult { + let res = treasury_manager::QueryMsg::Config {}.test_query( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + &chain, + )?; + match res { + treasury_manager::QueryAnswer::Config { config } => Ok(config), + _ => Err(StdError::generic_err(format!( + "Failed to.test_query treasury_manager claimable", + ))), + } +} + +pub fn pending_allowance_query( + chain: &App, + contracts: &DeployedContracts, + treasury_manager_contract: SupportedContracts, + snip20_symbol: &str, +) -> StdResult { + let res = treasury_manager::QueryMsg::PendingAllowance { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .address + .to_string(), + } + .test_query( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + &chain, + )?; + match res { + treasury_manager::QueryAnswer::PendingAllowance { amount } => Ok(amount), + _ => Err(StdError::generic_err(format!( + "Failed to.test_query treasury_manager pending_allowance", + ))), + } +} + +pub fn holding_query( + chain: &App, + contracts: &DeployedContracts, + treasury_manager_contract: SupportedContracts, + holder: String, +) -> StdResult { + let res = treasury_manager::QueryMsg::Holding { holder }.test_query( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + &chain, + )?; + match res { + treasury_manager::QueryAnswer::Holding { holding } => Ok(holding), + _ => Err(StdError::generic_err(format!( + "Failed to.test_query treasury_manager claimable", + ))), + } +} + +pub fn holders_query( + chain: &App, + contracts: &DeployedContracts, + treasury_manager_contract: SupportedContracts, +) -> StdResult> { + let res = treasury_manager::QueryMsg::Holders {}.test_query( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + &chain, + )?; + match res { + treasury_manager::QueryAnswer::Holders { holders } => Ok(holders), + _ => Err(StdError::generic_err(format!( + "Failed to.test_query treasury_manager holders", + ))), + } +} + +pub fn assets_query( + chain: &App, + contracts: &DeployedContracts, + treasury_manager_contract: SupportedContracts, +) -> StdResult> { + let res = treasury_manager::QueryMsg::Assets {}.test_query( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + &chain, + )?; + match res { + treasury_manager::QueryAnswer::Assets { assets } => Ok(assets), + _ => Err(StdError::generic_err(format!( + "Failed to.test_query treasury_manager holders", + ))), + } +} + +pub fn allocations_query( + chain: &App, + contracts: &DeployedContracts, + treasury_manager_contract: SupportedContracts, + snip20_symbol: &str, +) -> StdResult> { + let res = treasury_manager::QueryMsg::Allocations { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .address + .to_string(), + } + .test_query( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + &chain, + )?; + match res { + treasury_manager::QueryAnswer::Allocations { allocations } => Ok(allocations), + _ => Err(StdError::generic_err(format!( + "Failed to.test_query treasury_manager allocations", + ))), + } +} + +pub fn metrics_query( + chain: &App, + contracts: &DeployedContracts, + treasury_manager_contract: SupportedContracts, + date: Option, + epoch: Option, + period: Period, +) -> StdResult> { + let res = treasury_manager::QueryMsg::Metrics { + date, + epoch, + period, + } + .test_query( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + &chain, + )?; + match res { + treasury_manager::QueryAnswer::Metrics { metrics } => Ok(metrics), + _ => Err(StdError::generic_err(format!( + "Failed to.test_query treasury_manager metrics", + ))), + } +} + +pub fn unbonding_query( + chain: &App, + contracts: &DeployedContracts, + snip20_symbol: &str, + treasury_manager_contract: SupportedContracts, + holder: SupportedContracts, +) -> StdResult { + match treasury_manager::QueryMsg::Manager(manager::SubQueryMsg::Unbonding { + holder: contracts.get(&holder).unwrap().address.to_string(), + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .address + .to_string(), + }) + .test_query( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + &chain, + )? { + manager::QueryAnswer::Unbonding { amount } => Ok(amount), + _ => Err(StdError::generic_err( + "Failed to.test_query treasury_manager unbonding", + )), + } +} + +pub fn unbondable_query( + chain: &App, + contracts: &DeployedContracts, + snip20_symbol: &str, + treasury_manager_contract: SupportedContracts, + holder: SupportedContracts, +) -> StdResult { + match treasury_manager::QueryMsg::Manager(manager::SubQueryMsg::Unbondable { + holder: contracts.get(&holder).unwrap().address.to_string(), + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .address + .to_string(), + }) + .test_query( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + &chain, + )? { + manager::QueryAnswer::Unbondable { amount } => Ok(amount), + _ => Err(StdError::generic_err( + "Failed to.test_query treasury_manager unbondable", + )), + } +} + +pub fn reserves_query( + chain: &App, + contracts: &DeployedContracts, + snip20_symbol: &str, + treasury_manager_contract: SupportedContracts, + holder: SupportedContracts, +) -> StdResult { + match manager::QueryMsg::Manager(manager::SubQueryMsg::Reserves { + holder: contracts.get(&holder).unwrap().address.to_string(), + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .address + .to_string(), + }) + .test_query( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + &chain, + )? { + manager::QueryAnswer::Reserves { amount } => Ok(amount), + _ => Err(StdError::generic_err( + "Failed to query treasury_manager reserves", + )), + } +} + +pub fn batch_balance_query( + chain: &App, + contracts: &DeployedContracts, + snip20_symbols: Vec<&str>, + treasury_manager_contract: SupportedContracts, + holder: SupportedContracts, +) -> StdResult> { + let assets = { + let mut vec = vec![]; + for symbols in snip20_symbols { + vec.push( + contracts + .get(&SupportedContracts::Snip20(symbols.to_string())) + .unwrap() + .clone() + .address + .to_string(), + ); + } + vec + }; + match manager::QueryMsg::Manager(manager::SubQueryMsg::BatchBalance { + holder: contracts.get(&holder).unwrap().address.to_string(), + assets, + }) + .test_query( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + &chain, + )? { + manager::QueryAnswer::BatchBalance { amounts } => Ok(amounts), + _ => Err(StdError::generic_err( + "Failed to query treasury_manager reserves", + )), + } +} + +pub fn balance_query( + chain: &App, + contracts: &DeployedContracts, + snip20_symbol: &str, + treasury_manager_contract: SupportedContracts, + holder: SupportedContracts, +) -> StdResult { + match treasury_manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { + holder: contracts.get(&holder).unwrap().address.to_string(), + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .address + .to_string(), + }) + .test_query( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + &chain, + )? { + manager::QueryAnswer::Balance { amount } => Ok(amount), + _ => Err(StdError::generic_err( + "Failed to query treasury_manager balance", + )), + } +} + +pub fn update_config_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + treasury_manager_contract: SupportedContracts, + admin_auth: Option, + treasury: Option, +) -> StdResult<()> { + match (treasury_manager::ExecuteMsg::UpdateConfig { + admin_auth, + treasury, + } + .test_exec( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(_) => Err(StdError::generic_err("claim in treasury manager failed")), + } +} + +pub fn claim_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + treasury_manager_contract: SupportedContracts, +) -> StdResult<()> { + match treasury_manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Claim { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .address + .to_string(), + }) + .test_exec( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + ) { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(e.to_string())), + } +} + +pub fn unbond_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + treasury_manager_contract: SupportedContracts, + amount: Uint128, +) -> StdResult<()> { + match treasury_manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Unbond { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .address + .to_string(), + amount, + }) + .test_exec( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + ) { + Ok(_) => Ok(()), + Err(_) => Err(StdError::generic_err("update in treasury manager failed")), + } +} + +pub fn update_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + treasury_manager_contract: SupportedContracts, +) -> StdResult<()> { + match treasury_manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .address + .to_string(), + }) + .test_exec( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + ) { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(e.to_string())), + } +} + +pub fn register_holder_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + treasury_manager_contract: SupportedContracts, + holder: &str, +) -> StdResult<()> { + match (treasury_manager::ExecuteMsg::AddHolder { + holder: holder.to_string(), + } + .test_exec( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(_) => Err(StdError::generic_err( + "register_holder in treasury manager failed", + )), + } +} + +pub fn remove_holder_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + treasury_manager_contract: SupportedContracts, + holder: &str, +) -> StdResult<()> { + match (treasury_manager::ExecuteMsg::RemoveHolder { + holder: holder.to_string(), + } + .test_exec( + &contracts + .get(&treasury_manager_contract) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(_) => Err(StdError::generic_err( + "register_holder in treasury manager failed", + )), + } +} + +pub fn register_asset_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + tm_contract: SupportedContracts, +) -> StdResult<()> { + match (treasury_manager::ExecuteMsg::RegisterAsset { + contract: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .into(), + } + .test_exec( + &contracts.get(&tm_contract).unwrap().clone().into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(e.to_string())), + } +} + +pub fn allocate_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + nickname: Option, + contract_to_allocate_to: &SupportedContracts, + alloc_type: treasury_manager::AllocationType, + amount: Uint128, + tolerance: Uint128, + id: usize, +) -> StdResult<()> { + match (treasury_manager::ExecuteMsg::Allocate { + asset: contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .address + .to_string(), + allocation: treasury_manager::RawAllocation { + nick: nickname, + contract: RawContract::from(contracts.get(contract_to_allocate_to).unwrap().clone()), + alloc_type, + amount, + tolerance, + }, + } + .test_exec( + &contracts + .get(&SupportedContracts::TreasuryManager(id)) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(e.to_string())), + } +} diff --git a/packages/multi_test/src/interfaces/utils.rs b/packages/multi_test/src/interfaces/utils.rs new file mode 100644 index 0000000..f8b7524 --- /dev/null +++ b/packages/multi_test/src/interfaces/utils.rs @@ -0,0 +1,14 @@ +use shade_protocol::utils::asset::Contract; +use std::collections::HashMap; + +#[derive(Clone, Eq, PartialEq, Hash)] +pub enum SupportedContracts { + AdminAuth, + Snip20(String), + Treasury, + TreasuryManager(usize), + MockAdapter(usize), + ScrtStaking, +} + +pub type DeployedContracts = HashMap; diff --git a/packages/multi_test/src/lib.rs b/packages/multi_test/src/lib.rs new file mode 100644 index 0000000..51a504c --- /dev/null +++ b/packages/multi_test/src/lib.rs @@ -0,0 +1,4 @@ +#[cfg(not(target_arch = "wasm32"))] +pub mod multi; + +pub mod interfaces; diff --git a/packages/multi_test/src/multi.rs b/packages/multi_test/src/multi.rs new file mode 100644 index 0000000..9296174 --- /dev/null +++ b/packages/multi_test/src/multi.rs @@ -0,0 +1,140 @@ +#[cfg(feature = "admin")] +pub mod admin { + pub use admin; + use shade_protocol::{admin::InstantiateMsg, multi_test::App, utils::InstantiateCallback}; + multi_derive::implement_multi!(Admin, admin); + + // Multitest helper + pub fn init_admin_auth(app: &mut App, superadmin: &Addr) -> ContractInfo { + InstantiateMsg { + super_admin: Some(superadmin.clone().to_string()), + } + .test_init(Admin::default(), app, superadmin.clone(), "admin_auth", &[]) + .unwrap() + } +} + +#[cfg(feature = "snip20")] +pub mod snip20 { + use snip20; + multi_derive::implement_multi!(Snip20, snip20); +} + +#[cfg(feature = "liability_mint")] +pub mod liability_mint { + use liability_mint; + multi_derive::implement_multi!(LiabilityMint, liability_mint); +} + +#[cfg(feature = "stkd_scrt")] +pub mod stkd_scrt { + use stkd_scrt; + multi_derive::implement_multi!(StkdScrt, stkd_scrt); +} + +// #[cfg(feature = "mint")] +// pub mod mint { +// use mint; +// multi_derive::implement_multi!(Mint, mint); +// } + +// #[cfg(feature = "oracle")] +// pub mod oracle { +// use oracle; +// multi_derive::implement_multi!(Oracle, oracle); +// } + +// #[cfg(feature = "mock_band")] +// pub mod mock_band { +// use crate::multi_derive; +// use mock_band; + +// pub struct MockBand; +// multi_derive::implement_multi!(MockBand, mock_band); +// } + +#[cfg(feature = "governance")] +pub mod governance { + use governance; + + multi_derive::implement_multi_with_reply!(Governance, governance); +} + +// #[cfg(feature = "snip20_staking")] +// pub mod snip20_staking { +// use spip_stkd_0; +// +// multi_derive::implement_multi!(Snip20Staking, spip_stkd_0); +// } + +// #[cfg(feature = "bonds")] +// pub mod bonds { +// use crate::multi_derive; +// use bonds; + +// pub struct Bonds; +// multi_derive::implement_multi!(Bonds, bonds); +// } + +#[cfg(feature = "query_auth")] +pub mod query_auth { + use query_auth; + + multi_derive::implement_multi!(QueryAuth, query_auth); +} + +#[cfg(feature = "treasury_manager")] +pub mod treasury_manager { + use treasury_manager; + multi_derive::implement_multi!(TreasuryManager, treasury_manager); +} + +#[cfg(feature = "treasury")] +pub mod treasury { + use treasury; + multi_derive::implement_multi!(Treasury, treasury); +} + +#[cfg(feature = "mock_adapter")] +pub mod mock_adapter { + use mock_adapter; + multi_derive::implement_multi!(MockAdapter, mock_adapter); +} + +#[cfg(feature = "scrt_staking")] +pub mod scrt_staking { + use scrt_staking; + multi_derive::implement_multi!(ScrtStaking, scrt_staking); +} + +#[cfg(feature = "basic_staking")] +pub mod basic_staking { + use basic_staking; + multi_derive::implement_multi!(BasicStaking, basic_staking); +} + +#[cfg(feature = "peg_stability")] +pub mod peg_stability { + use peg_stability; + + multi_derive::implement_multi!(PegStability, peg_stability); +} + +#[cfg(feature = "mock_stkd")] +pub mod mock_stkd { + pub use mock_stkd; + multi_derive::implement_multi!(MockStkd, mock_stkd); +} + +#[cfg(feature = "mock_sienna")] +pub mod mock_sienna { + pub use mock_sienna; + multi_derive::implement_multi!(MockSienna, mock_sienna); +} + +#[cfg(feature = "snip20_migration")] +pub mod snip20_migration { + use snip20_migration; + + multi_derive::implement_multi!(Snip20Migration, snip20_migration); +} diff --git a/packages/secretcli/Cargo.toml b/packages/secretcli/Cargo.toml new file mode 100644 index 0000000..bc80738 --- /dev/null +++ b/packages/secretcli/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "secretcli" +version = "0.1.0" +authors = ["Guy Garcia "] +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = [] + +[dependencies] +serde = { version = "1.0.103", default-features = false, features = ["derive"] } +serde_json = { version = "1.0.67"} diff --git a/packages/secretcli/src/cli_types.rs b/packages/secretcli/src/cli_types.rs new file mode 100644 index 0000000..31b0a9b --- /dev/null +++ b/packages/secretcli/src/cli_types.rs @@ -0,0 +1,94 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct TxResponse { + pub height: String, + pub txhash: String, + pub codespace: String, + pub code: Option, + pub data: String, + pub raw_log: String, +} + +#[derive(Serialize, Deserialize)] +pub struct TxCompute { + //#[serde(rename="key")] + //pub msg_key: String, + pub input: String, +} + +#[derive(Serialize, Deserialize)] +pub struct TxQuery { + pub height: String, + pub txhash: String, + pub data: String, + pub raw_log: String, + pub logs: Vec, + pub gas_wanted: String, + pub gas_used: String, + //pub tx: String, + pub timestamp: String, +} + +#[derive(Serialize, Deserialize)] +pub struct TxQueryLogs { + pub msg_index: i128, + pub log: String, + pub events: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct TxQueryEvents { + #[serde(rename = "type")] + pub msg_type: String, + pub attributes: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct TxQueryKeyValue { + #[serde(rename = "key")] + pub msg_key: String, + pub value: String, +} + +#[derive(Serialize, Deserialize)] +pub struct ListCodeResponse { + pub id: u128, + pub creator: String, + pub data_hash: String, +} + +#[derive(Serialize, Deserialize)] +pub struct ListContractCode { + pub code_id: u128, + pub creator: String, + pub label: String, + pub address: String, +} + +#[derive(Serialize, Deserialize)] +pub struct NetContract { + pub label: String, + pub id: String, + pub address: String, + pub code_hash: String, +} + +#[derive(Serialize, Deserialize)] +pub struct StoredContract { + pub id: String, + pub code_hash: String, +} + +#[derive(Serialize, Deserialize)] +pub struct SignedTx { + pub pub_key: PubKey, + pub signature: String, +} + +#[derive(Serialize, Deserialize)] +pub struct PubKey { + #[serde(rename = "type")] + pub msg_type: String, + pub value: String, +} diff --git a/packages/secretcli/src/lib.rs b/packages/secretcli/src/lib.rs new file mode 100644 index 0000000..98dd50a --- /dev/null +++ b/packages/secretcli/src/lib.rs @@ -0,0 +1,2 @@ +pub mod cli_types; +pub mod secretcli; diff --git a/packages/secretcli/src/secretcli.rs b/packages/secretcli/src/secretcli.rs new file mode 100644 index 0000000..e1f17d2 --- /dev/null +++ b/packages/secretcli/src/secretcli.rs @@ -0,0 +1,526 @@ +use crate::cli_types::{ + ListCodeResponse, + ListContractCode, + NetContract, + SignedTx, + StoredContract, + TxCompute, + TxQuery, + TxResponse, +}; +use serde::{Deserialize, Serialize}; +use serde_json::{Result, Value}; +use std::{ + fs::File, + io::{self, Write}, + process::Command, + thread, + time, +}; + +//secretcli tx sign-doc tx_to_sign --from sign-test + +fn vec_str_to_vec_string(str_in: Vec<&str>) -> Vec { + let mut str_out: Vec = vec![]; + + for val in str_in { + str_out.push(val.to_string()); + } + + str_out +} + +/// +/// Contains that specific transaction's information +/// +#[derive(Serialize, Deserialize)] +pub struct Report { + pub msg_type: String, + pub message: String, + pub gas_used: String, +} + +/// +/// Will run any scretcli command and return its output +/// +/// # Arguments +/// +/// * 'command' - a string array that contains the command to forward\ +/// +fn secretcli_run(command: Vec, max_retry: Option) -> Result { + let retry = max_retry.unwrap_or(30); + let mut commands = command; + commands.append(&mut vec_str_to_vec_string(vec!["--output", "json"])); + let mut cli = Command::new("secretd".to_string()); + if !commands.is_empty() { + cli.args(commands); + } + + let mut result = cli.output().expect("Unexpected error"); + // We wait cause sometimes the query/action takes a while + for _ in 0..retry { + if !result.stderr.is_empty() { + thread::sleep(time::Duration::from_secs(1)); + } else { + break; + } + result = cli.output().expect("Unexpected error"); + } + let out = result.stdout; + if String::from_utf8_lossy(&out).contains("output_error") { + println!("{:?}", &String::from_utf8_lossy(&out)); + } + serde_json::from_str(&String::from_utf8_lossy(&out)) +} + +/// +/// Stores the given `contract +/// +/// # Arguments +/// +/// * 'contract' - Contract to be stored +/// * 'user' - User that will handle the tx, defaults to a +/// * 'gas' - Gas to pay, defaults to 10000000 +/// * 'backend' - The backend keyring, defaults to test +/// +fn store_contract( + contract: &str, + user: Option<&str>, + gas: Option<&str>, + backend: Option<&str>, +) -> Result { + let mut command_arr = vec![ + "tx", + "compute", + "store", + contract, + "--from", + user.unwrap_or("a"), + "--gas", + gas.unwrap_or("10000000"), + "-y", + ]; + + if let Some(backend) = backend { + command_arr.push("--keyring-backend"); + command_arr.push(backend); + } + + let command = vec_str_to_vec_string(command_arr); + let json = secretcli_run(command, None)?; + let out: Result = serde_json::from_value(json); + out +} + +/// +/// Queries the hash information +/// +fn query_hash(hash: String) -> Result { + let command = vec!["q", "tx", &hash]; + let a = secretcli_run(vec_str_to_vec_string(command), None)?; + serde_json::from_value(a) +} + +/// +/// Computes the hash information +/// +fn compute_hash(hash: String) -> Result { + let command = vec!["q", "compute", "tx", &hash]; + + serde_json::from_value(secretcli_run(vec_str_to_vec_string(command), None)?) +} + +/// +/// Lists all uploaded contracts +/// +fn list_code() -> Result> { + let command = vec!["query", "compute", "list-code"]; + + serde_json::from_value(secretcli_run(vec_str_to_vec_string(command), None)?) +} + +pub fn list_contracts_by_code(code: String) -> Result> { + let command = vec!["query", "compute", "list-contract-by-code", &code]; + + serde_json::from_value(secretcli_run(vec_str_to_vec_string(command), None)?) +} + +fn trim_newline(s: &mut String) { + if s.ends_with('\n') { + s.pop(); + if s.ends_with('\r') { + s.pop(); + } + } +} + +/// +/// Displays an account from the keyring +/// +/// # Arguments +/// +/// * 'acc' - The requested account +/// +pub fn account_address(acc: &str) -> Result { + let command = vec_str_to_vec_string(vec!["keys", "show", "-a", acc]); + + let retry = 40; + let mut cli = Command::new("secretd".to_string()); + if !command.is_empty() { + cli.args(command); + } + + let mut result = cli.output().expect("Unexpected error"); + + // We wait cause sometimes the query/action takes a while + for _ in 0..retry { + if !result.stderr.is_empty() { + thread::sleep(time::Duration::from_secs(1)); + } else { + break; + } + result = cli.output().expect("Unexpected error"); + } + + let out = result.stdout; + + let mut s: String = String::from_utf8_lossy(&out).parse().unwrap(); + + // Sometimes the resulting string has a newline, so we trim that + trim_newline(&mut s); + + Ok(s) +} + +pub fn create_key_account(name: &str) -> Result<()> { + let command = vec_str_to_vec_string(vec!["keys", "add", name]); + + let retry = 40; + let mut cli = Command::new("secretd".to_string()); + if !command.is_empty() { + cli.args(command); + } + + let mut result = cli.output().expect("Unexpected error"); + + // We wait cause sometimes the query/action takes a while + for _ in 0..retry { + if !result.stderr.is_empty() { + thread::sleep(time::Duration::from_secs(1)); + } else { + break; + } + result = cli.output().expect("Unexpected error"); + } + + Ok(()) +} + +/// +/// Instantiate a contract +/// +/// # Arguments +/// +/// * 'code_id' - The contract to interact with +/// * 'msg' - The init msg to serialize +/// * 'label' - The contract label +/// * 'sender' - Msg sender +/// * 'gas' - Gas price to use, defaults to 8000000 +/// * 'backend' - Keyring backend defaults to none +/// +fn instantiate_contract( + contract: &NetContract, + msg: Init, + label: &str, + sender: &str, + gas: Option<&str>, + backend: Option<&str>, +) -> Result { + let message = serde_json::to_string(&msg)?; + + let mut command = vec![ + "tx", + "compute", + "instantiate", + &contract.id, + &message, + "--from", + sender, + "--label", + label, + "--gas", + ]; + + command.push(match gas { + None => "10000000", + Some(gas) => gas, + }); + + if let Some(backend) = backend { + command.push("--keyring-backend"); + command.push(backend); + } + + command.push("-y"); + + let response: TxResponse = + serde_json::from_value(secretcli_run(vec_str_to_vec_string(command), None)?)?; + + Ok(response) +} + +/// +/// Store the given contract and return the stored contract information +/// +/// * 'contract_file' - Contract file to store +/// * 'sender' - Msg sender +/// * 'store_gas' - Gas price to use when storing the contract, defaults to 10000000 +/// * 'backend' - Keyring backend defaults to none +/// +pub fn store_and_return_contract( + contract_file: &str, + sender: &str, + store_gas: Option<&str>, + backend: Option<&str>, +) -> Result { + let store_response = store_contract(contract_file, Option::from(&*sender), store_gas, backend)?; + let store_query = query_hash(store_response.txhash)?; + let mut contract = StoredContract { + id: "".to_string(), + code_hash: "".to_string(), + }; + + for attribute in &store_query.logs[0].events[0].attributes { + if attribute.msg_key == "code_id" { + contract.id = attribute.value.clone(); + break; + } + } + + let listed_contracts = list_code()?; + + for item in listed_contracts { + if item.id.to_string() == contract.id { + contract.code_hash = item.data_hash; + break; + } + } + + Ok(contract) +} + +/// +/// Allows contract init to be used in test scripts +/// +/// # Arguments +/// +/// * `msg` - Contract's init message +/// * 'contract_file' - The contract to interact with +/// * 'label' - The contract label +/// * 'sender' - Msg sender - must be registered in keyring +/// * 'store_gas' - Gas price to use when storing the contract, defaults to 10000000 +/// * 'init_gas' - Gas price to use when initializing the contract, defaults to 8000000 +/// * 'backend' - Keyring backend defaults to none +/// * `report` - Records the contract`s message and instantiation price +/// +pub fn init( + msg: &Message, + contract_file: &str, + label: &str, + sender: &str, + store_gas: Option<&str>, + init_gas: Option<&str>, + backend: Option<&str>, + report: &mut Vec, +) -> Result { + io::stdout().flush().unwrap(); + let store_response = store_contract(contract_file, Option::from(&*sender), store_gas, backend)?; + let store_query = query_hash(store_response.txhash)?; + let mut contract = NetContract { + label: label.to_string(), + id: "".to_string(), + address: "".to_string(), + code_hash: "".to_string(), + }; + + // Look for the code ID + for attribute in &store_query.logs[0].events[0].attributes { + if attribute.msg_key == "code_id" { + contract.id = attribute.value.clone(); + break; + } + } + + // Instantiate and get the info + let tx = instantiate_contract(&contract, msg, label, sender, init_gas, backend)?; + let init_query = query_hash(tx.txhash)?; + + // Include the instantiation info in the report + report.push(Report { + msg_type: "Instantiate".to_string(), + message: serde_json::to_string(&msg)?, + gas_used: init_query.gas_used, + }); + + // Look for the contract's address + for attribute in &init_query.logs[0].events[0].attributes { + if attribute.msg_key == "contract_address" { + contract.address = attribute.value.clone(); + break; + } + } + // Look for the contract's code hash + let listed_contracts = list_code()?; + + // Find the code_hash + for item in listed_contracts { + if item.id.to_string() == contract.id { + contract.code_hash = item.data_hash; + break; + } + } + Ok(contract) +} + +/// +/// Executes a contract's handle +/// +/// # Arguments +/// +/// * 'contract' - The contract to interact with +/// * 'msg' - The handle msg to serialize +/// * 'sender' - Msg sender +/// * 'gas' - Gas price to use, defaults to 8000000 +/// * 'backend' - Keyring backend defaults to none +/// * 'amount' - Included L1 tokens to send, defaults to none +/// +fn execute_contract( + contract: &NetContract, + msg: Handle, + sender: &str, + gas: Option<&str>, + backend: Option<&str>, + amount: Option<&str>, + max_tries: Option, +) -> Result { + let message = serde_json::to_string(&msg)?; + + let mut command = vec![ + "tx", + "compute", + "execute", + &contract.address, + &message, + "--from", + sender, + "--gas", + ]; + + command.push(match gas { + None => "800000", + Some(gas) => gas, + }); + + if let Some(backend) = backend { + command.push("--keyring-backend"); + command.push(backend); + } + + if let Some(amount) = amount { + command.push("--amount"); + command.push(amount); + } + + command.push("-y"); + + let response: TxResponse = + serde_json::from_value(secretcli_run(vec_str_to_vec_string(command), max_tries)?)?; + + Ok(response) +} + +/// +/// Allows contract handle enums to be used in test scripts +/// +/// # Arguments +/// +/// * `msg` - ExecuteMsg +/// * 'contract' - The contract to interact with +/// * 'sender' - Msg sender +/// * 'gas' - Gas price to use, defaults to 8000000 +/// * 'backend' - Keyring backend defaults to none +/// * 'amount' - Included L1 tokens to send, defaults to none +/// * `report` - Records the contract`s message and handle price +/// +pub fn handle( + msg: &Message, + contract: &NetContract, + sender: &str, + gas: Option<&str>, + backend: Option<&str>, + amount: Option<&str>, + report: &mut Vec, + max_tries: Option, +) -> Result<(TxCompute, TxQuery)> { + let tx = execute_contract(contract, msg, sender, gas, backend, amount, max_tries)?; + + let computed_response = compute_hash(tx.txhash.clone())?; + let queried_response = query_hash(tx.txhash)?; + + // Include the instantiation info in the report + report.push(Report { + msg_type: "Handle".to_string(), + message: serde_json::to_string(&msg)?, + gas_used: queried_response.gas_used.clone(), + }); + + Ok((computed_response, queried_response)) +} + +/// +/// Queries a given contract +/// +/// # Arguments +/// +/// * 'contract' - The contract to query +/// * 'msg' - The query to serialize, must have serde::Serialized +/// +pub fn query( + contract: &NetContract, + msg: Query, + max_tries: Option, +) -> Result { + let command = vec_str_to_vec_string(vec![ + "query", + "compute", + "query", + &contract.address, + &serde_json::to_string(&msg)?, + ]); + + let response: Result = serde_json::from_value(secretcli_run(command, max_tries)?); + response +} + +/// +/// Create a signed permit +/// +/// # Arguments +/// +/// * 'tx' - The message to sign +/// * 'signer' - The key of the signer +/// +pub fn create_permit(tx: Tx, signer: &str) -> Result { + let msg = serde_json::to_string(&tx)?; + + // send to a file + let mut file = File::create("./tx_to_sign").unwrap(); + file.write_all(msg.as_bytes()).unwrap(); + + let command = vec!["tx", "sign-doc", "tx_to_sign", "--from", signer]; + + let response: SignedTx = + serde_json::from_value(secretcli_run(vec_str_to_vec_string(command), None)?)?; + + Ok(response) +} diff --git a/packages/shade_protocol/.cargo/config b/packages/shade_protocol/.cargo/config new file mode 100644 index 0000000..882fe08 --- /dev/null +++ b/packages/shade_protocol/.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/packages/shade_protocol/Cargo.toml b/packages/shade_protocol/Cargo.toml new file mode 100644 index 0000000..9e307df --- /dev/null +++ b/packages/shade_protocol/Cargo.toml @@ -0,0 +1,141 @@ +[package] +name = "shade-protocol" +version = "0.1.0" +authors = [ + "Guy Garcia ", + "Jackson Swenson ", + "Kyle Wahlberg ", + "Jack Sisson ", +] +edition = "2018" + +[[bin]] +name = "schemas" +path = "src/schemas.rs" +# Must have all of the contract_interfaces +required-features = [ + "admin", "airdrop", "bonds", "dao", "dex", "governance-impl", "mint", "oracles", + "peg_stability", "query_auth", "sky", "snip20", "staking", "mint_router", "snip20_migration", +] + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["utils"] + +# Utils +utils = ["chrono"] +dao-utils = ["snip20"] +errors = [] +flexible_msg = [] +math = [] +storage = ["dao-utils"] +storage_plus = ["storage", "dep:secret-storage-plus", "chrono"] +query_auth_lib = ["dep:query-authentication"] +multi-test = ["dep:secret-multi-test", "dep:anyhow", "interface"] + +# Implementing new contracts: +# Contracts packages are divided by two different features, the interface and the implementation +# When creating a new contract; try to keep all interface (Init, Handle and Query) related content in the `mod.rs` file +# and move all implementation related content into separate files inside the package. +# When defining a new interface, first create a feature that imports this: +interface = ["utils", "errors"] +# Like so +# contract_name = ["interface"] +# Then add the following inside `/src/contract_interfaces/mod.rs` +# #[cfg(feature = "contract_name")] +# pub mod contract_name; + +# For the implementation you need to import this in your feature and prepend the feature name with `_impl` +implementation = ["storage_plus", "storage", "admin"] +# Like so +# contract_name_impl = ["contract_name", "implementation"] + +# TODO: Normalize usage, some features are using - while others use _ + +# Templates +dex = ["math", "snip20", "mint", "band", "oracles"] +band = ["interface"] +secretswap = ["interface"] +sienna = ["interface", "math"] + +# Protocol contracts NOTE: interfaces that have other interfaces as features already automatically have `interface` as a feature +airdrop = ["query_auth", "snip20"] +basic_staking = ["snip20"] +bonds = ["airdrop", "snip20"] +governance = ["query_auth", "flexible_msg"] +mint = ["snip20"] +#liability_mint = ["snip20", "adapter", "dao"] +mint_router = ["snip20"] +oracles = ["snip20", "dex"] +scrt_staking = ["adapter", "treasury"] +stkd_scrt = ["adapter"] +treasury = ["adapter", "dao-utils"] +treasury_manager = ["adapter"] +# rewards_emission = ["adapter"] +# lp_shdswap = ["interface"] +adapter = ["interface"] +manager = ["interface"] +snip20 = ["query_auth_impl", "dep:base64"] +query_auth = ["interface", "query_auth_lib", "dep:remain"] +snip20_staking = ["interface", "implementation"] +sky = ["snip20", "dex", "dao"] +dao = ["interface", "cosmwasm-std/staking"] +admin = ["interface"] +peg_stability = ["sky-utils", "adapter"] +snip20_migration = [] + +chrono = ["dep:chrono"] + +stargate = ["cosmwasm-std/stargate"] +staking = ["cosmwasm-std/staking"] + +stkd = [] +mock = [] + +# Protocol Implementation Contracts +# Used in internal smart contracts +governance-impl = ["implementation", "governance"] +snip20-impl = ["snip20", "query_auth_impl"] +query_auth_impl = ["implementation", "query_auth", "dep:base64"] +sky-utils = ["implementation", "sky"] +admin_impl = ["implementation", "admin"] + +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +debug-print = [] # TODO: remove this from all cargo configs + +[dependencies] +cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" } +cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.0.0" } +cosmwasm-schema = "1.1.5" +contract-derive = { version = "0.1.0", path = "../contract_derive" } + +schemars = "0.8.9" +serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] } +thiserror = "1.0" + +secret-storage-plus = { git = "https://github.com/securesecrets/secret-plus-utils", tag = "v0.1.1", optional = true, features = [] } + +# Testing +anyhow = { version = "1", optional = true } + +chrono = { version = "=0.4.19", optional = true } +base64 = { version = "0.12.3", optional = true } +#query-authentication = {git = "https://github.com/securesecrets/query-authentication", tag = "v1.3.0", optional = true } +query-authentication = { git = "https://github.com/securesecrets/query-authentication", branch = "cosmwasm_v1_upgrade", optional = true } +remain = { version = "0.2.2", optional = true } +subtle = { version = "2.2.3", default-features = false } +sha2 = { version = "0.9.1", default-features = false } +rand_chacha = { version = "0.2.2", default-features = false } +rand_core = { version = "0.5.1", default-features = false } + +# for EnumIter +strum = "0.24" +strum_macros = "0.24" +const_format = "0.2.26" +#strum = { version = "0.24", features = ["derive"] } +[target.'cfg(not(target_arch="wasm32"))'.dependencies] +secret-multi-test = { git = "https://github.com/securesecrets/secret-plus-utils", version = "0.13.4", optional = true } diff --git a/packages/shade_protocol/src/contract_interfaces/admin/errors.rs b/packages/shade_protocol/src/contract_interfaces/admin/errors.rs new file mode 100644 index 0000000..1ab53bb --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/admin/errors.rs @@ -0,0 +1,74 @@ +use crate::{ + impl_into_u8, + utils::errors::{build_string, CodeType, DetailedError}, +}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::StdError; + +#[cw_serde] +#[repr(u8)] +pub enum Error { + UnregisteredAdmin, + UnauthorizedAdmin, + UnauthorizedSuper, + NoPermissions, + IsShutdown, + IsUnderMaintenance, + InvalidPermissionFormat, +} + +impl_into_u8!(Error); + +impl CodeType for Error { + fn to_verbose(&self, context: &Vec<&str>) -> String { + build_string( + match self { + Error::UnregisteredAdmin => "{} has not been registered as an admin", + Error::UnauthorizedAdmin => "{} does not have this permissions - {}", + Error::UnauthorizedSuper => "{} is not the authorized super admin", + Error::NoPermissions => "There are not permissions for {}", + Error::IsShutdown => { + "Contract is currently shutdown. It must be turned on for any changes to be made or any permissions to be validates" + } + Error::IsUnderMaintenance => { + "Contract is under maintenance. Oly registry updated may be made. Permission validation is disabled." + } + Error::InvalidPermissionFormat => { + "{} must be > 10 characters and only contains 0-9, A-Z, and underscores" + } + }, + context, + ) + } +} + +const ADMIN_TARGET: &str = "admin"; + +pub fn unregistered_admin(address: &str) -> StdError { + DetailedError::from_code(ADMIN_TARGET, Error::UnregisteredAdmin, vec![address]).to_error() +} + +pub fn unauthorized_admin(address: &str, permission: &str) -> StdError { + DetailedError::from_code(ADMIN_TARGET, Error::UnauthorizedAdmin, vec![ + address, permission, + ]) + .to_error() +} +pub fn unauthorized_super(super_admin: &str) -> StdError { + DetailedError::from_code(ADMIN_TARGET, Error::UnauthorizedSuper, vec![super_admin]).to_error() +} +pub fn no_permission(user: &str) -> StdError { + DetailedError::from_code(ADMIN_TARGET, Error::NoPermissions, vec![user]).to_error() +} +pub fn is_shutdown() -> StdError { + DetailedError::from_code(ADMIN_TARGET, Error::IsShutdown, vec![]).to_error() +} +pub fn is_under_maintenance() -> StdError { + DetailedError::from_code(ADMIN_TARGET, Error::IsUnderMaintenance, vec![]).to_error() +} +pub fn invalid_permission_format(permission: &str) -> StdError { + DetailedError::from_code(ADMIN_TARGET, Error::InvalidPermissionFormat, vec![ + permission, + ]) + .to_error() +} diff --git a/packages/shade_protocol/src/contract_interfaces/admin/helpers.rs b/packages/shade_protocol/src/contract_interfaces/admin/helpers.rs new file mode 100644 index 0000000..e6a3286 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/admin/helpers.rs @@ -0,0 +1,80 @@ +use crate::{ + admin::{errors::unauthorized_admin, QueryMsg, ValidateAdminPermissionResponse}, + utils::Query, + Contract, +}; +use cosmwasm_std::{QuerierWrapper, StdResult}; + +pub fn validate_admin + Clone>( + querier: &QuerierWrapper, + permission: AdminPermissions, + user: T, + admin_auth: &Contract, +) -> StdResult<()> { + if admin_is_valid(querier, permission.clone(), user.clone(), admin_auth)? { + Ok(()) + } else { + Err(unauthorized_admin(&user.into(), &permission.into_string())) + } +} + +pub fn admin_is_valid>( + querier: &QuerierWrapper, + permission: AdminPermissions, + user: T, + admin_auth: &Contract, +) -> StdResult { + let admin_resp: StdResult = + QueryMsg::ValidateAdminPermission { + permission: permission.into_string(), + user: user.into(), + } + .query(querier, admin_auth); + + match admin_resp { + Ok(resp) => Ok(resp.has_permission), + Err(err) => Err(err), + } +} + +#[derive(Clone)] +pub enum AdminPermissions { + QueryAuthAdmin, + ScrtStakingAdmin, + TreasuryManager, + TreasuryAdmin, + StabilityAdmin, + SkyAdmin, + LendAdmin, + OraclesAdmin, + OraclesPriceBot, + SilkAdmin, + ShadeSwapAdmin, + StakingAdmin, + DerivativeAdmin, + Snip20MigrationAdmin, +} + +// NOTE: SHADE_{CONTRACT_NAME}_{CONTRACT_ROLE}_{POTENTIAL IDs} + +impl AdminPermissions { + pub fn into_string(self) -> String { + match self { + AdminPermissions::QueryAuthAdmin => "SHADE_QUERY_AUTH_ADMIN", + AdminPermissions::ScrtStakingAdmin => "SHADE_SCRT_STAKING_ADMIN", + AdminPermissions::TreasuryManager => "SHADE_TREASURY_MANAGER", + AdminPermissions::TreasuryAdmin => "SHADE_TREASURY_ADMIN", + AdminPermissions::StabilityAdmin => "SHADE_STABILITY_ADMIN", + AdminPermissions::SkyAdmin => "SHADE_SKY_ADMIN", + AdminPermissions::LendAdmin => "SHADE_LEND_ADMIN", + AdminPermissions::OraclesAdmin => "SHADE_ORACLES_ADMIN", + AdminPermissions::OraclesPriceBot => "SHADE_ORACLES_PRICE_BOT", + AdminPermissions::SilkAdmin => "SHADE_SILK_ADMIN", + AdminPermissions::ShadeSwapAdmin => "SHADE_SWAP_ADMIN", + AdminPermissions::StakingAdmin => "SHADE_STAKING_ADMIN", + AdminPermissions::DerivativeAdmin => "SHADE_DERIVATIVE_ADMIN", + AdminPermissions::Snip20MigrationAdmin => "SNIP20_MIGRATION_ADMIN", + } + .to_string() + } +} diff --git a/packages/shade_protocol/src/contract_interfaces/admin/mod.rs b/packages/shade_protocol/src/contract_interfaces/admin/mod.rs new file mode 100644 index 0000000..4c6b8ae --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/admin/mod.rs @@ -0,0 +1,112 @@ +use crate::{ + admin::errors::{is_shutdown, is_under_maintenance}, + utils::{ExecuteCallback, InstantiateCallback, Query}, +}; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, StdResult}; + +pub mod errors; +pub mod helpers; + +#[cw_serde] +pub enum AdminAuthStatus { + Active, + Maintenance, + Shutdown, +} + +impl AdminAuthStatus { + // Throws an error if status is under maintenance + pub fn not_under_maintenance(&self) -> StdResult<&Self> { + if self.eq(&AdminAuthStatus::Maintenance) { + return Err(is_under_maintenance()); + } + Ok(self) + } + + // Throws an error if status is shutdown + pub fn not_shutdown(&self) -> StdResult<&Self> { + if self.eq(&AdminAuthStatus::Shutdown) { + return Err(is_shutdown()); + } + Ok(self) + } +} + +#[cw_serde] +pub struct InstantiateMsg { + pub super_admin: Option, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + UpdateRegistry { action: RegistryAction }, + UpdateRegistryBulk { actions: Vec }, + TransferSuper { new_super: String }, + SelfDestruct {}, + ToggleStatus { new_status: AdminAuthStatus }, +} + +#[cw_serde] +pub enum RegistryAction { + RegisterAdmin { + user: String, + }, + GrantAccess { + permissions: Vec, + user: String, + }, + RevokeAccess { + permissions: Vec, + user: String, + }, + DeleteAdmin { + user: String, + }, +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(ConfigResponse)] + GetConfig {}, + #[returns(AdminsResponse)] + GetAdmins {}, + #[returns(PermissionsResponse)] + GetPermissions { user: String }, + #[returns(ValidateAdminPermissionResponse)] + ValidateAdminPermission { permission: String, user: String }, +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub struct ConfigResponse { + pub super_admin: Addr, + pub status: AdminAuthStatus, +} + +#[cw_serde] +pub struct PermissionsResponse { + pub permissions: Vec, +} + +#[cw_serde] +pub struct AdminsResponse { + pub admins: Vec, +} + +#[cw_serde] +pub struct ValidateAdminPermissionResponse { + pub has_permission: bool, +} diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs new file mode 100644 index 0000000..287977a --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs @@ -0,0 +1,99 @@ +use crate::contract_interfaces::airdrop::errors::permit_rejected; +use crate::c_std::Uint128; +use crate::c_std::{Addr, StdResult, Api}; +use crate::query_authentication::{ + permit::{bech32_to_canonical, Permit}, + viewing_keys::ViewingKey, +}; + +use cosmwasm_schema::{cw_serde}; + +#[cw_serde] +pub struct Account { + pub addresses: Vec, + pub total_claimable: Uint128, +} + +impl Default for Account { + fn default() -> Self { + Self { + addresses: vec![], + total_claimable: Uint128::zero(), + } + } +} + +// Used for querying account information +pub type AccountPermit = Permit; + +#[remain::sorted] +#[cw_serde] +pub struct AccountPermitMsg { + pub contract: Addr, + pub key: String, +} + +#[remain::sorted] +#[cw_serde] +pub struct FillerMsg { + pub coins: Vec, + pub contract: String, + pub execute_msg: EmptyMsg, + pub sender: String, +} + +impl Default for FillerMsg { + fn default() -> Self { + Self { + coins: vec![], + contract: "".to_string(), + sender: "".to_string(), + execute_msg: EmptyMsg {}, + } + } +} + +#[remain::sorted] +#[cw_serde] +pub struct EmptyMsg {} + +// Used to prove ownership over IBC addresses +pub type AddressProofPermit = Permit; + +pub fn authenticate_ownership(api: &dyn Api, permit: &AddressProofPermit, permit_address: &str) -> StdResult<()> { + let signer_address = permit + .validate(api, Some("wasm/MsgExecuteContract".to_string()))? + .as_canonical(); + + if signer_address != bech32_to_canonical(permit_address) { + return Err(permit_rejected()); + } + + Ok(()) +} + +#[remain::sorted] +#[cw_serde] +pub struct AddressProofMsg { + // Address is necessary since we have other network permits present + pub address: Addr, + // Reward amount + pub amount: Uint128, + // Used to prevent permits from being used elsewhere + pub contract: Addr, + // Index of the address in the leaves array + pub index: u32, + // Used to identify permits + pub key: String, +} + +#[cw_serde] +pub struct AccountKey(pub String); + +impl ToString for AccountKey { + fn to_string(&self) -> String { + self.0.clone() + } +} + +impl ViewingKey<32> for AccountKey {} diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/claim_info.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/claim_info.rs new file mode 100644 index 0000000..64e05a1 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/claim_info.rs @@ -0,0 +1,8 @@ +use crate::c_std::{Uint128, Addr}; +use cosmwasm_schema::{cw_serde}; + +#[cw_serde] +pub struct RequiredTask { + pub address: Addr, + pub percent: Uint128, +} diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/errors.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/errors.rs new file mode 100644 index 0000000..ab9defb --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/errors.rs @@ -0,0 +1,167 @@ +use crate::{ + c_std::StdError, + impl_into_u8, + utils::errors::{build_string, CodeType, DetailedError}, +}; + +use cosmwasm_schema::cw_serde; + +#[cw_serde] +#[repr(u8)] +pub enum Error { + InvalidTaskPercentage, + InvalidDates, + PermitContractMismatch, + PermitKeyRevoked, + PermitRejected, + NotAdmin, + AccountAlreadyCreated, + AccountDoesntExist, + NothingToClaim, + DecayClaimed, + NoDecaySet, + ClaimAmountTooHigh, + AddressInAccount, + ExpectedMemo, + InvalidPartialTree, + AirdropNotStarted, + AirdropEnded, + InvalidViewingKey, + UnexpectedError, +} + +impl_into_u8!(Error); + +impl CodeType for Error { + fn to_verbose(&self, context: &Vec<&str>) -> String { + match self { + Error::InvalidTaskPercentage => { + build_string("Task total exceeds maximum of 100%, got {}", context) + } + Error::InvalidDates => build_string("{} ({}) cannot happen {} {} ({})", context), + Error::PermitContractMismatch => { + build_string("Permit is valid for {}, expected {}", context) + } + Error::PermitKeyRevoked => build_string("Permit key {} revoked", context), + Error::PermitRejected => build_string("Permit was rejected", context), + Error::NotAdmin => build_string("Can only be accessed by {}", context), + Error::AccountAlreadyCreated => build_string("Account already exists", context), + Error::AccountDoesntExist => build_string("Account does not exist", context), + Error::NothingToClaim => build_string("Amount to claim is 0", context), + Error::DecayClaimed => build_string("Decay already claimed", context), + Error::NoDecaySet => build_string("Decay has not been set", context), + Error::ClaimAmountTooHigh => { + build_string("Claim {} is higher than the maximum claim of {}", context) + } + Error::AddressInAccount => build_string("{} has already been claimed", context), + Error::ExpectedMemo => build_string("Expected a memo", context), + Error::InvalidPartialTree => build_string("Partial tree is not valid", context), + Error::AirdropNotStarted => { + build_string("Airdrop starts in {}, its currently {}", context) + } + Error::AirdropEnded => build_string("Airdrop ended on {}, its currently {}", context), + Error::InvalidViewingKey => build_string("Provided viewing key is invalid", context), + Error::UnexpectedError => build_string("Something unexpected happened", context), + } + } +} + +const AIRDROP_TARGET: &str = "airdrop"; + +pub fn invalid_task_percentage(percentage: &str) -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::InvalidTaskPercentage, vec![ + percentage, + ]) + .to_error() +} + +pub fn invalid_dates( + item_a: &str, + item_a_amount: &str, + precedence: &str, + item_b: &str, + item_b_amount: &str, +) -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::InvalidDates, vec![ + item_a, + item_a_amount, + precedence, + item_b, + item_b_amount, + ]) + .to_error() +} + +pub fn permit_contract_mismatch(contract: &str, expected: &str) -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::PermitContractMismatch, vec![ + contract, expected, + ]) + .to_error() +} + +pub fn permit_key_revoked(key: &str) -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::PermitKeyRevoked, vec![key]).to_error() +} + +pub fn permit_rejected() -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::PermitRejected, vec![]).to_error() +} + +pub fn not_admin(admin: &str) -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::NotAdmin, vec![admin]).to_error() +} + +pub fn account_already_created() -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::AccountAlreadyCreated, vec![]).to_error() +} + +pub fn account_does_not_exist() -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::AccountDoesntExist, vec![]).to_error() +} + +pub fn nothing_to_claim() -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::NothingToClaim, vec![]).to_error() +} + +pub fn decay_claimed() -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::DecayClaimed, vec![]).to_error() +} + +pub fn decay_not_set() -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::NoDecaySet, vec![]).to_error() +} + +pub fn claim_too_high(claim: &str, max: &str) -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::ClaimAmountTooHigh, vec![claim, max]).to_error() +} + +pub fn address_already_in_account(address: &str) -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::AddressInAccount, vec![address]).to_error() +} + +pub fn expected_memo() -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::ExpectedMemo, vec![]).to_error() +} + +pub fn invalid_partial_tree() -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::InvalidPartialTree, vec![]).to_error() +} + +pub fn airdrop_not_started(start: &str, current: &str) -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::AirdropNotStarted, vec![ + start, current, + ]) + .to_error() +} + +pub fn airdrop_ended(end: &str, current: &str) -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::AirdropEnded, vec![end, current]).to_error() +} + +pub fn invalid_viewing_key() -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::InvalidViewingKey, vec![]).to_error() +} + +pub fn unexpected_error() -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::UnexpectedError, vec![]).to_error() +} diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs new file mode 100644 index 0000000..54d4d1b --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs @@ -0,0 +1,230 @@ +pub mod account; +pub mod claim_info; +pub mod errors; + +use crate::{ + c_std::{Addr, Binary, Uint128}, + contract_interfaces::airdrop::{ + account::{AccountPermit, AddressProofPermit}, + claim_info::RequiredTask, + }, + utils::{asset::Contract, generic_response::ResponseStatus}, +}; + +use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub struct Config { + pub admin: Addr, + // Used for permit validation when querying + pub contract: Addr, + // Where the decayed tokens will be dumped, if none then nothing happens + pub dump_address: Option, + // The snip20 to be minted + pub airdrop_snip20: Contract, + // An optional, second snip20 to be minted + pub airdrop_snip20_optional: Option, + // Airdrop amount + pub airdrop_amount: Uint128, + // Required tasks + // pub task_claim: Vec, + // Checks if airdrop has started / ended + pub start_date: u64, + // Airdrop stops at end date if there is one + pub end_date: Option, + // Starts to decay at this date + // pub decay_start: Option, + // This is necessary to validate the airdrop information + // tree root + pub merkle_root: Binary, + // tree height + pub total_accounts: u32, + // max possible reward amount; used to prevent collision possibility + pub max_amount: Uint128, + // Protects from leaking user information by limiting amount detail + pub query_rounding: Uint128, + + pub claim_msg_plaintext: String, +} + +#[cw_serde] +pub struct InstantiateMsg { + pub admin: Option, + // Where the decayed tokens will be dumped, if none then nothing happens + pub dump_address: Option, + pub airdrop_token: Contract, + // Airdrop amount + pub airdrop_amount: Uint128, + // An optional, second snip20 to be minted + pub airdrop_snip20_optional: Option, + // The airdrop time limit + pub start_date: Option, + // Can be set to never end + pub end_date: Option, + // Starts to decay at this date + // pub decay_start: Option, + // Base64 encoded version of the tree root + pub merkle_root: Binary, + // Root height + pub total_accounts: u32, + // Max possible reward amount + pub max_amount: Uint128, + // Default gifted amount + // pub default_claim: Uint128, + // The task related claims + // pub task_claim: Vec, + // Protects from leaking user information by limiting amount detail + pub query_rounding: Uint128, + /// {wallet} + pub claim_msg_plaintext: String, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum sExecuteMsg { + UpdateConfig { + admin: Option, + dump_address: Option, + query_rounding: Option, + start_date: Option, + end_date: Option, + decay_start: Option, + padding: Option, + }, + // AddTasks { + // tasks: Vec, + // padding: Option, + // }, + // CompleteTask { + // address: Addr, + // padding: Option, + // }, + Account { + addresses: Vec, + partial_tree: Vec, + padding: Option, + }, + DisablePermitKey { + key: String, + padding: Option, + }, + SetViewingKey { + key: String, + padding: Option, + }, + Claim { + amount: Uint128, + eth_pubkey: String, + eth_sig: String, + proof: Vec, + padding: Option, + }, + // ClaimDecay { + // padding: Option, + // }, +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + UpdateConfig { + status: ResponseStatus, + }, + // AddTask { + // status: ResponseStatus, + // }, + // CompleteTask { + // status: ResponseStatus, + // }, + // Account { + // status: ResponseStatus, + // // Total eligible + // total: Uint128, + // // Total claimed + // claimed: Uint128, + // finished_tasks: Vec, + // // Addresses claimed + // addresses: Vec, + // }, + DisablePermitKey { + status: ResponseStatus, + }, + SetViewingKey { + status: ResponseStatus, + }, + Claim { + status: ResponseStatus, + // Total eligible + total: Uint128, + // Total claimed + claimed: Uint128, + // finished_tasks: Vec, + // Addresses claimed + addresses: Vec, + }, + // ClaimDecay { + // status: ResponseStatus, + // }, +} + +#[cw_serde] +pub enum QueryMsg { + Config {}, + Dates { + current_date: Option, + }, + TotalClaimed {}, + // Account { + // permit: AccountPermit, + // current_date: Option, + // }, + AccountWithKey { + account: Addr, + key: String, + current_date: Option, + }, +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + Config { + config: Config, + }, + Dates { + start: u64, + end: Option, + decay_start: Option, + decay_factor: Option, + }, + TotalClaimed { + claimed: Uint128, + }, + Account { + // Total eligible + total: Uint128, + // Total claimed + claimed: Uint128, + // Total unclaimed but available + unclaimed: Uint128, + finished_tasks: Vec, + // Addresses claimed + addresses: Vec, + }, +} + +#[cw_serde] +pub struct AccountVerification { + pub account: Addr, + pub claimed: bool, +} diff --git a/packages/shade_protocol/src/contract_interfaces/basic_staking/mod.rs b/packages/shade_protocol/src/contract_interfaces/basic_staking/mod.rs new file mode 100644 index 0000000..0a0b363 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/basic_staking/mod.rs @@ -0,0 +1,299 @@ +use crate::{ + c_std::{Addr, Binary, Decimal, Uint128}, + query_auth::{ + helpers::{authenticate_permit, authenticate_vk, PermitAuthentication}, + QueryPermit, + }, + utils::{ + asset::{Contract, RawContract}, + generic_response::ResponseStatus, + }, +}; + +use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub struct Config { + pub admin_auth: Contract, + pub query_auth: Contract, + pub airdrop: Option, + pub unbond_period: Uint128, + // Number of non-admin pools allowed + pub max_user_pools: Uint128, +} + +#[cw_serde] +pub struct StakingInfo { + pub stake_token: Addr, + pub total_staked: Uint128, + pub unbond_period: Uint128, + pub reward_pools: Vec, +} + +// For the Snip20 msg field +#[cw_serde] +pub enum Action { + // Deposit rewards to be distributed + Stake { + compound: Option, + airdrop_task: Option, + }, + Rewards { + start: Uint128, + end: Uint128, + }, +} + +#[cw_serde] +pub struct Unbonding { + pub id: Uint128, + pub amount: Uint128, + pub complete: Uint128, +} + +#[cw_serde] +pub struct Reward { + pub token: Contract, + pub amount: Uint128, +} + +// Internal storage +#[cw_serde] +pub struct RewardPoolInternal { + pub id: Uint128, + pub amount: Uint128, + pub start: Uint128, + pub end: Uint128, + pub token: Contract, + pub rate: Uint128, + pub reward_per_token: Uint128, + pub claimed: Uint128, + pub last_update: Uint128, + pub creator: Addr, + pub official: bool, +} + +// Query returned data +#[cw_serde] +pub struct RewardPool { + pub id: Uint128, + pub amount: Uint128, + pub start: Uint128, + pub end: Uint128, + pub token: Contract, + pub rate: Uint128, + pub official: bool, +} + +#[cw_serde] +pub struct InstantiateMsg { + pub admin_auth: RawContract, + pub query_auth: RawContract, + pub airdrop: Option, + pub stake_token: RawContract, + pub unbond_period: Uint128, + pub max_user_pools: Uint128, + pub viewing_key: String, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + UpdateConfig { + admin_auth: Option, + query_auth: Option, + airdrop: Option, + unbond_period: Option, + max_user_pools: Option, + padding: Option, + }, + RegisterRewards { + token: RawContract, + padding: Option, + }, + Receive { + sender: Addr, + from: Addr, + amount: Uint128, + memo: Option, + msg: Option, + }, + Unbond { + amount: Uint128, + compound: Option, + padding: Option, + }, + Withdraw { + ids: Option>, + padding: Option, + }, + Claim { + padding: Option, + }, + Compound { + padding: Option, + }, + EndRewardPool { + id: Uint128, + force: Option, + padding: Option, + }, + AddTransferWhitelist { + user: String, + padding: Option, + }, + RemoveTransferWhitelist { + user: String, + padding: Option, + }, + TransferStake { + amount: Uint128, + recipient: String, + compound: Option, + padding: Option, + }, +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + Init { + status: ResponseStatus, + address: Addr, + }, + UpdateConfig { + status: ResponseStatus, + }, + // Receive Response + Stake { + staked: Uint128, + status: ResponseStatus, + }, + // Receive Response + Rewards { + status: ResponseStatus, + }, + Claim { + //TODO multiple denoms? + // claimed: Uint128, + status: ResponseStatus, + }, + Unbond { + id: Uint128, + unbonded: Uint128, + status: ResponseStatus, + }, + Withdraw { + withdrawn: Uint128, + status: ResponseStatus, + }, + Compound { + compounded: Uint128, + status: ResponseStatus, + }, + RegisterRewards { + status: ResponseStatus, + }, + EndRewardPool { + deleted: bool, + extracted: Uint128, + status: ResponseStatus, + }, + RemoveTransferWhitelist { + status: ResponseStatus, + }, + AddTransferWhitelist { + status: ResponseStatus, + }, + TransferStake { + transferred: Uint128, + status: ResponseStatus, + }, +} + +#[cw_serde] +pub struct AuthPermit {} + +#[cw_serde] +pub enum Auth { + ViewingKey { key: String, address: String }, + Permit(QueryPermit), +} + +#[cw_serde] +pub enum QueryMsg { + Config {}, + // TotalShares {}, + StakeToken {}, + StakingInfo {}, + TotalStaked {}, + RewardTokens {}, + // All reward pools in progress + RewardPools {}, + + Balance { + auth: Auth, + unbonding_ids: Option>, + }, + Staked { + auth: Auth, + }, + Rewards { + auth: Auth, + }, + Unbonding { + auth: Auth, + ids: Option>, + }, + TransferWhitelist {}, +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + Config { + config: Config, + }, + StakeToken { + token: Addr, + }, + StakingInfo { + info: StakingInfo, + }, + TotalStaked { + amount: Uint128, + }, + RewardTokens { + tokens: Vec, + }, + RewardPools { + rewards: Vec, + }, + Balance { + staked: Uint128, + rewards: Vec, + unbondings: Vec, + }, + Staked { + amount: Uint128, + }, + Rewards { + rewards: Vec, + }, + Unbonding { + unbondings: Vec, + }, + TransferWhitelist { + whitelist: Vec, + }, +} diff --git a/packages/shade_protocol/src/contract_interfaces/bonds/errors.rs b/packages/shade_protocol/src/contract_interfaces/bonds/errors.rs new file mode 100644 index 0000000..8966d2b --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/bonds/errors.rs @@ -0,0 +1,331 @@ +use crate::impl_into_u8; +use crate::utils::errors::{build_string, CodeType, DetailedError}; +use crate::c_std::Uint128; +use crate::c_std::{Addr, StdError}; + +use cosmwasm_schema::{cw_serde}; + +#[cw_serde] +#[repr(u8)] +pub enum Error { + BondEnded, + BondNotStarted, + BondLimitReached, + GlobalLimitReached, + MintExceedsLimit, + ContractNotActive, + NoBondFound, + NoPendingBonds, + PermitContractMismatch, + PermitRevoked, + BondLimitExceedsGlobalLimit, + BondingPeriodBelowMinimumTime, + BondDiscountAboveMaximumRate, + BondIssuanceExceedsAllowance, + NotLimitAdmin, + DepositPriceExceedsLimit, + IssuedPriceBelowMinimum, + SlippageToleranceExceeded, + Blacklisted, + IssuedAssetDeposit, + NotTreasuryBond, + NoBondsClaimable, + NotAdmin, + QueryAuthBadResponse, +} + +impl_into_u8!(Error); + +impl CodeType for Error { + fn to_verbose(&self, context: &Vec<&str>) -> String { + match self{ + Error::BondEnded => { + build_string("Bond ended on {}, it is currently {}", context) + } + Error::BondNotStarted => { + build_string("Bond begins on {}, it is currently {}", context) + } + Error::BondLimitReached => { + build_string("Bond opportunity is not available due to issuance limit of {} being reached", context) + } + Error::GlobalLimitReached => { + build_string("Bond issuance limit of {} has been reached", context) + } + Error::MintExceedsLimit => { + build_string("Mint amount of {} exceeds available mint of {}", context) + } + Error::ContractNotActive => { + build_string("Bonds contract is currently not active. Governance must activate the contract before functionality can resume.", context) + } + Error::NoBondFound => { + build_string("No bond opportunity found for deposit contract {}", context) + } + Error::NoPendingBonds => { + build_string("No pending bonds for user address {}", context) + } + Error::BondLimitExceedsGlobalLimit => { + build_string("Proposed bond issuance limit of {} exceeds available bond limit of {}", context) + } + Error::BondingPeriodBelowMinimumTime => { + build_string("Bonding period of {} is below minimum limit of {}", context) + } + Error::BondDiscountAboveMaximumRate => { + build_string("Bond discount of {} is above maximum limit of {}", context) + } + Error::BondIssuanceExceedsAllowance => { + build_string("Bond issuance limit of {} exceeds available allowance of {}", context) + } + Error::NotLimitAdmin => { + build_string("Global limit parameters can only be changed by the limit admin", context) + } + Error::DepositPriceExceedsLimit => { + build_string("Deposit asset price of {} exceeds limit price of {}, cannot enter bond opportunity", context) + } + Error::IssuedPriceBelowMinimum => { + build_string("Issued asset price of {} is below minimum value of {}, cannot enter opportunity", context) + } + Error::SlippageToleranceExceeded => { + build_string("Calculated issuance amount of {} is below minimum accepted value of {}", context) + } + Error::PermitContractMismatch => { + build_string("Permit isn't valid for {}", context) + } + Error::PermitRevoked => { + build_string("Permit is revoked for user {}", context) + } + Error::Blacklisted => { + build_string("Cannot enter bond opportunity, sender address of {} is blacklisted", context) + } + Error::IssuedAssetDeposit => { + build_string("Cannot deposit using this contract's issued asset", context) + } + Error::NotTreasuryBond => { + build_string("Cannot perform function since this is not a treasury bond", context) + } + Error::NoBondsClaimable => { + build_string("Pending bonds not redeemable, nothing claimed", context) + } + Error::NotAdmin => { + build_string("Not registered as admin address via Shade-Admin", context) + } + Error::QueryAuthBadResponse => { + build_string("Query Authentication returned unrecognized response, cannot access information", context) + } + } + } +} + +const BOND_TARGET: &str = "bond"; + +pub fn bond_not_started(start: u64, current: u64) -> StdError { + DetailedError::from_code( + BOND_TARGET, + Error::BondNotStarted, + vec![&start.to_string(), ¤t.to_string()], + ) + .to_error() +} + +pub fn bond_ended(end: u64, current: u64) -> StdError { + DetailedError::from_code( + BOND_TARGET, + Error::BondEnded, + vec![&end.to_string(), ¤t.to_string()], + ) + .to_error() +} + +pub fn bond_limit_reached(limit: Uint128) -> StdError { + let limit_string: String = limit.into(); + let limit_str: &str = limit_string.as_str(); + DetailedError::from_code(BOND_TARGET, Error::BondLimitReached, vec![limit_str]).to_error() +} + +pub fn global_limit_reached(limit: Uint128) -> StdError { + let limit_string: String = limit.into(); + let limit_str: &str = limit_string.as_str(); + DetailedError::from_code(BOND_TARGET, Error::GlobalLimitReached, vec![limit_str]).to_error() +} + +pub fn mint_exceeds_limit(mint_amount: Uint128, available: Uint128) -> StdError { + let mint_string: String = mint_amount.into(); + let mint_str = mint_string.as_str(); + let available_string: String = available.into(); + let available_str: &str = available_string.as_str(); + DetailedError::from_code( + BOND_TARGET, + Error::MintExceedsLimit, + vec![mint_str, available_str], + ) + .to_error() +} + +pub fn contract_not_active() -> StdError { + DetailedError::from_code(BOND_TARGET, Error::ContractNotActive, vec![""]).to_error() +} + +pub fn no_bond_found(deposit_asset_address: &str) -> StdError { + DetailedError::from_code( + BOND_TARGET, + Error::NoBondFound, + vec![deposit_asset_address], + ) + .to_error() +} + +pub fn no_pending_bonds(account_address: &str) -> StdError { + DetailedError::from_code(BOND_TARGET, Error::NoPendingBonds, vec![account_address]).to_error() +} + +pub fn bond_limit_exceeds_global_limit( + global_issuance_limit: Uint128, + global_total_issued: Uint128, + bond_issuance_limit: Uint128, +) -> StdError { + let available = global_issuance_limit + .checked_sub(global_total_issued) + .unwrap(); + let available_string = available.to_string(); + let available_str = available_string.as_str(); + let bond_limit_string = bond_issuance_limit.to_string(); + let bond_limit_str = bond_limit_string.as_str(); + DetailedError::from_code( + BOND_TARGET, + Error::BondLimitExceedsGlobalLimit, + vec![bond_limit_str, available_str], + ) + .to_error() +} + +pub fn bonding_period_below_minimum_time( + bond_period: u64, + global_minimum_bonding_period: u64, +) -> StdError { + let bond_period_string = bond_period.to_string(); + let bond_period_str = bond_period_string.as_str(); + let global_minimum_bonding_period_string = global_minimum_bonding_period.to_string(); + let global_minimum_bonding_period_str = global_minimum_bonding_period_string.as_str(); + DetailedError::from_code( + BOND_TARGET, + Error::BondingPeriodBelowMinimumTime, + vec![bond_period_str, global_minimum_bonding_period_str], + ) + .to_error() +} + +pub fn bond_discount_above_maximum_rate( + bond_discount: Uint128, + global_maximum_discount: Uint128, +) -> StdError { + let bond_discount_string = bond_discount.to_string(); + let bond_discount_str = bond_discount_string.as_str(); + let global_maximum_discount_string = global_maximum_discount.to_string(); + let global_maximum_discount_str = global_maximum_discount_string.as_str(); + DetailedError::from_code( + BOND_TARGET, + Error::BondDiscountAboveMaximumRate, + vec![bond_discount_str, global_maximum_discount_str], + ) + .to_error() +} + +pub fn bond_issuance_exceeds_allowance( + snip20_allowance: Uint128, + allocated_allowance: Uint128, + bond_limit: Uint128, +) -> StdError { + let available = snip20_allowance.checked_sub(allocated_allowance).unwrap(); + let available_string = available.to_string(); + let available_str = available_string.as_str(); + let bond_limit_string = bond_limit.to_string(); + let bond_limit_str = bond_limit_string.as_str(); + DetailedError::from_code( + BOND_TARGET, + Error::BondIssuanceExceedsAllowance, + vec![bond_limit_str, available_str], + ) + .to_error() +} + +pub fn not_limit_admin() -> StdError { + DetailedError::from_code(BOND_TARGET, Error::NotLimitAdmin, vec![]).to_error() +} + +pub fn deposit_price_exceeds_limit(deposit_price: Uint128, limit: Uint128) -> StdError { + let deposit_string = deposit_price.to_string(); + let deposit_str = deposit_string.as_str(); + let limit_string = limit.to_string(); + let limit_str = limit_string.as_str(); + DetailedError::from_code( + BOND_TARGET, + Error::DepositPriceExceedsLimit, + vec![deposit_str, limit_str], + ) + .to_error() +} + +pub fn issued_price_below_minimum(issued_price: Uint128, limit: Uint128) -> StdError { + let issued_string = issued_price.to_string(); + let issued_str = issued_string.as_str(); + let limit_string = limit.to_string(); + let limit_str = limit_string.as_str(); + DetailedError::from_code( + BOND_TARGET, + Error::IssuedPriceBelowMinimum, + vec![issued_str, limit_str], + ) + .to_error() +} + +pub fn slippage_tolerance_exceeded( + amount_to_issue: Uint128, + min_expected_amount: Uint128, +) -> StdError { + let issue_string = amount_to_issue.to_string(); + let issue_str = issue_string.as_str(); + let min_amount_string = min_expected_amount.to_string(); + let min_amount_str = min_amount_string.as_str(); + DetailedError::from_code( + BOND_TARGET, + Error::SlippageToleranceExceeded, + vec![issue_str, min_amount_str], + ) + .to_error() +} + +pub fn permit_contract_mismatch(expected: &str) -> StdError { + DetailedError::from_code( + BOND_TARGET, + Error::PermitContractMismatch, + vec![expected], + ) + .to_error() +} + +pub fn permit_revoked(user: &str) -> StdError { + DetailedError::from_code(BOND_TARGET, Error::PermitRevoked, vec![user]).to_error() +} + +pub fn blacklisted(address: Addr) -> StdError { + DetailedError::from_code(BOND_TARGET, Error::Blacklisted, vec![address.as_str()]).to_error() +} + +pub fn issued_asset_deposit() -> StdError { + DetailedError::from_code(BOND_TARGET, Error::IssuedAssetDeposit, vec![]).to_error() +} + +pub fn not_treasury_bond() -> StdError { + DetailedError::from_code(BOND_TARGET, Error::NotTreasuryBond, vec![]).to_error() +} + +pub fn no_bonds_claimable() -> StdError { + DetailedError::from_code(BOND_TARGET, Error::NoBondsClaimable, vec![]).to_error() +} + +pub fn not_admin() -> StdError { + DetailedError::from_code(BOND_TARGET, Error::NotAdmin, vec![]).to_error() +} + +pub fn query_auth_bad_response() -> StdError { + DetailedError::from_code(BOND_TARGET, Error::QueryAuthBadResponse, vec![]).to_error() +} \ No newline at end of file diff --git a/packages/shade_protocol/src/contract_interfaces/bonds/mod.rs b/packages/shade_protocol/src/contract_interfaces/bonds/mod.rs new file mode 100644 index 0000000..18a6042 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/bonds/mod.rs @@ -0,0 +1,283 @@ +pub mod errors; +pub mod rand; +pub mod utils; + +use crate::c_std::Env; +use cosmwasm_std::MessageInfo; + +use crate::{ + c_std::{Addr, Binary, Uint128}, + contract_interfaces::{ + bonds::{ + rand::{sha_256, Prng}, + utils::{ + create_hashed_password, + ct_slice_compare, + VIEWING_KEY_PREFIX, + VIEWING_KEY_SIZE, + }, + }, + query_auth::QueryPermit, + snip20::helpers::Snip20Asset, + }, + utils::{asset::Contract, generic_response::ResponseStatus}, +}; + +use crate::utils::ExecuteCallback; +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub struct Config { + pub limit_admin: Addr, + pub shade_admin: Contract, + pub oracle: Contract, + pub treasury: Addr, + pub issued_asset: Contract, + pub activated: bool, + pub bond_issuance_limit: Uint128, + pub bonding_period: u64, + pub discount: Uint128, + pub global_issuance_limit: Uint128, + pub global_minimum_bonding_period: u64, + pub global_maximum_discount: Uint128, + pub global_min_accepted_issued_price: Uint128, + pub global_err_issued_price: Uint128, + pub contract: Addr, + pub airdrop: Option, + pub query_auth: Contract, +} + +#[cw_serde] +pub struct InstantiateMsg { + pub limit_admin: Addr, + pub global_issuance_limit: Uint128, + pub global_minimum_bonding_period: u64, + pub global_maximum_discount: Uint128, + pub shade_admin: Contract, + pub oracle: Contract, + pub treasury: Addr, + pub issued_asset: Contract, + pub activated: bool, + pub bond_issuance_limit: Uint128, + pub bonding_period: u64, + pub discount: Uint128, + pub global_min_accepted_issued_price: Uint128, + pub global_err_issued_price: Uint128, + pub allowance_key_entropy: String, + pub airdrop: Option, + pub query_auth: Contract, +} + +#[cw_serde] +pub enum ExecuteMsg { + UpdateLimitConfig { + limit_admin: Option, + shade_admin: Option, + global_issuance_limit: Option, + global_minimum_bonding_period: Option, + global_maximum_discount: Option, + reset_total_issued: Option, + reset_total_claimed: Option, + padding: Option, + }, + UpdateConfig { + oracle: Option, + treasury: Option, + issued_asset: Option, + activated: Option, + bond_issuance_limit: Option, + bonding_period: Option, + discount: Option, + global_min_accepted_issued_price: Option, + global_err_issued_price: Option, + allowance_key: Option, + airdrop: Option, + query_auth: Option, + padding: Option, + }, + OpenBond { + deposit_asset: Contract, + start_time: u64, + end_time: u64, + bond_issuance_limit: Option, + bonding_period: Option, + discount: Option, + max_accepted_deposit_price: Uint128, + err_deposit_price: Uint128, + minting_bond: bool, + padding: Option, + }, + CloseBond { + deposit_asset: Contract, + padding: Option, + }, + Receive { + sender: Addr, + from: Addr, + amount: Uint128, + msg: Option, + padding: Option, + }, + Claim { + padding: Option, + }, +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + UpdateLimitConfig { + status: ResponseStatus, + }, + UpdateConfig { + status: ResponseStatus, + }, + Deposit { + status: ResponseStatus, + deposit_amount: Uint128, + pending_claim_amount: Uint128, + end_date: u64, + }, + Claim { + status: ResponseStatus, + amount: Uint128, + }, + OpenBond { + status: ResponseStatus, + deposit_contract: Contract, + start_time: u64, + end_time: u64, + bond_issuance_limit: Uint128, + bonding_period: u64, + discount: Uint128, + max_accepted_deposit_price: Uint128, + err_deposit_price: Uint128, + minting_bond: bool, + }, + ClosedBond { + status: ResponseStatus, + deposit_asset: Contract, + }, +} + +#[cw_serde] +pub enum QueryMsg { + Config {}, + BondOpportunities {}, + Account { permit: QueryPermit }, + DepositAddresses {}, + PriceCheck { asset: String }, + BondInfo {}, + CheckAllowance {}, + CheckBalance {}, +} + +#[cw_serde] +pub enum QueryAnswer { + Config { + config: Config, + }, + BondOpportunities { + bond_opportunities: Vec, + }, + Account { + pending_bonds: Vec, + }, + DepositAddresses { + deposit_addresses: Vec, + }, + PriceCheck { + price: Uint128, + }, + BondInfo { + global_total_issued: Uint128, + global_total_claimed: Uint128, + issued_asset: Snip20Asset, + global_min_accepted_issued_price: Uint128, + global_err_issued_price: Uint128, + }, + CheckAllowance { + allowance: Uint128, + }, + CheckBalance { + balance: Uint128, + }, +} + +#[cw_serde] +pub struct Account { + pub address: Addr, + pub pending_bonds: Vec, +} + +#[cw_serde] +pub struct SnipViewingKey(pub String); + +impl SnipViewingKey { + 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(info: &MessageInfo, env: &Env, 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 key = sha_256(&rand_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() + } +} + +#[cw_serde] +pub struct PendingBond { + pub deposit_denom: Snip20Asset, + pub end_time: u64, // Will be turned into a time via block time calculations + pub deposit_amount: Uint128, + pub deposit_price: Uint128, + pub claim_amount: Uint128, + pub claim_price: Uint128, + pub discount: Uint128, + pub discount_price: Uint128, +} + +// When users deposit and try to use the bond, a Bond Opportunity is selected via deposit denom +#[cw_serde] +pub struct BondOpportunity { + pub issuance_limit: Uint128, + pub amount_issued: Uint128, + pub deposit_denom: Snip20Asset, + pub start_time: u64, + pub end_time: u64, + pub bonding_period: u64, + pub discount: Uint128, + pub max_accepted_deposit_price: Uint128, + pub err_deposit_price: Uint128, + pub minting_bond: bool, +} + +#[cw_serde] +pub struct SlipMsg { + pub minimum_expected_amount: Uint128, +} diff --git a/packages/shade_protocol/src/contract_interfaces/bonds/rand.rs b/packages/shade_protocol/src/contract_interfaces/bonds/rand.rs new file mode 100644 index 0000000..11da813 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/bonds/rand.rs @@ -0,0 +1,74 @@ +use rand_chacha::ChaChaRng; +use rand_core::{RngCore, SeedableRng}; +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/packages/shade_protocol/src/contract_interfaces/bonds/utils.rs b/packages/shade_protocol/src/contract_interfaces/bonds/utils.rs new file mode 100644 index 0000000..fd534df --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/bonds/utils.rs @@ -0,0 +1,17 @@ +use sha2::{Digest, Sha256}; +use std::convert::TryInto; +use subtle::ConstantTimeEq; + +pub const VIEWING_KEY_SIZE: usize = 32; +pub const VIEWING_KEY_PREFIX: &str = "api_key_"; + +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/packages/shade_protocol/src/contract_interfaces/dao/DAO_ADAPTER.md b/packages/shade_protocol/src/contract_interfaces/dao/DAO_ADAPTER.md new file mode 100644 index 0000000..c34c135 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/dao/DAO_ADAPTER.md @@ -0,0 +1,153 @@ +# DAO Adapter Interface +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Interface](#Interface) + * Messages + * [Unbond](#Unbond) + * [Claim](#Claim) + * [Update](#Update) + * Queries + * [Balance](#Balance) + * [Unbonding](#Unbonding) + * [Claimable](#Claimable) + * [Unbondable](#Unbondable) + +# Introduction +This is an interface for dapps to follow to integrate with the DAO, to receive funding fromthe treasury and later unbond those funds back to treasury when needed. +NOTE: Because of how the contract implements this, all messages will be enclosed as: +``` +{ + "adapter": { + + } +} +``` + +# Sections + +### Messages +#### Unbond +Begin unbonding of a given amount from a given asset + +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|asset | Addr | SNIP-20 asset to unbond + +##### Response +```json +{ + "unbond": { + "amount": "100" + "status": "success" + } +} +``` + +#### Claim +Claim a given amount from completed unbonding of a given asset + +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|asset | Addr | SNIP-20 asset to unbond + +##### Response +```json +{ + "claim": { + "amount": "100" + "status": "success" + } +} +``` + +#### Update +Update a given asset on the adapter, to perform regular maintenance tasks if needed +Examples: + - `scrt_staking` - Claim rewards and restake + - `treasury` - Rebalance funds + +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|asset | Addr | SNIP-20 asset to unbond + +##### Response +```json +{ + "update": { + "status": "success" + } +} +``` + +### Queries + +#### Balance +Get the balance of a given asset, Error if unrecognized + +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|asset | Addr | SNIP-20 asset to query + +##### Response +```json +{ + "balance": { + "amount": "100000", + } +} +``` + +#### Unbonding +Get the current unbonding amount of a given asset, Error if unrecognized + +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|asset | Addr | SNIP-20 asset to query + +##### Response +```json +{ + "unbonding": { + "amount": "100000", + } +} +``` + +#### Claimable +Get the current claimable amount of a given asset, Error if unrecognized + +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|asset | Addr | SNIP-20 asset to query + +##### Response +```json +{ + "claimable": { + "amount": "100000", + } +} +``` + +#### Unbondable +Get the current unbondable amount of a given asset, Error if unrecognized + +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|asset | Addr | SNIP-20 asset to query + +##### Response +```json +{ + "unbondable": { + "amount": "100000", + } +} +``` diff --git a/packages/shade_protocol/src/contract_interfaces/dao/adapter.rs b/packages/shade_protocol/src/contract_interfaces/dao/adapter.rs new file mode 100644 index 0000000..349b617 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/dao/adapter.rs @@ -0,0 +1,187 @@ +use crate::{ + c_std::{Addr, CosmosMsg, QuerierWrapper, StdError, StdResult, Uint128}, + utils::{asset::Contract, generic_response::ResponseStatus}, +}; + +use crate::utils::{ExecuteCallback, Query}; +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub enum SubExecuteMsg { + // Begin unbonding amount + Unbond { asset: String, amount: Uint128 }, + Claim { asset: String }, + // Maintenance trigger e.g. claim rewards and restake + Update { asset: String }, +} + +impl ExecuteCallback for SubExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + Adapter(SubExecuteMsg), +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + Init { + status: ResponseStatus, + address: Addr, + }, + Unbond { + status: ResponseStatus, + amount: Uint128, + }, + Claim { + status: ResponseStatus, + amount: Uint128, + }, + Update { + status: ResponseStatus, + }, +} + +#[cw_serde] +pub enum SubQueryMsg { + Balance { asset: String }, + Unbonding { asset: String }, + Claimable { asset: String }, + Unbondable { asset: String }, + Reserves { asset: String }, +} + +#[cw_serde] +pub enum QueryMsg { + Adapter(SubQueryMsg), +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + Balance { amount: Uint128 }, + Unbonding { amount: Uint128 }, + Claimable { amount: Uint128 }, + Unbondable { amount: Uint128 }, + Reserves { amount: Uint128 }, +} + +pub fn claimable_query( + querier: QuerierWrapper, + asset: &Addr, + adapter: Contract, +) -> StdResult { + match QueryMsg::Adapter(SubQueryMsg::Claimable { + asset: asset.to_string().clone(), + }) + .query(&querier, &adapter)? + { + QueryAnswer::Claimable { amount } => Ok(amount), + _ => Err(StdError::generic_err(format!( + "Failed to query adapter claimable from {}", + adapter.address + ))), + } +} + +pub fn unbonding_query( + querier: QuerierWrapper, + asset: &Addr, + adapter: Contract, +) -> StdResult { + match QueryMsg::Adapter(SubQueryMsg::Unbonding { + asset: asset.to_string().clone(), + }) + .query(&querier, &adapter)? + { + QueryAnswer::Unbonding { amount } => Ok(amount), + _ => Err(StdError::generic_err(format!( + "Failed to query adapter unbonding from {}", + adapter.address + ))), + } +} + +pub fn unbondable_query( + querier: QuerierWrapper, + asset: &Addr, + adapter: Contract, +) -> StdResult { + match QueryMsg::Adapter(SubQueryMsg::Unbondable { + asset: asset.to_string().clone(), + }) + .query(&querier, &adapter)? + { + QueryAnswer::Unbondable { amount } => Ok(amount), + _ => Err(StdError::generic_err(format!( + "Failed to query adapter unbondable from {}", + adapter.address + ))), + } +} + +pub fn reserves_query( + querier: QuerierWrapper, + asset: &Addr, + adapter: Contract, +) -> StdResult { + match QueryMsg::Adapter(SubQueryMsg::Reserves { + asset: asset.to_string().clone(), + }) + .query(&querier, &adapter)? + { + QueryAnswer::Reserves { amount } => Ok(amount), + _ => Err(StdError::generic_err(format!( + "Failed to query adapter unbondable from {}", + adapter.address + ))), + } +} + +pub fn balance_query( + querier: QuerierWrapper, + asset: &Addr, + adapter: Contract, +) -> StdResult { + match QueryMsg::Adapter(SubQueryMsg::Balance { + asset: asset.to_string().clone(), + }) + .query(&querier, &adapter)? + { + QueryAnswer::Balance { amount } => Ok(amount), + _ => Err(StdError::generic_err(format!( + "Failed to query adapter balance from {}", + adapter.address + ))), + } +} + +pub fn claim_msg(asset: &Addr, adapter: Contract) -> StdResult { + ExecuteMsg::Adapter(SubExecuteMsg::Claim { + asset: asset.to_string().clone(), + }) + .to_cosmos_msg(&adapter, vec![]) +} + +pub fn unbond_msg(asset: &Addr, amount: Uint128, adapter: Contract) -> StdResult { + ExecuteMsg::Adapter(SubExecuteMsg::Unbond { + asset: asset.to_string().clone(), + amount, + }) + .to_cosmos_msg(&adapter, vec![]) +} + +pub fn update_msg(asset: &Addr, adapter: Contract) -> StdResult { + ExecuteMsg::Adapter(SubExecuteMsg::Update { + asset: asset.to_string().clone(), + }) + .to_cosmos_msg(&adapter, vec![]) +} diff --git a/packages/shade_protocol/src/contract_interfaces/dao/lp_shdswap.rs b/packages/shade_protocol/src/contract_interfaces/dao/lp_shdswap.rs new file mode 100644 index 0000000..079b580 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/dao/lp_shdswap.rs @@ -0,0 +1,151 @@ +use crate::{ + c_std::{Addr, Binary, Uint128}, + contract_interfaces::dao::adapter, + utils::{ + asset::Contract, + generic_response::ResponseStatus, + ExecuteCallback, + InstantiateCallback, + Query, + }, +}; +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub enum SplitMethod { + Conversion { contract: Contract }, + //TODO implement + /* + Market { + // "market_buy" contract + contract: Contract, + }, + Lend { + overseer: Contract, + }, + */ +} + +#[cw_serde] +pub struct Config { + pub admin: Addr, + pub treasury: Addr, + pub pair: Contract, + pub token_a: Contract, + pub token_b: Contract, + pub liquidity_token: Contract, + pub staking_contract: Option, + pub reward_token: Option, + pub split: Option, +} + +#[cw_serde] +pub struct InstantiateMsg { + pub admin: Option, + pub treasury: Addr, + pub viewing_key: String, + pub pair: Contract, + pub token_a: Contract, + pub token_b: Contract, + pub staking_contract: Option, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + /* token_a || token_b + * - check and provide as much as you can based on balances + * + * LP share token + * - Bond the share token, to be used when unbonding + */ + Receive { + sender: Addr, + from: Addr, + amount: Uint128, + memo: Option, + msg: Option, + }, + // TODO Refresh approvals to max + // admin only + RefreshApprovals, + UpdateConfig { + config: Config, + }, + Adapter(adapter::SubExecuteMsg), +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + Init { + status: ResponseStatus, + address: Addr, + }, + UpdateConfig { + status: ResponseStatus, + config: Config, + }, + RefreshApprovals { + status: ResponseStatus, + }, + Receive { + status: ResponseStatus, + }, +} + +#[cw_serde] +pub enum QueryMsg { + Config {}, + //Ratio {}, + Adapter(adapter::SubQueryMsg), +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + Config { config: Config }, + // Should add to %100 + //Ratio { token_a: Uint128, token_b: Uint128 }, +} + +/* NOTE + * 'reward_token' isn't technically supported + * if it collides with one of the pair tokens + * it will be treated as such + * Otherwise it will be sent straight to treasury on claim + */ +pub fn is_supported_asset(config: &Config, asset: &Addr) -> bool { + if let Some(reward_token) = &config.reward_token { + if reward_token.address == *asset { + return true; + } + } + + vec![ + config.token_a.address.clone(), + config.token_b.address.clone(), + config.liquidity_token.address.clone(), + ] + .contains(asset) +} + +pub fn get_supported_asset(config: &Config, asset: &Addr) -> Contract { + vec![ + config.token_a.clone(), + config.token_b.clone(), + config.liquidity_token.clone(), + ] + .into_iter() + .find(|a| a.address == *asset) + .unwrap() +} diff --git a/packages/shade_protocol/src/contract_interfaces/dao/manager.rs b/packages/shade_protocol/src/contract_interfaces/dao/manager.rs new file mode 100644 index 0000000..5da264a --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/dao/manager.rs @@ -0,0 +1,240 @@ +use cosmwasm_std::{ + Addr, + CosmosMsg, + QuerierWrapper, + StdError, + StdResult, + Uint128, +}; + +use crate::utils::{asset::Contract, generic_response::ResponseStatus, ExecuteCallback, Query}; +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub enum SubExecuteMsg { + // Begin unbonding amount + Unbond { asset: String, amount: Uint128 }, + Claim { asset: String }, + // Maintenance trigger e.g. claim rewards and restake + Update { asset: String }, +} + +impl ExecuteCallback for SubExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + Manager(SubExecuteMsg), +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + Init { + status: ResponseStatus, + address: String, + }, + Unbond { + status: ResponseStatus, + amount: Uint128, + }, + Claim { + status: ResponseStatus, + amount: Uint128, + }, + Update { + status: ResponseStatus, + }, +} + +#[cw_serde] +pub enum SubQueryMsg { + BatchBalance { assets: Vec, holder: String }, + Balance { asset: String, holder: String }, + Unbonding { asset: String, holder: String }, + Claimable { asset: String, holder: String }, + Unbondable { asset: String, holder: String }, + Reserves { asset: String, holder: String }, +} + +#[cw_serde] +pub enum QueryMsg { + Manager(SubQueryMsg), +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + BatchBalance { amounts: Vec }, + Balance { amount: Uint128 }, + Unbonding { amount: Uint128 }, + Claimable { amount: Uint128 }, + Unbondable { amount: Uint128 }, + Reserves { amount: Uint128 }, +} + +pub fn claimable_query( + querier: QuerierWrapper, + asset: &Addr, + holder: Addr, + manager: Contract, +) -> StdResult { + match QueryMsg::Manager(SubQueryMsg::Claimable { + asset: asset.to_string().clone(), + holder: holder.to_string().clone(), + }) + .query(&querier, &manager)? + { + QueryAnswer::Claimable { amount } => Ok(amount), + _ => Err(StdError::generic_err(format!( + "Failed to query manager claimable from {}", + manager.address + ))), + } +} + +pub fn unbonding_query( + querier: QuerierWrapper, + asset: &Addr, + holder: Addr, + manager: Contract, +) -> StdResult { + match QueryMsg::Manager(SubQueryMsg::Unbonding { + asset: asset.to_string().clone(), + holder: holder.to_string().clone(), + }) + .query(&querier, &manager)? + { + QueryAnswer::Unbonding { amount } => Ok(amount), + _ => Err(StdError::generic_err(format!( + "Failed to query manager unbonding from {}", + manager.address + ))), + } +} + +pub fn unbondable_query( + querier: QuerierWrapper, + asset: &Addr, + holder: Addr, + manager: Contract, +) -> StdResult { + match QueryMsg::Manager(SubQueryMsg::Unbondable { + asset: asset.to_string().clone(), + holder: holder.to_string().clone(), + }) + .query(&querier, &manager)? + { + QueryAnswer::Unbondable { amount } => Ok(amount), + _ => Err(StdError::generic_err(format!( + "Failed to query manager unbondable from {}", + manager.address + ))), + } +} + +pub fn reserves_query( + querier: QuerierWrapper, + asset: &Addr, + holder: Addr, + manager: Contract, +) -> StdResult { + match QueryMsg::Manager(SubQueryMsg::Reserves { + asset: asset.to_string().clone(), + holder: holder.to_string().clone(), + }) + .query(&querier, &manager)? + { + QueryAnswer::Reserves { amount } => Ok(amount), + _ => Err(StdError::generic_err(format!( + "Failed to query manager unbondable from {}", + manager.address + ))), + } +} + +pub fn balance_query( + querier: QuerierWrapper, + asset: &Addr, + holder: Addr, + manager: Contract, +) -> StdResult { + match QueryMsg::Manager(SubQueryMsg::Balance { + asset: asset.to_string().clone(), + holder: holder.to_string().clone(), + }) + .query(&querier, &manager) + { + Ok(resp) => match resp { + QueryAnswer::Balance { amount } => Ok(amount), + _ => Err(StdError::generic_err(format!( + "Unexpected response from {} manager balance", + manager.address + ))), + }, + Err(e) => { + println!("HERERERER"); + return Err(StdError::generic_err(format!( + "Failed to query manager balance: {}", + e.to_string() + ))); + } + } +} + +pub fn batch_balance_query( + querier: QuerierWrapper, + assets: &Vec, + holder: Addr, + manager: Contract, +) -> StdResult> { + match QueryMsg::Manager(SubQueryMsg::BatchBalance { + assets: assets.iter().map(|a| a.to_string()).collect(), + holder: holder.to_string().clone(), + }) + .query(&querier, &manager) + { + Ok(resp) => match resp { + QueryAnswer::BatchBalance { amounts } => Ok(amounts), + _ => Err(StdError::generic_err(format!( + "Unexpected response from {} manager batch balance", + manager.address + ))), + }, + Err(e) => { + return Err(StdError::generic_err(format!( + "Failed to query manager batch balance: {}", + e.to_string() + ))); + } + } +} + +pub fn claim_msg(asset: &Addr, manager: Contract) -> StdResult { + ExecuteMsg::Manager(SubExecuteMsg::Claim { + asset: asset.to_string().clone(), + }) + .to_cosmos_msg(&manager, vec![]) +} + +pub fn unbond_msg(asset: &Addr, amount: Uint128, manager: Contract) -> StdResult { + ExecuteMsg::Manager(SubExecuteMsg::Unbond { + asset: asset.to_string().clone(), + amount, + }) + .to_cosmos_msg(&manager, vec![]) +} + +pub fn update_msg(asset: &Addr, manager: Contract) -> StdResult { + ExecuteMsg::Manager(SubExecuteMsg::Update { + asset: asset.to_string().clone(), + }) + .to_cosmos_msg(&manager, vec![]) +} diff --git a/packages/shade_protocol/src/contract_interfaces/dao/mod.rs b/packages/shade_protocol/src/contract_interfaces/dao/mod.rs new file mode 100644 index 0000000..9469417 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/dao/mod.rs @@ -0,0 +1,23 @@ +#[cfg(feature = "adapter")] +pub mod adapter; + +#[cfg(feature = "manager")] +pub mod manager; + +#[cfg(feature = "treasury_manager")] +pub mod treasury_manager; + +#[cfg(feature = "rewards_emission")] +pub mod rewards_emission; + +#[cfg(feature = "treasury")] +pub mod treasury; + +#[cfg(feature = "scrt_staking")] +pub mod scrt_staking; + +#[cfg(feature = "lp_shdswap")] +pub mod lp_shdswap; + +#[cfg(feature = "stkd_scrt")] +pub mod stkd_scrt; diff --git a/packages/shade_protocol/src/contract_interfaces/dao/rewards_emission.rs b/packages/shade_protocol/src/contract_interfaces/dao/rewards_emission.rs new file mode 100644 index 0000000..9b5822e --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/dao/rewards_emission.rs @@ -0,0 +1,102 @@ +use crate::{ + c_std::{Addr, Binary, Uint128}, + utils::{ + asset::{Contract, RawContract}, + cycle::Cycle, + generic_response::ResponseStatus, + }, +}; + +use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub struct Reward { + //pub token: Addr, + pub distributor: Contract, + pub amount: Uint128, + pub cycle: Cycle, + pub last_refresh: String, + // datetime string + pub expiration: Option, +} + +#[cw_serde] +pub struct Config { + pub admins: Vec, + pub treasury: Addr, +} + +#[cw_serde] +pub struct InstantiateMsg { + pub admins: Vec, + pub viewing_key: String, + pub treasury: String, + pub token: RawContract, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + UpdateConfig { + config: Config, + }, + Receive { + sender: Addr, + from: Addr, + amount: Uint128, + memo: Option, + msg: Option, + }, + RefillRewards {}, + RegisterRewards { + token: Addr, // Just for verification + distributor: Contract, + amount: Uint128, + cycle: Cycle, + expiration: Option, + }, +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + Init { + status: ResponseStatus, + address: Addr, + }, + UpdateConfig { + status: ResponseStatus, + }, + Receive { + status: ResponseStatus, + }, + RegisterReward { + status: ResponseStatus, + }, + RefillRewards { + status: ResponseStatus, + }, +} + +#[cw_serde] +pub enum QueryMsg { + Config {}, + //PendingAllowance { asset: Addr }, +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + Config { config: Config }, + //PendingAllowance { amount: Uint128 }, +} diff --git a/packages/shade_protocol/src/contract_interfaces/dao/scrt_staking.rs b/packages/shade_protocol/src/contract_interfaces/dao/scrt_staking.rs new file mode 100644 index 0000000..5716365 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/dao/scrt_staking.rs @@ -0,0 +1,100 @@ +use crate::utils::asset::RawContract; +use crate::utils::{asset::Contract, generic_response::ResponseStatus}; +use crate::c_std::{Binary, Decimal, Addr, Uint128, Validator}; + +use crate::contract_interfaces::dao::adapter; + +use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; +use cosmwasm_schema::{cw_serde}; + +#[cw_serde] +pub struct Config { + pub admin_auth: Contract, + //pub treasury: Addr, + // This is the contract that will "unbond" funds + pub owner: Addr, + pub sscrt: Contract, + pub validator_bounds: Option, +} + +#[cw_serde] +pub struct ValidatorBounds { + pub min_commission: Decimal, + pub max_commission: Decimal, + pub top_position: Uint128, + pub bottom_position: Uint128, +} + +#[cw_serde] +pub struct InstantiateMsg { + pub admin_auth: RawContract, + pub owner: String, + pub sscrt: RawContract, + pub validator_bounds: Option, + pub viewing_key: String, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + Receive { + sender: String, + from: String, + amount: Uint128, + memo: Option, + msg: Option, + }, + UpdateConfig { + config: Config, + }, + Adapter(adapter::SubExecuteMsg), +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + Init { + status: ResponseStatus, + address: String, + }, + UpdateConfig { + status: ResponseStatus, + }, + Receive { + status: ResponseStatus, + validator: Validator, + }, + /* + Claim { + status: ResponseStatus, + }, + Unbond { + status: ResponseStatus, + delegations: Vec, + }, + */ +} + +#[cw_serde] +pub enum QueryMsg { + Config {}, + Delegations {}, + Rewards {}, + Adapter(adapter::SubQueryMsg), +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + Config { config: Config }, + //Balance { amount: Uint128 }, +} diff --git a/packages/shade_protocol/src/contract_interfaces/dao/stkd_scrt.rs b/packages/shade_protocol/src/contract_interfaces/dao/stkd_scrt.rs new file mode 100644 index 0000000..16958ac --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/dao/stkd_scrt.rs @@ -0,0 +1,192 @@ +use crate::{ + c_std::{Addr, Binary, Uint128}, + cosmwasm_schema::cw_serde, + utils::{ + asset::{Contract, RawContract}, + generic_response::ResponseStatus, + }, +}; + +use crate::contract_interfaces::dao::adapter; + +use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; + +#[cw_serde] +pub struct Config { + pub admin_auth: Contract, + //pub treasury: Addr, + // This is the contract that will "unbond" funds + pub owner: Addr, + pub sscrt: Contract, + pub staking_derivatives: Contract, +} + +#[cw_serde] +pub struct InstantiateMsg { + pub admin_auth: RawContract, + pub owner: String, + pub sscrt: RawContract, + pub viewing_key: String, + pub staking_derivatives: RawContract, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + Receive { + sender: String, + from: String, + amount: Uint128, + memo: Option, + msg: Option, + }, + UpdateConfig { + config: Config, + }, + Adapter(adapter::SubExecuteMsg), +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + Init { + status: ResponseStatus, + address: String, + }, + UpdateConfig { + status: ResponseStatus, + }, + Receive { + status: ResponseStatus, + }, +} + +#[cw_serde] +pub enum QueryMsg { + Config {}, + Adapter(adapter::SubQueryMsg), +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + Config { config: Config }, +} + +// STAKING DERIVATIVES INTERFACE +// TODO move to common location +pub mod staking_derivatives { + use crate::{ + c_std::{Addr, Coin, CosmosMsg, QuerierWrapper, StdResult, Uint128}, + cosmwasm_schema::cw_serde, + utils::asset::Contract, + }; + + use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; + + #[cw_serde] + pub enum ExecuteMsg { + Stake {}, + Unbond { redeem_amount: Uint128 }, + Claim {}, + } + + impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; + } + + #[cw_serde] + pub enum QueryMsg { + Unbonding { + address: Addr, + key: String, + page: Option, + page_size: Option, + time: Option, + }, + Holdings { + address: Addr, + key: String, + time: u64, + }, + } + + #[cw_serde] + pub struct Unbond { + pub amount: Uint128, + pub unbonds_at: u64, + pub is_mature: Option, + } + + #[cw_serde] + pub struct WeightedValidator { + pub validator: Addr, + pub weight: u8, + } + + #[cw_serde] + pub enum QueryAnswer { + Unbonding { + count: u64, + claimable_scrt: Option, + unbondings: Vec, + unbond_amount_in_next_batch: Uint128, + estimated_time_of_maturity_for_next_batch: Option, + }, + Holdings { + claimable_scrt: Uint128, + unbonding_scrt: Uint128, + token_balance: Uint128, + token_balance_value_in_scrt: Uint128, + }, + } + + #[cw_serde] + pub struct Holdings { + pub claimable_scrt: Uint128, + pub unbonding_scrt: Uint128, + pub token_balance: Uint128, + pub token_balance_value_in_scrt: Uint128, + } + + impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; + } + + pub fn stake_msg(amount: Uint128, contract: &Contract) -> StdResult { + ExecuteMsg::Stake {}.to_cosmos_msg(contract, vec![Coin { + amount, + denom: "uscrt".to_string(), + }]) + } + + pub fn unbond_msg(amount: Uint128, contract: &Contract) -> StdResult { + ExecuteMsg::Unbond { + redeem_amount: amount, + } + .to_cosmos_msg(contract, vec![]) + } + + pub fn claim_msg(contract: &Contract) -> StdResult { + ExecuteMsg::Claim {}.to_cosmos_msg(contract, vec![]) + } + + pub fn holdings_query( + querier: &QuerierWrapper, + address: Addr, + key: String, + time: u64, + contract: &Contract, + ) -> StdResult { + QueryMsg::Holdings { address, key, time }.query(querier, contract) + } +} diff --git a/packages/shade_protocol/src/contract_interfaces/dao/treasury.rs b/packages/shade_protocol/src/contract_interfaces/dao/treasury.rs new file mode 100644 index 0000000..00ddcdb --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/dao/treasury.rs @@ -0,0 +1,250 @@ +use crate::utils::{ + asset::{Contract, RawContract}, + cycle::Cycle, + generic_response::ResponseStatus, +}; + +use crate::c_std::{Addr, Api, Binary, Coin, StdResult, Uint128}; + +use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; +use cosmwasm_schema::cw_serde; + +use crate::utils::storage::plus::period_storage::Period; + +/// The permission referenced in the Admin Auth contract to give a user +/// admin permissions for the Shade Treasury +//pub const SHADE_TREASURY_ADMIN: &str = "SHADE_TREASURY_ADMIN"; + +#[cw_serde] +pub struct Config { + pub admin_auth: Contract, + pub multisig: Addr, +} + +#[cw_serde] +pub enum RunLevel { + Normal, + Deactivated, + Migrating, +} + +#[cw_serde] +pub enum Context { + Receive, + Rebalance, + Migration, + Unbond, + Wrap, +} + +#[cw_serde] +pub enum Action { + IncreaseAllowance, + DecreaseAllowance, + Unbond, + Claim, + FundsReceived, + SendFunds, + Wrap, +} + +#[cw_serde] +pub struct Metric { + pub action: Action, + pub context: Context, + pub timestamp: u64, + pub token: Addr, + pub amount: Uint128, + pub user: Addr, +} + +#[cw_serde] +pub enum AllowanceType { + Amount, + Portion, +} + +#[cw_serde] +pub struct RawAllowance { + pub spender: String, + pub allowance_type: AllowanceType, + pub cycle: Cycle, + pub amount: Uint128, + pub tolerance: Uint128, +} + +impl RawAllowance { + pub fn valid(self, api: &dyn Api) -> StdResult { + Ok(Allowance { + spender: api.addr_validate(self.spender.as_str())?, + allowance_type: self.allowance_type, + cycle: self.cycle, + amount: self.amount, + tolerance: self.tolerance, + }) + } +} + +#[cw_serde] +pub struct Allowance { + pub spender: Addr, + pub allowance_type: AllowanceType, + pub cycle: Cycle, + pub amount: Uint128, + pub tolerance: Uint128, +} + +#[cw_serde] +pub struct AllowanceMeta { + pub spender: Addr, + pub allowance_type: AllowanceType, + pub cycle: Cycle, + pub amount: Uint128, + pub tolerance: Uint128, + pub last_refresh: String, +} + +#[cw_serde] +pub struct InstantiateMsg { + pub admin_auth: RawContract, + pub multisig: String, + pub viewing_key: String, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + Receive { + sender: String, + from: String, + amount: Uint128, + memo: Option, + msg: Option, + }, + UpdateConfig { + admin_auth: Option, + multisig: Option, + }, + RegisterAsset { + contract: RawContract, + }, + RegisterManager { + contract: RawContract, + }, + RegisterWrap { + denom: String, + contract: RawContract, + }, + WrapCoins {}, + // Setup a new allowance + Allowance { + asset: String, + allowance: RawAllowance, + refresh_now: bool, + }, + Update { + asset: String, + }, + SetRunLevel { + run_level: RunLevel, + }, +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + Init { + status: ResponseStatus, + address: String, + }, + UpdateConfig { + config: Config, + status: ResponseStatus, + }, + Receive { + status: ResponseStatus, + }, + RegisterAsset { + status: ResponseStatus, + }, + RegisterManager { + status: ResponseStatus, + }, + RegisterWrap { + status: ResponseStatus, + }, + Allowance { + status: ResponseStatus, + }, + Rebalance { + status: ResponseStatus, + }, + Migration { + status: ResponseStatus, + }, + Unbond { + status: ResponseStatus, + }, + Update { + status: ResponseStatus, + }, + RunLevel { + run_level: RunLevel, + }, + WrapCoins { + success: Vec, + failed: Vec, + }, +} + +#[cw_serde] +pub enum QueryMsg { + Config {}, + Assets {}, + // List of recurring allowances configured + Allowances { + asset: String, + }, + // Current allowance to spender + Allowance { + asset: String, + spender: String, + }, + RunLevel, + Metrics { + date: Option, + epoch: Option, + period: Period, + }, + Balance { + asset: String, + }, + BatchBalance { + assets: Vec, + }, + Reserves { + asset: String, + }, +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + Config { config: Config }, + Assets { assets: Vec }, + Allowances { allowances: Vec }, + Allowance { amount: Uint128 }, + RunLevel { run_level: RunLevel }, + Metrics { metrics: Vec }, + Balance { amount: Uint128 }, + Reserves { amount: Uint128 }, +} diff --git a/packages/shade_protocol/src/contract_interfaces/dao/treasury_manager.rs b/packages/shade_protocol/src/contract_interfaces/dao/treasury_manager.rs new file mode 100644 index 0000000..78960dd --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/dao/treasury_manager.rs @@ -0,0 +1,247 @@ +use crate::{ + c_std::{Addr, Api, Binary, StdResult, Uint128}, + contract_interfaces::dao::manager, + utils::{ + asset::{Contract, RawContract}, + generic_response::ResponseStatus, + storage::plus::period_storage::Period, + }, +}; + +use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub enum Context { + Receive, + Update, + Unbond, + Claim, + Holders, +} + +#[cw_serde] +pub enum Action { + Unbond, + Claim, + FundsReceived, + SendFunds, + SendFundsFrom, + RealizeGains, + RealizeLosses, + //TODO + AddHolder, + RemoveHolder, +} + +#[cw_serde] +pub struct Metric { + pub action: Action, + pub context: Context, + pub timestamp: u64, + pub token: Addr, + pub amount: Uint128, + pub user: Addr, +} + +#[cw_serde] +pub struct Config { + pub admin_auth: Contract, + pub treasury: Addr, +} + +#[cw_serde] +pub struct Balance { + pub token: Addr, + pub amount: Uint128, +} + +#[cw_serde] +pub enum Status { + Active, + Disabled, + Closed, + Transferred, +} + +//TODO: move accounts to treasury manager +#[cw_serde] +pub struct Holding { + pub balances: Vec, + pub unbondings: Vec, + //pub claimable: Vec, + pub status: Status, +} + +#[cw_serde] +pub struct Unbonding { + pub holder: Addr, + pub amount: Uint128, +} + +#[cw_serde] +pub struct RawAllocation { + pub nick: Option, + pub contract: RawContract, + pub alloc_type: AllocationType, + pub amount: Uint128, + pub tolerance: Uint128, +} + +impl RawAllocation { + pub fn valid(self, api: &dyn Api) -> StdResult { + Ok(Allocation { + nick: self.nick, + contract: self.contract.into_valid(api)?, + alloc_type: self.alloc_type, + amount: self.amount, + tolerance: self.tolerance, + }) + } +} + +#[cw_serde] +pub struct Allocation { + pub nick: Option, + pub contract: Contract, + pub alloc_type: AllocationType, + pub amount: Uint128, + pub tolerance: Uint128, +} + +#[cw_serde] +pub enum AllocationType { + // amount becomes percent * 10^18 + Portion, + Amount, +} + +//TODO remove - same as Allocation +#[cw_serde] +pub struct AllocationMeta { + pub nick: Option, + pub contract: Contract, + pub alloc_type: AllocationType, + pub amount: Uint128, + pub tolerance: Uint128, +} + +#[cw_serde] +pub struct AllocationTempData { + pub contract: Contract, + pub alloc_type: AllocationType, + pub amount: Uint128, + pub tolerance: Uint128, + pub balance: Uint128, + pub unbondable: Uint128, + pub unbonding: Uint128, +} + +#[cw_serde] +pub struct InstantiateMsg { + pub admin_auth: RawContract, + pub viewing_key: String, + pub treasury: String, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + Receive { + sender: String, + from: String, + amount: Uint128, + memo: Option, + msg: Option, + }, + UpdateConfig { + admin_auth: Option, + treasury: Option, + }, + RegisterAsset { + contract: RawContract, + }, + Allocate { + asset: String, + allocation: RawAllocation, + }, + AddHolder { + holder: String, + }, + RemoveHolder { + holder: String, + }, + Manager(manager::SubExecuteMsg), +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + Init { + status: ResponseStatus, + address: String, + }, + Receive { + status: ResponseStatus, + }, + UpdateConfig { + config: Config, + status: ResponseStatus, + }, + RegisterAsset { + status: ResponseStatus, + }, + Allocate { + status: ResponseStatus, + }, + AddHolder { + status: ResponseStatus, + }, + RemoveHolder { + status: ResponseStatus, + }, + Manager(manager::ExecuteAnswer), +} + +#[cw_serde] +pub enum QueryMsg { + Config {}, + Assets {}, + Allocations { + asset: String, + }, + PendingAllowance { + asset: String, + }, + Holders {}, + Holding { + holder: String, + }, + Metrics { + date: Option, + epoch: Option, + period: Period, + }, + Manager(manager::SubQueryMsg), +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + Config { config: Config }, + Assets { assets: Vec }, + Allocations { allocations: Vec }, + PendingAllowance { amount: Uint128 }, + Holders { holders: Vec }, + Holding { holding: Holding }, + Metrics { metrics: Vec }, +} diff --git a/packages/shade_protocol/src/contract_interfaces/dex/dex.rs b/packages/shade_protocol/src/contract_interfaces/dex/dex.rs new file mode 100644 index 0000000..205d957 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/dex/dex.rs @@ -0,0 +1,173 @@ +use crate::{ + contract_interfaces::{ + dex::{secretswap, sienna}, + oracles::band, + snip20::helpers::Snip20Asset, + }, + utils::{ + asset::Contract, + price::{normalize_price, translate_price}, + }, +}; +use crate::c_std::{Deps, StdError, StdResult}; + +use cosmwasm_schema::{cw_serde}; + +use crate::c_std::{Uint128, Uint512}; +use std::convert::TryFrom; + +#[cw_serde] +pub enum Dex { + SecretSwap, + SiennaSwap, + ShadeSwap, + Mint, +} + +#[cw_serde] +pub struct TradingPair { + pub dex: Dex, + pub contract: Contract, + pub asset: Snip20Asset, +} + +/* give_amount into give_pool + * returns how much to be received from take_pool + */ + +pub fn pool_take_amount(give_amount: Uint128, give_pool: Uint128, take_pool: Uint128) -> Uint128 { + Uint128::new( + take_pool.u128() - give_pool.u128() * take_pool.u128() / (give_pool + give_amount).u128(), + ) +} + +pub fn aggregate_price( + deps: &Deps, + pairs: Vec, + sscrt: Contract, + band: Contract, +) -> StdResult { + // indices will align with + let mut amounts_per_scrt = vec![]; + let mut pool_sizes: Vec = vec![]; + + for pair in pairs.clone() { + match &pair.dex { + Dex::SecretSwap => { + amounts_per_scrt.push(Uint512::from( + normalize_price( + secretswap::amount_per_scrt(&deps, pair.clone(), sscrt.clone())?, + pair.asset.token_info.decimals, + ) + .u128(), + )); + pool_sizes.push(Uint512::from(secretswap::pool_cp(&deps, pair)?.u128())); + } + Dex::SiennaSwap => { + amounts_per_scrt.push(Uint512::from( + normalize_price( + sienna::amount_per_scrt(&deps, pair.clone(), sscrt.clone())?, + pair.asset.token_info.decimals, + ) + .u128(), + )); + pool_sizes.push(Uint512::from(sienna::pool_cp(&deps, pair)?.u128())); + } + _ => {} /* + ShadeSwap => { + prices.push(shadeswap::price(&deps, pair.clone(), sscrt.clone(), band.clone())?); + pool_sizes.push(shadeswap::pool_size(&deps, pair)?); + return Err(StdErr::generic_err("ShadeSwap Unavailable")); + }, + */ + } + } + + let combined_cp: Uint512 = pool_sizes.iter().sum(); + + let weighted_sum: Uint512 = amounts_per_scrt + .into_iter() + .zip(pool_sizes.into_iter()) + .map(|(a, s)| a * s / combined_cp) + .sum(); + + // Translate price from SHD/SCRT -> SHD/USD + // And normalize to * 10^18 + let price = translate_price( + band::reference_data(deps, "SCRT".to_string(), "USD".to_string(), band)?.rate, + Uint128::new(Uint128::try_from(weighted_sum)?.u128()), + ); + + Ok(price) +} + +pub fn best_price( + deps: &Deps, + pairs: Vec, + sscrt: Contract, + band: Contract, +) -> StdResult<(Uint128, TradingPair)> { + // indices will align with + let mut results = vec![]; + + for pair in &pairs { + match pair.clone().dex { + Dex::SecretSwap => { + results.push(secretswap::price( + &deps, + pair.clone(), + sscrt.clone(), + band.clone(), + )?); + } + Dex::SiennaSwap => { + results.push(sienna::price( + &deps, + pair.clone(), + sscrt.clone(), + band.clone(), + )?); + } + _ => {} /* + ShadeSwap => { + return Err(StdErr::generic_err("ShadeSwap Unavailable")); + }, + */ + } + } + let max_amount = results.iter().max().unwrap(); + let index = results.iter().position(|e| e == max_amount).unwrap(); + let scrt_result = band::reference_data(deps, "SCRT".to_string(), "USD".to_string(), band)?; + + Ok(( + translate_price(scrt_result.rate, *max_amount), + pairs[index].clone(), + )) +} + +pub fn price( + deps: &Deps, + pair: TradingPair, + sscrt: Contract, + band: Contract, +) -> StdResult { + match pair.clone().dex { + Dex::SecretSwap => Ok(secretswap::price( + &deps, + pair.clone(), + sscrt.clone(), + band.clone(), + )?), + Dex::SiennaSwap => Ok(sienna::price( + &deps, + pair.clone(), + sscrt.clone(), + band.clone(), + )?), + _ => return Err(StdError::generic_err("ShadeSwap not implemented")), /* + ShadeSwap => { + return Err(StdErr::generic_err("ShadeSwap Unavailable")); + }, + */ + } +} diff --git a/packages/shade_protocol/src/contract_interfaces/dex/mod.rs b/packages/shade_protocol/src/contract_interfaces/dex/mod.rs new file mode 100644 index 0000000..3480d4a --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/dex/mod.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "dex")] +pub mod secretswap; + +#[cfg(feature = "dex")] +pub mod shadeswap; + +#[cfg(feature = "dex")] +pub mod sienna; + +#[cfg(feature = "dex")] +pub mod dex; diff --git a/packages/shade_protocol/src/contract_interfaces/dex/secretswap.rs b/packages/shade_protocol/src/contract_interfaces/dex/secretswap.rs new file mode 100644 index 0000000..37412a8 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/dex/secretswap.rs @@ -0,0 +1,136 @@ +use crate::{ + c_std::{Addr, Deps, StdResult, Uint128}, + contract_interfaces::{dex::dex, oracles::band}, + utils::{ + asset::Contract, + price::{normalize_price, translate_price}, + }, +}; + +use crate::utils::Query; +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub struct Token { + pub contract_addr: Addr, + pub token_code_hash: String, + pub viewing_key: String, +} + +#[cw_serde] +pub struct AssetInfo { + pub token: Token, +} + +#[cw_serde] +pub struct Asset { + pub amount: Uint128, + pub info: AssetInfo, +} + +#[cw_serde] +pub struct Simulation { + pub offer_asset: Asset, +} + +#[cw_serde] +pub enum PairQuery { + Pair {}, + Pool {}, + Simulation { offer_asset: Asset }, + //ReverseSimulation {}, +} + +impl Query for PairQuery { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub struct SimulationResponse { + pub return_amount: Uint128, + pub spread_amount: Uint128, + pub commission_amount: Uint128, +} + +#[cw_serde] +pub struct PairResponse { + pub asset_infos: Vec, + pub contract_addr: Addr, + pub liquidity_token: Addr, + pub token_code_hash: String, + pub asset0_volume: Uint128, + pub asset1_volume: Uint128, + pub factory: Contract, +} + +#[cw_serde] +pub struct PoolResponse { + pub assets: Vec, + pub total_share: Uint128, +} + +#[cw_serde] +pub struct CallbackMsg { + pub swap: CallbackSwap, +} +#[cw_serde] +pub struct CallbackSwap { + pub expected_return: Uint128, +} + +/*pub fn is_pair( + deps: DepsMut, + pair: Contract, +) -> StdResult { + Ok( + match (PairQuery::Pair {}).query::(&deps.querier, &pair) { + Ok(_) => true, + Err(_) => false, + }, + ) +}*/ + +pub fn price( + deps: &Deps, + pair: dex::TradingPair, + sscrt: Contract, + band: Contract, +) -> StdResult { + let scrt_result = band::reference_data(deps, "SCRT".to_string(), "USD".to_string(), band)?; + + // SCRT-USD / SCRT-symbol + Ok(translate_price( + scrt_result.rate, + normalize_price( + amount_per_scrt(&deps, pair.clone(), sscrt)?, + pair.asset.token_info.decimals, + ), + )) +} + +pub fn amount_per_scrt(deps: &Deps, pair: dex::TradingPair, sscrt: Contract) -> StdResult { + let response: SimulationResponse = PairQuery::Simulation { + offer_asset: Asset { + amount: Uint128::new(1_000_000), // 1 sSCRT (6 decimals) + info: AssetInfo { + token: Token { + contract_addr: sscrt.address, + token_code_hash: sscrt.code_hash, + viewing_key: "SecretSwap".to_string(), + }, + }, + }, + } + .query(&deps.querier, &pair.contract)?; + + Ok(response.return_amount) +} + +pub fn pool_cp(deps: &Deps, pair: dex::TradingPair) -> StdResult { + let pool: PoolResponse = PairQuery::Pool {}.query(&deps.querier, &pair.contract)?; + + // Constant Product + Ok(Uint128::new( + pool.assets[0].amount.u128() * pool.assets[1].amount.u128(), + )) +} diff --git a/packages/shade_protocol/src/contract_interfaces/dex/shadeswap.rs b/packages/shade_protocol/src/contract_interfaces/dex/shadeswap.rs new file mode 100644 index 0000000..feba1f5 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/dex/shadeswap.rs @@ -0,0 +1,229 @@ +use crate::{ + c_std::{Addr, Binary, Uint128}, + utils::{ + asset::Contract, + Query, + }, +}; +use cosmwasm_schema::cw_serde; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/* +#[cw_serde] +pub struct Token { + pub contract_addr: Addr, + pub token_code_hash: String, + pub viewing_key: String, +} + +#[cw_serde] +pub struct AssetInfo { + pub token: Token, +} + +#[cw_serde] +pub struct Asset { + pub amount: Uint128, + pub info: AssetInfo, +} + +#[cw_serde] +pub struct Simulation { + pub offer_asset: Asset, +} +*/ +#[cw_serde] +pub struct ContractLink { + pub address: Addr, + pub code_hash: String, +} + +#[cw_serde] +pub enum PairQuery { + GetPairInfo {}, + GetEstimatedPrice { offer: TokenAmount }, +} + +impl Query for PairQuery { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum TokenType { + CustomToken { + contract_addr: Addr, + token_code_hash: String, + }, + NativeToken { + denom: String, + }, +} + +#[cw_serde] +pub struct TokenPair { + pub token_0: TokenType, + pub token_1: TokenType, +} + +/* +#[cw_serde] +pub struct SimulationResponse { + pub return_amount: Uint128, + pub spread_amount: Uint128, + pub commission_amount: Uint128, +} +*/ + +#[cw_serde] +pub struct PairInfoResponse { + pub liquidity_token: Contract, + pub factory: Contract, + pub pair: TokenPair, + pub amount_0: Uint128, + pub amount_1: Uint128, + pub total_liquidity: Uint128, + pub contract_version: u32, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsgResponse { + GetPairInfo { + liquidity_token: Contract, + factory: Contract, + pair: TokenPair, + amount_0: Uint128, + amount_1: Uint128, + total_liquidity: Uint128, + contract_version: u32, + }, + GetTradeHistory { + data: Vec, + }, + GetWhiteListAddress { + addresses: Vec, + }, + GetTradeCount { + count: u64, + }, + GetAdminAddress { + address: Addr, + }, + GetClaimReward { + amount: Uint128, + }, + StakingContractInfo { + staking_contract: Contract, + }, + EstimatedPrice { + estimated_price: Uint128, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct TokenAmount { + pub token: TokenType, + pub amount: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct SwapTokens { + pub expected_return: Option, + pub to: Option, + pub router_link: Option, + pub callback_signature: Option, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub struct TradeHistory { + pub price: Uint128, + pub amount: Uint128, + pub timestamp: u64, + pub direction: String, + pub total_fee_amount: Uint128, + pub lp_fee_amount: Uint128, + pub shade_dao_fee_amount: Uint128, +} + +/* +#[cw_serde] +pub struct PoolResponse { + pub assets: Vec, + pub total_share: Uint128, +} +*/ + +/*pub fn is_pair(deps: DepsMut, pair: Contract) -> StdResult { + Ok( + match (PairQuery::PairInfo).query::(&deps.querier, &pair) { + Ok(_) => true, + Err(_) => false, + }, + ) +}*/ + +/* +pub fn price( + deps: Deps, + pair: dex::TradingPair, + sscrt: Contract, + band: Contract, +) -> StdResult { + + let scrt_result = band::reference_data(deps, "SCRT".to_string(), "USD".to_string(), band)?; + + // SCRT-USD / SCRT-symbol + Ok(translate_price( + scrt_result.rate, + normalize_price( + amount_per_scrt(deps, pair.clone(), sscrt)?, + pair.asset.token_info.decimals, + ), + )) +} + +pub fn amount_per_scrt( + deps: Deps, + pair: dex::TradingPair, + sscrt: Contract, +) -> StdResult { + + let response: SimulationResponse = PairQuery::Simulation { + offer_asset: Asset { + amount: Uint128::new(1_000_000), // 1 sSCRT (6 decimals) + info: AssetInfo { + token: Token { + contract_addr: sscrt.address, + token_code_hash: sscrt.code_hash, + viewing_key: "SecretSwap".to_string(), + }, + }, + }, + } + .query( + &deps.querier, + pair.contract.code_hash, + pair.contract.address, + )?; + + Ok(response.return_amount) +} + +pub fn pool_cp( + deps: Deps, + pair: dex::TradingPair, +) -> StdResult { + let pool: PoolResponse = PairQuery::Pool {}.query( + &deps.querier, + pair.contract.code_hash, + pair.contract.address, + )?; + + // Constant Product + Ok(Uint128::new(pool.assets[0].amount.u128() * pool.assets[1].amount.u128())) +} +*/ diff --git a/packages/shade_protocol/src/contract_interfaces/dex/sienna.rs b/packages/shade_protocol/src/contract_interfaces/dex/sienna.rs new file mode 100644 index 0000000..4d86271 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/dex/sienna.rs @@ -0,0 +1,170 @@ +use crate::{ + c_std::{Addr, Deps, StdResult, Uint128}, + contract_interfaces::{dex::dex, oracles::band}, + utils::{ + asset::Contract, + price::{normalize_price, translate_price}, + Query, + }, +}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Binary; + +#[cw_serde] +pub enum TokenType { + CustomToken { + contract_addr: Addr, + token_code_hash: String, + }, + NativeToken { + denom: String, + }, +} + +#[cw_serde] +pub struct Pair { + pub token_0: TokenType, + pub token_1: TokenType, +} + +/* +#[cw_serde] +pub struct AssetInfo { + pub token: Token, +} +*/ + +#[cw_serde] +pub struct TokenTypeAmount { + pub amount: Uint128, + pub token: TokenType, +} + +#[cw_serde] +pub struct Swap { + pub send: SwapOffer, +} + +#[cw_serde] +pub struct SwapOffer { + pub recipient: Addr, + pub amount: Uint128, + pub msg: Binary, +} + +#[cw_serde] +pub enum ReceiverCallbackMsg { + Swap { + expected_return: Option, + to: Option, + }, +} + +#[cw_serde] +pub struct CallbackMsg { + pub swap: CallbackSwap, +} + +#[cw_serde] +pub struct CallbackSwap { + pub expected_return: Uint128, +} + +#[cw_serde] +pub struct SwapSimulation { + pub offer: TokenTypeAmount, +} + +#[cw_serde] +pub enum PairQuery { + /* + Pool {}, + */ + PairInfo, + SwapSimulation { offer: TokenTypeAmount }, +} + +impl Query for PairQuery { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub struct SimulationResponse { + pub return_amount: Uint128, + pub spread_amount: Uint128, + pub commission_amount: Uint128, +} + +#[cw_serde] +pub struct PairInfo { + pub liquidity_token: Contract, + pub factory: Contract, + pub pair: Pair, + pub amount_0: Uint128, + pub amount_1: Uint128, + pub total_liquidity: Uint128, + pub contract_version: u32, +} + +#[cw_serde] +pub struct PairInfoResponse { + pub pair_info: PairInfo, +} + +/*pub fn is_pair( + deps: DepsMut, + pair: Contract, +) -> StdResult { + Ok( + match (PairQuery::PairInfo).query::( + &deps.querier, + &pair + ) { + Ok(_) => true, + Err(_) => false, + }, + ) +}*/ + +pub fn price( + deps: &Deps, + pair: dex::TradingPair, + sscrt: Contract, + band: Contract, +) -> StdResult { + // TODO: This should be passed in to avoid multipl BAND SCRT queries in one query + let scrt_result = band::reference_data(deps, "SCRT".to_string(), "USD".to_string(), band)?; + + // SCRT-USD / SCRT-symbol + Ok(translate_price( + scrt_result.rate, + normalize_price( + amount_per_scrt(deps, pair.clone(), sscrt)?, + pair.asset.token_info.decimals, + ), + )) +} + +pub fn amount_per_scrt(deps: &Deps, pair: dex::TradingPair, sscrt: Contract) -> StdResult { + let response: SimulationResponse = PairQuery::SwapSimulation { + offer: TokenTypeAmount { + amount: Uint128::new(1_000_000), // 1 sSCRT (6 decimals) + token: TokenType::CustomToken { + contract_addr: sscrt.address, + token_code_hash: sscrt.code_hash, + }, + }, + } + .query(&deps.querier, &pair.contract)?; + + Ok(response.return_amount) +} + +pub fn pool_cp(deps: &Deps, pair: dex::TradingPair) -> StdResult { + let pair_info: PairInfoResponse = PairQuery::PairInfo.query(&deps.querier, &pair.contract)?; + + // Constant Product + Ok(Uint128::new( + pair_info.pair_info.amount_0.u128() * pair_info.pair_info.amount_1.u128(), + )) +} diff --git a/packages/shade_protocol/src/contract_interfaces/governance/assembly.rs b/packages/shade_protocol/src/contract_interfaces/governance/assembly.rs new file mode 100644 index 0000000..47bed68 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/governance/assembly.rs @@ -0,0 +1,184 @@ +use crate::{ + c_std::{Addr, StdResult, Storage}, + contract_interfaces::governance::stored_id::ID, + utils::flexible_msg::FlexibleMsg, +}; + +use cosmwasm_schema::cw_serde; +use secret_storage_plus::Map; + +#[cfg(feature = "governance-impl")] +use crate::utils::storage::plus::MapStorage; + +#[cw_serde] +pub struct Assembly { + // Readable name + pub name: String, + // Description of the assembly, preferably in base64 + pub metadata: String, + // List of members in assembly + pub members: Vec, + // Selected profile + pub profile: u16, +} + +#[cfg(feature = "governance-impl")] +impl Assembly { + pub fn load(storage: &dyn Storage, id: u16) -> StdResult { + let desc = Self::description(storage, id)?; + let data = Self::data(storage, id)?; + + Ok(Self { + name: desc.name, + metadata: desc.metadata, + members: data.members, + profile: data.profile, + }) + } + + pub fn may_load(storage: &dyn Storage, id: u16) -> StdResult> { + if id > ID::assembly(storage)? { + return Ok(None); + } + Ok(Some(Self::load(storage, id)?)) + } + + pub fn save(&self, storage: &mut dyn Storage, id: u16) -> StdResult<()> { + AssemblyData { + members: self.members.clone(), + profile: self.profile, + } + .save(storage, id)?; + + AssemblyDescription { + name: self.name.clone(), + metadata: self.metadata.clone(), + } + .save(storage, id)?; + + Ok(()) + } + + pub fn data(storage: &dyn Storage, id: u16) -> StdResult { + AssemblyData::load(storage, id) + } + + pub fn save_data(storage: &mut dyn Storage, id: u16, data: AssemblyData) -> StdResult<()> { + data.save(storage, id) + } + + pub fn description(storage: &dyn Storage, id: u16) -> StdResult { + AssemblyDescription::load(storage, id) + } + + pub fn save_description( + storage: &mut dyn Storage, + id: u16, + desc: AssemblyDescription, + ) -> StdResult<()> { + desc.save(storage, id) + } +} + +#[cfg(feature = "governance-impl")] +#[cw_serde] +pub struct AssemblyData { + pub members: Vec, + pub profile: u16, +} + +#[cfg(feature = "governance-impl")] +impl MapStorage<'static, u16> for AssemblyData { + const MAP: Map<'static, u16, Self> = Map::new("assembly_data-"); +} + +#[cfg(feature = "governance-impl")] +#[cw_serde] +pub struct AssemblyDescription { + pub name: String, + pub metadata: String, +} + +#[cfg(feature = "governance-impl")] +impl MapStorage<'static, u16> for AssemblyDescription { + const MAP: Map<'static, u16, Self> = Map::new("assembly_description-"); +} + +#[cw_serde] // A generic msg is created at init, its a black msg where the variable is the start +pub struct AssemblyMsg { + pub name: String, + // Assemblies allowed to call this msg + pub assemblies: Vec, + // ExecuteMsg template + pub msg: FlexibleMsg, +} + +#[cfg(feature = "governance-impl")] +impl AssemblyMsg { + pub fn load(storage: &dyn Storage, id: u16) -> StdResult { + let desc = Self::description(storage, id)?; + let data = Self::data(storage, id)?; + + Ok(Self { + name: desc, + assemblies: data.assemblies, + msg: data.msg, + }) + } + + pub fn may_load(storage: &dyn Storage, id: u16) -> StdResult> { + if id > ID::assembly_msg(storage)? { + return Ok(None); + } + Ok(Some(Self::load(storage, id)?)) + } + + pub fn save(&self, storage: &mut dyn Storage, id: u16) -> StdResult<()> { + AssemblyMsgData { + assemblies: self.assemblies.clone(), + msg: self.msg.clone(), + } + .save(storage, id)?; + + AssemblyMsgDescription(self.name.clone()).save(storage, id)?; + + Ok(()) + } + + pub fn data(storage: &dyn Storage, id: u16) -> StdResult { + AssemblyMsgData::load(storage, id) + } + + pub fn save_data(storage: &mut dyn Storage, id: u16, data: AssemblyMsgData) -> StdResult<()> { + data.save(storage, id) + } + + pub fn description(storage: &dyn Storage, id: u16) -> StdResult { + Ok(AssemblyMsgDescription::load(storage, id)?.0) + } + + pub fn save_description(storage: &mut dyn Storage, id: u16, desc: String) -> StdResult<()> { + AssemblyMsgDescription(desc).save(storage, id) + } +} + +#[cfg(feature = "governance-impl")] +#[cw_serde] +pub struct AssemblyMsgData { + pub assemblies: Vec, + pub msg: FlexibleMsg, +} + +#[cfg(feature = "governance-impl")] +impl MapStorage<'static, u16> for AssemblyMsgData { + const MAP: Map<'static, u16, Self> = Map::new("assembly_msg_data-"); +} + +#[cfg(feature = "governance-impl")] +#[cw_serde] +struct AssemblyMsgDescription(pub String); + +#[cfg(feature = "governance-impl")] +impl MapStorage<'static, u16> for AssemblyMsgDescription { + const MAP: Map<'static, u16, Self> = Map::new("assembly_msg_description-"); +} diff --git a/packages/shade_protocol/src/contract_interfaces/governance/contract.rs b/packages/shade_protocol/src/contract_interfaces/governance/contract.rs new file mode 100644 index 0000000..b5e39a1 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/governance/contract.rs @@ -0,0 +1,105 @@ +use crate::{ + c_std::{StdResult, Storage}, + contract_interfaces::governance::stored_id::ID, + utils::asset::Contract, +}; + +use crate::utils::storage::plus::MapStorage; +use cosmwasm_schema::cw_serde; +use secret_storage_plus::Map; + +#[cw_serde] +pub struct AllowedContract { + pub name: String, + pub metadata: String, + // If none then anyone can use it + #[serde(skip_serializing_if = "Option::is_none")] + pub assemblies: Option>, + pub contract: Contract, +} + +#[cfg(feature = "governance-impl")] +impl AllowedContract { + pub fn load(storage: &dyn Storage, id: u16) -> StdResult { + let desc = Self::description(storage, id)?; + let data = Self::data(storage, id)?; + + Ok(Self { + name: desc.name, + metadata: desc.metadata, + contract: data.contract, + assemblies: data.assemblies, + }) + } + + pub fn may_load(storage: &dyn Storage, id: u16) -> StdResult> { + if id > ID::contract(storage)? { + return Ok(None); + } + Ok(Some(Self::load(storage, id)?)) + } + + pub fn save(&self, storage: &mut dyn Storage, id: u16) -> StdResult<()> { + AllowedContractData { + contract: self.contract.clone(), + assemblies: self.assemblies.clone(), + } + .save(storage, id)?; + + AllowedContractDescription { + name: self.name.clone(), + metadata: self.metadata.clone(), + } + .save(storage, id)?; + + Ok(()) + } + + pub fn data(storage: &dyn Storage, id: u16) -> StdResult { + AllowedContractData::load(storage, id) + } + + pub fn save_data( + storage: &mut dyn Storage, + id: u16, + data: AllowedContractData, + ) -> StdResult<()> { + data.save(storage, id) + } + + pub fn description(storage: &dyn Storage, id: u16) -> StdResult { + AllowedContractDescription::load(storage, id) + } + + pub fn save_description( + storage: &mut dyn Storage, + id: u16, + desc: AllowedContractDescription, + ) -> StdResult<()> { + desc.save(storage, id) + } +} + +#[cfg(feature = "governance-impl")] +#[cw_serde] +pub struct AllowedContractData { + pub contract: Contract, + pub assemblies: Option>, +} + +#[cfg(feature = "governance-impl")] +impl MapStorage<'static, u16> for AllowedContractData { + const MAP: Map<'static, u16, Self> = Map::new("allowed_contract_data-"); +} + +#[cfg(feature = "governance-impl")] +#[cw_serde] +pub struct AllowedContractDescription { + pub name: String, + pub metadata: String, +} + +#[cfg(feature = "governance-impl")] +impl MapStorage<'static, u16> for AllowedContractDescription { + const MAP: Map<'static, u16, Self> = Map::new("allowed_contract_description-"); +} diff --git a/packages/shade_protocol/src/contract_interfaces/governance/errors.rs b/packages/shade_protocol/src/contract_interfaces/governance/errors.rs new file mode 100644 index 0000000..dd79c96 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/governance/errors.rs @@ -0,0 +1,44 @@ +use crate::errors; + +errors!("governance"; + MissingFundingToken, "Funding token must be set", missing_funding_token, + MissingVotingToken, "Voting token must be set", missing_voting_token, + UnauthorizedVK, "Viewing key is invalid", bad_vk, + PermitRevoked, "Permit key was revoked", bad_pkey, + WasmEventNotFound, "Could not find \"wasm\" event", bad_event, + UnrecognizedReplyID, "Reply ID {} was not recognized", wrong_reply, + MissingMigrationEvents, "{} not found for instantiation", missing_migration_event, + ItemNotFound, "ID {} of type {} not found", item_not_found, + VotingEnded, "Voting ended at {}", voting_ended, + NotAssemblyVoting, "Proposal is not in assembly vote phase", not_assembly_voting, + AssemblyVoteQty, "Assembly vote can only be one", assembly_vote_qty, + MsgNotInAssembly, "Msg not allowed in assembly", msg_not_in_assembly, + MsgNotInContract, "Msg not allowed in contract", msg_not_in_contract, + ContractDisabled, "Contract is disabled", contract_disabled, + MigrationNotStarted, "Migration has not started", migration_not_started, + NoMigrationTarget, "No migration target found", migration_tartet, + NotMigrator, "Can only be called by old governance", no_migrator, + AssembliesLimited, "Runtime state only permits specific assemblies to operate", assemblies_limited, + ContractMigrated, "Contract has migrated and cannot be interacted with anymore", migrated, + ProfileDisabled, "Profile {} is disabled", profile_disabled, + NotInAssembly, "{} is not in Assembly {}", not_in_assembly, + NotSelf, "Message signer can only be Governance", not_self, + ProposalNotPassed, "Proposal has not passed", not_passed, + CannotCancel, "Proposal can be canceled at {}", cannot_cancel, + CannotUpdate, "Proposal in status {} cannot be updated until {}", cannot_update, + UnexpectedQueryResponse, "Unexpected query response", unexpected_query_response, + StateCannotUpdate, "Current proposal state cannot be updated through this message", state_update, + FundingMsgNotSet, "Funding message was not set", funding_msg_not_set, + FundingLimitReached, "Funding time limit has been reached", funding_limit_reached, + CompletelyFunded, "Proposal is completely funded", completely_funded, + NoFundingProfile, "Funding profile setting was removed", no_funding_profile, + NoFundingState, "Proposal not in funding state", no_funding_state, + FundingNotClaimable, "Cannot claim funding", funding_not_claimable, + FundingClaimed, "Funding already claimed", funding_claimed, + FundingNothing, "Noting to claim", funding_nothing, + SenderMustBeFunding, "Sender must be the funding token", sender_funding, + VotingMoreThanBalance, "Total vote is greater than available balance", voting_balance, + VotingMsgNotSet, "Msg missing voting information", voting_msg, + VotingTimeReached, "Voting time was reached on {}", voting_time, + VotingNotInState, "Not in public voting phase", voting_not_state +); diff --git a/packages/shade_protocol/src/contract_interfaces/governance/mod.rs b/packages/shade_protocol/src/contract_interfaces/governance/mod.rs new file mode 100644 index 0000000..7d0a99a --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/governance/mod.rs @@ -0,0 +1,455 @@ +pub mod assembly; +pub mod contract; +pub mod errors; +pub mod profile; +pub mod proposal; +#[cfg(feature = "governance-impl")] +pub mod stored_id; +pub mod vote; + +use crate::{ + c_std::{Addr, Binary, Uint128}, + contract_interfaces::governance::{ + assembly::{Assembly, AssemblyMsg}, + contract::AllowedContract, + profile::{Profile, UpdateProfile}, + proposal::{Proposal, ProposalMsg}, + vote::Vote, + }, + utils::{asset::Contract, generic_response::ResponseStatus}, +}; + +use crate::{ + governance::proposal::Funding, + query_auth::QueryPermit, + utils::{ExecuteCallback, InstantiateCallback, Query}, +}; +use cosmwasm_schema::cw_serde; +use secret_storage_plus::{Item, Json}; + +#[cfg(feature = "governance-impl")] +use crate::utils::storage::plus::ItemStorage; + +// TODO: add errors + +// Admin command variable spot +pub const MSG_VARIABLE: &str = "{~}"; + +#[cw_serde] +pub struct Config { + pub query: Contract, + pub treasury: Addr, + // When public voting is enabled, a voting token is expected + pub vote_token: Option, + // When funding is enabled, a funding token is expected + pub funding_token: Option, + + // Migration information + pub migrated_from: Option, + pub migrated_to: Option, +} + +#[cfg(feature = "governance-impl")] +impl ItemStorage for Config { + const ITEM: Item<'static, Self, Json> = Item::new("config-"); +} + +// Used for original instantiation +#[cw_serde] +pub struct AssemblyInit { + pub admin_members: Vec, + pub admin_profile: Profile, + pub public_profile: Profile, +} + +// Used for migration instantiation +#[cw_serde] +pub struct MigrationInit { + pub source: Contract, + pub assembly: u16, + pub assembly_msg: u16, + pub profile: u16, + pub contract: u16, +} + +#[cw_serde] +pub struct InstantiateMsg { + pub treasury: Addr, + pub query_auth: Contract, + + // Admin rules + pub assemblies: Option, + + // Token rules + pub funding_token: Option, + pub vote_token: Option, + + // Migration data + pub migrator: Option, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum RuntimeState { + // Run like normal + Normal, + // Allow only specific assemblies and admin + SpecificAssemblies { assemblies: Vec }, + // Migrated - points to the new version + Migrated, +} + +#[cfg(feature = "governance-impl")] +impl ItemStorage for RuntimeState { + const ITEM: Item<'static, Self, Json> = Item::new("runtime-state-"); +} + +#[cw_serde] +pub enum MigrationDataAsk { + Assembly, + AssemblyMsg, + Profile, + Contract, +} + +#[cw_serde] +pub enum MigrationData { + Assembly { data: Vec<(u16, Assembly)> }, + AssemblyMsg { data: Vec<(u16, AssemblyMsg)> }, + Profile { data: Vec<(u16, Profile)> }, + Contract { data: Vec<(u16, AllowedContract)> }, +} + +#[cw_serde] +pub enum ExecuteMsg { + // Internal config + SetConfig { + query_auth: Option, + treasury: Option, + funding_token: Option, + vote_token: Option, + padding: Option, + }, + SetRuntimeState { + state: RuntimeState, + padding: Option, + }, + + // Proposal interaction + /// Triggers the proposal when the MSG is approved + Trigger { + //TODO: Must be deprecated for v1 + proposal: u32, + padding: Option, + }, + /// Cancels the proposal if the msg keeps failing + Cancel { + //TODO: Must be deprecated for v1 + proposal: u32, + padding: Option, + }, + /// Forces a proposal update, + /// proposals automatically update on interaction + /// but this is a cheaper alternative + Update { + proposal: u32, + padding: Option, + }, + /// Funds a proposal, msg is a prop ID + Receive { + sender: Addr, + from: Addr, + amount: Uint128, + msg: Option, + memo: Option, + padding: Option, + }, + ClaimFunding { + id: u32, + }, + /// Votes on a assembly vote + AssemblyVote { + proposal: u32, + vote: Vote, + padding: Option, + }, + /// Votes on voting token + ReceiveBalance { + sender: Addr, + msg: Option, + balance: Uint128, + memo: Option, + }, + + // Assemblies + /// Creates a proposal under a assembly + AssemblyProposal { + assembly: u16, + title: String, + metadata: String, + + // Optionals, if none the proposal is assumed to be a text proposal + msgs: Option>, + padding: Option, + }, + + /// Creates a new assembly + AddAssembly { + name: String, + metadata: String, + members: Vec, + profile: u16, + padding: Option, + }, + /// Edits an existing assembly + SetAssembly { + id: u16, + name: Option, + metadata: Option, + members: Option>, + profile: Option, + padding: Option, + }, + + // AssemblyMsgs + /// Creates a new assembly message and its allowed users + AddAssemblyMsg { + name: String, + msg: String, + assemblies: Vec, + padding: Option, + }, + /// Edits an existing assembly msg + SetAssemblyMsg { + id: u16, + name: Option, + msg: Option, + assemblies: Option>, + padding: Option, + }, + AddAssemblyMsgAssemblies { + id: u16, + assemblies: Vec, + }, + + // Profiles + /// Creates a new profile that can be added to assemblys + AddProfile { + profile: Profile, + padding: Option, + }, + /// Edits an already existing profile and the assemblys using the profile + SetProfile { + id: u16, + profile: UpdateProfile, + padding: Option, + }, + + // Contracts + AddContract { + name: String, + metadata: String, + contract: Contract, + assemblies: Option>, + padding: Option, + }, + SetContract { + id: u16, + name: Option, + metadata: Option, + contract: Option, + disable_assemblies: bool, + assemblies: Option>, + padding: Option, + }, + AddContractAssemblies { + id: u16, + assemblies: Vec, + }, + // Migrations + // Export total numeric IDs + // Committee, msg, profile and contract keys must be exported + // Create a struct that stores the last migrated IDs + // Enum for migration targets + // migrate gives an array of items with their appropriate IDs + // Migrate Committee, Msg, Profile and Contract + // When receiving migration data, if given ID is greater then ignore + + // Add functions for exporting lists of data into the needed contracts + Migrate { + id: u64, + label: String, + code_hash: String, + }, + MigrateData { + data: MigrationDataAsk, + total: u16, + }, + ReceiveMigrationData { + data: MigrationData, + }, +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + SetConfig { status: ResponseStatus }, + SetRuntimeState { status: ResponseStatus }, + Proposal { status: ResponseStatus }, + ReceiveBalance { status: ResponseStatus }, + Trigger { status: ResponseStatus }, + Cancel { status: ResponseStatus }, + Update { status: ResponseStatus }, + Receive { status: ResponseStatus }, + ClaimFunding { status: ResponseStatus }, + AssemblyVote { status: ResponseStatus }, + AssemblyProposal { status: ResponseStatus }, + AddAssembly { status: ResponseStatus }, + SetAssembly { status: ResponseStatus }, + AddAssemblyMsg { status: ResponseStatus }, + SetAssemblyMsg { status: ResponseStatus }, + AddProfile { status: ResponseStatus }, + SetProfile { status: ResponseStatus }, + AddContract { status: ResponseStatus }, + SetContract { status: ResponseStatus }, + AddContractAssemblies { status: ResponseStatus }, + Migrate { status: ResponseStatus }, + MigrateData { status: ResponseStatus }, + ReceiveMigrationData { status: ResponseStatus }, +} + +#[cw_serde] +pub struct Pagination { + pub page: u16, + pub amount: u32, +} + +#[cw_serde] +pub enum AuthQuery { + Proposals { pagination: Pagination }, + AssemblyVotes { pagination: Pagination }, + Funding { pagination: Pagination }, + Votes { pagination: Pagination }, +} + +#[remain::sorted] +#[cw_serde] +pub struct QueryData {} + +#[cw_serde] +pub enum QueryMsg { + Config {}, + + TotalProposals {}, + + Proposals { + start: u32, + end: u32, + }, + + TotalAssemblies {}, + + Assemblies { + start: u16, + end: u16, + }, + + TotalAssemblyMsgs {}, + + AssemblyMsgs { + start: u16, + end: u16, + }, + + TotalProfiles {}, + + Profiles { + start: u16, + end: u16, + }, + + TotalContracts {}, + + Contracts { + start: u16, + end: u16, + }, + + WithVK { + user: Addr, + key: String, + query: AuthQuery, + }, + + WithPermit { + permit: QueryPermit, + query: AuthQuery, + }, +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub struct ResponseWithID { + pub prop_id: u32, + pub data: T, +} + +#[cw_serde] +pub enum QueryAnswer { + Config { + config: Config, + }, + + Proposals { + props: Vec, + }, + + Assemblies { + assemblies: Vec, + }, + + AssemblyMsgs { + msgs: Vec, + }, + + Profiles { + profiles: Vec, + }, + + Contracts { + contracts: Vec, + }, + + Total { + total: u32, + }, + + UserProposals { + props: Vec>, + total: u32, + }, + + UserAssemblyVotes { + votes: Vec>, + total: u32, + }, + + UserFunding { + funds: Vec>, + total: u32, + }, + + UserVotes { + votes: Vec>, + total: u32, + }, +} diff --git a/packages/shade_protocol/src/contract_interfaces/governance/profile.rs b/packages/shade_protocol/src/contract_interfaces/governance/profile.rs new file mode 100644 index 0000000..e5bcdad --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/governance/profile.rs @@ -0,0 +1,308 @@ +use crate::{ + c_std::{StdError, StdResult, Storage, Uint128}, + contract_interfaces::governance::stored_id::ID, +}; + +use cosmwasm_schema::cw_serde; +use secret_storage_plus::Map; + +#[cfg(feature = "governance-impl")] +use crate::utils::storage::plus::{MapStorage, NaiveMapStorage}; + +/// Allow better control over the safety and privacy features that proposals will need if +/// Assemblys are implemented. If a profile is disabled then its assembly will also be disabled. +/// All percentages are taken as follows 100000 = 100% +#[cw_serde] +pub struct Profile { + pub name: String, + // State of the current profile and its subsequent assemblies + pub enabled: bool, + // Require assembly voting + #[serde(skip_serializing_if = "Option::is_none")] + pub assembly: Option, + // Require funding + #[serde(skip_serializing_if = "Option::is_none")] + pub funding: Option, + // Require token voting + #[serde(skip_serializing_if = "Option::is_none")] + pub token: Option, + // Once the contract is approved, theres a deadline for the tx to be executed and completed + // else it will just be canceled and assume that the tx failed + pub cancel_deadline: u64, +} + +const COMMITTEE_PROFILE_KEY: Map = Map::new("assembly_vote_profile-"); +const TOKEN_PROFILE_KEY: Map = Map::new("token_vote_profile-"); + +#[cfg(feature = "governance-impl")] +impl Profile { + pub fn load(storage: &dyn Storage, id: u16) -> StdResult { + let data = Self::data(storage, id)?; + + Ok(Self { + name: data.name, + enabled: data.enabled, + assembly: Self::assembly_voting(storage, id)?, + funding: Self::funding(storage, id)?, + token: Self::public_voting(storage, id)?, + cancel_deadline: data.cancel_deadline, + }) + } + + pub fn may_load(storage: &dyn Storage, id: u16) -> StdResult> { + if id > ID::profile(storage)? { + return Ok(None); + } + Ok(Some(Self::load(storage, id)?)) + } + + pub fn save(&self, storage: &mut dyn Storage, id: u16) -> StdResult<()> { + ProfileData { + name: self.name.clone(), + enabled: self.enabled, + cancel_deadline: self.cancel_deadline, + } + .save(storage, id)?; + + Self::save_assembly_voting(storage, id, self.assembly.clone())?; + + Self::save_public_voting(storage, id, self.token.clone())?; + + Self::save_funding(storage, id, self.funding.clone())?; + + Ok(()) + } + + pub fn data(storage: &dyn Storage, id: u16) -> StdResult { + ProfileData::load(storage, id) + } + + pub fn save_data(storage: &mut dyn Storage, id: u16, data: ProfileData) -> StdResult<()> { + data.save(storage, id) + } + + pub fn assembly_voting(storage: &dyn Storage, id: u16) -> StdResult> { + Ok(VoteProfileType::load(storage, COMMITTEE_PROFILE_KEY, id)?.0) + } + + pub fn save_assembly_voting( + storage: &mut dyn Storage, + id: u16, + assembly: Option, + ) -> StdResult<()> { + VoteProfileType(assembly).save(storage, COMMITTEE_PROFILE_KEY, id) + } + + pub fn public_voting(storage: &dyn Storage, id: u16) -> StdResult> { + Ok(VoteProfileType::load(storage, TOKEN_PROFILE_KEY, id)?.0) + } + + pub fn save_public_voting( + storage: &mut dyn Storage, + id: u16, + token: Option, + ) -> StdResult<()> { + VoteProfileType(token).save(storage, TOKEN_PROFILE_KEY, id) + } + + pub fn funding(storage: &dyn Storage, id: u16) -> StdResult> { + Ok(FundProfileType::load(storage, id)?.0) + } + + pub fn save_funding( + storage: &mut dyn Storage, + id: u16, + funding: Option, + ) -> StdResult<()> { + FundProfileType(funding).save(storage, id) + } +} + +#[cfg(feature = "governance-impl")] +#[cw_serde] +pub struct ProfileData { + pub name: String, + pub enabled: bool, + pub cancel_deadline: u64, +} + +#[cfg(feature = "governance-impl")] +impl MapStorage<'static, u16> for ProfileData { + const MAP: Map<'static, u16, Self> = Map::new("profile_data-"); +} + +#[cfg(feature = "governance-impl")] +#[cw_serde] // NOTE: 100% = Uint128::new(10000) +pub struct VoteProfile { + // Deadline for voting + pub deadline: u64, + // Expected participation threshold + pub threshold: Count, + // Expected yes votes + pub yes_threshold: Count, + // Expected veto votes + pub veto_threshold: Count, +} + +#[cfg(feature = "governance-impl")] +#[cw_serde] +struct VoteProfileType(pub Option); + +#[cfg(feature = "governance-impl")] +impl NaiveMapStorage<'static> for VoteProfileType {} + +#[cfg(feature = "governance-impl")] +#[cw_serde] +pub struct FundProfile { + // Deadline for funding + pub deadline: u64, + // Amount required to fund + pub required: Uint128, + // Display voter information + pub privacy: bool, + // Deposit loss on vetoed proposal + pub veto_deposit_loss: Uint128, +} + +#[cfg(feature = "governance-impl")] +#[cw_serde] +struct FundProfileType(pub Option); + +#[cfg(feature = "governance-impl")] +impl MapStorage<'static, u16> for FundProfileType { + const MAP: Map<'static, u16, Self> = Map::new("fund_profile-"); +} + +/// Helps simplify the given limits +#[cw_serde] +pub enum Count { + Percentage { percent: u16 }, + LiteralCount { count: Uint128 }, +} + +#[cw_serde] +pub struct UpdateProfile { + pub name: Option, + // State of the current profile and its subsequent assemblies + pub enabled: Option, + // Assembly status + pub disable_assembly: bool, + // Require assembly voting + pub assembly: Option, + // Funding status + pub disable_funding: bool, + // Require funding + pub funding: Option, + // Require token voting + pub disable_token: bool, + // Require token voting + pub token: Option, + // Once the contract is approved, theres a deadline for the tx to be executed and completed + // else it will just be canceled and assume that the tx failed + pub cancel_deadline: Option, +} + +#[cw_serde] +pub struct UpdateVoteProfile { + // Deadline for voting + pub deadline: Option, + // Expected participation threshold + pub threshold: Option, + // Expected yes votes + pub yes_threshold: Option, + // Expected veto votes + pub veto_threshold: Option, +} + +impl UpdateVoteProfile { + pub fn update_profile(&self, profile: &Option) -> StdResult { + let new_profile: VoteProfile; + + if let Some(profile) = profile { + new_profile = VoteProfile { + deadline: self.deadline.unwrap_or(profile.deadline), + threshold: self.threshold.clone().unwrap_or(profile.threshold.clone()), + yes_threshold: self + .yes_threshold + .clone() + .unwrap_or(profile.yes_threshold.clone()), + veto_threshold: self + .veto_threshold + .clone() + .unwrap_or(profile.veto_threshold.clone()), + }; + } else { + new_profile = VoteProfile { + deadline: match self.deadline { + None => Err(StdError::generic_err("Vote profile must be set")), + Some(ret) => Ok(ret), + }?, + threshold: match self.threshold.clone() { + None => Err(StdError::generic_err("Vote profile must be set")), + Some(ret) => Ok(ret), + }?, + yes_threshold: match self.yes_threshold.clone() { + None => Err(StdError::generic_err("Vote profile must be set")), + Some(ret) => Ok(ret), + }?, + veto_threshold: match self.veto_threshold.clone() { + None => Err(StdError::generic_err("Vote profile must be set")), + Some(ret) => Ok(ret), + }?, + }; + } + + Ok(new_profile) + } +} + +#[cw_serde] +pub struct UpdateFundProfile { + // Deadline for funding + pub deadline: Option, + // Amount required to fund + pub required: Option, + // Display voter information + pub privacy: Option, + // Deposit loss on vetoed proposal + pub veto_deposit_loss: Option, +} + +impl UpdateFundProfile { + pub fn update_profile(&self, profile: &Option) -> StdResult { + let new_profile: FundProfile; + + if let Some(profile) = profile { + new_profile = FundProfile { + deadline: self.deadline.unwrap_or(profile.deadline), + required: self.required.unwrap_or(profile.required), + privacy: self.privacy.unwrap_or(profile.privacy), + veto_deposit_loss: self + .veto_deposit_loss + .clone() + .unwrap_or(profile.veto_deposit_loss.clone()), + }; + } else { + new_profile = FundProfile { + deadline: match self.deadline { + None => Err(StdError::generic_err("Fund profile must be set")), + Some(ret) => Ok(ret), + }?, + required: match self.required { + None => Err(StdError::generic_err("Fund profile must be set")), + Some(ret) => Ok(ret), + }?, + privacy: match self.privacy { + None => Err(StdError::generic_err("Fund profile must be set")), + Some(ret) => Ok(ret), + }?, + veto_deposit_loss: match self.veto_deposit_loss.clone() { + None => Err(StdError::generic_err("Fund profile must be set")), + Some(ret) => Ok(ret), + }?, + }; + } + + Ok(new_profile) + } +} diff --git a/packages/shade_protocol/src/contract_interfaces/governance/proposal.rs b/packages/shade_protocol/src/contract_interfaces/governance/proposal.rs new file mode 100644 index 0000000..174652b --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/governance/proposal.rs @@ -0,0 +1,399 @@ +use crate::{ + c_std::{Addr, Binary, Coin, StdResult, Storage, Uint128}, + contract_interfaces::governance::{ + assembly::Assembly, + profile::Profile, + stored_id::{UserID, ID}, + vote::Vote, + }, +}; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Timestamp; +use secret_storage_plus::Map; + +#[cfg(feature = "governance-impl")] +use crate::utils::storage::plus::{MapStorage, NaiveMapStorage}; + +#[cw_serde] +pub struct Proposal { + // Description + // Address of the proposal proposer + pub proposer: Addr, + // Proposal title + pub title: String, + // Description of proposal, can be in base64 + pub metadata: String, + + // Msg + #[serde(skip_serializing_if = "Option::is_none")] + pub msgs: Option>, + + // Assembly + // Assembly that called the proposal + pub assembly: u16, + + #[serde(skip_serializing_if = "Option::is_none")] + pub assembly_vote_tally: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub public_vote_tally: Option, + + // Status + pub status: Status, + + // Status History + pub status_history: Vec, + + // Funders + // Leave as an option so we can hide the data if None + #[serde(skip_serializing_if = "Option::is_none")] + pub funders: Option>, +} + +const ASSEMBLY_VOTE: Map<'static, (u32, Addr), Vote> = Map::new("user-assembly-vote-"); +const ASSEMBLY_VOTES: Map<'static, u32, Vote> = Map::new("total-assembly-votes-"); +const PUBLIC_VOTE: Map<'static, (u32, Addr), Vote> = Map::new("user-public-vote-"); +const PUBLIC_VOTES: Map<'static, u32, Vote> = Map::new("total-public-votes-"); + +#[cfg(feature = "governance-impl")] +impl Proposal { + pub fn save(&self, storage: &mut dyn Storage) -> StdResult<()> { + // Create new ID + let id = ID::add_proposal(storage)?; + + // Create proposers id + UserID::add_proposal(storage, self.proposer.clone(), &id)?; + + if let Some(msgs) = self.msgs.clone() { + Self::save_msg(storage, id, msgs)?; + } + + Self::save_description(storage, id, ProposalDescription { + proposer: self.proposer.clone(), + title: self.title.clone(), + metadata: self.metadata.clone(), + })?; + + Self::save_assembly(storage, id, self.assembly)?; + + Self::save_status(storage, id, self.status.clone())?; + + Self::save_status_history(storage, id, self.status_history.clone())?; + + if let Some(funder_list) = self.funders.clone() { + let mut funders = vec![]; + for (funder, funding) in funder_list.iter() { + funders.push(funder.clone()); + Self::save_funding(storage, id, &funder, Funding { + amount: *funding, + claimed: false, + })? + } + Self::save_funders(storage, id, funders)?; + } + + Ok(()) + } + + pub fn may_load(storage: &dyn Storage, id: u32) -> StdResult> { + if id > ID::proposal(storage)? { + return Ok(None); + } + Ok(Some(Self::load(storage, id)?)) + } + + pub fn load(storage: &dyn Storage, id: u32) -> StdResult { + let msgs = Self::msg(storage, id)?; + let description = Self::description(storage, id)?; + let assembly = Self::assembly(storage, id)?; + let status = Self::status(storage, id)?; + let status_history = Self::status_history(storage, id)?; + + let mut funders_arr = vec![]; + for funder in Self::funders(storage, id)?.iter() { + funders_arr.push((funder.clone(), Self::funding(storage, id, &funder)?.amount)) + } + + let mut funders: Option> = None; + if !funders_arr.is_empty() { + if let Some(prof) = + Profile::funding(storage, Assembly::data(storage, assembly)?.profile)? + { + if !prof.privacy { + funders = Some(funders_arr); + } + } + } + + let assembly_data = Assembly::data(storage, assembly)?; + + Ok(Self { + title: description.title, + proposer: description.proposer, + metadata: description.metadata, + msgs, + assembly, + assembly_vote_tally: match Profile::assembly_voting(storage, assembly_data.profile)? { + None => None, + Some(_) => Some(Self::assembly_votes(storage, id)?), + }, + public_vote_tally: match Profile::public_voting(storage, assembly_data.profile)? { + None => None, + Some(_) => Some(Self::public_votes(storage, id)?), + }, + status, + status_history, + funders, + }) + } + + pub fn msg(storage: &dyn Storage, id: u32) -> StdResult>> { + match ProposalMsgs::may_load(storage, id)? { + None => Ok(None), + Some(i) => Ok(Some(i.0)), + } + } + + pub fn save_msg(storage: &mut dyn Storage, id: u32, data: Vec) -> StdResult<()> { + ProposalMsgs(data).save(storage, id) + } + + pub fn description(storage: &dyn Storage, id: u32) -> StdResult { + ProposalDescription::load(storage, id) + } + + pub fn save_description( + storage: &mut dyn Storage, + id: u32, + data: ProposalDescription, + ) -> StdResult<()> { + data.save(storage, id) + } + + pub fn assembly(storage: &dyn Storage, id: u32) -> StdResult { + Ok(ProposalAssembly::load(storage, id)?.0) + } + + pub fn save_assembly(storage: &mut dyn Storage, id: u32, data: u16) -> StdResult<()> { + ProposalAssembly(data).save(storage, id) + } + + pub fn status(storage: &dyn Storage, id: u32) -> StdResult { + Status::load(storage, id) + } + + pub fn save_status(storage: &mut dyn Storage, id: u32, data: Status) -> StdResult<()> { + data.save(storage, id) + } + + pub fn status_history(storage: &dyn Storage, id: u32) -> StdResult> { + Ok(StatusHistory::load(storage, id)?.0) + } + + pub fn save_status_history( + storage: &mut dyn Storage, + id: u32, + data: Vec, + ) -> StdResult<()> { + StatusHistory(data).save(storage, id) + } + + pub fn funders(storage: &dyn Storage, id: u32) -> StdResult> { + let funders = match Funders::may_load(storage, id)? { + None => vec![], + Some(item) => item.0, + }; + Ok(funders) + } + + pub fn save_funders(storage: &mut dyn Storage, id: u32, data: Vec) -> StdResult<()> { + Funders(data).save(storage, id) + } + + pub fn funding(storage: &dyn Storage, id: u32, user: &Addr) -> StdResult { + Funding::load(storage, (id, user.clone())) + } + + pub fn save_funding( + storage: &mut dyn Storage, + id: u32, + user: &Addr, + data: Funding, + ) -> StdResult<()> { + data.save(storage, (id, user.clone())) + } + + // User assembly votes + pub fn assembly_vote(storage: &dyn Storage, id: u32, user: &Addr) -> StdResult> { + Ok(Vote::may_load(storage, ASSEMBLY_VOTE, (id, user.clone()))?) + } + + pub fn save_assembly_vote( + storage: &mut dyn Storage, + id: u32, + user: &Addr, + data: &Vote, + ) -> StdResult<()> { + data.save(storage, ASSEMBLY_VOTE, (id, user.clone())) + } + + // Total assembly votes + pub fn assembly_votes(storage: &dyn Storage, id: u32) -> StdResult { + match Vote::may_load(storage, ASSEMBLY_VOTES, id)? { + None => Ok(Vote::default()), + Some(vote) => Ok(vote), + } + } + + pub fn save_assembly_votes(storage: &mut dyn Storage, id: u32, data: &Vote) -> StdResult<()> { + data.save(storage, ASSEMBLY_VOTES, id) + } + + // User public votes + pub fn public_vote(storage: &dyn Storage, id: u32, user: &Addr) -> StdResult> { + Ok(Vote::may_load(storage, PUBLIC_VOTE, (id, user.clone()))?) + } + + pub fn save_public_vote( + storage: &mut dyn Storage, + id: u32, + user: &Addr, + data: &Vote, + ) -> StdResult<()> { + data.save(storage, PUBLIC_VOTE, (id, user.clone())) + } + + // Total public votes + pub fn public_votes(storage: &dyn Storage, id: u32) -> StdResult { + match Vote::may_load(storage, PUBLIC_VOTES, id)? { + None => Ok(Vote::default()), + Some(vote) => Ok(vote), + } + } + + pub fn save_public_votes(storage: &mut dyn Storage, id: u32, data: &Vote) -> StdResult<()> { + data.save(storage, PUBLIC_VOTES, id) + } +} + +#[cw_serde] +pub struct ProposalDescription { + pub proposer: Addr, + pub title: String, + pub metadata: String, +} + +#[cfg(feature = "governance-impl")] +impl MapStorage<'static, u32> for ProposalDescription { + const MAP: Map<'static, u32, Self> = Map::new("proposal_description-"); +} + +#[cw_serde] +pub struct ProposalMsg { + pub target: u16, + pub assembly_msg: u16, + // Used as both Vec when calling a handleMsg and Vec when saving the msg + pub msg: Binary, + pub send: Vec, +} + +#[cw_serde] +struct ProposalMsgs(pub Vec); + +#[cfg(feature = "governance-impl")] +impl MapStorage<'static, u32> for ProposalMsgs { + const MAP: Map<'static, u32, Self> = Map::new("proposal_msgs-"); +} + +#[cw_serde] +struct ProposalAssembly(pub u16); + +#[cfg(feature = "governance-impl")] +impl MapStorage<'static, u32> for ProposalAssembly { + const MAP: Map<'static, u32, Self> = Map::new("proposal_assembly-"); +} + +#[cw_serde] +pub enum Status { + // Assembly voting period + AssemblyVote { + start: u64, + end: u64, + }, + // In funding period + Funding { + amount: Uint128, + start: u64, + end: u64, + }, + // Voting in progress + Voting { + start: u64, + end: u64, + }, + // Total votes did not reach minimum total votes + Expired, + // Proposal was rejected + Rejected, + // Proposal was vetoed + // NOTE: percent it stored because proposal settings can change before claiming + Vetoed { + slash_percent: Uint128, + }, + // Proposal was approved, has a set timeline before it can be canceled + Passed { + start: u64, + end: u64, + }, + // If proposal is a msg then it was executed and was successful + Success, + // Proposal never got executed after a cancel deadline, + // assumed that tx failed everytime it got triggered + Canceled, +} + +impl Status { + pub fn passed(storage: &dyn Storage, profile: u16, time: &Timestamp) -> StdResult { + let seconds = time.seconds(); + Ok(Self::Passed { + start: seconds, + end: seconds + Profile::data(storage, profile)?.cancel_deadline, + }) + } +} + +#[cfg(feature = "governance-impl")] +impl MapStorage<'static, u32> for Status { + const MAP: Map<'static, u32, Self> = Map::new("proposal_status-"); +} + +#[cfg(feature = "governance-impl")] +#[cw_serde] +struct StatusHistory(pub Vec); + +#[cfg(feature = "governance-impl")] +impl MapStorage<'static, u32> for StatusHistory { + const MAP: Map<'static, u32, Self> = Map::new("proposal_status_history-"); +} + +#[cfg(feature = "governance-impl")] +#[cw_serde] +struct Funders(pub Vec); + +#[cfg(feature = "governance-impl")] +impl MapStorage<'static, u32> for Funders { + const MAP: Map<'static, u32, Self> = Map::new("proposal_funders-"); +} + +#[cfg(feature = "governance-impl")] +#[cw_serde] +pub struct Funding { + pub amount: Uint128, + pub claimed: bool, +} + +#[cfg(feature = "governance-impl")] +impl MapStorage<'static, (u32, Addr)> for Funding { + const MAP: Map<'static, (u32, Addr), Self> = Map::new("proposal_funding-"); +} diff --git a/packages/shade_protocol/src/contract_interfaces/governance/stored_id.rs b/packages/shade_protocol/src/contract_interfaces/governance/stored_id.rs new file mode 100644 index 0000000..39ef223 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/governance/stored_id.rs @@ -0,0 +1,261 @@ +use crate::{ + c_std::{StdResult, Storage}, + utils::storage::plus::{NaiveItemStorage, NaiveMapStorage}, +}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Addr; +use secret_storage_plus::{Item, Json, Map}; + +#[cw_serde] // Used to get total IDs +pub struct ID(u16); + +impl NaiveItemStorage for ID {} + +const PROP_KEY: Item<'static, u32, Json> = Item::new("proposal_id-"); +const ASSEMBLY_KEY: Item<'static, ID, Json> = Item::new("assembly_id-"); +const ASSEMBLY_MSG_KEY: Item<'static, ID, Json> = Item::new("assembly_msg_id-"); +const PROFILE_KEY: Item<'static, ID, Json> = Item::new("profile_id-"); +const CONTRACT_KEY: Item<'static, ID, Json> = Item::new("allowed_contract_id-"); + +// Migration specific data +// Used to determine the next ID to migrate over +const LAST_ASSEMBLY_KEY: Item<'static, ID, Json> = Item::new("last_assembly_id-"); +const LAST_ASSEMBLY_MSG_KEY: Item<'static, ID, Json> = Item::new("last_assembly_msg_id-"); +const LAST_PROFILE_KEY: Item<'static, ID, Json> = Item::new("last_profile_id-"); +const LAST_CONTRACT_KEY: Item<'static, ID, Json> = Item::new("last_allowed_contract_id-"); + +impl ID { + // Load current ID related proposals + pub fn set_proposal(storage: &mut dyn Storage, id: u32) -> StdResult<()> { + PROP_KEY.save(storage, &id) + } + + pub fn proposal(storage: &dyn Storage) -> StdResult { + Ok(PROP_KEY.load(storage)?) + } + + pub fn add_proposal(storage: &mut dyn Storage) -> StdResult { + let item = match PROP_KEY.may_load(storage)? { + None => 0, + Some(i) => i.checked_add(1).unwrap(), + }; + PROP_KEY.save(storage, &item)?; + Ok(item) + } + + // Assembly + pub fn set_assembly(storage: &mut dyn Storage, id: u16) -> StdResult<()> { + ID(id).save(storage, ASSEMBLY_KEY) + } + + pub fn assembly(storage: &dyn Storage) -> StdResult { + Ok(ID::load(storage, ASSEMBLY_KEY)?.0) + } + + pub fn add_assembly(storage: &mut dyn Storage) -> StdResult { + let mut item = ID::load(storage, ASSEMBLY_KEY)?; + item.0 = item.0.checked_add(1).unwrap(); + item.save(storage, ASSEMBLY_KEY)?; + Ok(item.0) + } + + // Committee Msg + pub fn set_assembly_msg(storage: &mut dyn Storage, id: u16) -> StdResult<()> { + ID(id).save(storage, ASSEMBLY_MSG_KEY) + } + + pub fn assembly_msg(storage: &dyn Storage) -> StdResult { + Ok(ID::load(storage, ASSEMBLY_MSG_KEY)?.0) + } + + pub fn add_assembly_msg(storage: &mut dyn Storage) -> StdResult { + let mut item = ID::load(storage, ASSEMBLY_MSG_KEY)?; + item.0 = item.0.checked_add(1).unwrap(); + item.save(storage, ASSEMBLY_MSG_KEY)?; + Ok(item.0) + } + + // Profile + pub fn set_profile(storage: &mut dyn Storage, id: u16) -> StdResult<()> { + ID(id).save(storage, PROFILE_KEY) + } + + pub fn profile(storage: &dyn Storage) -> StdResult { + Ok(ID::load(storage, PROFILE_KEY)?.0) + } + + pub fn add_profile(storage: &mut dyn Storage) -> StdResult { + let mut item = ID::load(storage, PROFILE_KEY)?; + item.0 = item.0.checked_add(1).unwrap(); + item.save(storage, PROFILE_KEY)?; + Ok(item.0) + } + + // Contract + // Profile + pub fn set_contract(storage: &mut dyn Storage, id: u16) -> StdResult<()> { + ID(id).save(storage, CONTRACT_KEY) + } + + pub fn contract(storage: &dyn Storage) -> StdResult { + Ok(ID::load(storage, CONTRACT_KEY)?.0) + } + + pub fn add_contract(storage: &mut dyn Storage) -> StdResult { + let mut item = ID::load(storage, CONTRACT_KEY)?; + item.0 = item.0.checked_add(1).unwrap(); + item.save(storage, CONTRACT_KEY)?; + Ok(item.0) + } + + // Migration + pub fn init_migration(storage: &mut dyn Storage) -> StdResult<()> { + let id = ID(0); + id.save(storage, LAST_ASSEMBLY_KEY)?; + id.save(storage, LAST_ASSEMBLY_MSG_KEY)?; + id.save(storage, LAST_PROFILE_KEY)?; + id.save(storage, LAST_CONTRACT_KEY)?; + Ok(()) + } + + pub fn set_assembly_migration(storage: &mut dyn Storage, id: u16) -> StdResult<()> { + ID(id).save(storage, LAST_ASSEMBLY_KEY) + } + + pub fn assembly_migration(storage: &dyn Storage) -> StdResult { + Ok(ID::load(storage, LAST_ASSEMBLY_KEY)?.0) + } + + pub fn set_assembly_msg_migration(storage: &mut dyn Storage, id: u16) -> StdResult<()> { + ID(id).save(storage, LAST_ASSEMBLY_MSG_KEY) + } + + pub fn assembly_msg_migration(storage: &dyn Storage) -> StdResult { + Ok(ID::load(storage, LAST_ASSEMBLY_MSG_KEY)?.0) + } + + pub fn set_profile_migration(storage: &mut dyn Storage, id: u16) -> StdResult<()> { + ID(id).save(storage, LAST_PROFILE_KEY) + } + + pub fn profile_migration(storage: &dyn Storage) -> StdResult { + Ok(ID::load(storage, LAST_PROFILE_KEY)?.0) + } + + pub fn set_contract_migration(storage: &mut dyn Storage, id: u16) -> StdResult<()> { + ID(id).save(storage, LAST_CONTRACT_KEY) + } + + pub fn contract_migration(storage: &dyn Storage) -> StdResult { + Ok(ID::load(storage, LAST_CONTRACT_KEY)?.0) + } +} + +#[cw_serde] +// Used for ease of querying +pub struct UserID(u32); + +impl NaiveMapStorage<'static> for UserID {} + +// Using user ID cause its practically the same type +const USER_PROP_ID: Map<'static, Addr, UserID> = Map::new("user_proposal_id-"); +const USER_PROP: Map<'static, (Addr, u32), UserID> = Map::new("user_proposal_list-"); + +const USER_ASSEMBLY_VOTE_ID: Map<'static, Addr, UserID> = Map::new("user_assembly_votes_id-"); +const USER_ASSEMBLY_VOTE: Map<'static, (Addr, u32), UserID> = Map::new("user_assembly_votes_list-"); + +const USER_FUNDING_ID: Map<'static, Addr, UserID> = Map::new("user_funding_id-"); +const USER_FUNDING: Map<'static, (Addr, u32), UserID> = Map::new("user_funding_list-"); + +const USER_VOTES_ID: Map<'static, Addr, UserID> = Map::new("user_votes_id-"); +const USER_VOTES: Map<'static, (Addr, u32), UserID> = Map::new("user_votes_list-"); + +impl UserID { + // Stores the proposal's id + pub fn total_proposals(storage: &dyn Storage, user: Addr) -> StdResult { + Ok(UserID::may_load(storage, USER_PROP_ID, user)? + .unwrap_or(UserID(0)) + .0) + } + + pub fn proposal(storage: &dyn Storage, user: Addr, id: u32) -> StdResult { + Ok(UserID::load(storage, USER_PROP, (user, id))?.0) + } + + pub fn add_proposal(storage: &mut dyn Storage, user: Addr, prop_id: &u32) -> StdResult { + let item = match UserID::may_load(storage, USER_PROP_ID, user.clone())? { + None => 0, + Some(i) => i.0.checked_add(1).unwrap(), + }; + UserID(item).save(storage, USER_PROP_ID, user.clone())?; + UserID(prop_id.clone()).save(storage, USER_PROP, (user, item))?; + Ok(item) + } + + // Stores the proposal's ID so it can be cross searched + pub fn total_assembly_votes(storage: &dyn Storage, user: Addr) -> StdResult { + Ok(UserID::may_load(storage, USER_ASSEMBLY_VOTE_ID, user)? + .unwrap_or(UserID(0)) + .0) + } + + pub fn assembly_vote(storage: &dyn Storage, user: Addr, id: u32) -> StdResult { + Ok(UserID::load(storage, USER_ASSEMBLY_VOTE, (user, id))?.0) + } + + pub fn add_assembly_vote( + storage: &mut dyn Storage, + user: Addr, + prop_id: u32, + ) -> StdResult { + let item = match UserID::may_load(storage, USER_ASSEMBLY_VOTE_ID, user.clone())? { + None => 0, + Some(i) => i.0.checked_add(1).unwrap(), + }; + UserID(item).save(storage, USER_ASSEMBLY_VOTE_ID, user.clone())?; + UserID(prop_id).save(storage, USER_ASSEMBLY_VOTE, (user, item))?; + Ok(item) + } + + // Stores the proposal's ID so it can be cross searched + pub fn total_funding(storage: &dyn Storage, user: Addr) -> StdResult { + Ok(UserID::may_load(storage, USER_FUNDING_ID, user)? + .unwrap_or(UserID(0)) + .0) + } + + pub fn funding(storage: &dyn Storage, user: Addr, id: u32) -> StdResult { + Ok(UserID::load(storage, USER_FUNDING, (user, id))?.0) + } + + pub fn add_funding(storage: &mut dyn Storage, user: Addr, prop_id: u32) -> StdResult { + let item = match UserID::may_load(storage, USER_FUNDING_ID, user.clone())? { + None => 0, + Some(i) => i.0.checked_add(1).unwrap(), + }; + UserID(item).save(storage, USER_FUNDING_ID, user.clone())?; + UserID(prop_id).save(storage, USER_FUNDING, (user, item))?; + Ok(item) + } + + // Stores the proposal's ID so it can be cross searched + pub fn total_votes(storage: &dyn Storage, user: Addr) -> StdResult { + Ok(UserID::may_load(storage, USER_VOTES_ID, user)? + .unwrap_or(UserID(0)) + .0) + } + + pub fn votes(storage: &dyn Storage, user: Addr, id: u32) -> StdResult { + Ok(UserID::load(storage, USER_VOTES, (user, id))?.0) + } + + pub fn add_vote(storage: &mut dyn Storage, user: Addr, prop_id: u32) -> StdResult { + let item = match UserID::may_load(storage, USER_VOTES_ID, user.clone())? { + None => 0, + Some(i) => i.0.checked_add(1).unwrap(), + }; + UserID(item).save(storage, USER_VOTES_ID, user.clone())?; + UserID(prop_id).save(storage, USER_VOTES, (user, item))?; + Ok(item) + } +} diff --git a/packages/shade_protocol/src/contract_interfaces/governance/vote.rs b/packages/shade_protocol/src/contract_interfaces/governance/vote.rs new file mode 100644 index 0000000..001b208 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/governance/vote.rs @@ -0,0 +1,79 @@ +use crate::c_std::{StdResult, Uint128}; + +use cosmwasm_schema::cw_serde; + +#[cfg(feature = "governance-impl")] +use crate::utils::storage::plus::NaiveMapStorage; + +#[cw_serde] +pub struct ReceiveBalanceMsg { + pub vote: Vote, + pub proposal: u32, +} + +#[cw_serde] +pub struct Vote { + pub yes: Uint128, + pub no: Uint128, + pub no_with_veto: Uint128, + pub abstain: Uint128, +} + +#[cfg(feature = "governance-impl")] +impl NaiveMapStorage<'static> for Vote {} + +impl Default for Vote { + fn default() -> Self { + Self { + yes: Uint128::zero(), + no: Uint128::zero(), + no_with_veto: Uint128::zero(), + abstain: Uint128::zero(), + } + } +} + +impl Vote { + pub fn total_count(&self) -> StdResult { + Ok(self.yes.checked_add( + self.no + .checked_add(self.no_with_veto.checked_add(self.abstain)?)?, + )?) + } + + pub fn checked_sub(&self, vote: &Self) -> StdResult { + Ok(Self { + yes: self.yes.checked_sub(vote.yes)?, + no: self.no.checked_sub(vote.no)?, + no_with_veto: self.no_with_veto.checked_sub(vote.no_with_veto)?, + abstain: self.abstain.checked_sub(vote.abstain)?, + }) + } + + pub fn checked_add(&self, vote: &Self) -> StdResult { + Ok(Self { + yes: self.yes.checked_add(vote.yes)?, + no: self.no.checked_add(vote.no)?, + no_with_veto: self.no_with_veto.checked_add(vote.no_with_veto)?, + abstain: self.abstain.checked_add(vote.abstain)?, + }) + } +} + +pub struct TalliedVotes { + pub yes: Uint128, + pub no: Uint128, + pub veto: Uint128, + pub total: Uint128, +} + +impl TalliedVotes { + pub fn tally(votes: Vote) -> Self { + Self { + yes: votes.yes, + no: votes.no + votes.no_with_veto, + veto: votes.no_with_veto, + total: votes.yes + votes.no + votes.no_with_veto + votes.abstain, + } + } +} diff --git a/packages/shade_protocol/src/contract_interfaces/mint/liability_mint.rs b/packages/shade_protocol/src/contract_interfaces/mint/liability_mint.rs new file mode 100644 index 0000000..1622cd5 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/mint/liability_mint.rs @@ -0,0 +1,127 @@ +use crate::{ + c_std::{Addr, Binary, Uint128}, + contract_interfaces::snip20::helpers::Snip20Asset, + utils::{asset::Contract, generic_response::ResponseStatus}, +}; + +use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; +use cosmwasm_schema::cw_serde; +use std::convert::TryFrom; + +#[cw_serde] +pub struct Config { + //TODO shade_admin contract + pub admin: Addr, + pub token: Contract, + pub debt_ratio: Uint128, + pub oracle: Contract, + pub treasury: Contract, + //pub interest: Uint128 <- probs not +} + +#[cw_serde] +pub struct InstantiateMsg { + pub admin: Option, + pub token: Contract, + pub debt_ratio: Uint128, + pub oracle: Contract, + pub treasury: Contract, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + UpdateConfig { + config: Config, + }, + RemoveWhitelist { + // Contract? + address: Addr, + }, + AddWhitelist { + // Contract? + address: Addr, + }, + AddCollateral { + asset: Contract, + }, + RemoveCollateral { + asset: Contract, + }, + Mint { + amount: Uint128, + }, + // Receive config.token to pay back liabilities + Receive { + sender: Addr, + from: Addr, + amount: Uint128, + memo: Option, + msg: Option, + }, +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + Init { + status: ResponseStatus, + address: Addr, + }, + RemoveWhitelist { + status: ResponseStatus, + }, + AddWhitelist { + status: ResponseStatus, + }, + RemoveCollateral { + status: ResponseStatus, + }, + AddCollateral { + status: ResponseStatus, + }, + UpdateConfig { + status: ResponseStatus, + }, + Mint { + status: ResponseStatus, + amount: Uint128, + }, +} + +#[cw_serde] +pub enum QueryMsg { + Whitelist {}, + Liabilities {}, + //TODO add once moved to storage + //Collateral {}, + Token {}, + Config {}, +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + Whitelist { + whitelist: Vec, + }, + Liabilities { + outstanding: Uint128, + limit: Uint128, + }, + Token { + token: Snip20Asset, + }, + Config { + config: Config, + }, +} diff --git a/packages/shade_protocol/src/contract_interfaces/mint/mint.rs b/packages/shade_protocol/src/contract_interfaces/mint/mint.rs new file mode 100644 index 0000000..321c4ca --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/mint/mint.rs @@ -0,0 +1,174 @@ +use crate::{ + contract_interfaces::snip20::helpers::Snip20Asset, + utils::{asset::Contract, generic_response::ResponseStatus}, +}; +use crate::c_std::Uint128; +use crate::c_std::{Binary, Addr}; + +use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; +use cosmwasm_schema::{cw_serde}; + + +#[cw_serde] +pub struct Config { + pub admin: Addr, + pub oracle: Contract, + // Both treasury & Commission must be set to function + pub treasury: Addr, + pub secondary_burn: Option, + pub activated: bool, + pub limit: Option, +} + +/// Used to store the assets allowed to be burned +#[cw_serde] +pub struct SupportedAsset { + pub asset: Snip20Asset, + // Capture a percentage of burned assets + pub capture: Uint128, + // Fee taken off the top of a given burned asset + pub fee: Uint128, + pub unlimited: bool, +} + +#[cw_serde] +pub enum Limit { + Daily { + supply_portion: Uint128, + days: Uint128, + }, + Monthly { + supply_portion: Uint128, + months: Uint128, + }, +} + +#[cw_serde] +pub struct InstantiateMsg { + pub admin: Option, + pub oracle: Contract, + + // Asset that is minted + pub native_asset: Contract, + + //Symbol to peg to, default to snip20 symbol + pub peg: Option, + + // Both treasury & asset capture must be set to function properly + pub treasury: Addr, + + // This is where the non-burnable assets will go, if not defined they will stay in this contract + pub secondary_burn: Option, + + pub limit: Option, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + UpdateConfig { + config: Config, + }, + RegisterAsset { + contract: Contract, + // Commission * 100 e.g. 5 == .05 == 5% + capture: Option, + fee: Option, + unlimited: Option, + }, + RemoveAsset { + address: Addr, + }, + Receive { + sender: Addr, + from: Addr, + amount: Uint128, + memo: Option, + msg: Option, + }, +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub struct SnipMsgHook { + pub minimum_expected_amount: Uint128, + pub to_mint: Addr, +} + +#[cw_serde] +pub struct MintMsgHook { + pub minimum_expected_amount: Uint128, +} + +#[cw_serde] +pub enum ExecuteAnswer { + Init { + status: ResponseStatus, + address: Addr, + }, + UpdateConfig { + status: ResponseStatus, + }, + RegisterAsset { + status: ResponseStatus, + }, + RemoveAsset { + status: ResponseStatus, + }, + Mint { + status: ResponseStatus, + amount: Uint128, + }, +} + +#[cw_serde] +pub enum QueryMsg { + NativeAsset {}, + SupportedAssets {}, + Asset { + contract: String, + }, + Config {}, + Limit {}, + Mint { + offer_asset: Addr, + amount: Uint128, + }, +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + NativeAsset { + asset: Snip20Asset, + peg: String, + }, + SupportedAssets { + assets: Vec, + }, + Asset { + asset: SupportedAsset, + burned: Uint128, + }, + Config { + config: Config, + }, + Limit { + minted: Uint128, + limit: Uint128, + last_refresh: String, + }, + Mint { + asset: Contract, + amount: Uint128, + }, +} diff --git a/packages/shade_protocol/src/contract_interfaces/mint/mint_router.rs b/packages/shade_protocol/src/contract_interfaces/mint/mint_router.rs new file mode 100644 index 0000000..c6bfcb5 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/mint/mint_router.rs @@ -0,0 +1,92 @@ +use crate::{ + c_std::{Addr, Binary, Uint128}, + contract_interfaces::snip20::helpers::Snip20Asset, + utils::{asset::Contract, generic_response::ResponseStatus}, +}; + +use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub struct Config { + pub admin: Addr, + pub path: Vec, +} + +/* +#[cw_serde] +pub struct MintMsgHook { + pub minimum_expected_amount: Option, + pub routing_flag: Option, +} +*/ + +#[cw_serde] +pub struct PathNode { + pub input_asset: Addr, + pub input_amount: Uint128, + pub mint: Addr, + pub output_asset: Addr, + pub output_amount: Uint128, +} + +#[cw_serde] +pub struct InstantiateMsg { + pub admin: Option, + pub path: Vec, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + UpdateConfig { + config: Config, + }, + Receive { + sender: Addr, + from: Addr, + amount: Uint128, + memo: Option, + msg: Option, + }, +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + Init { + status: ResponseStatus, + address: Addr, + }, + UpdateConfig { + status: ResponseStatus, + }, + Mint { + status: ResponseStatus, + amount: Uint128, + }, +} + +#[cw_serde] +pub enum QueryMsg { + Config {}, + Assets {}, + Route { asset: Addr, amount: Uint128 }, +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + Config { config: Config }, + Assets { assets: Vec }, + Route { path: Vec }, +} diff --git a/packages/shade_protocol/src/contract_interfaces/mint/mod.rs b/packages/shade_protocol/src/contract_interfaces/mint/mod.rs new file mode 100644 index 0000000..707ecad --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/mint/mod.rs @@ -0,0 +1,6 @@ +#[cfg(feature = "mint")] +pub mod mint; +#[cfg(feature = "mint_router")] +pub mod mint_router; +#[cfg(feature = "liability_mint")] +pub mod liability_mint; diff --git a/packages/shade_protocol/src/contract_interfaces/mod.rs b/packages/shade_protocol/src/contract_interfaces/mod.rs new file mode 100644 index 0000000..afd066f --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/mod.rs @@ -0,0 +1,46 @@ +#[cfg(feature = "dex")] +pub mod dex; + +#[cfg(feature = "dao")] +pub mod dao; + +pub mod oracles; + +#[cfg(feature = "mint")] +pub mod mint; + +#[cfg(feature = "sky")] +pub mod sky; + +#[cfg(feature = "snip20")] +pub mod snip20; + +// Protocol init libraries +#[cfg(feature = "airdrop")] +pub mod airdrop; + +// Protocol libraries +#[cfg(feature = "governance")] +pub mod governance; + +// Bonds +#[cfg(feature = "bonds")] +pub mod bonds; + +#[cfg(feature = "query_auth")] +pub mod query_auth; + +#[cfg(feature = "admin")] +pub mod admin; + +#[cfg(feature = "peg_stability")] +pub mod peg_stability; + +#[cfg(feature = "stkd")] +pub mod stkd; + +#[cfg(feature = "basic_staking")] +pub mod basic_staking; + +#[cfg(feature = "snip20_migration")] +pub mod snip20_migration; diff --git a/packages/shade_protocol/src/contract_interfaces/oracles/band.rs b/packages/shade_protocol/src/contract_interfaces/oracles/band.rs new file mode 100644 index 0000000..3267c2e --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/oracles/band.rs @@ -0,0 +1,62 @@ +use crate::utils::asset::Contract; +use crate::c_std::{Deps, StdResult}; +use crate::c_std::Uint128; + +use crate::utils::{InstantiateCallback, Query}; +use cosmwasm_schema::{cw_serde}; + +#[cw_serde] +pub struct InstantiateMsg {} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum BandQuery { + GetReferenceData { + base_symbol: String, + quote_symbol: String, + }, + GetReferenceDataBulk { + base_symbols: Vec, + quote_symbols: Vec, + }, +} + +impl Query for BandQuery { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub struct ReferenceData { + pub rate: Uint128, + pub last_updated_base: u64, + pub last_updated_quote: u64, +} + +pub fn reference_data( + deps: &Deps, + base_symbol: String, + quote_symbol: String, + band: Contract, +) -> StdResult { + BandQuery::GetReferenceData { + base_symbol, + quote_symbol, + } + .query(&deps.querier, &band) +} + +pub fn reference_data_bulk( + deps: &Deps, + base_symbols: Vec, + quote_symbols: Vec, + band: Contract, +) -> StdResult> { + BandQuery::GetReferenceDataBulk { + base_symbols, + quote_symbols, + } + .query(&deps.querier, &band) +} diff --git a/packages/shade_protocol/src/contract_interfaces/oracles/mod.rs b/packages/shade_protocol/src/contract_interfaces/oracles/mod.rs new file mode 100644 index 0000000..2be5080 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/oracles/mod.rs @@ -0,0 +1,4 @@ +#[cfg(feature = "band")] +pub mod band; +#[cfg(feature = "oracles")] +pub mod oracle; diff --git a/packages/shade_protocol/src/contract_interfaces/oracles/oracle.rs b/packages/shade_protocol/src/contract_interfaces/oracles/oracle.rs new file mode 100644 index 0000000..98296e1 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/oracles/oracle.rs @@ -0,0 +1,97 @@ +use crate::c_std::Uint128; +use crate::c_std::Addr; + +use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; +use cosmwasm_schema::{cw_serde}; + +use crate::{ + contract_interfaces::{ + dex::dex::TradingPair, + }, + utils::{asset::Contract, generic_response::ResponseStatus}, +}; + +#[cw_serde] +pub struct IndexElement { + pub symbol: String, + pub weight: Uint128, +} + +#[cw_serde] +pub struct OracleConfig { + pub admin: Addr, + pub band: Contract, + pub sscrt: Contract, +} + +#[cw_serde] +pub struct InstantiateMsg { + pub admin: Option, + pub band: Contract, + pub sscrt: Contract, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + UpdateConfig { + admin: Option, + band: Option, + }, + // Register Secret Swap or Sienna Pair (should be */sSCRT or sSCRT/*) + RegisterPair { + pair: Contract, + }, + // Unregister Secret Swap Pair (opposite action to RegisterSswapPair) + UnregisterPair { + symbol: String, + pair: Contract, + }, + + RegisterIndex { + symbol: String, + basket: Vec, + }, +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + UpdateConfig { + status: ResponseStatus, + }, + + RegisterPair { + status: ResponseStatus, + symbol: String, + pair: TradingPair, + }, + UnregisterPair { + status: ResponseStatus, + }, + RegisterIndex { + status: ResponseStatus, + }, +} + +#[cw_serde] +pub enum QueryMsg { + Config {}, + Price { symbol: String }, + Prices { symbols: Vec }, +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + Config { config: OracleConfig }, +} diff --git a/packages/shade_protocol/src/contract_interfaces/peg_stability/mod.rs b/packages/shade_protocol/src/contract_interfaces/peg_stability/mod.rs new file mode 100644 index 0000000..ce3a3ae --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/peg_stability/mod.rs @@ -0,0 +1,146 @@ +use crate::{ + contract_interfaces::sky::cycles::{ArbPair, Offer}, + utils::{ + asset::Contract, + generic_response::ResponseStatus, + storage::plus::{GenericItemStorage, ItemStorage}, + ExecuteCallback, + InstantiateCallback, + Query, + }, +}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Decimal, Uint128}; +use secret_storage_plus::Item; + +#[cw_serde] +pub struct Config { + pub admin_auth: Contract, + pub snip20: Contract, + pub pairs: Vec, + pub oracle: Contract, + pub treasury: Contract, + pub symbols: Vec, + pub payback: Decimal, + pub self_addr: Addr, + pub dump_contract: Contract, +} + +impl ItemStorage for Config { + const ITEM: Item<'static, Config> = Item::new("item_config"); +} + +#[cw_serde] +pub struct ViewingKey; + +impl GenericItemStorage for ViewingKey { + const ITEM: Item<'static, String> = Item::new("item_view_key"); +} + +#[cw_serde] +pub struct InstantiateMsg { + pub admin_auth: Contract, + pub snip20: Contract, + pub oracle: Contract, + pub treasury: Contract, + pub payback: Decimal, + pub viewing_key: String, + pub dump_contract: Contract, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + UpdateConfig { + admin_auth: Option, + snip20: Option, + oracle: Option, + treasury: Option, + symbols: Option>, + payback: Option, + dump_contract: Option, + padding: Option, + }, + SetPairs { + pairs: Vec, + symbol: Option, + padding: Option, + }, + AppendPairs { + pairs: Vec, + symbol: Option, + padding: Option, + }, + RemovePair { + pair_address: String, + padding: Option, + }, + Swap { + padding: Option, + }, +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + Init { + status: ResponseStatus, + }, + UpdateConfig { + config: Config, + status: ResponseStatus, + }, + SetPairs { + pairs: Vec, + status: ResponseStatus, + }, + AppendPairs { + pairs: Vec, + status: ResponseStatus, + }, + RemovePair { + pairs: Vec, + status: ResponseStatus, + }, + Swap { + profit: Uint128, + payback: Uint128, + status: ResponseStatus, + }, +} + +#[cw_serde] +pub enum QueryMsg { + GetConfig {}, + Balance {}, + GetPairs {}, + Profitable {}, +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + Config { config: Config }, + Balance { snip20_bal: Uint128 }, + GetPairs { pairs: Vec }, + Profitable { profit: Uint128, payback: Uint128 }, +} + +#[cw_serde] +pub struct CalculateRes { + pub profit: Uint128, + pub payback: Uint128, + pub index: usize, + pub config: Config, + pub offer: Offer, + pub min_expected: Uint128, +} diff --git a/packages/shade_protocol/src/contract_interfaces/query_auth/auth.rs b/packages/shade_protocol/src/contract_interfaces/query_auth/auth.rs new file mode 100644 index 0000000..b77ea24 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/query_auth/auth.rs @@ -0,0 +1,78 @@ +use cosmwasm_std::MessageInfo; +use crate::c_std::{Env, Addr, StdResult, Storage}; +use cosmwasm_schema::{cw_serde}; + +use crate::query_authentication::viewing_keys::ViewingKey; +use secret_storage_plus::Map; +use crate::utils::crypto::{Prng, sha_256}; +use crate::utils::storage::plus::MapStorage; + +#[cw_serde] +pub struct Key(pub String); + +impl Key { + pub fn generate(info: &MessageInfo, env: &Env, 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_bytes()); + rng_entropy.extend_from_slice(entropy); + + let mut rng = Prng::new(seed, &rng_entropy); + + let rand_slice = rng.rand_bytes(); + + let key = sha_256(&rand_slice); + + Self(base64::encode(key)) + } + + pub fn verify(storage: &dyn Storage, address: Addr, key: String) -> StdResult { + Ok(match HashedKey::may_load(storage, address)? { + None => { + // Empty compare for security reasons + Key(key).compare(&[0u8; KEY_SIZE]); + false + } + Some(hashed) => Key(key).compare(&hashed.0) + }) + } +} + +impl ToString for Key { + fn to_string(&self) -> String { + self.0.clone() + } +} +const KEY_SIZE: usize = 32; +impl ViewingKey for Key{} + +#[cw_serde] +pub struct HashedKey(pub [u8; KEY_SIZE]); + +impl MapStorage<'static, Addr> for HashedKey { + const MAP: Map<'static, Addr, Self> = Map::new("hashed-viewing-key-"); +} + + +#[cw_serde] +pub struct PermitKey(pub bool); + +impl MapStorage<'static, (Addr, String)> for PermitKey { + const MAP: Map<'static, (Addr, String), Self> = Map::new("permit-key-"); +} + +impl PermitKey { + pub fn revoke(storage: &mut dyn Storage, key: String, user: Addr) -> StdResult<()> { + PermitKey(true).save(storage, (user, key)) + } + + pub fn is_revoked(storage: &mut dyn Storage, key: String, user: Addr) -> StdResult { + Ok(match PermitKey::may_load(storage, (user, key))? { + None => false, + Some(_) => true + }) + } +} \ No newline at end of file diff --git a/packages/shade_protocol/src/contract_interfaces/query_auth/helpers.rs b/packages/shade_protocol/src/contract_interfaces/query_auth/helpers.rs new file mode 100644 index 0000000..d16ae68 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/query_auth/helpers.rs @@ -0,0 +1,56 @@ +use cosmwasm_std::{Addr, from_binary, QuerierWrapper, StdError, StdResult}; +use serde::de::DeserializeOwned; +use crate::{Contract, query_auth}; +use crate::query_auth::QueryPermit; +use crate::utils::Query; + +pub struct PermitAuthentication { + pub sender: Addr, + pub revoked: bool, + pub data: T +} + +pub fn authenticate_permit( + permit: QueryPermit, + querier: &QuerierWrapper, + authenticator: Contract +) -> StdResult> { + let res: query_auth::QueryAnswer = query_auth::QueryMsg::ValidatePermit { permit: permit.clone() } + .query(querier, &authenticator)?; + + let sender: Addr; + let revoked: bool; + + match res { + query_auth::QueryAnswer::ValidatePermit { user, is_revoked } => { + sender = user; + revoked = is_revoked; + } + _ => return Err(StdError::generic_err("Wrong query response")), + } + + Ok(PermitAuthentication { + sender, + revoked, + data: from_binary(&permit.params.data)? + }) +} + +pub fn authenticate_vk( + address: Addr, + key: String, + querier: &QuerierWrapper, + authenticator: &Contract +) -> StdResult { + let res: query_auth::QueryAnswer = query_auth::QueryMsg::ValidateViewingKey { + user: address, + key, + }.query(querier, authenticator)?; + + match res { + query_auth::QueryAnswer::ValidateViewingKey { is_valid } => { + Ok(is_valid) + } + _ => Err(StdError::generic_err("Unauthorized")), + } +} \ No newline at end of file diff --git a/packages/shade_protocol/src/contract_interfaces/query_auth/mod.rs b/packages/shade_protocol/src/contract_interfaces/query_auth/mod.rs new file mode 100644 index 0000000..29413af --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/query_auth/mod.rs @@ -0,0 +1,145 @@ +#[cfg(feature = "query_auth_impl")] +pub mod auth; +pub mod helpers; + +use crate::c_std::{Addr, Binary}; + +#[cfg(feature = "query_auth_impl")] +use crate::utils::storage::plus::ItemStorage; +use crate::{ + query_authentication::permit::Permit, + utils::{ + asset::Contract, + crypto::sha_256, + generic_response::ResponseStatus, + ExecuteCallback, + InstantiateCallback, + Query, + }, +}; +use cosmwasm_schema::cw_serde; +#[cfg(feature = "query_auth_impl")] +use secret_storage_plus::Item; + +#[cfg(feature = "query_auth_impl")] +#[cw_serde] +pub struct Admin(pub Contract); + +#[cfg(feature = "query_auth_impl")] +impl ItemStorage for Admin { + const ITEM: Item<'static, Self> = Item::new("admin-"); +} + +#[cfg(feature = "query_auth_impl")] +#[cw_serde] +pub struct RngSeed(pub Vec); + +#[cfg(feature = "query_auth_impl")] +impl ItemStorage for RngSeed { + const ITEM: Item<'static, Self> = Item::new("rng-seed-"); +} + +#[cfg(feature = "query_auth_impl")] +impl RngSeed { + pub fn new(seed: Binary) -> Self { + Self(sha_256(&seed.0).to_vec()) + } +} + +#[cw_serde] +pub struct InstantiateMsg { + pub admin_auth: Contract, + pub prng_seed: Binary, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ContractStatus { + Default, + DisablePermit, + DisableVK, + DisableAll, +} + +#[cfg(feature = "query_auth_impl")] +impl ItemStorage for ContractStatus { + const ITEM: Item<'static, Self> = Item::new("contract-status-"); +} + +#[cw_serde] +pub enum ExecuteMsg { + SetAdminAuth { + admin: Contract, + padding: Option, + }, + SetRunState { + state: ContractStatus, + padding: Option, + }, + + SetViewingKey { + key: String, + padding: Option, + }, + CreateViewingKey { + entropy: String, + padding: Option, + }, + + BlockPermitKey { + key: String, + padding: Option, + }, +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + SetAdminAuth { status: ResponseStatus }, + SetRunState { status: ResponseStatus }, + SetViewingKey { status: ResponseStatus }, + CreateViewingKey { key: String }, + BlockPermitKey { status: ResponseStatus }, +} + +pub type QueryPermit = Permit; + +#[remain::sorted] +#[cw_serde] +pub struct PermitData { + pub data: Binary, + pub key: String, +} + +#[cw_serde] +pub enum QueryMsg { + Config {}, + + ValidateViewingKey { user: Addr, key: String }, + ValidatePermit { permit: QueryPermit }, +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + Config { + admin: Contract, + state: ContractStatus, + }, + ValidateViewingKey { + is_valid: bool, + }, + ValidatePermit { + user: Addr, + is_revoked: bool, + }, +} diff --git a/packages/shade_protocol/src/contract_interfaces/shade_oracles.rs b/packages/shade_protocol/src/contract_interfaces/shade_oracles.rs new file mode 100644 index 0000000..bd088f0 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/shade_oracles.rs @@ -0,0 +1,106 @@ +//#! +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{StdResult, QuerierWrapper}; +use crate::{ + Contract, + BLOCK_SIZE, + utils::{Query}, +}; +use std::collections::HashMap; + +#[cw_serde] +#[derive(Default)] +pub struct OraclePrice { + pub key: String, + pub data: ReferenceData, +} + +pub type PriceResponse = OraclePrice; +pub type PricesResponse = Vec; + +#[cw_serde] +pub enum RouterQueryMsg { + GetOracle { key: String }, + GetOracles { keys: Vec }, +} + +impl Query for RouterQueryMsg { + const BLOCK_SIZE: usize = BLOCK_SIZE; +} + +#[cw_serde] +pub enum OracleQueryMsg { + GetPrice { key: String }, + GetPrices { keys: Vec }, +} + +impl Query for OracleQueryMsg { + const BLOCK_SIZE: usize = BLOCK_SIZE; +} + +#[cw_serde] +pub struct OracleResponse { + pub key: String, + pub oracle: Contract, +} + +/// Gets the oracle for the key from the router & calls GetPrice on it. +/// +/// Has a query depth of 1. +pub fn query_price( + router: &Contract, + querier: &QuerierWrapper, + key: String, +) -> StdResult { + let oracle_resp: OracleResponse = + RouterQueryMsg::GetOracle { key: key.clone() }.query(querier, router)?; + query_oracle_price(&oracle_resp.oracle, querier, key) +} + +/// Groups the keys by their respective oracles and sends bulk GetPrices queries to each of those oracles. +/// +/// Done to reduce impact on query depth. +pub fn query_prices( + router: &Contract, + querier: &QuerierWrapper, + keys: Vec, +) -> StdResult> { + let oracle_resps: Vec = + RouterQueryMsg::GetOracles { keys }.query(querier, router)?; + let mut map: HashMap> = HashMap::new(); + let mut prices: Vec = vec![]; + + for resp in oracle_resps { + // Get the current vector of symbols at that oracle and add the current key to it + map.entry(resp.oracle).or_insert(vec![]).push(resp.key); + } + + for (oracle, keys) in map { + if keys.len() == 1 { + let queried_price = query_oracle_price(&oracle, querier, keys[0].clone())?; + prices.push(queried_price); + } else { + let mut queried_prices = query_oracle_prices(&oracle, querier, keys)?; + prices.append(&mut queried_prices); + } + } + Ok(prices) +} + +pub fn query_oracle_price( + oracle: &Contract, + querier: &QuerierWrapper, + key: String, +) -> StdResult { + let resp: PriceResponse = OracleQueryMsg::GetPrice { key }.query(querier, oracle)?; + Ok(resp.price) +} + +pub fn query_oracle_prices( + oracle: &Contract, + querier: &QuerierWrapper, + keys: Vec, +) -> StdResult> { + let resp: PricesResponse = OracleQueryMsg::GetPrices { keys }.query(querier, oracle)?; + Ok(resp.prices) +} diff --git a/packages/shade_protocol/src/contract_interfaces/sky/cycles.rs b/packages/shade_protocol/src/contract_interfaces/sky/cycles.rs new file mode 100644 index 0000000..ea23f0a --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/sky/cycles.rs @@ -0,0 +1,339 @@ +use crate::{ + contract_interfaces::{ + dex::{dex::Dex, secretswap, shadeswap, sienna}, + mint::mint, + snip20::helpers::send_msg, + }, + utils::{asset::Contract, Query}, +}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + to_binary, + CosmosMsg, + Deps, + StdError, + StdResult, + Uint128, +}; + +#[cw_serde] +pub struct ArbPair { + pub pair_contract: Option, + pub mint_info: Option, + pub token0: Contract, + pub token0_decimals: Uint128, + pub token0_amount: Option, + pub token1: Contract, + pub token1_decimals: Uint128, + pub token1_amount: Option, + pub dex: Dex, +} + +impl ArbPair { + // Returns pool amounts in a tuple where 0 is the amount for token0 + pub fn pool_amounts(&mut self, deps: Deps) -> StdResult<(Uint128, Uint128)> { + self.validate_pair()?; + match self.dex { + Dex::SecretSwap => { + let res = secretswap::PairQuery::Pool {} + .query(&deps.querier, &self.pair_contract.clone().unwrap())?; + match res { + secretswap::PoolResponse { assets, .. } => { + if assets[0].info.token.contract_addr.clone() == self.token0.address.clone() + { + self.token0_amount = Some(assets[0].amount); + self.token1_amount = Some(assets[1].amount); + Ok((assets[0].amount, assets[1].amount)) + } else { + self.token0_amount = Some(assets[1].amount); + self.token1_amount = Some(assets[0].amount); + Ok((assets[1].amount, assets[0].amount)) + } + } + } + } + Dex::ShadeSwap => { + let res = shadeswap::PairQuery::GetPairInfo {} + .query(&deps.querier, &self.pair_contract.clone().unwrap())?; + match res { + shadeswap::PairInfoResponse { + pair, + amount_0, + amount_1, + .. + } => match pair.token_0 { + shadeswap::TokenType::CustomToken { contract_addr, .. } => { + if contract_addr == self.token0.address.clone() { + self.token0_amount = Some(amount_0); + self.token1_amount = Some(amount_1); + Ok((amount_0, amount_1)) + } else { + self.token0_amount = Some(amount_1); + self.token1_amount = Some(amount_0); + Ok((amount_1, amount_0)) + } + } + _ => Err(StdError::generic_err("Unexpected")), + }, + } + } + Dex::SiennaSwap => { + let res = sienna::PairQuery::PairInfo + .query(&deps.querier, &self.pair_contract.clone().unwrap())?; + + match res { + sienna::PairInfoResponse { pair_info } => match pair_info.pair.token_0 { + sienna::TokenType::CustomToken { contract_addr, .. } => { + if contract_addr == self.token0.address.clone() { + self.token0_amount = Some(pair_info.amount_0); + self.token1_amount = Some(pair_info.amount_1); + Ok((pair_info.amount_0, pair_info.amount_1)) + } else { + self.token0_amount = Some(pair_info.amount_1); + self.token1_amount = Some(pair_info.amount_0); + Ok((pair_info.amount_1, pair_info.amount_0)) + } + } + _ => Err(StdError::generic_err("Unexpected")), + }, + } + } + Dex::Mint => Err(StdError::generic_err("Not available")), + } + } + + // Returns the calculated swap result when passed an offer with respect to the dex enum option + pub fn simulate_swap(self, deps: Deps, offer: Offer) -> StdResult { + let mut swap_result = Uint128::zero(); + match self.dex { + Dex::SecretSwap => { + let res = secretswap::PairQuery::Simulation { + offer_asset: secretswap::Asset { + amount: offer.amount, + info: secretswap::AssetInfo { + token: secretswap::Token { + contract_addr: offer.asset.address, + token_code_hash: offer.asset.code_hash, + viewing_key: "".to_string(), //TODO will sky have to make viewing keys for every asset? + }, + }, + }, + } + .query(&deps.querier, &self.pair_contract.clone().unwrap())?; + match res { + secretswap::SimulationResponse { return_amount, .. } => { + swap_result = return_amount + } + } + } + Dex::SiennaSwap => { + let res = sienna::PairQuery::SwapSimulation { + offer: sienna::TokenTypeAmount { + token: sienna::TokenType::CustomToken { + token_code_hash: offer.asset.code_hash.clone(), + contract_addr: offer.asset.address.clone(), + }, + amount: offer.amount, + }, + } + .query(&deps.querier, &self.pair_contract.clone().unwrap())?; + match res { + sienna::SimulationResponse { return_amount, .. } => swap_result = return_amount, + } + } + Dex::ShadeSwap => { + let res = shadeswap::PairQuery::GetEstimatedPrice { + offer: shadeswap::TokenAmount { + token: shadeswap::TokenType::CustomToken { + token_code_hash: offer.asset.code_hash.clone(), + contract_addr: offer.asset.address.clone(), + }, + amount: offer.amount, + }, + } + .query(&deps.querier, &self.pair_contract.clone().unwrap())?; + match res { + shadeswap::QueryMsgResponse::EstimatedPrice { estimated_price } => { + swap_result = estimated_price + } + _ => {} + } + } + Dex::Mint => { + let mint_contract = self.get_mint_contract(offer.asset.clone())?; + let res = mint::QueryMsg::Mint { + offer_asset: offer.asset.address, + amount: offer.amount, + } + .query(&deps.querier, &mint_contract)?; + match res { + mint::QueryAnswer::Mint { amount, .. } => swap_result = amount, + _ => {} + } + } + } + Ok(swap_result) + } + + // Returns the snip20 send_msg that will execute a swap for each of the possible Dex enum + // options + pub fn to_cosmos_msg(&self, offer: Offer, expected_return: Uint128) -> StdResult { + match self.dex { + Dex::SiennaSwap => send_msg( + self.pair_contract.clone().unwrap().address, + Uint128::new(offer.amount.u128()), + Some(to_binary(&sienna::CallbackMsg { + swap: sienna::CallbackSwap { expected_return }, + })?), + None, + None, + &offer.asset, + ), + Dex::SecretSwap => send_msg( + self.pair_contract.clone().unwrap().address, + Uint128::new(offer.amount.u128()), + Some(to_binary(&secretswap::CallbackMsg { + swap: secretswap::CallbackSwap { expected_return }, + })?), + None, + None, + &offer.asset, + ), + Dex::ShadeSwap => send_msg( + self.pair_contract.clone().unwrap().address, + Uint128::new(offer.amount.u128()), + Some(to_binary(&shadeswap::SwapTokens { + expected_return: Some(expected_return), + to: None, + router_link: None, + callback_signature: None, + })?), + None, + None, + &offer.asset, + ), + Dex::Mint => { + let mint_contract = self.get_mint_contract(offer.asset.clone())?; + send_msg( + mint_contract.address.clone(), + Uint128::new(offer.amount.u128()), + Some(to_binary(&mint::MintMsgHook { + minimum_expected_amount: expected_return, + })?), + None, + None, + &offer.asset, + ) + } + } + } + + // Returns either the silk mint or the shade mint contract depending on what the input asset is + pub fn get_mint_contract(&self, offer_contract: Contract) -> StdResult { + if offer_contract.clone() == self.mint_info.clone().unwrap().shd_token { + Ok(self.mint_info.clone().unwrap().mint_contract_silk) + } else if offer_contract == self.mint_info.clone().unwrap().silk_token { + Ok(self.mint_info.clone().unwrap().mint_contract_shd) + } else { + Err(StdError::generic_err( + "Must be sending either silk or shd to mint contracts", + )) + } + } + + // Gatekeeper that validates the ArbPair for entry into contract storage + pub fn validate_pair(&self) -> StdResult { + match self.dex { + Dex::Mint => { + if self.mint_info == None { + return Err(StdError::generic_err("Dex mint must include mint_info")); + } + } + _ => { + if self.pair_contract == None { + return Err(StdError::generic_err( + "Dex pairs must include pair contract", + )); + } + } + } + Ok(true) + } +} + +#[cw_serde] +pub struct Cycle { + pub pair_addrs: Vec, + pub start_addr: Contract, +} + +impl Cycle { + // Gatekeeper that validates if the contract should accept the cycle into storage + pub fn validate_cycle(&self) -> StdResult { + // check if start address is in both the first arb pair and the last arb pair + let start_addr_in_first_pair = self.start_addr == self.pair_addrs[0].token0 + || self.start_addr == self.pair_addrs[0].token1; + let start_addr_in_last_pair = self.start_addr + == self.pair_addrs[self.pair_addrs.len() - 1].token0 + || self.start_addr == self.pair_addrs[self.pair_addrs.len() - 1].token1; + if !(start_addr_in_first_pair && start_addr_in_last_pair) { + return Err(StdError::generic_err( + "First and last pair in cycle must contain start addr", + )); + } + // check to see if each arb pair has the necessary information and if there is an actual + // path + + // initialize this for later use + let mut hash_vec = vec![]; + let mut cur_asset = self.start_addr.clone(); + for arb_pair in self.pair_addrs.clone() { + match arb_pair.dex { + Dex::Mint => { + arb_pair + .mint_info + .expect("Mint arb pairs must include mint info"); + } + _ => { + arb_pair + .pair_contract + .clone() + .expect("Dex pairs must include pair contract"); + hash_vec.push(arb_pair.pair_contract.unwrap().code_hash.clone()); + } + } + if arb_pair.token0 == cur_asset { + cur_asset = arb_pair.token1; + } else if arb_pair.token1 == cur_asset { + cur_asset = arb_pair.token0; + } else { + return Err(StdError::generic_err("cycle not complete")); + } + } + let initial_len = hash_vec.clone().len(); + // Sorting and dedup ing will remove any dublicates and tell us if there's 2 of the same + // pair contract included in the cycle + hash_vec.sort(); + hash_vec.dedup(); + if hash_vec.len() < initial_len { + return Err(StdError::generic_err( + "cycles should include one copy of each pair", + )); + } + Ok(true) + } +} + +#[cw_serde] +pub struct Offer { + pub asset: Contract, + pub amount: Uint128, +} + +#[cw_serde] +pub struct MintInfo { + pub mint_contract_shd: Contract, + pub mint_contract_silk: Contract, + pub shd_token: Contract, + pub silk_token: Contract, +} diff --git a/packages/shade_protocol/src/contract_interfaces/sky/mod.rs b/packages/shade_protocol/src/contract_interfaces/sky/mod.rs new file mode 100644 index 0000000..5446f5f --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/sky/mod.rs @@ -0,0 +1,182 @@ +#[cfg(feature = "sky-utils")] +pub mod cycles; + +use crate::{ + contract_interfaces::{dao::adapter, sky::cycles::Cycle}, + utils::{ + asset::Contract, + storage::plus::ItemStorage, + ExecuteCallback, + InstantiateCallback, + Query, + }, +}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Decimal, Uint128}; +use secret_storage_plus::Item; + +#[cw_serde] +pub struct Config { + pub shade_admin: Contract, + pub shd_token: Contract, + pub silk_token: Contract, + pub sscrt_token: Contract, + pub treasury: Contract, + pub payback_rate: Decimal, +} + +impl ItemStorage for Config { + const ITEM: Item<'static, Config> = Item::new("item_config"); +} + +#[cw_serde] +pub struct ViewingKeys(pub String); + +impl ItemStorage for ViewingKeys { + const ITEM: Item<'static, ViewingKeys> = Item::new("item_view_keys"); +} + +#[cw_serde] +pub struct SelfAddr(pub Addr); + +impl ItemStorage for SelfAddr { + const ITEM: Item<'static, SelfAddr> = Item::new("item_self_addr"); +} + +#[cw_serde] +pub struct Cycles(pub Vec); + +impl ItemStorage for Cycles { + const ITEM: Item<'static, Cycles> = Item::new("item_cycles"); +} + +#[cw_serde] +pub struct InstantiateMsg { + pub shade_admin: Contract, + pub shd_token: Contract, + pub silk_token: Contract, + pub sscrt_token: Contract, + pub treasury: Contract, + pub viewing_key: String, + pub payback_rate: Decimal, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + UpdateConfig { + shade_admin: Option, + shd_token: Option, + silk_token: Option, + sscrt_token: Option, + treasury: Option, + payback_rate: Option, + padding: Option, + }, + SetCycles { + cycles: Vec, + padding: Option, + }, + AppendCycles { + cycle: Vec, + padding: Option, + }, + UpdateCycle { + cycle: Cycle, + index: Uint128, + padding: Option, + }, + RemoveCycle { + index: Uint128, + padding: Option, + }, + ArbCycle { + amount: Uint128, + index: Uint128, + padding: Option, + }, + ArbAllCycles { + amount: Uint128, + padding: Option, + }, + Adapter(adapter::SubExecuteMsg), +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + Init { + status: bool, + }, + UpdateConfig { + status: bool, + }, + SetCycles { + status: bool, + }, + AppendCycles { + status: bool, + }, + UpdateCycle { + status: bool, + }, + RemoveCycle { + status: bool, + }, + ExecuteArbCycle { + status: bool, + swap_amounts: Vec, + payback_amount: Uint128, + }, + ArbAllCycles { + status: bool, + payback_amount: Uint128, + }, +} + +#[cw_serde] +pub enum QueryMsg { + GetConfig {}, + Balance {}, + GetCycles {}, + IsCycleProfitable { amount: Uint128, index: Uint128 }, + IsAnyCycleProfitable { amount: Uint128 }, + Adapter(adapter::SubQueryMsg), +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + Config { + config: Config, + }, + Balance { + shd_bal: Uint128, + silk_bal: Uint128, //should be zero or close to + sscrt_bal: Uint128, + }, + GetCycles { + cycles: Vec, + }, + IsCycleProfitable { + is_profitable: bool, + direction: Cycle, + swap_amounts: Vec, + profit: Uint128, + }, + IsAnyCycleProfitable { + is_profitable: Vec, + direction: Vec, + swap_amounts: Vec>, + profit: Vec, + }, +} diff --git a/packages/shade_protocol/src/contract_interfaces/snip20/batch.rs b/packages/shade_protocol/src/contract_interfaces/snip20/batch.rs new file mode 100644 index 0000000..36e4bf7 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/snip20/batch.rs @@ -0,0 +1,52 @@ +use crate::c_std::Binary; + +use crate::c_std::Uint128; +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub struct TransferAction { + pub recipient: String, + pub amount: Uint128, + pub memo: Option, +} + +#[cw_serde] +pub struct SendAction { + pub recipient: String, + pub recipient_code_hash: Option, + pub amount: Uint128, + pub msg: Option, + pub memo: Option, +} + +#[cw_serde] +pub struct TransferFromAction { + pub owner: String, + pub recipient: String, + pub amount: Uint128, + pub memo: Option, +} + +#[cw_serde] +pub struct SendFromAction { + pub owner: String, + pub recipient: String, + pub recipient_code_hash: Option, + pub amount: Uint128, + pub msg: Option, + pub memo: Option, +} + +#[cw_serde] +pub struct MintAction { + pub recipient: String, + pub amount: Uint128, + pub memo: Option, +} + +#[cw_serde] +pub struct BurnFromAction { + pub owner: String, + pub amount: Uint128, + pub memo: Option, +} diff --git a/packages/shade_protocol/src/contract_interfaces/snip20/errors.rs b/packages/shade_protocol/src/contract_interfaces/snip20/errors.rs new file mode 100644 index 0000000..092f357 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/snip20/errors.rs @@ -0,0 +1,199 @@ +use crate::{ + impl_into_u8, + utils::errors::{build_string, CodeType, DetailedError}, +}; +use crate::c_std::{Addr, StdError}; + +use cosmwasm_schema::{cw_serde}; +use crate::c_std::Uint128; +use crate::contract_interfaces::snip20::Permission; + +#[cw_serde]#[repr(u8)] +pub enum Error { + // Init Errors + InvalidNameFormat, + InvalidSymbolFormat, + InvalidDecimals, + + // User errors + NoFunds, + NotEnoughFunds, + AllowanceExpired, + InsufficientAllowance, + + // Auth errors + NotAdmin, + PermitRevoked, + PermitNotFound, + UnauthorisedPermit, + InvalidViewingKey, + + // Config errors + TransfersDisabled, + MintingDisabled, + NotMinter, + BurningDisabled, + RedeemDisabled, + DepositDisabled, + NotEnoughTokens, + NoTokensReceived, + UnsupportedToken, + + // Run state errors + ActionDisabled, + + NotAuthenticatedMsg, + + // Errors that shouldnt happen + ContractStatusLevelInvalidConversion, + TxCodeInvalidConversion, + LegacyCannotConvertFromTx, +} + +impl_into_u8!(Error); + +impl CodeType for Error { + fn to_verbose(&self, context: &Vec<&str>) -> String { + match self { + Error::InvalidNameFormat => build_string("{} is not in the expected name format (3-30 UTF-8 bytes)", context), + Error::InvalidSymbolFormat => build_string("{} is not in the expected symbol format [A-Z]{3,6}", context), + Error::InvalidDecimals => build_string("Decimals must not exceed 18", context), + Error::NoFunds => build_string("Account has no funds", context), + Error::NotEnoughFunds => build_string("Account doesnt have enough funds", context), + Error::AllowanceExpired => build_string("Allowance expired on {}", context), + Error::InsufficientAllowance => build_string("Insufficient allowance", context), + Error::NotAdmin => build_string("Only admin is allowed to do this action", context), + Error::PermitRevoked => build_string("Permit key {} is revoked", context), + Error::UnauthorisedPermit => build_string("Permit lacks the required authorisation, expecting {}", context), + Error::PermitNotFound => build_string("No permit was included in this query.", context), + Error::InvalidViewingKey => build_string("Viewing key is invalid", context), + Error::TransfersDisabled => build_string("Transfers are disabled", context), + Error::MintingDisabled => build_string("Minting is disabled", context), + Error::NotMinter => build_string("{} is not an allowed minter", context), + Error::BurningDisabled => build_string("Burning is disabled", context), + Error::RedeemDisabled => build_string("Redemptions are disabled", context), + Error::DepositDisabled => build_string("Deposits are disabled", context), + Error::NotEnoughTokens => build_string("Asking to redeem {} when theres only {} held by the reserve", context), + Error::NoTokensReceived => build_string("Found no tokens to deposit", context), + Error::UnsupportedToken => build_string("Sent tokens are not supported", context), + Error::ActionDisabled => build_string("This action has been disabled", context), + Error::NotAuthenticatedMsg => build_string("Message doesnt require authentication", context), + Error::ContractStatusLevelInvalidConversion => build_string("Stored enum id {} is greater than total supported enum items", context), + Error::TxCodeInvalidConversion => build_string("Stored action id {} is greater than total supported enum items", context), + Error::LegacyCannotConvertFromTx => build_string("Legacy Txs only supports Transfer", context), + } + } +} + +const TARGET: &str = "snip20"; + +pub fn invalid_name_format(name: &str) -> StdError { + DetailedError::from_code(TARGET, Error::InvalidNameFormat, vec![name]).to_error() +} + +pub fn invalid_symbol_format(symbol: &str) -> StdError { + DetailedError::from_code(TARGET, Error::InvalidSymbolFormat, vec![symbol]).to_error() +} + +pub fn invalid_decimals() -> StdError { + DetailedError::from_code(TARGET, Error::InvalidDecimals, vec![]).to_error() +} + +pub fn no_funds() -> StdError { + DetailedError::from_code(TARGET, Error::NoFunds, vec![]).to_error() +} + +pub fn not_enough_funds() -> StdError { + DetailedError::from_code(TARGET, Error::NotEnoughFunds, vec![]).to_error() +} + +pub fn allowance_expired(date: u64) -> StdError { + DetailedError::from_code(TARGET, Error::AllowanceExpired, vec![&date.to_string()]).to_error() +} + +pub fn not_admin() -> StdError { + DetailedError::from_code(TARGET, Error::NotAdmin, vec![]).to_error() +} + +pub fn permit_revoked(key: String) -> StdError { + DetailedError::from_code(TARGET, Error::PermitRevoked, vec![&key]).to_error() +} + +pub fn permit_not_found() -> StdError { + DetailedError::from_code(TARGET, Error::PermitNotFound, vec![]).to_error() +} + +pub fn unauthorized_permit(auth: Permission) -> StdError { + let perm = match auth { + Permission::Allowance => String::from("allowance"), + Permission::Balance => String::from("balance"), + Permission::History => String::from("history"), + Permission::Owner => String::from("owner"), + }; + DetailedError::from_code(TARGET, Error::UnauthorisedPermit, vec![&perm]).to_error() +} + +pub fn invalid_viewing_key() -> StdError { + DetailedError::from_code(TARGET, Error::InvalidViewingKey, vec![]).to_error() +} + +pub fn transfer_disabled() -> StdError { + DetailedError::from_code(TARGET, Error::TransfersDisabled, vec![]).to_error() +} + +pub fn minting_disabled() -> StdError { + DetailedError::from_code(TARGET, Error::MintingDisabled, vec![]).to_error() +} + +pub fn not_minter(user: &Addr) -> StdError { + DetailedError::from_code(TARGET, Error::NotMinter, vec![user.as_str()]).to_error() +} + +pub fn burning_disabled() -> StdError { + DetailedError::from_code(TARGET, Error::BurningDisabled, vec![]).to_error() +} + +pub fn redeem_disabled() -> StdError { + DetailedError::from_code(TARGET, Error::RedeemDisabled, vec![]).to_error() +} + +pub fn deposit_disabled() -> StdError { + DetailedError::from_code(TARGET, Error::DepositDisabled, vec![]).to_error() +} + +pub fn not_enough_tokens(sent: Uint128, max: Uint128) -> StdError { + DetailedError::from_code(TARGET, Error::NotEnoughTokens, vec![&sent.to_string(), &max.to_string()]).to_error() +} + +pub fn no_tokens_received() -> StdError { + DetailedError::from_code(TARGET, Error::NoTokensReceived, vec![]).to_error() +} + +pub fn unsupported_token() -> StdError { + DetailedError::from_code(TARGET, Error::UnsupportedToken, vec![]).to_error() +} + +pub fn action_disabled() -> StdError { + DetailedError::from_code(TARGET, Error::ActionDisabled, vec![]).to_error() +} + +pub fn not_authenticated_msg() -> StdError { + DetailedError::from_code(TARGET, Error::NotAuthenticatedMsg, vec![]).to_error() +} + +pub fn insufficient_allowance() -> StdError { + DetailedError::from_code(TARGET, Error::InsufficientAllowance, vec![]).to_error() +} + +pub fn contract_status_level_invalid(id: u8) -> StdError { + DetailedError::from_code(TARGET, Error::ContractStatusLevelInvalidConversion, vec![&id.to_string()]).to_error() +} + +pub fn tx_code_invalid_conversion(id: u8) -> StdError { + DetailedError::from_code(TARGET, Error::TxCodeInvalidConversion, vec![&id.to_string()]).to_error() +} + +pub fn legacy_cannot_convert_from_tx() -> StdError { + DetailedError::from_code(TARGET, Error::LegacyCannotConvertFromTx, vec![]).to_error() +} + diff --git a/packages/shade_protocol/src/contract_interfaces/snip20/helpers.rs b/packages/shade_protocol/src/contract_interfaces/snip20/helpers.rs new file mode 100644 index 0000000..89d19e8 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/snip20/helpers.rs @@ -0,0 +1,345 @@ +use super::{batch, manager::AllowanceResponse, ExecuteMsg, QueryAnswer, QueryMsg}; +use crate::{ + c_std::{Addr, Binary, CosmosMsg, QuerierWrapper, StdError, StdResult, Uint128}, + utils::{asset::Contract, ExecuteCallback, Query}, +}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Coin; + +#[cw_serde] +pub struct Snip20Asset { + pub contract: Contract, + pub token_info: TokenInfo, + pub token_config: Option, +} + +pub fn fetch_snip20(contract: &Contract, querier: &QuerierWrapper) -> StdResult { + Ok(Snip20Asset { + contract: contract.clone(), + token_info: token_info(querier, contract)?, + token_config: Some(token_config(querier, contract)?), + }) +} + +/// Returns a StdResult used to execute Send +#[allow(clippy::too_many_arguments)] +pub fn send_msg( + recipient: Addr, + amount: Uint128, + msg: Option, + memo: Option, + padding: Option, + contract: &Contract, +) -> StdResult { + Ok(ExecuteMsg::Send { + recipient: recipient.to_string(), + recipient_code_hash: None, + amount, + msg, + memo, + padding, + } + .to_cosmos_msg(contract, vec![])?) +} + +/// Returns a StdResult used to execute Redeem +pub fn redeem_msg( + amount: Uint128, + denom: Option, + padding: Option, + contract: &Contract, +) -> StdResult { + ExecuteMsg::Redeem { + amount, + denom, + padding, + } + .to_cosmos_msg(contract, vec![]) +} + +/// Returns a StdResult used to execute Deposit +pub fn deposit_msg( + amount: Uint128, + padding: Option, + contract: &Contract, +) -> StdResult { + ExecuteMsg::Deposit { padding }.to_cosmos_msg(contract, vec![Coin { + denom: "uscrt".to_string(), + amount, + }]) +} + +/// Returns a StdResult used to execute Mint +pub fn mint_msg( + recipient: Addr, + amount: Uint128, + memo: Option, + padding: Option, + contract: &Contract, +) -> StdResult { + ExecuteMsg::Mint { + recipient: recipient.to_string(), + amount, + memo, + padding, + } + .to_cosmos_msg(contract, vec![]) +} + +/// Returns a StdResult used to execute Burn +pub fn burn_msg( + amount: Uint128, + memo: Option, + padding: Option, + contract: &Contract, +) -> StdResult { + ExecuteMsg::Burn { + amount, + memo, + padding, + } + .to_cosmos_msg(contract, vec![]) +} + +/// Returns a StdResult used to execute RegisterReceive +pub fn register_receive( + register_hash: String, + padding: Option, + contract: &Contract, +) -> StdResult { + ExecuteMsg::RegisterReceive { + code_hash: register_hash, + padding, + } + .to_cosmos_msg(contract, vec![]) +} + +pub fn set_viewing_key_msg( + viewing_key: String, + padding: Option, + contract: &Contract, +) -> StdResult { + ExecuteMsg::SetViewingKey { + key: viewing_key, + padding, + } + .to_cosmos_msg(contract, vec![]) +} + +pub fn batch_send_msg( + actions: Vec, + padding: Option, + contract: &Contract, +) -> StdResult { + ExecuteMsg::BatchSend { actions, padding }.to_cosmos_msg(contract, vec![]) +} + +pub fn batch_send_from_msg( + actions: Vec, + padding: Option, + contract: &Contract, +) -> StdResult { + ExecuteMsg::BatchSendFrom { actions, padding }.to_cosmos_msg(contract, vec![]) +} + +/// TokenInfo response +#[cw_serde] +pub struct TokenInfo { + pub name: String, + pub symbol: String, + pub decimals: u8, + #[serde(skip_serializing_if = "Option::is_none")] + pub total_supply: Option, +} +/// Returns a StdResult from performing TokenInfo query +pub fn token_info(querier: &QuerierWrapper, contract: &Contract) -> StdResult { + let answer: QueryAnswer = QueryMsg::TokenInfo {}.query(querier, contract)?; + + match answer { + QueryAnswer::TokenInfo { + name, + symbol, + decimals, + total_supply, + } => Ok(TokenInfo { + name, + symbol, + decimals, + total_supply, + }), + _ => Err(StdError::generic_err("Wrong answer")), //TODO: better error + } +} + +/// Returns a StdResult from performing a Balance query +pub fn balance_query( + querier: &QuerierWrapper, + address: Addr, + key: String, + contract: &Contract, +) -> StdResult { + let answer: QueryAnswer = QueryMsg::Balance { + address: address.to_string(), + key, + } + .query(querier, contract)?; + + match answer { + QueryAnswer::Balance { amount, .. } => Ok(amount), + _ => Err(StdError::generic_err("Invalid Balance Response")), //TODO: better error + } +} + +/// TokenConfig response +#[cw_serde] +pub struct TokenConfig { + pub public_total_supply: bool, + pub deposit_enabled: bool, + pub redeem_enabled: bool, + pub mint_enabled: bool, + pub burn_enabled: bool, + // Optionals only relevant to some snip20a + #[serde(skip_serializing_if = "Option::is_none")] + pub transfer_enabled: Option, +} +/// Returns a StdResult from performing TokenConfig query +pub fn token_config(querier: &QuerierWrapper, contract: &Contract) -> StdResult { + let answer: QueryAnswer = QueryMsg::TokenConfig {}.query(querier, contract)?; + + match answer { + QueryAnswer::TokenConfig { + public_total_supply, + deposit_enabled, + redeem_enabled, + mint_enabled, + burn_enabled, + .. + } => Ok(TokenConfig { + public_total_supply, + deposit_enabled, + redeem_enabled, + mint_enabled, + burn_enabled, + transfer_enabled: None, + }), + _ => Err(StdError::generic_err("Wrong answer")), //TODO: better error + } +} + +/// Returns a StdResult used to execute IncreaseAllowance +/// +/// # Arguments +/// +/// * `spender` - the address of the allowed spender +/// * `amount` - Uint128 additional amount the spender is allowed to send/burn +/// * `expiration` - Optional u64 denoting the epoch time in seconds that the allowance will expire +/// * `padding` - Optional String used as padding if you don't want to use block padding +/// * `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 +pub fn increase_allowance_msg( + spender: Addr, + amount: Uint128, + expiration: Option, + padding: Option, + block_size: usize, + contract: &Contract, + funds: Vec, +) -> StdResult { + ExecuteMsg::IncreaseAllowance { + spender: spender.to_string(), + amount, + expiration, + padding, + } + .to_cosmos_msg(contract, funds) +} + +/// Returns a StdResult used to execute DecreaseAllowance +/// +/// # Arguments +/// +/// * `spender` - the address of the allowed spender +/// * `amount` - Uint128 amount the spender is no longer allowed to send/burn +/// * `expiration` - Optional u64 denoting the epoch time in seconds that the allowance will expire +/// * `padding` - Optional String used as padding if you don't want to use block padding +/// * `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 +pub fn decrease_allowance_msg( + spender: Addr, + amount: Uint128, + expiration: Option, + padding: Option, + block_size: usize, + contract: &Contract, + funds: Vec, +) -> StdResult { + ExecuteMsg::DecreaseAllowance { + spender: spender.to_string(), + amount, + expiration, + padding, + } + .to_cosmos_msg(contract, funds) +} + +/// Returns a StdResult from performing Allowance query +/// +/// # Arguments +/// +/// * `querier` - a reference to the Querier dependency of the querying contract +/// * `owner` - the address that owns the tokens +/// * `spender` - the address allowed to send/burn tokens +/// * `key` - String holding the authentication key needed to view the allowance +/// * `block_size` - pad the message to blocks of this size +/// * `callback_code_hash` - String holding the code hash of the contract being queried +/// * `contract_addr` - address of the contract being queried +#[allow(clippy::too_many_arguments)] +pub fn allowance_query( + querier: &QuerierWrapper, + owner: Addr, + spender: Addr, + key: String, + block_size: usize, + contract: &Contract, +) -> StdResult { + let answer: QueryAnswer = QueryMsg::Allowance { + owner: owner.to_string(), + spender: spender.to_string(), + key, + } + .query(querier, contract)?; + match answer { + QueryAnswer::Allowance { + spender, + owner, + allowance, + expiration, + } => Ok(AllowanceResponse { + spender, + owner, + allowance, + expiration, + }), + QueryAnswer::ViewingKeyError { .. } => Err(StdError::generic_err("Unauthorized")), + _ => Err(StdError::generic_err("Invalid Allowance query response")), + } +} + +pub fn transfer_from_msg( + owner: String, + recipient: String, + amount: Uint128, + memo: Option, + padding: Option, + contract: &Contract +) -> StdResult { + ExecuteMsg::TransferFrom { + owner, + recipient, + amount, + memo, + padding, + }.to_cosmos_msg(contract, vec![]) +} \ No newline at end of file diff --git a/packages/shade_protocol/src/contract_interfaces/snip20/manager.rs b/packages/shade_protocol/src/contract_interfaces/snip20/manager.rs new file mode 100644 index 0000000..9e927d9 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/snip20/manager.rs @@ -0,0 +1,345 @@ +use crate::c_std::{Addr, BlockInfo, StdResult, Storage}; +use cosmwasm_std::Timestamp; + +#[cfg(feature = "snip20-impl")] +use crate::utils::storage::plus::{ItemStorage, MapStorage, NaiveItemStorage}; +use crate::{ + c_std::Uint128, + contract_interfaces::snip20::errors::{ + allowance_expired, + contract_status_level_invalid, + insufficient_allowance, + no_funds, + not_enough_funds, + }, + impl_into_u8, + Contract, +}; +use cosmwasm_schema::cw_serde; +#[cfg(feature = "snip20-impl")] +use secret_storage_plus::{Item, Map}; + +#[cw_serde] +#[repr(u8)] +pub enum ContractStatusLevel { + NormalRun, + StopAllButRedeems, + StopAll, +} + +#[cfg(feature = "snip20-impl")] +impl ContractStatusLevel { + pub fn save(self, storage: &mut dyn Storage) -> StdResult<()> { + ContractStatus(self.into()).save(storage) + } + + pub fn load(storage: &dyn Storage) -> StdResult { + let i = ContractStatus::load(storage)?.0; + let item = match i { + 0 => ContractStatusLevel::NormalRun, + 1 => ContractStatusLevel::StopAllButRedeems, + 2 => ContractStatusLevel::StopAll, + _ => return Err(contract_status_level_invalid(i)), + }; + Ok(item) + } +} +impl_into_u8!(ContractStatusLevel); + +// TODO: group all of these snip20-impl features into its own package + +#[cw_serde] +pub struct ContractStatus(pub u8); + +#[cfg(feature = "snip20-impl")] +impl ItemStorage for ContractStatus { + const ITEM: Item<'static, Self> = Item::new("contract-status-level-"); +} + +#[cw_serde] +pub struct CoinInfo { + pub name: String, + pub symbol: String, + pub decimals: u8, +} + +#[cfg(feature = "snip20-impl")] +impl ItemStorage for CoinInfo { + const ITEM: Item<'static, Self> = Item::new("coin-info-"); +} + +#[cw_serde] +pub struct Admin(pub Addr); + +#[cfg(feature = "snip20-impl")] +impl ItemStorage for Admin { + const ITEM: Item<'static, Self> = Item::new("admin-"); +} + +#[cw_serde] +pub struct QueryAuth(pub Contract); + +#[cfg(feature = "snip20-impl")] +impl ItemStorage for QueryAuth { + const ITEM: Item<'static, Self> = Item::new("query_auth-"); +} + +#[cw_serde] +pub struct RandSeed(pub Vec); + +#[cfg(feature = "snip20-impl")] +impl ItemStorage for RandSeed { + const ITEM: Item<'static, Self> = Item::new("rand-seed-"); +} + +#[cw_serde] +pub struct Setting(pub bool); + +#[cfg(feature = "snip20-impl")] +impl NaiveItemStorage for Setting {} + +#[cfg(feature = "snip20-impl")] +const PUBLIC_TOTAL_SUPPLY: Item<'static, Setting> = Item::new("public-total-supply-"); +#[cfg(feature = "snip20-impl")] +const ENABLE_DEPOSIT: Item<'static, Setting> = Item::new("enable-deposit-"); +#[cfg(feature = "snip20-impl")] +const ENABLE_REDEEM: Item<'static, Setting> = Item::new("enable-redeem-"); +#[cfg(feature = "snip20-impl")] +const ENABLE_MINT: Item<'static, Setting> = Item::new("enable-mint-"); +#[cfg(feature = "snip20-impl")] +const ENABLE_BURN: Item<'static, Setting> = Item::new("enable-burn-"); +#[cfg(feature = "snip20-impl")] +const ENABLE_TRANSFER: Item<'static, Setting> = Item::new("enable-transfer-"); + +#[cw_serde] +pub struct Config { + pub public_total_supply: bool, + pub enable_deposit: bool, + pub enable_redeem: bool, + pub enable_mint: bool, + pub enable_burn: bool, + pub enable_transfer: bool, +} + +#[cfg(feature = "snip20-impl")] +impl Config { + pub fn save(&self, storage: &mut dyn Storage) -> StdResult<()> { + Self::set_public_total_supply(storage, self.public_total_supply)?; + Self::set_deposit_enabled(storage, self.enable_deposit)?; + Self::set_redeem_enabled(storage, self.enable_redeem)?; + Self::set_mint_enabled(storage, self.enable_mint)?; + Self::set_burn_enabled(storage, self.enable_burn)?; + Self::set_transfer_enabled(storage, self.enable_transfer)?; + Ok(()) + } + + pub fn public_total_supply(storage: &dyn Storage) -> StdResult { + Ok(Setting::load(storage, PUBLIC_TOTAL_SUPPLY)?.0) + } + + pub fn set_public_total_supply(storage: &mut dyn Storage, setting: bool) -> StdResult<()> { + Setting(setting).save(storage, PUBLIC_TOTAL_SUPPLY)?; + Ok(()) + } + + pub fn deposit_enabled(storage: &dyn Storage) -> StdResult { + Ok(Setting::load(storage, ENABLE_DEPOSIT)?.0) + } + + pub fn set_deposit_enabled(storage: &mut dyn Storage, setting: bool) -> StdResult<()> { + Setting(setting).save(storage, ENABLE_DEPOSIT)?; + Ok(()) + } + + pub fn redeem_enabled(storage: &dyn Storage) -> StdResult { + Ok(Setting::load(storage, ENABLE_REDEEM)?.0) + } + + pub fn set_redeem_enabled(storage: &mut dyn Storage, setting: bool) -> StdResult<()> { + Setting(setting).save(storage, ENABLE_REDEEM)?; + Ok(()) + } + + pub fn mint_enabled(storage: &dyn Storage) -> StdResult { + Ok(Setting::load(storage, ENABLE_MINT)?.0) + } + + pub fn set_mint_enabled(storage: &mut dyn Storage, setting: bool) -> StdResult<()> { + Setting(setting).save(storage, ENABLE_MINT)?; + Ok(()) + } + + pub fn burn_enabled(storage: &dyn Storage) -> StdResult { + Ok(Setting::load(storage, ENABLE_BURN)?.0) + } + + pub fn set_burn_enabled(storage: &mut dyn Storage, setting: bool) -> StdResult<()> { + Setting(setting).save(storage, ENABLE_BURN)?; + Ok(()) + } + + pub fn transfer_enabled(storage: &dyn Storage) -> StdResult { + Ok(Setting::load(storage, ENABLE_TRANSFER)?.0) + } + + pub fn set_transfer_enabled(storage: &mut dyn Storage, setting: bool) -> StdResult<()> { + Setting(setting).save(storage, ENABLE_TRANSFER)?; + Ok(()) + } +} + +#[cw_serde] +pub struct TotalSupply(pub Uint128); + +#[cfg(feature = "snip20-impl")] +impl ItemStorage for TotalSupply { + const ITEM: Item<'static, Self> = Item::new("total-supply-"); +} + +#[cfg(feature = "snip20-impl")] +impl TotalSupply { + pub fn set(storage: &mut dyn Storage, amount: Uint128) -> StdResult<()> { + TotalSupply(amount).save(storage) + } + + pub fn add(storage: &mut dyn Storage, amount: Uint128) -> StdResult { + let supply = TotalSupply::load(storage)?.0.checked_add(amount)?; + TotalSupply::set(storage, supply)?; + Ok(supply) + } + + pub fn sub(storage: &mut dyn Storage, amount: Uint128) -> StdResult { + let supply = TotalSupply::load(storage)?.0.checked_sub(amount)?; + TotalSupply::set(storage, supply)?; + Ok(supply) + } +} + +#[cw_serde] +pub struct Balance(pub Uint128); + +#[cfg(feature = "snip20-impl")] +impl MapStorage<'static, Addr> for Balance { + const MAP: Map<'static, Addr, Self> = Map::new("balance-"); +} + +#[cfg(feature = "snip20-impl")] +impl Balance { + pub fn set(storage: &mut dyn Storage, amount: Uint128, addr: &Addr) -> StdResult<()> { + Balance(amount).save(storage, addr.clone()) + } + + pub fn add(storage: &mut dyn Storage, amount: Uint128, addr: &Addr) -> StdResult { + let supply = Self::may_load(storage, addr.clone())? + .unwrap_or(Self(Uint128::zero())) + .0 + .checked_add(amount)?; + + Balance::set(storage, supply, addr)?; + Ok(supply) + } + + pub fn sub(storage: &mut dyn Storage, amount: Uint128, addr: &Addr) -> StdResult { + let subtractee = match Self::load(storage, addr.clone()) { + Ok(amount) => amount.0, + Err(_) => return Err(no_funds()), + }; + let supply = match subtractee.checked_sub(amount) { + Ok(supply) => supply, + Err(_) => return Err(not_enough_funds()), + }; + Balance::set(storage, supply, addr)?; + Ok(supply) + } + + pub fn transfer( + storage: &mut dyn Storage, + amount: Uint128, + sender: &Addr, + recipient: &Addr, + ) -> StdResult<()> { + Self::sub(storage, amount, sender)?; + Self::add(storage, amount, recipient)?; + Ok(()) + } +} + +#[cw_serde] +pub struct Minters(pub Vec); + +#[cfg(feature = "snip20-impl")] +impl ItemStorage for Minters { + const ITEM: Item<'static, Self> = Item::new("minters-"); +} + +#[cw_serde] +pub struct AllowanceResponse { + pub spender: Addr, + pub owner: Addr, + pub allowance: Uint128, + pub expiration: Option, +} + +#[cw_serde] +pub struct Allowance { + pub amount: Uint128, + pub expiration: Option, +} + +impl Default for Allowance { + fn default() -> Self { + Self { + amount: Uint128::zero(), + expiration: None, + } + } +} + +#[cfg(feature = "snip20-impl")] +impl Allowance { + pub fn is_expired(&self, block: &BlockInfo) -> bool { + match self.expiration { + Some(time) => block.time >= Timestamp::from_seconds(time), + None => false, + } + } + + pub fn spend( + storage: &mut dyn Storage, + owner: &Addr, + spender: &Addr, + amount: Uint128, + block: &BlockInfo, + ) -> StdResult<()> { + let mut allowance = Allowance::load(storage, (owner.clone(), spender.clone()))?; + + if allowance.is_expired(block) { + return Err(allowance_expired(allowance.expiration.unwrap())); + } + if let Ok(new_allowance) = allowance.amount.checked_sub(amount) { + allowance.amount = new_allowance; + } else { + return Err(insufficient_allowance()); + } + + allowance.save(storage, (owner.clone(), spender.clone()))?; + + Ok(()) + } +} +// (Owner, Spender) +#[cfg(feature = "snip20-impl")] +impl MapStorage<'static, (Addr, Addr)> for Allowance { + const MAP: Map<'static, (Addr, Addr), Self> = Map::new("allowance-"); +} + +#[cw_serde] +pub struct ReceiverHash(pub String); + +#[cfg(feature = "snip20-impl")] +impl MapStorage<'static, Addr> for ReceiverHash { + const MAP: Map<'static, Addr, Self> = Map::new("receiver-hash-"); +} + +// Auth +pub use crate::contract_interfaces::query_auth::auth::{HashedKey, Key, PermitKey}; diff --git a/packages/shade_protocol/src/contract_interfaces/snip20/mod.rs b/packages/shade_protocol/src/contract_interfaces/snip20/mod.rs new file mode 100644 index 0000000..47a23f2 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/snip20/mod.rs @@ -0,0 +1,649 @@ +pub mod batch; +pub mod errors; +pub mod helpers; +pub mod manager; +pub mod transaction_history; + +use crate::{ + c_std::{Addr, Binary, Env, StdResult, Storage}, + query_authentication::permit::Permit, +}; +use cosmwasm_std::{Api, MessageInfo}; + +#[cfg(feature = "snip20-impl")] +use crate::contract_interfaces::snip20::transaction_history::store_mint; +#[cfg(feature = "snip20-impl")] +use crate::utils::storage::plus::ItemStorage; +use crate::{ + c_std::Uint128, + contract_interfaces::{ + query_auth::QueryPermit as AuthQueryPermit, + snip20::{ + errors::{invalid_decimals, invalid_name_format, invalid_symbol_format}, + manager::{ + Admin, + Balance, + CoinInfo, + Config, + ContractStatusLevel, + Minters, + RandSeed, + TotalSupply, + }, + transaction_history::{RichTx, Tx}, + }, + }, + snip20::manager::QueryAuth, + utils::{ + crypto::sha_256, + generic_response::ResponseStatus, + ExecuteCallback, + InstantiateCallback, + Query, + }, + Contract, +}; +use cosmwasm_schema::cw_serde; + +pub const VERSION: &str = "SNIP24"; + +#[cw_serde] +pub struct InitialBalance { + pub address: String, + pub amount: Uint128, +} + +#[cw_serde] +pub struct InstantiateMsg { + pub name: String, + pub admin: Option, + pub symbol: String, + pub decimals: u8, + pub initial_balances: Option>, + pub prng_seed: Binary, + pub config: Option, + pub query_auth: Option, +} + +fn is_valid_name(name: &str) -> bool { + let len = name.len(); + (3..=30).contains(&len) +} + +fn is_valid_symbol(symbol: &str) -> bool { + let len = symbol.len(); + let len_is_valid = (3..=6).contains(&len); + + len_is_valid && symbol.bytes().all(|byte| (b'A'..=b'Z').contains(&byte)) +} + +#[cfg(feature = "snip20-impl")] +impl InstantiateMsg { + pub fn save( + &self, + storage: &mut dyn Storage, + api: &dyn Api, + env: Env, + info: MessageInfo, + ) -> StdResult<()> { + if !is_valid_name(&self.name) { + return Err(invalid_name_format(&self.name)); + } + + if !is_valid_symbol(&self.symbol) { + return Err(invalid_symbol_format(&self.symbol)); + } + + if self.decimals > 18 { + return Err(invalid_decimals()); + } + + let config = self.config.clone().unwrap_or_default(); + config.save(storage)?; + + CoinInfo { + name: self.name.clone(), + symbol: self.symbol.clone(), + decimals: self.decimals, + } + .save(storage)?; + + let admin_addr; + if let Some(admin) = &self.admin { + admin_addr = api.addr_validate(admin.as_str())? + } else { + admin_addr = info.sender; + } + + Admin(admin_addr.clone()).save(storage)?; + RandSeed(sha_256(&self.prng_seed.0).to_vec()).save(storage)?; + + let mut total_supply = Uint128::zero(); + + if let Some(initial_balances) = &self.initial_balances { + for balance in initial_balances.iter() { + let address = api.addr_validate(balance.address.as_str())?; + Balance::set(storage, balance.amount.clone(), &address)?; + total_supply = total_supply.checked_add(balance.amount)?; + + store_mint( + storage, + &admin_addr, + &address, + balance.amount, + self.symbol.clone(), + Some("Initial Balance".to_string()), + &env.block, + )?; + } + } + + TotalSupply::set(storage, total_supply)?; + + ContractStatusLevel::NormalRun.save(storage)?; + + Minters(vec![]).save(storage)?; + + if let Some(query_auth) = self.query_auth.clone() { + QueryAuth(query_auth).save(storage)?; + } + + Ok(()) + } +} + +#[cw_serde] +pub struct InitConfig { + /// Indicates whether the total supply is public or should be kept secret. + /// default: False + pub public_total_supply: Option, + /// Indicates whether deposit functionality should be enabled + /// default: False + pub enable_deposit: Option, + /// Indicates whether redeem functionality should be enabled + /// default: False + pub enable_redeem: Option, + /// Indicates whether mint functionality should be enabled + /// default: False + pub enable_mint: Option, + /// Indicates whether burn functionality should be enabled + /// default: False + pub enable_burn: Option, + /// Indicates whether transferring tokens should be enables + /// default: True + pub enable_transfer: Option, +} + +impl Default for InitConfig { + fn default() -> Self { + Self { + public_total_supply: None, + enable_deposit: None, + enable_redeem: None, + enable_mint: None, + enable_burn: None, + enable_transfer: None, + } + } +} + +#[cfg(feature = "snip20-impl")] +impl InitConfig { + pub fn save(self, storage: &mut dyn Storage) -> StdResult<()> { + Config { + public_total_supply: self.public_total_supply(), + enable_deposit: self.deposit_enabled(), + enable_redeem: self.redeem_enabled(), + enable_mint: self.mint_enabled(), + enable_burn: self.burn_enabled(), + enable_transfer: self.transfer_enabled(), + } + .save(storage)?; + Ok(()) + } + + pub fn public_total_supply(&self) -> bool { + self.public_total_supply.unwrap_or(false) + } + + pub fn deposit_enabled(&self) -> bool { + self.enable_deposit.unwrap_or(false) + } + + pub fn redeem_enabled(&self) -> bool { + self.enable_redeem.unwrap_or(false) + } + + pub fn mint_enabled(&self) -> bool { + self.enable_mint.unwrap_or(false) + } + + pub fn burn_enabled(&self) -> bool { + self.enable_burn.unwrap_or(false) + } + + pub fn transfer_enabled(&self) -> bool { + self.enable_transfer.unwrap_or(true) + } +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + // Native coin interactions + Redeem { + amount: Uint128, + denom: Option, + padding: Option, + }, + Deposit { + padding: Option, + }, + + // Base ERC-20 stuff + Transfer { + recipient: String, + amount: Uint128, + memo: Option, + padding: Option, + }, + Send { + recipient: String, + recipient_code_hash: Option, + amount: Uint128, + msg: Option, + memo: Option, + padding: Option, + }, + BatchTransfer { + actions: Vec, + padding: Option, + }, + BatchSend { + actions: Vec, + padding: Option, + }, + Burn { + amount: Uint128, + memo: Option, + padding: Option, + }, + RegisterReceive { + code_hash: String, + padding: Option, + }, + CreateViewingKey { + entropy: String, + padding: Option, + }, + SetViewingKey { + key: String, + padding: Option, + }, + + // Allowance + IncreaseAllowance { + spender: String, + amount: Uint128, + expiration: Option, + padding: Option, + }, + DecreaseAllowance { + spender: String, + amount: Uint128, + expiration: Option, + padding: Option, + }, + TransferFrom { + owner: String, + recipient: String, + amount: Uint128, + memo: Option, + padding: Option, + }, + SendFrom { + owner: String, + recipient: String, + recipient_code_hash: Option, + amount: Uint128, + msg: Option, + memo: Option, + padding: Option, + }, + BatchTransferFrom { + actions: Vec, + padding: Option, + }, + BatchSendFrom { + actions: Vec, + padding: Option, + }, + BurnFrom { + owner: String, + amount: Uint128, + memo: Option, + padding: Option, + }, + BatchBurnFrom { + actions: Vec, + padding: Option, + }, + + // Mint + Mint { + recipient: String, + amount: Uint128, + memo: Option, + padding: Option, + }, + BatchMint { + actions: Vec, + padding: Option, + }, + AddMinters { + minters: Vec, + padding: Option, + }, + RemoveMinters { + minters: Vec, + padding: Option, + }, + SetMinters { + minters: Vec, + padding: Option, + }, + + // Admin + ChangeAdmin { + address: String, + padding: Option, + }, + SetContractStatus { + level: ContractStatusLevel, + padding: Option, + }, + // Updated the auth setting + UpdateQueryAuth { + auth: Option, + }, + + // Permit + RevokePermit { + permit_name: String, + padding: Option, + }, +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub struct Snip20ReceiveMsg { + pub sender: String, + pub from: String, + pub amount: Uint128, + #[serde(skip_serializing_if = "Option::is_none")] + pub memo: Option, + pub msg: Option, +} + +#[cw_serde] +pub enum ReceiverHandleMsg { + Receive(Snip20ReceiveMsg), +} + +impl ReceiverHandleMsg { + pub fn new( + sender: String, + from: String, + amount: Uint128, + memo: Option, + msg: Option, + ) -> Self { + Self::Receive(Snip20ReceiveMsg { + sender, + from, + amount, + memo, + msg, + }) + } +} + +impl ExecuteCallback for ReceiverHandleMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + // Native + Deposit { + status: ResponseStatus, + }, + Redeem { + status: ResponseStatus, + }, + + // Base + Transfer { + status: ResponseStatus, + }, + Send { + status: ResponseStatus, + }, + BatchTransfer { + status: ResponseStatus, + }, + BatchSend { + status: ResponseStatus, + }, + Burn { + status: ResponseStatus, + }, + RegisterReceive { + status: ResponseStatus, + }, + CreateViewingKey { + key: String, + }, + SetViewingKey { + status: ResponseStatus, + }, + + // Allowance + IncreaseAllowance { + spender: Addr, + owner: Addr, + allowance: Uint128, + }, + DecreaseAllowance { + spender: Addr, + owner: Addr, + allowance: Uint128, + }, + TransferFrom { + status: ResponseStatus, + }, + SendFrom { + status: ResponseStatus, + }, + BatchTransferFrom { + status: ResponseStatus, + }, + BatchSendFrom { + status: ResponseStatus, + }, + BurnFrom { + status: ResponseStatus, + }, + BatchBurnFrom { + status: ResponseStatus, + }, + + // Mint + Mint { + status: ResponseStatus, + }, + BatchMint { + status: ResponseStatus, + }, + AddMinters { + status: ResponseStatus, + }, + RemoveMinters { + status: ResponseStatus, + }, + SetMinters { + status: ResponseStatus, + }, + + // Other + ChangeAdmin { + status: ResponseStatus, + }, + SetContractStatus { + status: ResponseStatus, + }, + UpdateQueryAuth { + status: ResponseStatus, + }, + + // Permit + RevokePermit { + status: ResponseStatus, + }, +} + +pub type QueryPermit = Permit; + +#[cw_serde] +pub struct PermitParams { + pub allowed_tokens: Vec, + pub permit_name: String, + pub permissions: Vec, +} + +impl PermitParams { + pub fn contains(&self, perm: Permission) -> bool { + self.permissions.contains(&perm) + } +} + +#[cw_serde] +pub enum Permission { + /// Allowance for SNIP-20 - Permission to query allowance of the owner & spender + Allowance, + /// Balance for SNIP-20 - Permission to query balance + Balance, + /// History for SNIP-20 - Permission to query transfer_history & transaction_hisotry + History, + /// 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, +} + +#[cw_serde] +pub enum QueryMsg { + TokenInfo {}, + TokenConfig {}, + ContractStatus {}, + ExchangeRate {}, + Allowance { + owner: String, + spender: String, + key: String, + }, + Balance { + address: String, + key: String, + }, + TransferHistory { + address: String, + key: String, + page: Option, + page_size: u32, + }, + TransactionHistory { + address: String, + key: String, + page: Option, + page_size: u32, + }, + Minters {}, + WithPermit { + permit: Option, + // Extra parameter because of snip20s standards + auth_permit: Option, + query: QueryWithPermit, + }, +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryWithPermit { + Allowance { owner: String, spender: String }, + Balance {}, + TransferHistory { page: Option, page_size: u32 }, + TransactionHistory { page: Option, page_size: u32 }, +} + +#[cw_serde] +pub enum QueryAnswer { + TokenInfo { + name: String, + symbol: String, + decimals: u8, + total_supply: Option, + }, + TokenConfig { + // TODO: add other config items as optionals so they can be ignored in other snip20s + public_total_supply: bool, + deposit_enabled: bool, + redeem_enabled: bool, + mint_enabled: bool, + burn_enabled: bool, + transfer_enabled: bool, + }, + ContractStatus { + status: ContractStatusLevel, + }, + ExchangeRate { + rate: Uint128, + denom: String, + }, + Allowance { + spender: Addr, + owner: Addr, + allowance: Uint128, + expiration: Option, + }, + Balance { + amount: Uint128, + }, + TransferHistory { + txs: Vec, + total: Option, + }, + TransactionHistory { + txs: Vec, + total: Option, + }, + ViewingKeyError { + msg: String, + }, + Minters { + minters: Vec, + }, +} diff --git a/packages/shade_protocol/src/contract_interfaces/snip20/transaction_history.rs b/packages/shade_protocol/src/contract_interfaces/snip20/transaction_history.rs new file mode 100644 index 0000000..f9fc7f8 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/snip20/transaction_history.rs @@ -0,0 +1,496 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Timestamp; + +use crate::{ + c_std::{Addr, BlockInfo, Coin, StdError, StdResult, Storage, Uint128}, + contract_interfaces::snip20::errors::{ + legacy_cannot_convert_from_tx, + tx_code_invalid_conversion, + }, +}; + +#[cfg(feature = "snip20-impl")] +use crate::utils::storage::plus::{ItemStorage, MapStorage}; +#[cfg(feature = "snip20-impl")] +use secret_storage_plus::{Item, Map}; + +// Note that id is a globally incrementing counter. +// Since it's 64 bits long, even at 50 tx/s it would take +// over 11 billion years for it to rollback. I'm pretty sure +// we'll have bigger issues by then. +#[cw_serde] +pub struct Tx { + pub id: u64, + pub from: Addr, + pub sender: Addr, + pub receiver: Addr, + pub coins: Coin, + #[serde(skip_serializing_if = "Option::is_none")] + pub memo: Option, + // The block time and block height are optional so that the JSON schema + // reflects that some SNIP-20 contracts may not include this info. + pub block_time: Option, + pub block_height: Option, +} + +#[cfg(feature = "snip20-impl")] +impl Tx { + // Inefficient but compliant, not recommended to use deprecated features + pub fn get( + storage: &dyn Storage, + for_address: &Addr, + page: u32, + page_size: u32, + ) -> StdResult<(Vec, u64)> { + let id = UserTXTotal::load(storage, for_address.clone())?.0; + let start_index = page as u64 * page_size as u64; + + // Since we dont know where the legacy txs are then we iterate over everything + let mut total = 0u64; + let mut txs = vec![]; + for i in 0..id { + match StoredRichTx::load(storage, (for_address.clone(), i))?.into_legacy() { + Ok(tx) => { + total += 1; + if total >= (start_index + page_size as u64) { + break; + } else if total >= start_index { + txs.push(tx); + } + } + Err(_) => {} + } + } + + let length = txs.len() as u64; + Ok((txs, length)) + } +} + +#[cw_serde] +pub enum TxAction { + Transfer { + from: Addr, + sender: Addr, + recipient: Addr, + }, + Mint { + minter: Addr, + recipient: Addr, + }, + Burn { + burner: Addr, + owner: Addr, + }, + Deposit {}, + Redeem {}, +} + +// Note that id is a globally incrementing counter. +// Since it's 64 bits long, even at 50 tx/s it would take +// over 11 billion years for it to rollback. I'm pretty sure +// we'll have bigger issues by then. +#[cw_serde] +pub struct RichTx { + pub id: u64, + pub action: TxAction, + pub coins: Coin, + #[serde(skip_serializing_if = "Option::is_none")] + pub memo: Option, + pub block_time: Timestamp, + pub block_height: u64, +} + +#[cfg(feature = "snip20-impl")] +impl RichTx { + pub fn get( + storage: &dyn Storage, + for_address: &Addr, + page: u32, + page_size: u32, + ) -> StdResult<(Vec, u64)> { + let id = UserTXTotal::load(storage, for_address.clone())?.0; + let start_index = page as u64 * page_size as u64; + let size: u64; + if (start_index + page_size as u64) > id { + size = id; + } else { + size = page_size as u64 + start_index; + } + + let mut txs = vec![]; + for index in start_index..size { + let stored_tx = StoredRichTx::load(storage, (for_address.clone(), index))?; + txs.push(stored_tx.into_humanized()?); + } + + let length = txs.len() as u64; + Ok((txs, length)) + } +} + +// Stored types: +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +enum TxCode { + Transfer = 0, + Mint = 1, + Burn = 2, + Deposit = 3, + Redeem = 4, +} + +impl TxCode { + fn to_u8(self) -> u8 { + self as u8 + } + + fn from_u8(n: u8) -> StdResult { + use TxCode::*; + match n { + 0 => Ok(Transfer), + 1 => Ok(Mint), + 2 => Ok(Burn), + 3 => Ok(Deposit), + 4 => Ok(Redeem), + _ => Err(tx_code_invalid_conversion(n)), + } + } +} + +#[cw_serde] +struct StoredTxAction { + tx_type: u8, + address1: Option, + address2: Option, + address3: Option, +} + +impl StoredTxAction { + fn transfer(from: Addr, sender: Addr, recipient: Addr) -> Self { + Self { + tx_type: TxCode::Transfer.to_u8(), + address1: Some(from), + address2: Some(sender), + address3: Some(recipient), + } + } + + fn mint(minter: Addr, recipient: Addr) -> Self { + Self { + tx_type: TxCode::Mint.to_u8(), + address1: Some(minter), + address2: Some(recipient), + address3: None, + } + } + + fn burn(owner: Addr, burner: Addr) -> Self { + Self { + tx_type: TxCode::Burn.to_u8(), + address1: Some(burner), + address2: Some(owner), + address3: None, + } + } + + fn deposit() -> Self { + Self { + tx_type: TxCode::Deposit.to_u8(), + address1: None, + address2: None, + address3: None, + } + } + + fn redeem() -> Self { + Self { + tx_type: TxCode::Redeem.to_u8(), + address1: None, + address2: None, + address3: None, + } + } + + fn into_humanized(self) -> StdResult { + let transfer_addr_err = || { + StdError::generic_err( + "Missing address in stored Transfer transaction. Storage is corrupt", + ) + }; + let mint_addr_err = || { + StdError::generic_err("Missing address in stored Mint transaction. Storage is corrupt") + }; + let burn_addr_err = || { + StdError::generic_err("Missing address in stored Burn transaction. Storage is corrupt") + }; + + // In all of these, we ignore fields that we don't expect to find populated + let action = match TxCode::from_u8(self.tx_type)? { + TxCode::Transfer => { + let from = self.address1.ok_or_else(transfer_addr_err)?; + let sender = self.address2.ok_or_else(transfer_addr_err)?; + let recipient = self.address3.ok_or_else(transfer_addr_err)?; + TxAction::Transfer { + from, + sender, + recipient, + } + } + TxCode::Mint => { + let minter = self.address1.ok_or_else(mint_addr_err)?; + let recipient = self.address2.ok_or_else(mint_addr_err)?; + TxAction::Mint { minter, recipient } + } + TxCode::Burn => { + let burner = self.address1.ok_or_else(burn_addr_err)?; + let owner = self.address2.ok_or_else(burn_addr_err)?; + TxAction::Burn { burner, owner } + } + TxCode::Deposit => TxAction::Deposit {}, + TxCode::Redeem => TxAction::Redeem {}, + }; + + Ok(action) + } +} + +#[cw_serde] +struct StoredRichTx { + id: u64, + action: StoredTxAction, + coins: Coin, + memo: Option, + block_time: Timestamp, + block_height: u64, +} + +impl StoredRichTx { + fn new( + id: u64, + action: StoredTxAction, + coins: Coin, + memo: Option, + block: &BlockInfo, + ) -> Self { + Self { + id, + action, + coins, + memo, + block_time: block.time, + block_height: block.height, + } + } + + fn into_humanized(self) -> StdResult { + Ok(RichTx { + id: self.id, + action: self.action.into_humanized()?, + coins: self.coins, + memo: self.memo, + block_time: self.block_time, + block_height: self.block_height, + }) + } + + fn into_legacy(self) -> StdResult { + if self.action.tx_type == 0 { + Ok(Tx { + id: self.id, + from: self.action.address1.unwrap(), + sender: self.action.address2.unwrap(), + receiver: self.action.address3.unwrap(), + coins: self.coins, + memo: self.memo, + block_time: Some(self.block_time), + block_height: Some(self.block_height), + }) + } else { + Err(legacy_cannot_convert_from_tx()) + } + } +} + +#[cfg(feature = "snip20-impl")] +impl MapStorage<'static, (Addr, u64)> for StoredRichTx { + const MAP: Map<'static, (Addr, u64), Self> = Map::new("stored-rich-tx-"); +} + +// Storage functions: +#[cw_serde] +struct TXCount(pub u64); + +#[cfg(feature = "snip20-impl")] +impl ItemStorage for TXCount { + const ITEM: Item<'static, Self> = Item::new("tx-count-"); +} + +#[cfg(feature = "snip20-impl")] +fn increment_tx_count(storage: &mut dyn Storage) -> StdResult { + let id = TXCount::may_load(storage)?.unwrap_or(TXCount(0)).0 + 1; + TXCount(id).save(storage)?; + Ok(id) +} + +// User tx index +#[cw_serde] +struct UserTXTotal(pub u64); + +#[cfg(feature = "snip20-impl")] +impl UserTXTotal { + pub fn append( + storage: &mut dyn Storage, + for_address: &Addr, + tx: &StoredRichTx, + ) -> StdResult<()> { + let id = UserTXTotal::may_load(storage, for_address.clone())? + .unwrap_or(UserTXTotal(0)) + .0; + UserTXTotal(id + 1).save(storage, for_address.clone())?; + tx.save(storage, (for_address.clone(), id))?; + + Ok(()) + } +} + +#[cfg(feature = "snip20-impl")] +impl MapStorage<'static, Addr> for UserTXTotal { + const MAP: Map<'static, Addr, Self> = Map::new("user-tx-total-"); +} + +#[cfg(feature = "snip20-impl")] +#[allow(clippy::too_many_arguments)] // We just need them +pub fn store_transfer( + storage: &mut dyn Storage, + owner: &Addr, + sender: &Addr, + receiver: &Addr, + amount: Uint128, + denom: String, + memo: Option, + block: &BlockInfo, +) -> StdResult<()> { + let id = increment_tx_count(storage)?; + let coins = Coin { + denom, + amount: amount.into(), + }; + let tx = StoredRichTx::new( + id, + StoredTxAction::transfer(owner.clone(), sender.clone(), receiver.clone()), + coins, + memo, + block, + ); + + // Write to the owners history if it's different from the other two addresses + if owner != sender && owner != receiver { + // crate::c_std::debug_print("saving transaction history for owner"); + UserTXTotal::append(storage, owner, &tx)?; + } + // Write to the sender's history if it's different from the receiver + if sender != receiver { + // crate::c_std::debug_print("saving transaction history for sender"); + UserTXTotal::append(storage, sender, &tx)?; + } + // Always write to the recipient's history + // crate::c_std::debug_print("saving transaction history for receiver"); + UserTXTotal::append(storage, receiver, &tx)?; + + Ok(()) +} + +#[cfg(feature = "snip20-impl")] +pub fn store_mint( + storage: &mut dyn Storage, + minter: &Addr, + recipient: &Addr, + amount: Uint128, + denom: String, + memo: Option, + block: &BlockInfo, +) -> StdResult<()> { + let id = increment_tx_count(storage)?; + let coins = Coin { + denom, + amount: amount.into(), + }; + let action = StoredTxAction::mint(minter.clone(), recipient.clone()); + let tx = StoredRichTx::new(id, action, coins, memo, block); + + if minter != recipient { + UserTXTotal::append(storage, recipient, &tx)?; + } + UserTXTotal::append(storage, minter, &tx)?; + + Ok(()) +} + +#[cfg(feature = "snip20-impl")] +pub fn store_burn( + storage: &mut dyn Storage, + owner: &Addr, + burner: &Addr, + amount: Uint128, + denom: String, + memo: Option, + block: &BlockInfo, +) -> StdResult<()> { + let id = increment_tx_count(storage)?; + let coins = Coin { + denom, + amount: amount.into(), + }; + let action = StoredTxAction::burn(owner.clone(), burner.clone()); + let tx = StoredRichTx::new(id, action, coins, memo, block); + + if burner != owner { + UserTXTotal::append(storage, owner, &tx)?; + } + UserTXTotal::append(storage, burner, &tx)?; + + Ok(()) +} + +#[cfg(feature = "snip20-impl")] +pub fn store_deposit( + storage: &mut dyn Storage, + recipient: &Addr, + amount: Uint128, + denom: String, + block: &BlockInfo, +) -> StdResult<()> { + let id = increment_tx_count(storage)?; + let coins = Coin { + denom, + amount: amount.into(), + }; + let action = StoredTxAction::deposit(); + let tx = StoredRichTx::new(id, action, coins, None, block); + + UserTXTotal::append(storage, recipient, &tx)?; + + Ok(()) +} + +#[cfg(feature = "snip20-impl")] +pub fn store_redeem( + storage: &mut dyn Storage, + redeemer: &Addr, + amount: Uint128, + denom: String, + block: &BlockInfo, +) -> StdResult<()> { + let id = increment_tx_count(storage)?; + let coins = Coin { + denom, + amount: amount.into(), + }; + let action = StoredTxAction::redeem(); + let tx = StoredRichTx::new(id, action, coins, None, block); + + UserTXTotal::append(storage, redeemer, &tx)?; + + Ok(()) +} diff --git a/packages/shade_protocol/src/contract_interfaces/snip20_migration/mod.rs b/packages/shade_protocol/src/contract_interfaces/snip20_migration/mod.rs new file mode 100644 index 0000000..db86cec --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/snip20_migration/mod.rs @@ -0,0 +1,89 @@ +use crate::utils::{ + asset::{Contract, RawContract}, + generic_response::ResponseStatus, + ExecuteCallback, + InstantiateCallback, + Query, +}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Binary, Uint128}; + +#[cw_serde] +pub struct RegisteredToken { + pub burn_token: Contract, + pub mint_token: Contract, + pub burnable: Option, +} + +#[cw_serde] +pub struct Config { + pub admin: Contract, +} + +#[cw_serde] +pub struct InstantiateMsg { + pub admin: Contract, + pub tokens: Option, +} + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteMsg { + UpdateConfig { + admin: RawContract, + padding: Option, + }, + RegisterMigrationTokens { + burn_token: RawContract, + mint_token: RawContract, + burnable: Option, + padding: Option, + }, + Receive { + sender: String, + from: String, + amount: Uint128, + msg: Option, + memo: Option, + padding: Option, + }, +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum ExecuteAnswer { + SetConfig { + status: ResponseStatus, + config: Config, + }, + RegisterMigrationTokens { + status: ResponseStatus, + }, + Receive { + status: ResponseStatus, + }, +} + +#[cw_serde] +pub enum QueryMsg { + Config {}, + Metrics { token: String }, + RegistrationStatus { token: String }, +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + Config { config: Config }, + Metrics { amount_minted: Uint128 }, + RegistrationStatus { status: RegisteredToken }, +} diff --git a/packages/shade_protocol/src/contract_interfaces/stkd/mod.rs b/packages/shade_protocol/src/contract_interfaces/stkd/mod.rs new file mode 100644 index 0000000..73752c8 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/stkd/mod.rs @@ -0,0 +1,170 @@ +// Types imported from staking-derivatives private repo used for interfacing with stkd-SCRT +// and updated to v1. Types copied as needed, feel free to add. +// Types also included for mock_stkd contract + +use cosmwasm_std::{Addr, Binary}; +use cosmwasm_std::Uint128; + +use crate::utils::{ + ExecuteCallback, Query, +}; +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub enum HandleMsg { + /// stake the sent SCRT + Stake {}, + /// Unbond SCRT + Unbond { + /// amount of derivative tokens to redeem + redeem_amount: Uint128, + }, + /// claim matured unbondings + Claim {}, + + SetViewingKey { + key: String, + padding: Option, + }, + Send { + recipient: Addr, + recipient_code_hash: Option, + amount: Uint128, + msg: Option, + memo: Option, + padding: Option, + }, + + // For mock_stkd contract, to simulate passage of time for unbondings + MockFastForward { + steps: u32, + }, +} + +#[cw_serde] +pub enum HandleAnswer { + Stake { + /// amount of uSCRT staked + scrt_staked: Uint128, + /// amount of derivative token minted + tokens_returned: Uint128, + }, + Unbond { + /// amount of derivative tokens redeemed + tokens_redeemed: Uint128, + /// amount of scrt to be unbonded (available in 21 days after the batch processes) + scrt_to_be_received: Uint128, + /// estimated time of maturity + estimated_time_of_maturity: u64, + }, + Claim { + /// amount of SCRT claimed + withdrawn: Uint128, + /// fees collected + fees: Uint128, + }, +} + +impl ExecuteCallback for HandleMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryMsg { + /// display the validator addresses, amount of bonded SCRT, amount of available SCRT not + /// reserved for mature unbondings, amount of pending staking rewards not yet claimed, + /// the derivative token supply, and the price of the derivative token in SCRT to 6 decimals + StakingInfo { + /// time in seconds since 01/01/1970. + time: u64, + }, + Unbonding { + /// address whose unclaimed unbondings to display + address: Addr, + /// the address' viewing key + key: String, + /// optional page number to display + page: Option, + /// optional page size + page_size: Option, + /// optional time in seconds since 01/01/1970. If provided, response will + /// include the amount of SCRT that can be withdrawn at that time + time: Option, + }, + Balance { + address: Addr, + key: String, + }, +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +#[cw_serde] +pub enum QueryAnswer { + /// displays staking info + StakingInfo { + /// validator addresses and their weights + validators: Vec, + /// unbonding time + unbonding_time: u32, + /// minimum number of seconds between unbonding batches + unbonding_batch_interval: u32, + /// earliest time of next batch unbonding + next_unbonding_batch_time: u64, + /// amount of SCRT that will unbond in the next batch + unbond_amount_of_next_batch: Uint128, + /// true if a batch unbonding is in progress + batch_unbond_in_progress: bool, + /// amount of bonded SCRT + bonded_scrt: Uint128, + /// amount of SCRT reserved for mature unbondings + reserved_scrt: Uint128, + /// amount of available SCRT not reserved for mature unbondings + available_scrt: Uint128, + /// unclaimed staking rewards + rewards: Uint128, + /// total supply of derivative token + total_derivative_token_supply: Uint128, + /// price of derivative token in SCRT to 6 decimals + price: Uint128, + }, + /// displays user's unclaimed unbondings + Unbonding { + /// number of unclaimed unbondings + count: u64, + /// amount of claimable SCRT at the specified time (if given) + claimable_scrt: Option, + /// unclaimed unbondings + unbondings: Vec, + /// total amount of pending unbondings that will begin maturing in the next batch + unbond_amount_in_next_batch: Uint128, + /// optional estimated time the next batch of unbondings will mature. Only provided + /// if the user has SCRT waiting to be unbonded in the next batch + estimated_time_of_maturity_for_next_batch: Option, + }, + Balance { + amount: Uint128, + }, +} + +/// validators and their weights +#[cw_serde] +pub struct WeightedValidator { + /// the validator's address + pub validator: Addr, + /// the validator's weight in whole percents + pub weight: u8, +} + +/// Unbonding data +#[cw_serde] +pub struct Unbond { + /// amount of SCRT unbonding + pub amount: Uint128, + /// time of maturation in seconds since 01/01/1970 + pub unbonds_at: u64, + /// optional bool if time was supplied, which is true if the unbonding is mature + pub is_mature: Option, +} diff --git a/packages/shade_protocol/src/lib.rs b/packages/shade_protocol/src/lib.rs new file mode 100644 index 0000000..5200bae --- /dev/null +++ b/packages/shade_protocol/src/lib.rs @@ -0,0 +1,43 @@ +// TODO: make private later +pub mod contract_interfaces; +pub use contract_interfaces::*; + +pub const BLOCK_SIZE: usize = 256; +pub mod utils; + +// Forward important libs to avoid constantly importing them in the cargo crates, could help reduce compile times +pub mod c_std { + pub use contract_derive::shd_entry_point; + pub use cosmwasm_std::*; +} + +pub mod storage { + pub use cosmwasm_storage::*; +} + +pub use cosmwasm_schema; +pub use schemars; +#[cfg(feature = "storage_plus")] +pub use secret_storage_plus; +pub use serde; +pub use thiserror; + +#[cfg(feature = "query_auth_lib")] +pub use query_authentication; + +#[cfg(feature = "ensemble")] +pub use fadroma; + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(feature = "multi-test")] +pub use secret_multi_test as multi_test; + +#[cfg(feature = "multi-test")] +pub use anyhow::Result as AnyResult; + +// Expose contract in root since its so used +#[cfg(feature = "utils")] +pub use utils::asset::Contract; + +#[cfg(feature = "chrono")] +pub use chrono; diff --git a/packages/shade_protocol/src/schemas.rs b/packages/shade_protocol/src/schemas.rs new file mode 100644 index 0000000..5a50a2d --- /dev/null +++ b/packages/shade_protocol/src/schemas.rs @@ -0,0 +1,77 @@ +use cosmwasm_schema::{export_schema, remove_schemas}; +use schemars::schema_for; +use std::{env::current_dir, fs::create_dir_all}; + +macro_rules! generate_schemas { + ($($Contract:ident),+) => { + $( + use shade_protocol::contract_interfaces::$Contract; + + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + out_dir.push(stringify!($Contract)); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!($Contract::InstantiateMsg), &out_dir); + export_schema(&schema_for!($Contract::ExecuteMsg), &out_dir); + export_schema(&schema_for!($Contract::ExecuteAnswer), &out_dir); + export_schema(&schema_for!($Contract::QueryMsg), &out_dir); + export_schema(&schema_for!($Contract::QueryAnswer), &out_dir); + )+ + }; +} + +macro_rules! generate_nested_schemas { + ($Folder:ident, $($Contract:ident),+) => { + $( + use shade_protocol::contract_interfaces::$Folder::$Contract; + + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + out_dir.push(stringify!($Folder)); + out_dir.push(stringify!($Contract)); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!($Contract::InstantiateMsg), &out_dir); + export_schema(&schema_for!($Contract::ExecuteMsg), &out_dir); + export_schema(&schema_for!($Contract::ExecuteAnswer), &out_dir); + export_schema(&schema_for!($Contract::QueryMsg), &out_dir); + export_schema(&schema_for!($Contract::QueryAnswer), &out_dir); + )+ + }; +} + +pub fn main() { + generate_schemas!( + airdrop, + bonds, + governance, + peg_stability, + query_auth, + sky, + snip20 + ); + + // generate_nested_schemas!(mint, liability_mint, mint, mint_router); + + generate_nested_schemas!(oracles, oracle); + + generate_nested_schemas!(dao, treasury_manager, treasury, scrt_staking); + + // generate_nested_schemas!(staking, snip20_staking); + + // TODO: make admin interface up to standard + use shade_protocol::contract_interfaces::admin; + + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + out_dir.push("admin"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(admin::InstantiateMsg), &out_dir); + export_schema(&schema_for!(admin::ExecuteMsg), &out_dir); + export_schema(&schema_for!(admin::QueryMsg), &out_dir); +} diff --git a/packages/shade_protocol/src/utils/asset.rs b/packages/shade_protocol/src/utils/asset.rs new file mode 100644 index 0000000..013b8f2 --- /dev/null +++ b/packages/shade_protocol/src/utils/asset.rs @@ -0,0 +1,225 @@ +use std::vec; + +use crate::{ + c_std::{Addr, Api, BalanceResponse, BankQuery, ContractInfo, Deps, StdResult, Uint128}, + BLOCK_SIZE, +}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{CosmosMsg, DepsMut, Env}; + +/// Validates an optional address. +pub fn optional_addr_validate(api: &dyn Api, addr: Option) -> StdResult> { + let addr = if let Some(addr) = addr { + Some(api.addr_validate(&addr)?) + } else { + None + }; + + Ok(addr) +} + +/// Validates an optional RawContract. +pub fn optional_raw_contract_validate( + api: &dyn Api, + contract: Option, +) -> StdResult> { + let contract = if let Some(contract) = contract { + Some(contract.into_valid(api)?) + } else { + None + }; + + Ok(contract) +} + +/// Validates an optional RawContract. +pub fn optional_validate( + api: &dyn Api, + contract: Option, +) -> StdResult> { + let contract = if let Some(contract) = contract { + Some(contract.valid(api)?) + } else { + None + }; + + Ok(contract) +} + +/// Validates a vector of Strings as Addrs +pub fn validate_vec(api: &dyn Api, unvalidated_addresses: Vec) -> StdResult> { + let items: Result, _> = unvalidated_addresses + .iter() + .map(|f| api.addr_validate(f.as_str())) + .collect(); + Ok(items?) +} + +/// A contract that does not contain a validated address. +/// Should be accepted as user input because we shouldn't assume addresses are verified Addrs. +/// https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Addr.html +#[derive(Hash, Eq, Default)] +#[cw_serde] +pub struct RawContract { + pub address: String, + pub code_hash: String, +} + +impl RawContract { + #[allow(clippy::ptr_arg)] + pub fn new(address: &String, code_hash: &String) -> Self { + RawContract { + address: address.clone(), + code_hash: code_hash.clone(), + } + } + + /// Being deprecated in favor of `valid` which turns this into ContractInfo + /// instead of a Contract (which we are getting rid of) + pub fn into_valid(self, api: &dyn Api) -> StdResult { + let valid_addr = api.addr_validate(self.address.as_str())?; + Ok(Contract::new(&valid_addr, &self.code_hash)) + } + + pub fn valid(self, api: &dyn Api) -> StdResult { + let valid_addr = api.addr_validate(self.address.as_str())?; + Ok(ContractInfo { + address: valid_addr, + code_hash: self.code_hash.clone(), + }) + } +} + +impl From for RawContract { + fn from(item: Contract) -> Self { + RawContract { + address: item.address.into(), + code_hash: item.code_hash, + } + } +} + +impl From for RawContract { + fn from(item: ContractInfo) -> Self { + RawContract { + address: item.address.into(), + code_hash: item.code_hash, + } + } +} + +#[derive(Hash, Eq)] +#[cw_serde] +/// In the process of being deprecated for [cosmwasm_std::ContractInfo] so use that +/// instead when possible. +pub struct Contract { + pub address: Addr, + pub code_hash: String, +} + +impl Default for Contract { + fn default() -> Self { + Self { + address: Addr::unchecked(String::default()), + code_hash: Default::default(), + } + } +} + +impl Contract { + #[allow(clippy::ptr_arg)] + pub fn new(address: &Addr, code_hash: &String) -> Self { + Contract { + address: address.clone(), + code_hash: code_hash.clone(), + } + } + + pub fn validate_new(deps: Deps, address: &str, code_hash: &String) -> StdResult { + let valid_addr = deps.api.addr_validate(address)?; + Ok(Contract::new(&valid_addr, code_hash)) + } +} + +impl From for Contract { + fn from(item: ContractInfo) -> Self { + Contract { + address: item.address, + code_hash: item.code_hash, + } + } +} + +impl Into for Contract { + fn into(self) -> ContractInfo { + ContractInfo { + address: self.address, + code_hash: self.code_hash, + } + } +} + +//TODO: move away from here +pub fn scrt_balance(deps: Deps, address: Addr) -> StdResult { + let resp: BalanceResponse = deps.querier.query( + &BankQuery::Balance { + address: address.into(), + denom: "uscrt".to_string(), + } + .into(), + )?; + + Ok(resp.amount.amount) +} + +#[cfg(feature = "snip20")] +pub fn set_allowance( + deps: DepsMut, + env: &Env, + spender: Addr, + amount: Uint128, + key: String, + asset: &Contract, + cur_allowance: Option, +) -> StdResult> { + use crate::snip20::helpers::{allowance_query, decrease_allowance_msg, increase_allowance_msg}; + + let allowance = match cur_allowance { + Some(cur) => cur, + None => { + allowance_query( + &deps.querier, + env.contract.address.clone(), + spender.clone(), + key, + 1, + asset, + )? + .allowance + } + }; + + match amount.cmp(&allowance) { + // Decrease Allowance + std::cmp::Ordering::Less => Ok(vec![decrease_allowance_msg( + spender, + allowance.checked_sub(amount)?, + None, + None, + BLOCK_SIZE, + asset, + vec![], + )?]), + // Increase Allowance + std::cmp::Ordering::Greater => Ok(vec![increase_allowance_msg( + spender, + amount.checked_sub(amount)?, + None, + None, + BLOCK_SIZE, + asset, + vec![], + )?]), + _ => Ok(vec![]), + } +} diff --git a/packages/shade_protocol/src/utils/calc.rs b/packages/shade_protocol/src/utils/calc.rs new file mode 100644 index 0000000..e6baac2 --- /dev/null +++ b/packages/shade_protocol/src/utils/calc.rs @@ -0,0 +1,25 @@ +use crate::c_std::{Uint256, StdResult}; + +// For generic purpose math formulas +pub fn sqrt(value: Uint256) -> StdResult { + let mut z = Uint256::zero(); + + if value.gt(&Uint256::from(3u128)) { + z = value; + let mut x = value + .checked_div(Uint256::from(2u128))? + .checked_add(Uint256::from(1u128))?; + + while x.lt(&z) { + z = x; + x = value + .checked_div(x)? + .checked_add(x)? + .checked_div(Uint256::from(2u128))?; + } + } else if !value.is_zero() { + z = Uint256::from(1u128); + } + + Ok(z) +} \ No newline at end of file diff --git a/packages/shade_protocol/src/utils/callback.rs b/packages/shade_protocol/src/utils/callback.rs new file mode 100644 index 0000000..6c0ebba --- /dev/null +++ b/packages/shade_protocol/src/utils/callback.rs @@ -0,0 +1,376 @@ +use super::space_pad; +#[cfg(not(target_arch = "wasm32"))] +#[cfg(feature = "multi-test")] +use crate::multi_test::{App, AppResponse, Contract as MultiContract, Executor}; +#[cfg(not(target_arch = "wasm32"))] +#[cfg(feature = "multi-test")] +use crate::AnyResult; +use crate::{ + c_std::{ + to_binary, Addr, Coin, ContractInfo, CosmosMsg, Empty, QuerierWrapper, QueryRequest, + StdResult, WasmMsg, WasmQuery, + }, + serde::{de::DeserializeOwned, Serialize}, + Contract, +}; + +/// A trait marking types that define the instantiation message of a contract +/// +/// This trait requires specifying a padding block size and provides a method to create the +/// CosmosMsg used to instantiate a contract +pub trait InstantiateCallback: Serialize { + /// pad the message to blocks of this size + const BLOCK_SIZE: usize; + + /// Returns StdResult + /// + /// Tries to convert the instance of the implementing type to a CosmosMsg that will trigger the + /// instantiation of a contract. The BLOCK_SIZE specified in the implementation is used when + /// padding the message + /// + /// # Arguments + /// + /// * `label` - String holding the label for the new contract instance + /// * `code_id` - code ID of the contract to be instantiated + /// * `callback_code_hash` - String holding the code hash of the contract to be instantiated + /// * `send_amount` - Optional Uint128 amount of native coin to send with instantiation message + fn to_cosmos_msg( + &self, + label: String, + code_id: u64, + code_hash: String, + funds: Vec, + ) -> StdResult { + let mut msg = to_binary(self)?; + // can not have 0 block size + let padding = if Self::BLOCK_SIZE == 0 { + 1 + } else { + Self::BLOCK_SIZE + }; + space_pad(&mut msg.0, padding); + let init = WasmMsg::Instantiate { + code_id, + code_hash, + msg, + label, + funds, + admin: None, + }; + Ok(init.into()) + } + + /// Returns ContractInfo + /// + /// Tries to instantiate a contract into the multi test app. + /// + /// # Arguments + /// + /// * `testable` - a struct implementing the MultiTestable trait + /// * `router` - mutable reference to multi test app + /// * `sender` - user performing init + /// * `label` - label used to reference this contract + /// * `send_funds` - any funds sent with this init + #[cfg(not(target_arch = "wasm32"))] + #[cfg(feature = "multi-test")] + fn test_init( + &self, + testable: impl MultiTestable, + router: &mut App, + sender: Addr, + label: &str, + send_funds: &[Coin], + ) -> AnyResult { + let stored_code = router.store_code(testable.contract()); + router.instantiate_contract(stored_code, sender, &self, send_funds, label, None) + } +} + +/// A trait marking types that define the handle message(s) of a contract +/// +/// This trait requires specifying a padding block size and provides a method to create the +/// CosmosMsg used to execute a handle method of a contract +pub trait ExecuteCallback: Serialize { + /// pad the message to blocks of this size + const BLOCK_SIZE: usize; + + /// Returns StdResult + /// + /// Tries to convert the instance of the implementing type to a CosmosMsg that will trigger a + /// handle function of a contract. The BLOCK_SIZE specified in the implementation is used when + /// padding the message + /// + /// # Arguments + /// + /// * `callback_code_hash` - String holding the code hash of the contract to be executed + /// * `contract_addr` - address of the contract being called + /// * `send_amount` - Optional Uint128 amount of native coin to send with the handle message + fn to_cosmos_msg( + &self, + contract: &(impl Into + Clone), + funds: Vec, + ) -> StdResult { + let mut msg = to_binary(self)?; + // can not have 0 block size + let padding = if Self::BLOCK_SIZE == 0 { + 1 + } else { + Self::BLOCK_SIZE + }; + let contract: Contract = contract.clone().into(); + space_pad(&mut msg.0, padding); + let execute = WasmMsg::Execute { + msg, + contract_addr: contract.address.to_string(), + code_hash: contract.code_hash, + funds, + }; + Ok(execute.into()) + } + + /// Returns AnyResult + /// + /// Tries to execute a message on a contract in the multi-test App. + /// + /// # Arguments + /// + /// * `contract` - ContractInfo of an existing contract on the multi-test App + /// * `router` - a mutable reference to the multi-test App + /// * `sender` - the user executing this message in the test env + /// * `send_funds` - any funds transferred with this exec + #[cfg(not(target_arch = "wasm32"))] + #[cfg(feature = "multi-test")] + fn test_exec( + &self, + contract: &ContractInfo, + router: &mut App, + sender: Addr, + send_funds: &[Coin], + ) -> AnyResult + where + Self: Serialize + std::fmt::Debug, + { + router.execute_contract(sender, &contract, &self, send_funds) + } +} + +/// A trait marking types that define the query message(s) of a contract +/// +/// This trait requires specifying a padding block size and provides a method to query a contract +pub trait Query: Serialize { + /// pad the message to blocks of this size + const BLOCK_SIZE: usize; + + /// Returns StdResult, where T is the type defining the query response + /// + /// Tries to query a contract and deserialize the query response. The BLOCK_SIZE specified in the + /// implementation is used when padding the message + /// + /// # Arguments + /// + /// * `querier` - a reference to the Querier dependency of the querying contract + /// * `callback_code_hash` - String holding the code hash of the contract to be queried + /// * `contract_addr` - address of the contract being queried + fn query( + &self, + querier: &QuerierWrapper, + contract: &(impl Into + Clone), + ) -> StdResult { + let mut msg = to_binary(self)?; + // can not have 0 block size + let padding = if Self::BLOCK_SIZE == 0 { + 1 + } else { + Self::BLOCK_SIZE + }; + space_pad(&mut msg.0, padding); + let contract: Contract = contract.clone().into(); + querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: contract.address.to_string(), + msg, + code_hash: contract.code_hash, + })) + } + + /// Returns StdResult, where T is the type defining the query response + /// + /// Tries to query a contract in the multi-test App. + /// + /// # Arguments + /// + /// * `info` - contract info of instantiated contract + /// * `router` - a reference to the multi-test App + #[cfg(not(target_arch = "wasm32"))] + #[cfg(feature = "multi-test")] + fn test_query(&self, info: &ContractInfo, router: &App) -> StdResult { + let mut msg = to_binary(self)?; + // can not have 0 block size + let padding = if Self::BLOCK_SIZE == 0 { + 1 + } else { + Self::BLOCK_SIZE + }; + space_pad(&mut msg.0, padding); + router.wrap().query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: info.address.to_string(), + msg, + code_hash: info.code_hash.clone(), + })) + } +} + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(feature = "multi-test")] +/// Trait for making integration with multi-test easier. +pub trait MultiTestable { + fn contract(&self) -> Box>; + fn default() -> Self; +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::{to_vec, Binary, Querier, QuerierResult}; + use serde::Deserialize; + + #[derive(Serialize)] + struct FooInit { + pub f1: i8, + pub f2: i8, + } + + impl InstantiateCallback for FooInit { + const BLOCK_SIZE: usize = 256; + } + + #[derive(Serialize)] + enum FooHandle { + Var1 { f1: i8, f2: i8 }, + } + + // All you really need to do is make people give you the padding block size. + impl ExecuteCallback for FooHandle { + const BLOCK_SIZE: usize = 256; + } + + #[derive(Serialize)] + enum FooQuery { + Query1 { f1: i8, f2: i8 }, + } + + impl Query for FooQuery { + const BLOCK_SIZE: usize = 256; + } + + #[test] + fn test_handle_callback_implementation_works() -> StdResult<()> { + let address = Addr::unchecked("secret1xyzasdf".to_string()); + let hash = "asdf".to_string(); + let amount = vec![Coin::new(1234, "uscrt")]; + let contract = Contract::new(&address, &hash); + + let cosmos_message: CosmosMsg = + FooHandle::Var1 { f1: 1, f2: 2 }.to_cosmos_msg(&contract, amount.clone())?; + + match cosmos_message { + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr, + code_hash, + msg, + funds, + }) => { + assert_eq!(contract_addr, address); + assert_eq!(code_hash, hash); + let mut expected_msg = r#"{"Var1":{"f1":1,"f2":2}}"#.as_bytes().to_vec(); + space_pad(&mut expected_msg, 256); + assert_eq!(msg.0, expected_msg); + assert_eq!(funds, amount) + } + other => panic!("unexpected CosmosMsg variant: {:?}", other), + }; + + Ok(()) + } + + #[test] + fn test_init_callback_implementation_works() -> StdResult<()> { + let lbl = "testlabel".to_string(); + let id = 17u64; + let hash = "asdf".to_string(); + let amount = vec![Coin::new(1234, "uscrt")]; + + let cosmos_message: CosmosMsg = FooInit { f1: 1, f2: 2 }.to_cosmos_msg( + lbl.clone(), + id, + hash.clone(), + amount.clone(), + )?; + + match cosmos_message { + CosmosMsg::Wasm(WasmMsg::Instantiate { + code_id, + msg, + code_hash, + funds, + label, + admin: None, + }) => { + assert_eq!(code_id, id); + let mut expected_msg = r#"{"f1":1,"f2":2}"#.as_bytes().to_vec(); + space_pad(&mut expected_msg, 256); + assert_eq!(msg.0, expected_msg); + assert_eq!(code_hash, hash); + assert_eq!(funds, amount); + assert_eq!(label, lbl) + } + other => panic!("unexpected CosmosMsg variant: {:?}", other), + }; + + Ok(()) + } + + #[test] + fn test_query_works() -> StdResult<()> { + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct QueryResponse { + bar1: i8, + bar2: i8, + } + + struct MyMockQuerier {} + + impl Querier for MyMockQuerier { + fn raw_query( + &self, + request: &[u8], + ) -> cosmwasm_std::SystemResult> + { + let mut expected_msg = r#"{"Query1":{"f1":1,"f2":2}}"#.as_bytes().to_vec(); + space_pad(&mut expected_msg, 256); + let expected_request: QueryRequest = + QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: "secret1xyzasdf".to_string(), + code_hash: "asdf".to_string(), + msg: Binary(expected_msg), + }); + let test_req: &[u8] = &to_vec(&expected_request).unwrap(); + assert_eq!(request, test_req); + cosmwasm_std::SystemResult::Ok(cosmwasm_std::ContractResult::Ok( + to_binary(&QueryResponse { bar1: 1, bar2: 2 }).unwrap(), + )) + } + } + + let querier = MyMockQuerier {}; + let address = "secret1xyzasdf".to_string(); + let hash = "asdf".to_string(); + let contract = Contract::new(&Addr::unchecked(address), &hash); + + // Was getting an error here + // let response: QueryResponse = + // FooQuery::Query1 { f1: 1, f2: 2 }.query(&querier, &contract)?; + // assert_eq!(response, QueryResponse { bar1: 1, bar2: 2 }); + + Ok(()) + } +} diff --git a/packages/shade_protocol/src/utils/crypto/hash.rs b/packages/shade_protocol/src/utils/crypto/hash.rs new file mode 100644 index 0000000..c7094e7 --- /dev/null +++ b/packages/shade_protocol/src/utils/crypto/hash.rs @@ -0,0 +1,35 @@ +use sha2::{Digest, Sha256}; + +pub const SHA256_HASH_SIZE: usize = 32; + +pub fn sha_256(data: &[u8]) -> [u8; SHA256_HASH_SIZE] { + 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 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sha_256() { + let r = sha_256(b"test"); + let r_expected: [u8; SHA256_HASH_SIZE] = [ + 159, 134, 208, 129, 136, 76, 125, 101, 154, 47, 234, 160, 197, 90, 208, 21, 163, 191, + 79, 27, 43, 11, 130, 44, 209, 93, 108, 21, 176, 240, 10, 8, + ]; + assert_eq!(r, r_expected); + + let r = sha_256(b"random_string_123"); + let r_expected: [u8; SHA256_HASH_SIZE] = [ + 167, 75, 46, 161, 27, 233, 254, 146, 245, 218, 2, 19, 171, 56, 78, 166, 42, 211, 88, 7, + 205, 191, 2, 6, 226, 158, 43, 144, 8, 149, 170, 164, + ]; + assert_eq!(r, r_expected); + } +} diff --git a/packages/shade_protocol/src/utils/crypto/mod.rs b/packages/shade_protocol/src/utils/crypto/mod.rs new file mode 100644 index 0000000..bb43515 --- /dev/null +++ b/packages/shade_protocol/src/utils/crypto/mod.rs @@ -0,0 +1,5 @@ +mod hash; +mod rng; + +pub use hash::{sha_256, SHA256_HASH_SIZE}; +pub use rng::Prng; diff --git a/packages/shade_protocol/src/utils/crypto/rng.rs b/packages/shade_protocol/src/utils/crypto/rng.rs new file mode 100644 index 0000000..e094554 --- /dev/null +++ b/packages/shade_protocol/src/utils/crypto/rng.rs @@ -0,0 +1,85 @@ +use rand_chacha::ChaChaRng; +use rand_core::{RngCore, SeedableRng}; +use sha2::{Digest, Sha256}; + +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::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 + } + + pub fn set_word_pos(&mut self, count: u32) { + self.rng.set_word_pos(count.into()); + } +} + +#[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()); + } + + #[test] + fn test_rand_bytes_counter() { + let mut rng = Prng::new(b"foo", b"bar"); + + let r1: [u8; 32] = [ + 114, 227, 179, 76, 120, 34, 236, 42, 204, 27, 153, 74, 44, 29, 158, 162, 180, 202, 165, + 46, 155, 90, 178, 252, 127, 80, 162, 79, 3, 146, 153, 88, + ]; + + rng.set_word_pos(8); + assert_eq!(r1, rng.rand_bytes()); + rng.set_word_pos(8); + assert_eq!(r1, rng.rand_bytes()); + rng.set_word_pos(9); + assert_ne!(r1, rng.rand_bytes()); + } +} diff --git a/packages/shade_protocol/src/utils/cycle.rs b/packages/shade_protocol/src/utils/cycle.rs new file mode 100644 index 0000000..15282c2 --- /dev/null +++ b/packages/shade_protocol/src/utils/cycle.rs @@ -0,0 +1,327 @@ +use crate::{ + c_std::{Env, StdError, StdResult, Timestamp, Uint128}, + chrono::prelude::*, +}; + +use cosmwasm_schema::cw_serde; +use std::convert::TryInto; + +#[cw_serde] +pub enum Cycle { + Once, + Constant, + /* + Block { + blocks: Uint128, + }, + */ + Yearly { years: Uint128 }, + Monthly { months: Uint128 }, + Daily { days: Uint128 }, + Hourly { hours: Uint128 }, + Minutes { minutes: Uint128 }, + Seconds { seconds: Uint128 }, +} + +pub fn utc_from_seconds(seconds: i64) -> DateTime { + DateTime::from_utc(NaiveDateTime::from_timestamp(seconds, 0), Utc) +} + +pub fn utc_from_timestamp(timestamp: Timestamp) -> DateTime { + DateTime::from_utc( + NaiveDateTime::from_timestamp(timestamp.seconds() as i64, 0), + Utc, + ) +} + +pub fn parse_utc_datetime(rfc3339: &String) -> StdResult> { + DateTime::parse_from_rfc3339(&rfc3339) + .map(|dt| dt.with_timezone(&Utc)) + .map_err(|_| StdError::generic_err(format!("Failed to parse rfc3339 datetime {}", rfc3339))) +} + +pub fn utc_now(env: &Env) -> DateTime { + DateTime::from_utc( + NaiveDateTime::from_timestamp(env.block.time.seconds() as i64, 0), + Utc, + ) +} + +pub fn exceeds_cycle(now: &DateTime, last_refresh: &DateTime, cycle: Cycle) -> bool { + if now.timestamp() < last_refresh.timestamp() && cycle != Cycle::Constant { + return false; + } + match cycle { + Cycle::Constant => true, + Cycle::Once => false, + Cycle::Seconds { seconds } => { + seconds <= Uint128::new((now.timestamp() - last_refresh.timestamp()) as u128) + } + Cycle::Minutes { minutes } => { + println!( + "{} >= {} -> {} - {}", + minutes, + ((now.timestamp() - last_refresh.timestamp()) / 60), + now.timestamp(), + last_refresh.timestamp() + ); + minutes + <= Uint128::new( + ((now.timestamp() - last_refresh.timestamp()) / 60) + .try_into() + .unwrap(), + ) + } + Cycle::Hourly { hours } => { + println!( + "{} >= {} -> {} - {}", + hours, + ((now.timestamp() - last_refresh.timestamp()) / 60 / 60), + now.timestamp(), + last_refresh.timestamp() + ); + hours + <= Uint128::new( + ((now.timestamp() - last_refresh.timestamp()) / 60 / 60) + .try_into() + .unwrap(), + ) + } + Cycle::Daily { days } => { + days.u128() as i32 <= now.num_days_from_ce() - last_refresh.num_days_from_ce() + } + Cycle::Monthly { months } => { + let month_diff: u32; + + if now.year() > last_refresh.year() { + month_diff = (12u32 - last_refresh.month()) + now.month(); + } else { + month_diff = now.month() - last_refresh.month(); + } + + months.u128() as u32 <= month_diff + } + Cycle::Yearly { years } => { + years.u128() as u32 <= now.year_ce().1 - last_refresh.year_ce().1 + } + } +} + +#[cfg(test)] +mod test { + + use super::*; + + fn test_exceeds_cycle(last_refresh: String, now: String, cycle: Cycle, exceeds: bool) { + let last_refresh = parse_utc_datetime(&last_refresh).unwrap(); + let now = parse_utc_datetime(&now).unwrap(); + assert_eq!(exceeds_cycle(&now, &last_refresh, cycle), exceeds); + } + + macro_rules! exceeds_cycle_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (start, now, cycle, exceeds) = $value; + test_exceeds_cycle(start.to_string(), now.to_string(), cycle, exceeds); + } + )* + } + } + + exceeds_cycle_tests! { + constant_cycle: ( + "2019-10-12T00:00:00.00Z", + "2019-10-12T23:59:59.59Z", + Cycle::Constant, + true, + ), + once_cycle: ( + "2019-10-12T00:00:00.00Z", + "2019-10-12T23:59:59.59Z", + Cycle::Once, + false, + ), + seconds_cycle_well_under: ( + "2019-10-12T00:00:00.00Z", + "2019-10-12T00:00:05.00Z", // 5 sec diff + Cycle::Seconds { seconds: Uint128::new(10) }, + false, + ), + seconds_cycle_just_short: ( + "2019-10-12T00:00:00.00Z", + "2019-10-12T00:00:09.99Z", // 9.99 sec diff + Cycle::Seconds { seconds: Uint128::new(10) }, + false, + ), + seconds_cycle_exact: ( + "2019-10-12T00:00:00.00Z", + "2019-10-12T00:00:10.00Z", // 10 sec diff + Cycle::Seconds { seconds: Uint128::new(10) }, + true, + ), + seconds_cycle_well_over: ( + "2019-10-12T00:00:00.00Z", + "2019-10-12T00:20:00.00Z", // 20 min diff + Cycle::Seconds { seconds: Uint128::new(10) }, + true, + ), + seconds_cycle_overflow: ( + "2019-10-12T00:20:00.00Z", // 20 min diff + "2019-10-12T00:00:00.00Z", + Cycle::Seconds { seconds: Uint128::new(10) }, + false, + ), + minutes_cycle_well_under: ( + "2019-10-12T00:00:00.00Z", + "2019-10-12T00:00:30.00Z", // 30 sec diff + Cycle::Minutes { minutes: Uint128::new(1) }, + false, + ), + minutes_cycle_just_short: ( + "2019-10-12T00:00:00.00Z", + "2019-10-12T00:00:59.99Z", // 59.99 sec diff + Cycle::Minutes { minutes: Uint128::new(1) }, + false, + ), + minutes_cycle_exact: ( + "2019-10-12T00:00:00.00Z", + "2019-10-12T00:01:00.00Z", // 1 min diff + Cycle::Minutes { minutes: Uint128::new(1) }, + true, + ), + minutes_cycle_well_over: ( + "2019-10-12T00:00:00.00Z", + "2019-10-12T00:20:00.00Z", // 20 min diff + Cycle::Minutes { minutes: Uint128::new(1) }, + true, + ), + minutes_cycle_overflow: ( + "2019-10-12T00:20:00.00Z", // 20 min diff + "2019-10-12T00:00:00.00Z", + Cycle::Minutes { minutes: Uint128::new(1) }, + false, + ), + hours_cycle_well_under: ( + "2019-10-12T00:00:00.00Z", + "2019-10-12T00:30:00.00Z", // 30 min diff + Cycle::Hourly { hours: Uint128::new(1) }, + false, + ), + hours_cycle_just_short: ( + "2019-10-12T00:00:00.00Z", + "2019-10-12T00:59:59.99Z", // 59 mint 59.99 sec diff + Cycle::Hourly { hours: Uint128::new(1) }, + false, + ), + hours_cycle_exact: ( + "2019-10-12T00:00:00.00Z", + "2019-10-12T01:00:00.00Z", // 1 hour diff + Cycle::Hourly { hours: Uint128::new(1) }, + true, + ), + hours_cycle_well_over: ( + "2019-10-12T00:00:00.00Z", + "2019-10-12T01:30:00.00Z", // 1 hour 30 min diff + Cycle::Hourly { hours: Uint128::new(1) }, + true, + ), + hours_cycle_overflow: ( + "2019-10-12T01:30:00.00Z", // 1 hour 30 min diff + "2019-10-12T00:00:00.00Z", + Cycle::Hourly { hours: Uint128::new(1) }, + false, + ), + daily_cycle_well_under: ( + "2019-10-12T00:00:00.00Z", + "2019-10-12T23:00:00.00Z", // 23 hour diff + Cycle::Daily { days: Uint128::new(1)}, + false, + ), + daily_cycle_just_short: ( + "2019-10-12T00:00:00.00Z", + "2019-10-12T23:59:59.99Z", // 23 hours, 59 min, 59.99 sec diff + Cycle::Daily { days: Uint128::new(1) }, + false, + ), + daily_cycle_exact: ( + "2019-10-12T00:00:00.00Z", + "2019-10-13T00:00:00.00Z", // 1 day diff + Cycle::Daily { days: Uint128::new(1) }, + true, + ), + daily_cycle_well_over: ( + "2019-10-12T00:00:00.00Z", + "2019-10-13T12:00:00.00Z", // 1 day 12 hr diff + Cycle::Daily { days: Uint128::new(1) }, + true, + ), + daily_cycle_overflow: ( + "2019-10-13T12:00:00.00Z", // 1 day 12 hr diff + "2019-10-12T00:00:00.00Z", + Cycle::Daily { days: Uint128::new(1) }, + false, + ), + monthly_cycle_well_under: ( + "2019-10-01T00:00:00.00Z", + "2019-10-15T00:00:00.00Z", // 14 day diff + Cycle::Monthly { months: Uint128::new(1) }, + false, + ), + monthly_cycle_just_short: ( + "2019-10-01T00:00:00.00Z", + "2019-10-31T23:59:59.99Z", // 30 day, 23 hr, 59 min, 59.99 sec diff + Cycle::Monthly { months: Uint128::new(1) }, + false, + ), + monthly_cycle_exact: ( + "2019-10-01T00:00:00.00Z", + "2019-11-01T00:00:00.00Z", // 1 mo diff + Cycle::Monthly { months: Uint128::new(1) }, + true, + ), + monthly_cycle_well_over: ( + "2019-10-01T00:00:00.00Z", + "2019-11-15T00:00:00.00Z", // 1 mo, 15 day diff + Cycle::Monthly { months: Uint128::new(1) }, + true, + ), + monthly_cycle_overflow: ( + "2019-11-15T00:00:00.00Z", // 1 mo, 15 day diff + "2019-10-01T00:00:00.00Z", + Cycle::Monthly { months: Uint128::new(1) }, + false, + ), + yearly_cycle_well_under: ( + "2019-01-01T00:00:00.00Z", + "2019-10-01T00:00:00.00Z", // 9 mo diff + Cycle::Yearly { years: Uint128::new(1) }, + false, + ), + yearly_cycle_just_short: ( + "2019-01-01T00:00:00.00Z", + "2019-12-31T23:59:59.99Z", // 11 mo, 31 days, 23 hrs, 59 mins 59.99 sec diff + Cycle::Yearly { years: Uint128::new(1) }, + false, + ), + yearly_cycle_exact: ( + "2019-01-01T00:00:00.00Z", + "2020-01-01T00:00:00.00Z", // 1 yr diff + Cycle::Yearly { years: Uint128::new(1) }, + true, + ), + yearly_cycle_well_over: ( + "2019-01-01T00:00:00.00Z", + "2020-06-01T00:00:00.00Z", // 1 yr, 6 mo diff + Cycle::Yearly { years: Uint128::new(1) }, + true, + ), + yearly_cycle_overflow: ( + "2020-06-01T00:00:00.00Z", // 1 yr, 6 mo diff + "2019-01-01T00:00:00.00Z", + Cycle::Yearly { years: Uint128::new(1) }, + false, + ), + } +} diff --git a/packages/shade_protocol/src/utils/errors.rs b/packages/shade_protocol/src/utils/errors.rs new file mode 100644 index 0000000..b233ff7 --- /dev/null +++ b/packages/shade_protocol/src/utils/errors.rs @@ -0,0 +1,287 @@ +use crate::c_std::StdError; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Response, StdResult}; +use schemars::_serde_json::to_string; +use serde::Serialize; + +// TODO: make another that auto imports + +/// Generates the errors +/// Macro takes in an array of error name, error msg and error function +#[macro_export] +macro_rules! errors { + ($Target:tt; $($EnumError:ident, $VerboseError:tt, $Function:ident),+) => { + use crate::{ + c_std::StdError, + generate_errors, + impl_into_u8, + utils::errors::{build_string, CodeType, DetailedError}, + }; + use cosmwasm_schema::cw_serde; + + generate_errors!($Target; $($EnumError, $VerboseError, $Function),+); + } +} + +#[macro_export] +macro_rules! generate_errors { + ($Target:tt; $($EnumError:ident, $VerboseError:tt, $Function:ident),+) => { + #[cw_serde] + #[repr(u8)] + pub enum Error { $($EnumError,)+ } + impl_into_u8!(Error); + + impl CodeType for Error { + fn to_verbose(&self, context: &Vec<&str>) -> String { + match self { + $( + Error::$EnumError => { + build_string($VerboseError, context) + } + )+ + } + } + } + + const TARGET: &str = $Target ; + + impl Error { + $( + pub fn $Function(args: Vec<&str>) -> StdError { + DetailedError::from_code(TARGET, Error::$EnumError, args).to_error() + } + )+ + } + + }; +} + +#[macro_export] +macro_rules! impl_into_u8 { + ($error:ident) => { + impl From<$error> for u8 { + fn from(err: $error) -> u8 { + err as _ + } + } + }; +} + +#[cw_serde] +pub struct DetailedError { + pub target: String, + pub code: u8, + pub r#type: T, + pub context: Vec, + pub verbose: String, +} + +impl DetailedError { + pub fn to_full_error(&self) -> StdResult { + Err(self.to_error()) + } + + pub fn to_error(&self) -> StdError { + StdError::generic_err(self.to_string()) + } + + pub fn to_string(&self) -> String { + to_string(&self).unwrap_or("".to_string()) + } + + pub fn from_code(target: &str, code: T, context: Vec<&str>) -> Self { + let verbose = code.to_verbose(&context); + Self { + target: target.to_string(), + code: code.to_code(), + r#type: code, + context: context.iter().map(|s| s.to_string()).collect(), + verbose, + } + } +} + +pub fn build_string(verbose: &str, context: &Vec<&str>) -> String { + let mut msg = verbose.to_string(); + for arg in context.iter() { + msg = msg.replacen("{}", arg, 1); + } + msg +} + +pub trait CodeType: Into + Clone { + fn to_code(&self) -> u8 { + self.clone().into() + } + fn to_verbose(&self, context: &Vec<&str>) -> String; +} + +#[cfg(test)] +pub mod tests { + use crate::{ + c_std::{Response, StdError, StdResult}, + utils::errors::{build_string, CodeType, DetailedError}, + }; + + use cosmwasm_schema::cw_serde; + + #[cw_serde] + #[repr(u8)] + enum TestCode { + Error1, + Error2, + Error3, + } + + impl_into_u8!(TestCode); + + impl CodeType for TestCode { + fn to_verbose(&self, context: &Vec<&str>) -> String { + match self { + TestCode::Error1 => build_string("Error", context), + TestCode::Error2 => build_string("Broke in {}", context), + TestCode::Error3 => build_string("Expecting {} but got {}", context), + } + } + } + + // Because of set variables, you could implement something like this + + fn error_1() -> StdError { + DetailedError::from_code("contract", TestCode::Error1, vec![]).to_error() + } + + fn error_2(context: &[&str; 1]) -> StdError { + DetailedError::from_code("contract", TestCode::Error2, context.to_vec()).to_error() + } + + fn error_3(context: &[&str; 2]) -> StdError { + DetailedError::from_code("contract", TestCode::Error3, context.to_vec()).to_error() + } + + #[test] + fn string_builder() { + assert_eq!( + build_string("Test string {}", &vec!["arg"]), + "Test string arg".to_string() + ) + } + + #[test] + fn to_code() { + let code1 = TestCode::Error1; + assert_eq!(code1.to_code(), 0); + + let code2 = TestCode::Error2; + assert_eq!(code2.to_code(), 1); + + let code3 = TestCode::Error3; + assert_eq!(code3.to_code(), 2); + } + + #[test] + fn to_verbose() { + assert_eq!(TestCode::Error1.to_verbose(&vec![]), "Error".to_string()); + assert_eq!( + TestCode::Error2.to_verbose(&vec!["function"]), + "Broke in function".to_string() + ); + assert_eq!( + TestCode::Error3.to_verbose(&vec!["address", "amount"]), + "Expecting address but got amount".to_string() + ); + } + + #[test] + fn from_code() { + let err1 = DetailedError::from_code("contract", TestCode::Error1, vec![]); + assert_eq!(err1.code, 0); + assert_eq!(err1.r#type, TestCode::Error1); + let empty: Vec = vec![]; + assert_eq!(err1.context, empty); + assert_eq!(err1.verbose, "Error".to_string()); + + let err2 = DetailedError::from_code("contract", TestCode::Error2, vec!["function"]); + assert_eq!(err2.code, 1); + assert_eq!(err2.r#type, TestCode::Error2); + assert_eq!(err2.context, vec!["function".to_string()]); + assert_eq!(err2.verbose, "Broke in function".to_string()); + + let err3 = + DetailedError::from_code("contract", TestCode::Error3, vec!["address", "amount"]); + assert_eq!(err3.code, 2); + assert_eq!(err3.r#type, TestCode::Error3); + assert_eq!(err3.context, vec![ + "address".to_string(), + "amount".to_string() + ]); + assert_eq!(err3.verbose, "Expecting address but got amount".to_string()); + } + + #[test] + fn to_string() { + assert_eq!(DetailedError::from_code("contract", TestCode::Error1, vec![]).to_string(), + "{\"target\":\"contract\",\"code\":0,\"type\":\"error1\",\"context\":[],\"verbose\":\"Error\"}".to_string()); + assert_eq!(DetailedError::from_code("contract", TestCode::Error2, vec!["function"]).to_string(), + "{\"target\":\"contract\",\"code\":1,\"type\":\"error2\",\"context\":[\"function\"],\"verbose\":\"Broke in function\"}".to_string()); + assert_eq!(DetailedError::from_code("contract", TestCode::Error3, vec!["address", "amount"]).to_string(), + "{\"target\":\"contract\",\"code\":2,\"type\":\"error3\",\"context\":[\"address\",\"amount\"],\"verbose\":\"Expecting address but got amount\"}".to_string()); + } + + #[test] + fn to_error() { + let err1 = DetailedError::from_code("contract", TestCode::Error1, vec![]).to_error(); + match err1 { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "{\"target\":\"contract\",\"code\":0,\"type\":\"error1\",\"context\":[],\"verbose\":\"Error\"}".to_string()), + _ => assert!(false) + } + + let err2 = + DetailedError::from_code("contract", TestCode::Error2, vec!["function"]).to_error(); + match err2 { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "{\"target\":\"contract\",\"code\":1,\"type\":\"error2\",\"context\":[\"function\"],\"verbose\":\"Broke in function\"}".to_string()), + _ => assert!(false) + } + + let err3 = + DetailedError::from_code("contract", TestCode::Error3, vec!["address", "amount"]) + .to_error(); + match err3 { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "{\"target\":\"contract\",\"code\":2,\"type\":\"error3\",\"context\":[\"address\",\"amount\"],\"verbose\":\"Expecting address but got amount\"}".to_string()), + _ => assert!(false) + } + } + + #[test] + fn helpers() { + let err1 = error_1(); + match err1 { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "{\"target\":\"contract\",\"code\":0,\"type\":\"error1\",\"context\":[],\"verbose\":\"Error\"}".to_string()), + _ => assert!(false) + } + + let err2 = error_2(&["function"]); + match err2 { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "{\"target\":\"contract\",\"code\":1,\"type\":\"error2\",\"context\":[\"function\"],\"verbose\":\"Broke in function\"}".to_string()), + _ => assert!(false) + } + + let err3 = error_3(&["address", "amount"]); + match err3 { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "{\"target\":\"contract\",\"code\":2,\"type\":\"error3\",\"context\":[\"address\",\"amount\"],\"verbose\":\"Expecting address but got amount\"}".to_string()), + _ => assert!(false) + } + } + + generate_errors!("test"; + Test1, "Verb1", Test1Func, + Test2, "Ver2", Test2Func, + Test3, "Verb3", Test3Func); + + #[test] + fn macro_errors() { + let test = Error::Test2; + + let test1 = Error::Test1Func(vec![]); + } +} diff --git a/packages/shade_protocol/src/utils/flexible_msg.rs b/packages/shade_protocol/src/utils/flexible_msg.rs new file mode 100644 index 0000000..91fc9c5 --- /dev/null +++ b/packages/shade_protocol/src/utils/flexible_msg.rs @@ -0,0 +1,34 @@ +use crate::c_std::{StdError, StdResult}; + +use cosmwasm_schema::{cw_serde}; + +#[cw_serde] +pub struct FlexibleMsg { + pub msg: String, + pub arguments: u16, +} + +impl FlexibleMsg { + pub fn new(msg: String, msg_variable: &str) -> FlexibleMsg { + FlexibleMsg { + msg: msg.clone(), + arguments: msg.matches(msg_variable).count() as u16, + } + } + + pub fn create_msg(&self, args: Vec, msg_variable: &str) -> StdResult { + if args.len() as u16 != self.arguments { + return Err(StdError::generic_err(format!( + "Msg expected {:?} arguments; received {:?}", + self.arguments, + args.len() + ))); + } + + let mut msg = self.msg.clone(); + for arg in args.iter() { + msg = msg.replacen(msg_variable, arg, 1); + } + Ok(msg) + } +} diff --git a/packages/shade_protocol/src/utils/generic_response.rs b/packages/shade_protocol/src/utils/generic_response.rs new file mode 100644 index 0000000..276894b --- /dev/null +++ b/packages/shade_protocol/src/utils/generic_response.rs @@ -0,0 +1,8 @@ + +use cosmwasm_schema::{cw_serde}; + +#[cw_serde] +pub enum ResponseStatus { + Success, + Failure, +} diff --git a/packages/shade_protocol/src/utils/mod.rs b/packages/shade_protocol/src/utils/mod.rs new file mode 100644 index 0000000..c906cb7 --- /dev/null +++ b/packages/shade_protocol/src/utils/mod.rs @@ -0,0 +1,35 @@ +// Helper libraries + +#[cfg(feature = "interface")] +pub mod callback; +#[cfg(feature = "interface")] +pub use callback::*; + +pub mod padding; +pub use padding::*; +pub mod crypto; + +#[cfg(feature = "utils")] +pub mod asset; + +#[cfg(feature = "errors")] +pub mod errors; + +#[cfg(feature = "flexible_msg")] +pub mod flexible_msg; + +#[cfg(feature = "utils")] +pub mod generic_response; + +pub mod storage; + +#[cfg(feature = "dao-utils")] +pub mod cycle; +#[cfg(feature = "dao-utils")] +pub mod wrap; + +#[cfg(feature = "math")] +pub mod price; + +#[cfg(feature = "math")] +pub mod calc; diff --git a/packages/shade_protocol/src/utils/padding.rs b/packages/shade_protocol/src/utils/padding.rs new file mode 100644 index 0000000..f2fdb6d --- /dev/null +++ b/packages/shade_protocol/src/utils/padding.rs @@ -0,0 +1,43 @@ +use crate::c_std::{Response, Binary, StdResult}; + +/// 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 +} + +/// Pad the data and logs in a `Response` to the block size, with spaces. +// The big `where` clause is based on the `where` clause of `Response`. +// Users don't need to care about it as the type `T` has a default, and will +// always be known in the context of the caller. +pub fn pad_handle_result(response: StdResult, block_size: usize) -> StdResult{ + response.map(|mut response| { + response.data = response.data.map(|mut data| { + space_pad(&mut data.0, block_size); + data + }); + for log in &mut response.attributes { + // Safety: These two are safe because we know the characters that + // `space_pad` appends are valid UTF-8 + unsafe { space_pad(log.key.as_mut_vec(), block_size) }; + unsafe { space_pad(log.value.as_mut_vec(), block_size) }; + } + response + }) +} + +/// Pad a `StdResult` with spaces +pub fn pad_query_result(response: StdResult, block_size: usize) -> StdResult { + response.map(|mut response| { + space_pad(&mut response.0, block_size); + response + }) +} diff --git a/packages/shade_protocol/src/utils/price.rs b/packages/shade_protocol/src/utils/price.rs new file mode 100644 index 0000000..a9094e5 --- /dev/null +++ b/packages/shade_protocol/src/utils/price.rs @@ -0,0 +1,116 @@ +use crate::c_std::Uint128; +use std::convert::TryFrom; + +/// Returns price of `token_1` in `denom` from `token_1/token_2` pair +/// +/// Denom is whatever `token_2` is quoted in. +/// +/// ### Arguments (normalized to 10^18) +/// * `scrt_price` - amount of denom per 1 token_2 from some oracle (ex: 9.18USD per 1 SCRT) +/// * `trade_price` - amount of 1 token_2 in token_1 (ex: 0.1 SHD per 1 SCRT) +pub fn translate_price(token_2_price: Uint128, trade_amount: Uint128) -> Uint128 { + token_2_price.multiply_ratio(10u128.pow(18), trade_amount) +} + +/// Normalize the price from some amount (usually snip20) with decimals to 10^18. +/// +/// ### Arguments +/// * `amount` - unsigned quantity +/// * `decimals` - number of decimals for received quantity +pub fn normalize_price(amount: Uint128, decimals: u8) -> Uint128 { + (amount.u128() * 10u128.pow(18u32 - u32::try_from(decimals).unwrap())).into() +} + +/// Returns 1 * 10^factor. +pub fn get_precision(factor: u8) -> Uint128 { + Uint128::from(10u128.pow(factor.into())) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::c_std::Uint128; + + macro_rules! normalize_price_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (amount, decimals, expected) = $value; + assert_eq!(normalize_price(amount, decimals), expected) + } + )* + } + } + + normalize_price_tests! { + normalize_0: ( + Uint128::new(1_413_500_852_332_497), + 18u8, + Uint128::new(1_413_500_852_332_497) + ), + normalize_1: ( + // amount of TKN received for 1 sSCRT + Uint128::new(1_000_000), + // TKN 6 decimals + 6u8, + // price * 10^18 + Uint128::new(1_000_000_000_000_000_000) + ), + normalize_2: ( + // amount of TKN received for 1 sSCRT + Uint128::new(1_000_000), + // TKN 6 decimals + 6u8, + // price * 10^18 + Uint128::new(1_000_000_000_000_000_000) + ), + } + + macro_rules! translate_price_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (scrt_price, trade_price, expected) = $value; + assert_eq!(translate_price(scrt_price, trade_price), expected) + } + )* + } + } + + translate_price_tests! { + translate_0: ( + // 1.62 USD per SCRT + Uint128::new( 1_622_110_000_000_000_000), + // 1 sSCRT -> sETH + Uint128::new( 1_413_500_852_332_497), + // sETH/USD price + Uint128::new(1_147_583_319_333_175_746_166), + ), + translate_1: ( + // 1.62 USD per SCRT + Uint128::new( 1_622_110_000_000_000_000), + // .000425 ETH per sSCRT + Uint128::new( 425_600_000_000_000), + // 3811.34 ETH per USD + Uint128::new(3_811_348_684_210_526_315_789), + ), + translate_2: ( + // 1 USD per scrt + Uint128::new( 1_000_000_000_000_000_000), + // 1 sscrt for .1 SHD + Uint128::new( 100_000_000_000_000_000), + // 10 SHD per USD + Uint128::new(10_000_000_000_000_000_000), + ), + translate_3: ( + // 1 USD per scrt + Uint128::new( 1_000_000_000_000_000_000), + // 1 sscrt for .02 SHD + Uint128::new( 20_000_000_000_000_000), + // 50 SHD per USD + Uint128::new(50_000_000_000_000_000_000), + ), + } +} diff --git a/packages/shade_protocol/src/utils/storage/default.rs b/packages/shade_protocol/src/utils/storage/default.rs new file mode 100644 index 0000000..2d75ede --- /dev/null +++ b/packages/shade_protocol/src/utils/storage/default.rs @@ -0,0 +1,113 @@ +use crate::c_std::{StdResult, Storage}; +use crate::storage::{ + bucket, + bucket_read, + singleton, + singleton_read, + Bucket, + ReadonlyBucket, + ReadonlySingleton, + Singleton, +}; +use crate::serde::{de::DeserializeOwned, Serialize}; + +pub trait NaiveSingletonStorage: Serialize + DeserializeOwned { + fn read<'a>(storage: &'a dyn Storage, namespace: &'a [u8]) -> ReadonlySingleton<'a, Self> { + singleton_read(storage, namespace) + } + + fn load<'a>(storage: &'a dyn Storage, namespace: &'a [u8]) -> StdResult { + Self::read(storage, namespace).load() + } + + fn may_load<'a>(storage: &'a dyn Storage, namespace: &'a [u8]) -> StdResult> { + Self::read(storage, namespace).may_load() + } + + fn write<'a>(storage: &'a mut dyn Storage, namespace: &'a [u8]) -> Singleton<'a, Self> { + singleton(storage, namespace) + } + + fn save<'a>(&self, storage: &mut dyn Storage, namespace: &'a [u8]) -> StdResult<()> { + Self::write(storage, namespace).save(self) + } +} + +pub trait SingletonStorage: Serialize + DeserializeOwned { + const NAMESPACE: &'static [u8]; + + fn read(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, Self::NAMESPACE) + } + + fn load(storage: &dyn Storage) -> StdResult { + Self::read(storage).load() + } + + fn may_load(storage: &dyn Storage) -> StdResult> { + Self::read(storage).may_load() + } + + fn write(storage: &mut dyn Storage) -> Singleton { + singleton(storage, Self::NAMESPACE) + } + + fn save(&self, storage: &mut dyn Storage) -> StdResult<()> { + Self::write(storage).save(self) + } +} + +pub trait NaiveBucketStorage: Serialize + DeserializeOwned { + fn read<'a>(storage: &'a dyn Storage, namespace: &'a [u8]) -> ReadonlyBucket<'a, Self> { + bucket_read(storage, namespace) + } + + fn load<'a>(storage: &'a dyn Storage, namespace: &'a [u8], key: &'a [u8]) -> StdResult { + Self::read(storage, namespace).load(key) + } + + fn may_load<'a>( + storage: &'a dyn Storage, + namespace: &'a [u8], + key: &'a [u8], + ) -> StdResult> { + Self::read(storage, namespace).may_load(key) + } + + fn write<'a>(storage: &'a mut dyn Storage, namespace: &'a [u8]) -> Bucket<'a, Self> { + bucket(storage, namespace) + } + + fn save<'a>( + &self, + storage: &mut dyn Storage, + namespace: &'a [u8], + key: &'a [u8], + ) -> StdResult<()> { + Self::write(storage, namespace).save(key, self) + } +} + +pub trait BucketStorage: Serialize + DeserializeOwned { + const NAMESPACE: &'static [u8]; + + fn read(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, Self::NAMESPACE) + } + + fn load(storage: &dyn Storage, key: &[u8]) -> StdResult { + Self::read(storage).load(key) + } + + fn may_load(storage: &dyn Storage, key: &[u8]) -> StdResult> { + Self::read(storage).may_load(key) + } + + fn write(storage: &mut dyn Storage) -> Bucket { + bucket(storage, Self::NAMESPACE) + } + + fn save(&self, storage: &mut dyn Storage, key: &[u8]) -> StdResult<()> { + Self::write(storage).save(key, self) + } +} diff --git a/packages/shade_protocol/src/utils/storage/mod.rs b/packages/shade_protocol/src/utils/storage/mod.rs new file mode 100644 index 0000000..adb15ed --- /dev/null +++ b/packages/shade_protocol/src/utils/storage/mod.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "storage_plus")] +pub mod plus; + +#[cfg(feature = "storage")] +pub mod default; diff --git a/packages/shade_protocol/src/utils/storage/plus/iter_item.rs b/packages/shade_protocol/src/utils/storage/plus/iter_item.rs new file mode 100644 index 0000000..42675b7 --- /dev/null +++ b/packages/shade_protocol/src/utils/storage/plus/iter_item.rs @@ -0,0 +1,329 @@ +use crate::utils::storage::plus::iter_map::{Increment, IterKey}; +use cosmwasm_std::{StdError, StdResult, Storage}; +use secret_storage_plus::{Item, Map}; +use serde::{de::DeserializeOwned, Serialize}; +use std::ops::{Add, AddAssign, Sub}; + +pub struct IterItem<'a, T, N> +where + T: Serialize + DeserializeOwned, + N: Add + + AddAssign + + Increment + + Sub + + Serialize + + DeserializeOwned + + Clone, +{ + storage: Map<'a, Vec, T>, + id_storage: Item<'a, IterKey>, +} + +#[macro_export] +macro_rules! new_iter_item { + ($StoragePath:tt) => { + IterItem::new_override( + $StoragePath, + concat!("iter-item-size-namespace-", $StoragePath), + ) + }; +} + +impl<'a, T, N> IterItem<'a, T, N> +where + T: Serialize + DeserializeOwned, + N: Add + + AddAssign + + Increment + + Sub + + Serialize + + DeserializeOwned + + Clone, +{ + pub const fn new_override(namespace: &'a str, size_namespace: &'a str) -> Self { + IterItem { + storage: Map::new(namespace), + id_storage: Item::new(size_namespace), + } + } +} + +impl<'a, T, N> IterItem<'a, T, N> +where + T: Serialize + DeserializeOwned, + N: Add + + AddAssign + + Increment + + Sub + + Serialize + + DeserializeOwned + + Clone, +{ + pub fn set(&self, store: &mut dyn Storage, id: N, data: &T) -> StdResult<()> { + self.storage.save(store, IterKey::new(id).to_bytes()?, data) + } + + pub fn get(&self, store: &dyn Storage, id: N) -> StdResult { + self.storage.load(store, IterKey::new(id).to_bytes()?) + } + + pub fn push(&self, store: &mut dyn Storage, data: &T) -> StdResult { + let id = IterKey::new(match self.id_storage.may_load(store)? { + None => N::zero(), + Some(id) => id.item + N::one(), + }); + + self.storage.save(store, id.to_bytes()?, data)?; + + self.id_storage.save(store, &id)?; + + Ok(id.item) + } + + pub fn remove(&self, store: &mut dyn Storage) -> StdResult<()> { + let id = match self.id_storage.may_load(store)? { + None => return Err(StdError::generic_err("Iter map is empty")), + Some(id) => id, + }; + + self.storage.remove(store, id.to_bytes()?); + + let new_id = IterKey::new(id.item - N::one()); + self.id_storage.save(store, &new_id)?; + + Ok(()) + } + + pub fn pop(&self, store: &mut dyn Storage) -> StdResult { + let id = match self.id_storage.may_load(store)? { + None => return Err(StdError::generic_err("Iter map is empty")), + Some(id) => id, + }; + + let item = self.storage.load(store, id.to_bytes()?)?; + self.storage.remove(store, id.to_bytes()?); + + let new_id = IterKey::new(id.item - N::one()); + self.id_storage.save(store, &new_id)?; + + Ok(item) + } + + pub fn size(&'a self, store: &dyn Storage) -> StdResult { + Ok(match self.id_storage.may_load(store)? { + None => N::zero(), + Some(i) => i.item + N::one(), + }) + } + + pub fn iter_from( + &'a self, + store: &'a dyn Storage, + start_from: N, + ) -> IndexableIterItem<'a, T, N> { + IndexableIterItem { + iter_map: self, + storage: store, + index: start_from, + } + } + + pub fn iter(&'a self, store: &'a dyn Storage) -> IndexableIterItem<'a, T, N> { + self.iter_from(store, N::zero()) + } +} + +// Make struct IterMapIndexable and implement the cool stuff there +pub struct IndexableIterItem<'a, T, N> +where + T: Serialize + DeserializeOwned, + N: Add + + AddAssign + + Increment + + Sub + + Serialize + + DeserializeOwned + + Clone, +{ + iter_map: &'a IterItem<'a, T, N>, + storage: &'a dyn Storage, + index: N, +} + +impl<'a, T, N> IndexableIterItem<'a, T, N> +where + T: Serialize + DeserializeOwned, + N: Add + + AddAssign + + Increment + + Sub + + Serialize + + DeserializeOwned + + Clone, +{ + fn next_index(&mut self) { + self.index += N::one(); + } +} + +impl<'a, T, N> Iterator for IndexableIterItem<'a, T, N> +where + T: Serialize + DeserializeOwned, + N: Add + + AddAssign + + Increment + + Sub + + Serialize + + DeserializeOwned + + Clone, +{ + type Item = T; + + fn next(&mut self) -> Option { + let item = self.iter_map.get(self.storage.clone(), self.index.clone()); + + self.next_index(); + + match item { + Ok(i) => Some(i), + Err(_) => None, + } + } +} + +#[cfg(test)] +mod tests { + use crate::utils::storage::plus::iter_item::IterItem; + use cosmwasm_std::{ + testing::{MockApi, MockQuerier, MockStorage}, + Addr, + CustomQuery, + OwnedDeps, + Storage, + Uint64, + }; + use serde::{ + de::{self, DeserializeOwned}, + ser, + Deserialize, + Serialize, + }; + use std::marker::PhantomData; + + #[derive(Clone, Serialize, Deserialize)] + struct MyQuery; + impl CustomQuery for MyQuery {} + + const MACRO_TEST: IterItem = new_iter_item!("MACRO_TEST"); + + #[test] + fn initialization() { + let mut storage = MockStorage::new(); + + let iter: IterItem = IterItem::new_override("TEST", "SIZE-TEST"); + } + + fn generate(size: u8, storage: &mut dyn Storage) -> IterItem { + let iter: IterItem = IterItem::new_override("TEST", "SIZE-TEST"); + + for i in 0..size { + iter.push(storage, &Uint64::new(i as u64)).unwrap(); + } + + iter + } + + #[test] + fn push() { + let mut storage = MockStorage::new(); + + generate(10, &mut storage); + } + + #[test] + fn remove() { + let mut storage = MockStorage::new(); + + let iter: IterItem = IterItem::new_override("TEST", "SIZE-TEST"); + + for i in 0..10 { + iter.push(&mut storage, &Uint64::new(i as u64)).unwrap(); + } + + let item = iter.remove(&mut storage).unwrap(); + + assert_eq!(9, iter.size(&storage).unwrap()); + } + + #[test] + fn pop() { + let mut storage = MockStorage::new(); + + let iter: IterItem = IterItem::new_override("TEST", "SIZE-TEST"); + + for i in 0..10 { + iter.push(&mut storage, &Uint64::new(i as u64)).unwrap(); + } + + let item = iter.pop(&mut storage).unwrap(); + + assert_eq!(item, Uint64::new(9)); + assert_eq!(9, iter.size(&storage).unwrap()); + } + + #[test] + fn set() { + let mut storage = MockStorage::new(); + + let iter: IterItem = IterItem::new_override("TEST", "SIZE-TEST"); + + for i in 0..10 { + iter.push(&mut storage, &Uint64::new(i as u64)).unwrap(); + } + + iter.set(&mut storage, 3, &Uint64::new(5)).unwrap(); + + assert_eq!(Uint64::new(5), iter.get(&storage, 3).unwrap()) + } + + #[test] + fn get() { + let mut storage = MockStorage::new(); + + let iter: IterItem = IterItem::new_override("TEST", "SIZE-TEST"); + + for i in 0..10 { + iter.push(&mut storage, &Uint64::new(i as u64)).unwrap(); + } + + assert_eq!(Uint64::new(3), iter.get(&storage, 3).unwrap()) + } + + #[test] + fn total() { + let mut storage = MockStorage::new(); + + let iter: IterItem = IterItem::new_override("TEST", "SIZE-TEST"); + + for i in 0..10 { + iter.push(&mut storage, &Uint64::new(i as u64)).unwrap(); + } + + assert_eq!(10, iter.size(&storage).unwrap()) + } + + #[test] + fn iterate() { + let mut storage = MockStorage::new(); + + let iter: IterItem = IterItem::new_override("TEST", "SIZE-TEST"); + + for i in 0..10 { + iter.push(&mut storage, &Uint64::new(i as u64)).unwrap(); + } + + for (i, item) in iter.iter(&storage).enumerate() { + assert_eq!(item, Uint64::new(i as u64)) + } + } +} diff --git a/packages/shade_protocol/src/utils/storage/plus/iter_map.rs b/packages/shade_protocol/src/utils/storage/plus/iter_map.rs new file mode 100644 index 0000000..4ba9172 --- /dev/null +++ b/packages/shade_protocol/src/utils/storage/plus/iter_map.rs @@ -0,0 +1,389 @@ +use cosmwasm_std::{to_binary, StdError, StdResult, Storage}; +use secret_storage_plus::{KeyDeserialize, Map, Prefixer, PrimaryKey}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::ops::{Add, AddAssign, Sub}; + +pub trait Increment { + fn one() -> Self; + fn zero() -> Self; +} + +macro_rules! impl_increment { + ($($t:ty)*) => ($( + impl Increment for $t { + fn one() -> Self { + 1 + } + + fn zero() -> Self { + 0 + } + } + )*) +} + +impl_increment! { usize u8 u16 u32 u64 u128 } + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct IterKey< + N: Add + AddAssign + Sub + Clone + Serialize + Increment, +> { + pub item: N, +} + +impl IterKey +where + N: Add + Increment + AddAssign + Sub + Clone + Serialize, +{ + pub fn new(item: N) -> Self { + Self { item } + } + + pub fn to_bytes(&self) -> StdResult> { + Ok(to_binary(self)?.0) + } +} + +pub struct IterMap<'a, K, T, N> +where + T: Serialize + DeserializeOwned, + K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize, + N: Add + + AddAssign + + Increment + + Sub + + Serialize + + DeserializeOwned + + Clone, +{ + storage: Map<'a, (K, Vec), T>, + id_storage: Map<'a, K, IterKey>, +} + +#[macro_export] +macro_rules! new_iter_map { + ($StoragePath:tt) => { + IterMap::new_override( + $StoragePath, + concat!("iter-map-size-namespace-", $StoragePath), + ) + }; +} + +impl<'a, K, T, N> IterMap<'a, K, T, N> +where + T: Serialize + DeserializeOwned, + K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize, + N: Add + + AddAssign + + Increment + + Sub + + Serialize + + DeserializeOwned + + Clone, +{ + pub const fn new_override(namespace: &'a str, size_namespace: &'a str) -> Self { + IterMap { + storage: Map::new(namespace), + id_storage: Map::new(size_namespace), + } + } +} + +impl<'a, K, T, N> IterMap<'a, K, T, N> +where + T: Serialize + DeserializeOwned, + K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize, + N: Add + + AddAssign + + Increment + + Sub + + Serialize + + DeserializeOwned + + Clone, +{ + pub fn set(&self, store: &mut dyn Storage, key: K, id: N, data: &T) -> StdResult<()> { + self.storage + .save(store, (key, IterKey::new(id).to_bytes()?), data) + } + + pub fn get(&self, store: &dyn Storage, key: K, id: N) -> StdResult { + self.storage + .load(store, (key, IterKey::new(id).to_bytes()?)) + } + + pub fn push(&self, store: &mut dyn Storage, key: K, data: &T) -> StdResult { + let id = IterKey::new(match self.id_storage.may_load(store, key.clone())? { + None => N::zero(), + Some(id) => id.item + N::one(), + }); + + self.storage + .save(store, (key.clone(), id.to_bytes()?), data)?; + + self.id_storage.save(store, key, &id)?; + + Ok(id.item) + } + + pub fn remove(&self, store: &mut dyn Storage, key: K) -> StdResult<()> { + let id = match self.id_storage.may_load(store, key.clone())? { + None => return Err(StdError::generic_err("Iter map is empty")), + Some(id) => id, + }; + + self.storage.remove(store, (key.clone(), id.to_bytes()?)); + + let new_id = IterKey::new(id.item - N::one()); + self.id_storage.save(store, key, &new_id)?; + + Ok(()) + } + + pub fn pop(&self, store: &mut dyn Storage, key: K) -> StdResult { + let id = match self.id_storage.may_load(store, key.clone())? { + None => return Err(StdError::generic_err("Iter map is empty")), + Some(id) => id, + }; + + let item = self.storage.load(store, (key.clone(), id.to_bytes()?))?; + self.storage.remove(store, (key.clone(), id.to_bytes()?)); + + let new_id = IterKey::new(id.item - N::one()); + self.id_storage.save(store, key, &new_id)?; + + Ok(item) + } + + pub fn size(&'a self, store: &dyn Storage, key: K) -> StdResult { + Ok(match self.id_storage.may_load(store, key)? { + None => N::zero(), + Some(i) => i.item + N::one(), + }) + } + + pub fn iter_from( + &'a self, + store: &'a dyn Storage, + key: K, + start_from: N, + ) -> IndexableIterMap<'a, K, T, N> { + IndexableIterMap { + iter_map: self, + storage: store, + key: key.clone(), + index: start_from, + } + } + + pub fn iter(&'a self, store: &'a dyn Storage, key: K) -> IndexableIterMap<'a, K, T, N> { + self.iter_from(store, key, N::zero()) + } +} + +pub struct IndexableIterMap<'a, K, T, N> +where + T: Serialize + DeserializeOwned, + K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize, + N: Add + + AddAssign + + Increment + + Sub + + Serialize + + DeserializeOwned + + Clone, +{ + iter_map: &'a IterMap<'a, K, T, N>, + storage: &'a dyn Storage, + key: K, + index: N, +} + +impl<'a, K, T, N> IndexableIterMap<'a, K, T, N> +where + T: Serialize + DeserializeOwned, + K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize, + N: Add + + AddAssign + + Increment + + Sub + + Serialize + + DeserializeOwned + + Clone, +{ + fn next_index(&mut self) { + self.index += N::one(); + } +} + +impl<'a, K, T, N> Iterator for IndexableIterMap<'a, K, T, N> +where + T: Serialize + DeserializeOwned, + K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize, + N: Add + + AddAssign + + Increment + + Sub + + Serialize + + DeserializeOwned + + Clone, +{ + type Item = T; + + fn next(&mut self) -> Option { + let item = self + .iter_map + .get(self.storage.clone(), self.key.clone(), self.index.clone()); + + self.next_index(); + + match item { + Ok(i) => Some(i), + Err(_) => None, + } + } +} + +#[cfg(test)] +mod tests { + use crate::utils::storage::plus::iter_map::IterMap; + use cosmwasm_std::{ + testing::{MockApi, MockQuerier, MockStorage}, + Addr, + CustomQuery, + OwnedDeps, + Storage, + Uint64, + }; + use serde::{ + de::{self, DeserializeOwned}, + ser, + Deserialize, + Serialize, + }; + use std::marker::PhantomData; + + #[derive(Clone, Serialize, Deserialize)] + struct MyQuery; + impl CustomQuery for MyQuery {} + + const MACRO_TEST: IterMap<(Addr), Uint64, u64> = new_iter_map!("MACRO_TEST"); + + #[test] + fn initialization() { + let mut storage = MockStorage::new(); + + let iter: IterMap<(Addr), Uint64, u64> = IterMap::new_override("TEST", "SIZE-TEST"); + } + + fn generate(size: u8, storage: &mut dyn Storage) -> IterMap<(String), Uint64, u64> { + let iter: IterMap<(String), Uint64, u64> = IterMap::new_override("TEST", "SIZE-TEST"); + + for i in 0..size { + iter.push(storage, "TESTING".to_string(), &Uint64::new(i as u64)) + .unwrap(); + } + + iter + } + + #[test] + fn push() { + let mut storage = MockStorage::new(); + + let iter: IterMap = IterMap::new_override("TEST", "SIZE-TEST"); + + for i in 0..10 { + iter.push(&mut storage, "TESTING".to_string(), &Uint64::new(i as u64)) + .unwrap(); + } + + let item = iter.pop(&mut storage, "TESTING".to_string()).unwrap(); + + assert_eq!(item, Uint64::new(9)); + assert_eq!(9, iter.size(&storage, "TESTING".to_string()).unwrap()); + } + + #[test] + fn remove() { + let mut storage = MockStorage::new(); + + let iter: IterMap = IterMap::new_override("TEST", "SIZE-TEST"); + + for i in 0..10 { + iter.push(&mut storage, "TESTING".to_string(), &Uint64::new(i as u64)) + .unwrap(); + } + + iter.remove(&mut storage, "TESTING".to_string()).unwrap(); + + assert_eq!(9, iter.size(&storage, "TESTING".to_string()).unwrap()); + } + + #[test] + fn set() { + let mut storage = MockStorage::new(); + + let iter: IterMap = IterMap::new_override("TEST", "SIZE-TEST"); + + for i in 0..10 { + iter.push(&mut storage, "TESTING".to_string(), &Uint64::new(i as u64)) + .unwrap(); + } + + iter.set(&mut storage, "TESTING".to_string(), 3, &Uint64::new(5)) + .unwrap(); + + assert_eq!( + Uint64::new(5), + iter.get(&storage, "TESTING".to_string(), 3).unwrap() + ) + } + + #[test] + fn get() { + let mut storage = MockStorage::new(); + + let iter: IterMap = IterMap::new_override("TEST", "SIZE-TEST"); + + for i in 0..10 { + iter.push(&mut storage, "TESTING".to_string(), &Uint64::new(i as u64)) + .unwrap(); + } + + assert_eq!( + Uint64::new(3), + iter.get(&storage, "TESTING".to_string(), 3).unwrap() + ) + } + + #[test] + fn total() { + let mut storage = MockStorage::new(); + + let iter: IterMap = IterMap::new_override("TEST", "SIZE-TEST"); + + for i in 0..10 { + iter.push(&mut storage, "TESTING".to_string(), &Uint64::new(i as u64)) + .unwrap(); + } + + assert_eq!(10, iter.size(&storage, "TESTING".to_string()).unwrap()) + } + + #[test] + fn iterate() { + let mut storage = MockStorage::new(); + + let iter: IterMap = IterMap::new_override("TEST", "SIZE-TEST"); + + for i in 0..10 { + iter.push(&mut storage, "TESTING".to_string(), &Uint64::new(i as u64)) + .unwrap(); + } + + for (i, item) in iter.iter(&storage, "TESTING".to_string()).enumerate() { + assert_eq!(item, Uint64::new(i as u64)) + } + } +} diff --git a/packages/shade_protocol/src/utils/storage/plus/mod.rs b/packages/shade_protocol/src/utils/storage/plus/mod.rs new file mode 100644 index 0000000..51d2dc0 --- /dev/null +++ b/packages/shade_protocol/src/utils/storage/plus/mod.rs @@ -0,0 +1,208 @@ +pub mod iter_item; +pub mod iter_map; +pub mod period_storage; + +use crate::{ + c_std::{StdError, StdResult, Storage}, + serde::{de::DeserializeOwned, Serialize}, +}; + +pub use secret_storage_plus::{Bincode2, Item, Json, Map, PrimaryKey, Serde}; + +pub trait NaiveItemStorage: Serialize + DeserializeOwned +where + Ser: Serde, +{ + fn load(storage: &dyn Storage, item: Item) -> StdResult { + item.load(storage) + } + + fn may_load(storage: &dyn Storage, item: Item) -> StdResult> { + item.may_load(storage) + } + + fn remove(storage: &mut dyn Storage, item: Item) { + item.remove(storage) + } + + fn save(&self, storage: &mut dyn Storage, item: Item) -> StdResult<()> { + item.save(storage, self) + } + + fn update( + &self, + storage: &mut dyn Storage, + item: Item, + action: A, + ) -> Result + where + A: FnOnce(Self) -> Result, + E: From, + { + item.update(storage, action) + } +} + +pub trait ItemStorage: Serialize + DeserializeOwned +where + Ser: Serde, +{ + const ITEM: Item<'static, Self, Ser>; + + fn load(storage: &dyn Storage) -> StdResult { + Self::ITEM.load(storage) + } + + fn may_load(storage: &dyn Storage) -> StdResult> { + Self::ITEM.may_load(storage) + } + + fn remove(storage: &mut dyn Storage) { + Self::ITEM.remove(storage) + } + + fn save(&self, storage: &mut dyn Storage) -> StdResult<()> { + Self::ITEM.save(storage, self) + } + + fn update(&self, storage: &mut dyn Storage, action: A) -> Result + where + A: FnOnce(Self) -> Result, + E: From, + { + Self::ITEM.update(storage, action) + } +} + +pub trait GenericItemStorage +where + Ser: Serde, +{ + const ITEM: Item<'static, T, Ser>; + + fn load(storage: &dyn Storage) -> StdResult { + Self::ITEM.load(storage) + } + + fn may_load(storage: &dyn Storage) -> StdResult> { + Self::ITEM.may_load(storage) + } + + fn save(storage: &mut dyn Storage, item: &T) -> StdResult<()> { + Self::ITEM.save(storage, item) + } + + fn update(storage: &mut dyn Storage, action: A) -> Result + where + A: FnOnce(T) -> Result, + E: From, + { + Self::ITEM.update(storage, action) + } +} + +pub trait NaiveMapStorage<'a, Ser = Json>: Serialize + DeserializeOwned +where + Ser: Serde, +{ + fn load>( + storage: &dyn Storage, + map: Map<'a, K, Self, Ser>, + key: K, + ) -> StdResult { + map.load(storage, key) + } + + fn may_load>( + storage: &dyn Storage, + map: Map<'a, K, Self, Ser>, + key: K, + ) -> StdResult> { + map.may_load(storage, key) + } + + fn remove>(storage: &mut dyn Storage, map: Map<'a, K, Self, Ser>, key: K) { + map.remove(storage, key) + } + + fn save>( + &self, + storage: &mut dyn Storage, + map: Map<'a, K, Self, Ser>, + key: K, + ) -> StdResult<()> { + map.save(storage, key, self) + } + + fn update>( + &self, + storage: &mut dyn Storage, + map: Map<'a, K, Self, Ser>, + key: K, + action: A, + ) -> Result + where + A: FnOnce(Option) -> Result, + E: From, + { + map.update(storage, key, action) + } +} + +pub trait MapStorage<'a, K: PrimaryKey<'a>, Ser = Json>: Serialize + DeserializeOwned +where + Ser: Serde, +{ + const MAP: Map<'static, K, Self, Ser>; + + fn load(storage: &dyn Storage, key: K) -> StdResult { + Self::MAP.load(storage, key) + } + + fn may_load(storage: &dyn Storage, key: K) -> StdResult> { + Self::MAP.may_load(storage, key) + } + + fn remove(storage: &mut dyn Storage, key: K) { + Self::MAP.remove(storage, key) + } + + fn save(&self, storage: &mut dyn Storage, key: K) -> StdResult<()> { + Self::MAP.save(storage, key, self) + } + + fn update(&self, storage: &mut dyn Storage, key: K, action: A) -> Result + where + A: FnOnce(Option) -> Result, + E: From, + { + Self::MAP.update(storage, key, action) + } +} + +pub trait GenericMapStorage<'a, K: PrimaryKey<'a>, T: Serialize + DeserializeOwned, Ser = Json> +where + Ser: Serde, +{ + const MAP: Map<'static, K, T, Ser>; + + fn load(storage: &dyn Storage, key: K) -> StdResult { + Self::MAP.load(storage, key) + } + + fn may_load(storage: &dyn Storage, key: K) -> StdResult> { + Self::MAP.may_load(storage, key) + } + + fn save(storage: &mut dyn Storage, key: K, item: &T) -> StdResult<()> { + Self::MAP.save(storage, key, item) + } + + fn update(&self, storage: &mut dyn Storage, key: K, action: A) -> Result + where + A: FnOnce(Option) -> Result, + E: From, + { + Self::MAP.update(storage, key, action) + } +} diff --git a/packages/shade_protocol/src/utils/storage/plus/period_storage.rs b/packages/shade_protocol/src/utils/storage/plus/period_storage.rs new file mode 100644 index 0000000..025b778 --- /dev/null +++ b/packages/shade_protocol/src/utils/storage/plus/period_storage.rs @@ -0,0 +1,359 @@ +use crate::{ + c_std::{StdResult, Storage, Timestamp}, + cosmwasm_schema::cw_serde, + serde::{de::DeserializeOwned, Serialize}, + utils::cycle::*, +}; +pub use secret_storage_plus::{Item, Json, Map, PrimaryKey, Serde}; + + +use strum::IntoEnumIterator; +use strum_macros::EnumIter; + +#[cw_serde] +#[derive(EnumIter)] +pub enum Period { + Hour, + Day, + Month, +} + +pub fn map_key(seconds: u64, period: Period) -> String { + let datetime = utc_from_seconds(seconds as i64); + match period { + Period::Hour => datetime.format("%Y-%m-%dT%H").to_string(), + Period::Day => datetime.format("%Y-%m-%d").to_string(), + Period::Month => datetime.format("%Y-%m").to_string(), + } +} + +pub struct PeriodStorage<'a, T, Ser = Json> +where + T: Serialize + DeserializeOwned + Clone, + Ser: Serde, +{ + all: Map<'a, u64, Vec, Ser>, + recent: Item<'a, Vec>, + + /* keys are date formatted strings "%Y-%m-%dT%h" + * right-most data is truncated to categorize by higher order + * e.g. month format is "%Y-%m" + */ + timed: Map<'a, String, Vec, Ser>, +} + +impl<'a, T, Ser> PeriodStorage<'a, T, Ser> +where + T: Serialize + DeserializeOwned + Clone, + Ser: Serde, +{ + pub const fn new(all: &'a str, recent: &'a str, timed: &'a str) -> Self { + PeriodStorage { + all: Map::new(all), + recent: Item::new(recent), + timed: Map::new(timed), + } + } + + pub fn load(&self, storage: &dyn Storage, ts: Timestamp) -> StdResult> { + self.all.load(storage, ts.seconds()) + } + + pub fn load_period( + &self, + storage: &dyn Storage, + seconds: u64, + period: Period, + ) -> StdResult> { + Ok(self + .timed + .load(storage, map_key(seconds, period)) + .unwrap_or(vec![])) + } + + pub fn may_load(&self, storage: &dyn Storage, ts: Timestamp) -> StdResult> { + Ok(self.all.may_load(storage, ts.seconds())?.unwrap_or(vec![])) + } + + pub fn push(&self, storage: &mut dyn Storage, ts: Timestamp, item: T) -> StdResult<()> { + let key = ts.seconds(); + let mut recent = self.recent.may_load(storage)?.unwrap_or(vec![]); + if !recent.contains(&key) { + recent.push(key); + self.recent.save(storage, &recent)?; + } + let mut all = self.all.may_load(storage, key)?.unwrap_or(vec![]); + all.push(item); + self.all.save(storage, key, &all)?; + self.flush(storage) + } + + pub fn append( + &self, + storage: &mut dyn Storage, + ts: Timestamp, + items: &mut Vec, + ) -> StdResult<()> { + let key = ts.seconds(); + let mut recent = self.recent.may_load(storage)?.unwrap_or(vec![]); + if !recent.contains(&key) { + recent.push(key); + self.recent.save(storage, &recent)?; + } + let mut all = self.all.may_load(storage, key)?.unwrap_or(vec![]); + all.append(items); + self.all.save(storage, key, &all)?; + + self.flush(storage) + } + + /* This will move all "recents" into the time based storage + * This should likely be called at the end of execution that adds items + */ + fn flush(&self, storage: &mut dyn Storage) -> StdResult<()> { + for seconds in self.recent.load(storage)? { + let mut items = self.all.load(storage, seconds)?; + + for period in Period::iter() { + let k = map_key(seconds, period); + let mut cur_items = self.timed.may_load(storage, k.clone())?.unwrap_or(vec![]); + cur_items.append(&mut items.clone()); + self.timed.save(storage, k, &cur_items)?; + } + } + self.recent.save(storage, &vec![]) + } +} + +#[cfg(test)] +mod test { + + use super::*; + use crate::c_std::{MemoryStorage, Timestamp, Uint128}; + + fn test_push(now: String) { + let now = parse_utc_datetime(&"1995-11-13T00:00:00.00Z".to_string()).unwrap(); + let mut storage = MemoryStorage::new(); + pub const STORAGE: PeriodStorage = PeriodStorage::new("all", "recent", "timed"); + + let data = vec![1, 2, 3, 5, 10]; + + for d in data.clone() { + STORAGE + .push( + &mut storage, + Timestamp::from_seconds(now.timestamp() as u64), + d, + ) + .unwrap(); + } + assert_eq!( + STORAGE + .load_period(&storage, now.timestamp() as u64, Period::Hour) + .unwrap(), + data + ); + assert_eq!( + STORAGE + .load_period(&storage, now.timestamp() as u64, Period::Day) + .unwrap(), + data + ); + assert_eq!( + STORAGE + .load_period(&storage, now.timestamp() as u64, Period::Month) + .unwrap(), + data + ); + } + + fn test_append(now: String) { + let now = parse_utc_datetime(&"1995-11-13T00:00:00.00Z".to_string()).unwrap(); + let mut storage = MemoryStorage::new(); + pub const STORAGE: PeriodStorage = PeriodStorage::new("all", "recent", "timed"); + + let mut data = vec![1, 2, 3, 5, 10]; + + STORAGE + .append( + &mut storage, + Timestamp::from_seconds(now.timestamp() as u64), + &mut data.clone(), + ) + .unwrap(); + assert_eq!( + STORAGE + .load_period(&storage, now.timestamp() as u64, Period::Hour) + .unwrap(), + data + ); + assert_eq!( + STORAGE + .load_period(&storage, now.timestamp() as u64, Period::Day) + .unwrap(), + data + ); + assert_eq!( + STORAGE + .load_period(&storage, now.timestamp() as u64, Period::Month) + .unwrap(), + data + ); + } + + fn test_hour_timed(now: String) { + let mut now = parse_utc_datetime(&"1995-11-13T00:00:00.00Z".to_string()).unwrap(); + + let mut storage = MemoryStorage::new(); + pub const STORAGE: PeriodStorage = PeriodStorage::new("all", "recent", "timed"); + + let mut data = vec![1, 2, 3, 5, 10]; + let mut added = vec![11, 12, 13, 15, 20]; + + STORAGE + .append( + &mut storage, + Timestamp::from_seconds(now.timestamp() as u64), + &mut data, + ) + .unwrap(); + assert_eq!( + STORAGE + .load_period(&storage, now.timestamp() as u64, Period::Hour) + .unwrap(), + data + ); + + let now = parse_utc_datetime(&"1995-11-13T01:00:00.00Z".to_string()).unwrap(); + assert!( + STORAGE + .load_period(&storage, now.timestamp() as u64, Period::Hour) + .unwrap() + .is_empty(), + ); + + STORAGE + .append( + &mut storage, + Timestamp::from_seconds(now.timestamp() as u64), + &mut added, + ) + .unwrap(); + assert_eq!( + STORAGE + .load_period(&storage, now.timestamp() as u64, Period::Hour) + .unwrap(), + added + ); + + let mut all_data = data; + all_data.append(&mut added); + assert_eq!( + STORAGE + .load_period(&storage, now.timestamp() as u64, Period::Day) + .unwrap(), + all_data + ); + } + + fn test_day_timed(now: String) { + let mut now = parse_utc_datetime(&"1995-11-13T00:00:00.00Z".to_string()).unwrap(); + + let mut storage = MemoryStorage::new(); + pub const STORAGE: PeriodStorage = PeriodStorage::new("all", "recent", "timed"); + + let mut data = vec![1, 2, 3, 5, 10]; + let mut added = vec![11, 12, 13, 15, 20]; + + STORAGE + .append( + &mut storage, + Timestamp::from_seconds(now.timestamp() as u64), + &mut data, + ) + .unwrap(); + assert_eq!( + STORAGE + .load_period(&storage, now.timestamp() as u64, Period::Day) + .unwrap(), + data + ); + + let now = parse_utc_datetime(&"1995-11-14T00:00:00.00Z".to_string()).unwrap(); + assert!( + STORAGE + .load_period(&storage, now.timestamp() as u64, Period::Day) + .unwrap() + .is_empty(), + ); + + STORAGE + .append( + &mut storage, + Timestamp::from_seconds(now.timestamp() as u64), + &mut added, + ) + .unwrap(); + assert_eq!( + STORAGE + .load_period(&storage, now.timestamp() as u64, Period::Day) + .unwrap(), + added + ); + + let mut all_data = data; + all_data.append(&mut added); + assert_eq!( + STORAGE + .load_period(&storage, now.timestamp() as u64, Period::Month) + .unwrap(), + all_data + ); + } + + fn test_month_timed(now: String) { + let mut now = parse_utc_datetime(&"1995-11-13T00:00:00.00Z".to_string()).unwrap(); + + let mut storage = MemoryStorage::new(); + pub const STORAGE: PeriodStorage = PeriodStorage::new("all", "recent", "timed"); + + let mut data = vec![1, 2, 3, 5, 10]; + let mut added = vec![11, 12, 13, 15, 20]; + + STORAGE + .append( + &mut storage, + Timestamp::from_seconds(now.timestamp() as u64), + &mut data, + ) + .unwrap(); + assert_eq!( + STORAGE + .load_period(&storage, now.timestamp() as u64, Period::Month) + .unwrap(), + data + ); + + let now = parse_utc_datetime(&"1995-12-13T00:00:00.00Z".to_string()).unwrap(); + assert!( + STORAGE + .load_period(&storage, now.timestamp() as u64, Period::Month) + .unwrap() + .is_empty(), + ); + + STORAGE + .append( + &mut storage, + Timestamp::from_seconds(now.timestamp() as u64), + &mut added, + ) + .unwrap(); + assert_eq!( + STORAGE + .load_period(&storage, now.timestamp() as u64, Period::Month) + .unwrap(), + added + ); + } +} diff --git a/packages/shade_protocol/src/utils/wrap.rs b/packages/shade_protocol/src/utils/wrap.rs new file mode 100644 index 0000000..efbe7cb --- /dev/null +++ b/packages/shade_protocol/src/utils/wrap.rs @@ -0,0 +1,35 @@ +use crate::{ + c_std::{Addr, Binary, Coin, CosmosMsg, StdResult, Uint128}, + contract_interfaces::snip20, + snip20::helpers::{deposit_msg, redeem_msg, send_msg}, + utils::{asset::Contract, callback::ExecuteCallback}, +}; + +pub fn wrap(amount: Uint128, token: Contract) -> StdResult { + Ok(deposit_msg(amount, None, &token)?) +} + +pub fn wrap_coin(coin: Coin, token: Contract) -> StdResult { + snip20::ExecuteMsg::Deposit { padding: None }.to_cosmos_msg(&token, vec![coin]) +} + +pub fn wrap_and_send( + amount: Uint128, + recipient: Addr, + token: Contract, + //denom: Option, + msg: Option, +) -> StdResult> { + Ok(vec![ + wrap(amount, token.clone())?, + send_msg(recipient, amount, msg, None, None, &token)?, + ]) +} + +pub fn unwrap( + amount: Uint128, + token: Contract, + //denom: Option, +) -> StdResult { + Ok(redeem_msg(amount, None, None, &token)?) +} diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..4934985 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +1.69.0 diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..de1af2a --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,15 @@ +# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md + +# Stable configurations +edition = "2018" +max_width = 100 +merge_derives = true +use_field_init_shorthand = true +use_try_shorthand = true + +# Nightly configurations +imports_layout = "HorizontalVertical" +imports_granularity = "Crate" +overflow_delimited_expr = true +reorder_impl_items = true +version = "Two" diff --git a/temp-contracts/liability_mint/.cargo/config b/temp-contracts/liability_mint/.cargo/config new file mode 100644 index 0000000..882fe08 --- /dev/null +++ b/temp-contracts/liability_mint/.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/temp-contracts/liability_mint/.circleci/config.yml b/temp-contracts/liability_mint/.circleci/config.yml new file mode 100644 index 0000000..127e1ae --- /dev/null +++ b/temp-contracts/liability_mint/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/temp-contracts/liability_mint/Cargo.toml b/temp-contracts/liability_mint/Cargo.toml new file mode 100644 index 0000000..0a4f1e0 --- /dev/null +++ b/temp-contracts/liability_mint/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "liability_mint" +version = "0.1.0" +authors = [ + "Guy Garcia ", + "Jackson Swenson ", +] +edition = "2018" + +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"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["shade-protocol/backtraces"] +debug-print = ["shade-protocol/debug-print"] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ "mint", "liability_mint", "snip20", "storage_plus", "chrono"] } +schemars = "0.7" +cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" } +cosmwasm-schema = "1.1.5" +shade-oracles = { git = "https://github.com/securesecrets/shade-oracle" } + +[dev-dependencies] +shade-multi-test = { path = "../../packages/multi_test", features = [ + "liability_mint", + "snip20" +] } diff --git a/temp-contracts/liability_mint/Makefile b/temp-contracts/liability_mint/Makefile new file mode 100644 index 0000000..2493c22 --- /dev/null +++ b/temp-contracts/liability_mint/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 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 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# 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/temp-contracts/liability_mint/README.md b/temp-contracts/liability_mint/README.md new file mode 100644 index 0000000..e7d2c66 --- /dev/null +++ b/temp-contracts/liability_mint/README.md @@ -0,0 +1,215 @@ + +# Mint Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Init](#Init) + * [Admin](#Admin) + * Messages + * [UpdateConfig](#UpdateConfig) + * [UpdateMintLimit](#UpdateMintLimit) + * [RegisterAsset](#RegisterAsset) + * [RemoveAsset](#RemoveAsset) + * [User](#User) + * Messages + * [Receive](#Receive) + * Queries + * [GetNativeAsset](#GetNativeAsset) + * [GetConfig](#GetConfig) + * [GetMintLimit](#GetMintLimit) + * [GetSupportedAssets](#GetSupportedAssets) + * [GetAsset](#GetAsset) +# Introduction +Contract responsible to mint a paired snip20 asset + +# Sections + +## Init +##### Request +|Name |Type |Description | optional | +|-----------------|------------|-------------------------------------------------------------------------------|----------| +|admin | string | New contract owner; SHOULD be a valid bech32 address | yes | +|native_asset | Contract | Asset to mint | no | +|oracle | Contract | Oracle contract | no | +|peg | String | Symbol to peg to when querying oracle (defaults to native_asset symbol) | yes | +|treasury | Contract | Treasury contract | yes | +|secondary_burn | Addrr | Where non-burnable assets will go | yes | +|start_epoch | String | The starting epoch | yes | +|epoch_frequency | String | The frequency in which the mint limit resets, if 0 then no limit is enforced | yes | +|epoch_mint_limit | String | The limit of uTokens to mint per epoch | yes | +## Admin + +### Messages +#### UpdateConfig +Updates the given values +##### Request +|Name |Type |Description | optional | +|---------------|------------|-------------------------------------------------------|----------| +|admin | string | New contract admin; SHOULD be a valid bech32 address | yes | +|oracle | Contract | Oracle contract | yes | +|treasury | Contract | Treasury contract | yes | +|secondary_burn | Addrr | Where non-burnable assets will go | yes | +##### Response +```json +{ + "update_config": { + "status": "success" + } +} +``` + +#### UpdateMintLimit +Updates the mint limit and epoch time +##### Request +|Name |Type |Description | optional | +|-----------------|----------|--------------------------------------------------------------------------------|----------| +|start_epoch | String | The starting epoch | yes | +|epoch_frequency | String | The frequency in which the mint limit resets, if 0 then no limit is enforced | yes | +|epoch_mint_limit | String | The limit of uTokens to mint per epoch | yes | +##### Response +```json +{ + "update_mint_limit": { + "status": "success" + } +} +``` + +#### RegisterAsset +Registers a supported asset. The asset must be SNIP-20 compliant since [RegisterReceive](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md#RegisterReceive) is called. + +##### Request +|Name |Type |Description | optional | +|------------|--------|-------------------------------------|----------| +|contract | Contract | Type explained [here](#Contract) | no | +##### Response +```json +{ + "register_asset": { + "status": "success" + } +} +``` + +#### RemoveAsset +Remove a registered asset. +##### Request +|Name |Type |Description | optional | +|------------|--------|--------------------------------|----------| +|address | String | The asset to remove's address | no | +##### Response +```json +{ + "remove_asset": { + "status": "success" + } +} +``` + +##User + +### Messages + +#### Receive +To mint the user must use a supported asset's send function and send the amount over to the contract's address. The contract will take care of the rest. + +In the msg field of a snip20 send command you must send a base64 encoded json like this one +```json +{"minimum_expected_amount": "Uint128" } +``` + +### Queries + +#### GetNativeAsset +Gets the contract's minted asset +#### Response +```json +{ + "native_asset": { + "asset": "Snip20Asset Object", + "peg": "Pegged symbol" + } +} +``` + +#### GetConfig +Gets the contract's configuration variables +##### Response +```json +{ + "config": { + "config": { + "admin": "Owner address", + "oracle": { + "address": "Asset contract address", + "code_hash": "Asset callback code hash" + }, + "treasury": { + "address": "Asset contract address", + "code_hash": "Asset callback code hash" + }, + "secondary_burn": "Optional burn address", + "activated": "Boolean of contract's actviation status" + } + } +} +``` + +#### GetMintLimit +Gets the contract's configuration variables +##### Response +```json +{ + "limit": { + "mint_limit": { + "frequency": "Frequency per epoch reset", + "mint_capacity": "Mint capacity per epoch", + "total_minted": "Total minted in current epoch", + "next_epoch": "Timestamp for the next epoch" + } + } +} +``` + +#### GetSupportedAssets +Get all the contract's supported assets. +##### Response +```json +{ + "supported_assets": { + "assets": ["asset address"] + } +} +``` + +#### GetAsset +Get specific information on a supported asset. +##### Request +|Name |Type |Description | optional | +|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| +|contract | string | Snip20 contract address; SHOULD be a valid bech32 address, but contracts may use a different naming scheme as well | no | +##### Response +```json +{ + "asset": { + "asset": { + "Snip20Asset": { + "contract": "Asset contract", + "token_info": "Token info as per Snip20", + "token_config": "Optional information about the config if the Snip20 supports it" + }, + "burned": "Total burned on this contract" + } + } +} +``` + +## Contract +Type used in many of the admin commands +```json +{ + "config": { + "address": "Asset contract address", + "code_hash": "Asset callback code hash" + } +} +``` \ No newline at end of file diff --git a/temp-contracts/liability_mint/src/contract.rs b/temp-contracts/liability_mint/src/contract.rs new file mode 100644 index 0000000..5910fab --- /dev/null +++ b/temp-contracts/liability_mint/src/contract.rs @@ -0,0 +1,71 @@ +use shade_protocol::c_std::{ + entry_point, to_binary, Api, Binary, Deps, DepsMut, Env, MessageInfo, Querier, Response, + StdResult, Storage, Uint128, +}; +use shade_protocol::snip20::helpers::{fetch_snip20, register_receive, token_config, token_info}; + +use shade_protocol::contract_interfaces::{ + mint::liability_mint::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}, + snip20::helpers::Snip20Asset, +}; + +use crate::{execute, query, storage::*}; + +#[entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let config = Config { + admin: match msg.admin { + None => info.sender.clone(), + Some(admin) => admin, + }, + token: msg.token, + debt_ratio: msg.debt_ratio, + oracle: msg.oracle, + treasury: msg.treasury, + }; + + CONFIG.save(deps.storage, &config)?; + TOKEN.save(deps.storage, &fetch_snip20(&msg.token, &deps.querier)?)?; + LIABILITIES.save(deps.storage, &Uint128::zero())?; + WHITELIST.save(deps.storage, &Vec::new())?; + COLLATERAL.save(deps.storage, &Vec::new())?; + + deps.api + .debug(&format!("Contract was initialized by {}", info.sender)); + + Ok(Response::new().add_message(register_receive(env.contract.code_hash, None, &msg.token)?)) +} + +#[entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg { + ExecuteMsg::UpdateConfig { config } => execute::try_update_config(deps, env, info, config), + ExecuteMsg::Receive { + sender, + from, + amount, + msg, + .. + } => execute::receive(deps, env, info, sender, from, amount, msg), + ExecuteMsg::AddWhitelist { address } => execute::add_whitelist(deps, env, info, address), + ExecuteMsg::RemoveWhitelist { address } => execute::rm_whitelist(deps, env, info, address), + ExecuteMsg::AddCollateral { asset } => execute::add_collateral(deps, env, info, asset), + ExecuteMsg::RemoveCollateral { asset } => execute::rm_collateral(deps, env, info, asset), + ExecuteMsg::Mint { amount } => execute::mint(deps, env, info, amount), + } +} + +#[entry_point] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => to_binary(&query::config(deps)?), + QueryMsg::Token {} => to_binary(&query::token(deps)?), + QueryMsg::Liabilities {} => to_binary(&query::liabilities(deps)?), + QueryMsg::Whitelist {} => to_binary(&query::whitelist(deps)?), + } +} diff --git a/temp-contracts/liability_mint/src/execute.rs b/temp-contracts/liability_mint/src/execute.rs new file mode 100644 index 0000000..9859be5 --- /dev/null +++ b/temp-contracts/liability_mint/src/execute.rs @@ -0,0 +1,311 @@ +use shade_protocol::{ + c_std::{ + from_binary, + to_binary, + Addr, + Api, + Binary, + CosmosMsg, + Deps, + DepsMut, + Env, + MessageInfo, + Querier, + QuerierWrapper, + Response, + StdError, + StdResult, + Storage, + Uint128, + }, + chrono::prelude::*, + dao::adapter, + mint::liability_mint::{Config, HandleAnswer}, + snip20::helpers::{ + self, + burn_msg, + fetch_snip20, + mint_msg, + send_msg, + token_config, + token_info, + Snip20Asset, + TokenConfig, + }, + utils::{asset::Contract, callback::Query, generic_response::ResponseStatus}, +}; + +use crate::storage::*; +use shade_oracles::{ + common::{querier::query_prices, OraclePrice}, + interfaces::router, +}; + +pub fn receive( + deps: DepsMut, + env: Env, + info: MessageInfo, + _sender: Addr, + from: Addr, + amount: Uint128, + msg: Option, +) -> StdResult { + let token = TOKEN.load(deps.storage)?; + + if info.sender == token.contract.address { + //TODO Burn tokens + let liab = LIABILITIES.load(deps.storage)?; + + let mut messages = vec![]; + let mut burn_amount = amount; + + // Handle excess tokens + if liab < amount { + burn_amount = liab; + + //TODO to treasury? + messages.push(send_msg( + from, + liab - amount, + None, + None, + None, + &token.contract, + )?); + } + + messages.push(burn_msg(burn_amount, None, None, &token.contract)?); + + LIABILITIES.save(deps.storage, &(liab - burn_amount))?; + + Ok(Response::new() + .add_messages(messages) + .set_data(to_binary(&ExecuteAnswer::Mint { + status: ResponseStatus::Success, + amount, + })?)) + } else { + return Err(StdError::generic_err(format!( + "Unrecognized token {}", + info.sender + ))); + } +} + +pub fn try_update_config( + deps: DepsMut, + env: Env, + info: MessageInfo, + config: Config, +) -> StdResult { + let cur_config = CONFIG.load(deps.storage)?; + + // Admin-only + if info.sender != cur_config.admin { + return Err(StdError::generic_err("unauthorized")); + } + + CONFIG.save(deps.storage, &config)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { + status: ResponseStatus::Success, + })?), + ) +} + +/* Queries the treasury for 'collateral_assets' balances + * Queries oracle for 'collateral_assets' + 'debt_asset' USD prices + * Returns the debt limit of 'debt_asset' + * + * NOTE + * This is an N time operation, treasury should + * implement batch queries + * maybe manager & adapter as well? + */ +pub fn debt_limit( + deps: Deps, + debt_asset: Snip20Asset, + collateral_assets: Vec, + debt_ratio: Uint128, + oracle: Contract, + treasury: Contract, +) -> StdResult { + let mut balances: Vec = vec![]; + let mut symbols: Vec = vec![]; + for asset in COLLATERAL.load(deps.storage)? { + balances.push(adapter::balance_query( + deps.querier, + &asset.contract.address, + treasury, + )?); + symbols.push(asset.token_info.symbol); + } + + symbols.push(debt_asset.token_info.symbol); + + let mut prices = query_prices(&oracle, &deps.querier, symbols.iter())? + .iter() + .map(|p| p.data.rate) + .collect(); + + let debt_price = prices.pop().data.rate; + let asset_value = prices + .iter() + .zip(balances.iter()) + .map(|(b, p)| b * p.data.rate) + .sum::(); + + Ok(asset_value.multiply_ratio(debt_ratio, 10u128.pow(18))) +} + +pub fn mint(deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + // Check if admin + if !WHITELIST.load(deps.storage)?.contains(&info.sender) { + return Err(StdError::generic_err("Unauthorized")); + } + + let limit = debt_limit( + deps.as_ref(), + TOKEN.load(deps.storage)?, + COLLATERAL.load(deps.storage)?, + config.debt_ratio, + config.oracle, + config.treasury, + )?; + // change to 'debt' nomenclature everywhere? + let debt = LIABILITIES.load(deps.storage)?; + + if debt + amount > limit { + return Err(StdError::generic_err(format!( + "Additional debt would exceed limit, current: {} / {}", + debt, limit, + ))); + } + + LIABILITIES.save(deps.storage, &(debt + amount))?; + + Ok(Response::new() + .add_message(mint_msg( + info.sender, + amount, + None, + None, + &TOKEN.load(deps.storage)?.contract, + )?) + .set_data(to_binary(&ExecuteAnswer::Mint { + status: ResponseStatus::Success, + amount, + })?)) +} + +pub fn add_whitelist( + deps: DepsMut, + env: Env, + info: MessageInfo, + address: Addr, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + // Check if admin + if info.sender != config.admin { + return Err(StdError::generic_err("Unauthorized")); + } + + let mut ws = WHITELIST.load(deps.storage)?; + ws.push(address); + WHITELIST.save(deps.storage, &ws)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::AddWhitelist { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn rm_whitelist( + deps: DepsMut, + env: Env, + info: MessageInfo, + address: Addr, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + // Check if admin + if info.sender != config.admin { + return Err(StdError::generic_err("Unauthorized")); + } + + let mut ws = WHITELIST.load(deps.storage)?; + + if let Some(i) = ws.iter().position(|a| *a == address.clone()) { + ws.remove(i); + } else { + return Err(StdError::generic_err("Not on whitelist")); + } + + WHITELIST.save(deps.storage, &ws)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::RemoveWhitelist { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn add_collateral( + deps: DepsMut, + env: Env, + info: MessageInfo, + asset: Contract, +) -> StdResult { + let mut config = CONFIG.load(deps.storage)?; + + // Check if admin + if info.sender != config.admin { + return Err(StdError::generic_err("Unauthorized")); + } + + //TODO verify snip20 with msg + let mut collateral = COLLATERAL.load(deps.storage)?; + collateral.push(fetch_snip20(&asset, &deps.querier)?); + COLLATERAL.save(deps.storage, &collateral)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::AddCollateral { + status: ResponseStatus::Success, + })?), + ) +} + +pub fn rm_collateral( + deps: DepsMut, + env: Env, + info: MessageInfo, + asset: Contract, +) -> StdResult { + let mut config = CONFIG.load(deps.storage)?; + + // Check if admin + if info.sender != config.admin { + return Err(StdError::generic_err("Unauthorized")); + } + + //TODO verify snip20 with msg + let mut collateral = COLLATERAL.load(deps.storage)?; + if let Some(pos) = collateral.iter().position(|a| a.contract == asset) { + collateral.swap_remove(pos); + } else { + return Err(StdError::generic_err("Not valid collateral")); + } + + COLLATERAL.save(deps.storage, &collateral)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::RemoveCollateral { + status: ResponseStatus::Success, + })?), + ) +} diff --git a/temp-contracts/liability_mint/src/lib.rs b/temp-contracts/liability_mint/src/lib.rs new file mode 100644 index 0000000..25ee0d9 --- /dev/null +++ b/temp-contracts/liability_mint/src/lib.rs @@ -0,0 +1,4 @@ +pub mod contract; +pub mod execute; +pub mod query; +pub mod storage; diff --git a/temp-contracts/liability_mint/src/query.rs b/temp-contracts/liability_mint/src/query.rs new file mode 100644 index 0000000..6c64042 --- /dev/null +++ b/temp-contracts/liability_mint/src/query.rs @@ -0,0 +1,39 @@ +use super::execute::debt_limit; +use crate::storage::*; +use shade_protocol::{ + c_std::{Deps, StdResult}, + contract_interfaces::mint::liability_mint::QueryAnswer, +}; + +pub fn config(deps: Deps) -> StdResult { + Ok(QueryAnswer::Config { + config: CONFIG.load(deps.storage)?, + }) +} +pub fn token(deps: Deps) -> StdResult { + Ok(QueryAnswer::Token { + token: TOKEN.load(deps.storage)?, + }) +} + +pub fn liabilities(deps: Deps) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let limit = debt_limit( + deps, + TOKEN.load(deps.storage)?, + COLLATERAL.load(deps.storage)?, + config.debt_ratio, + config.oracle, + config.treasury, + )?; + Ok(QueryAnswer::Liabilities { + outstanding: LIABILITIES.load(deps.storage)?, + limit, + }) +} + +pub fn whitelist(deps: Deps) -> StdResult { + Ok(QueryAnswer::Whitelist { + whitelist: WHITELIST.load(deps.storage)?, + }) +} diff --git a/temp-contracts/liability_mint/src/storage.rs b/temp-contracts/liability_mint/src/storage.rs new file mode 100644 index 0000000..993ab4e --- /dev/null +++ b/temp-contracts/liability_mint/src/storage.rs @@ -0,0 +1,12 @@ +use shade_protocol::c_std::{Addr, Storage, Uint128}; +use shade_protocol::contract_interfaces::mint::liability_mint::Config; +use shade_protocol::secret_storage_plus::Item; +use shade_protocol::snip20::helpers::Snip20Asset; + +pub const CONFIG: Item = Item::new("config"); +pub const LIABILITIES: Item = Item::new("liabilities"); +pub const TOKEN: Item = Item::new("token"); +pub const WHITELIST: Item> = Item::new("whitelist"); + +// iter item? +pub const COLLATERAL: Item> = Item::new("collateral"); diff --git a/temp-contracts/liability_mint/tests/integration.rs b/temp-contracts/liability_mint/tests/integration.rs new file mode 100644 index 0000000..78171b3 --- /dev/null +++ b/temp-contracts/liability_mint/tests/integration.rs @@ -0,0 +1,195 @@ +use shade_protocol::c_std::{ + coins, + from_binary, + to_binary, + Binary, + Env, + DepsMut, + Addr, + Response, + StdError, + StdResult, +}; + +use shade_protocol::c_std::Uint128; +use shade_protocol::{ + contract_interfaces::{ + snip20, + mint::liability_mint, + }, + utils::{ + MultiTestable, + InstantiateCallback, + ExecuteCallback, + Query, + asset::Contract, + price::{normalize_price, translate_price}, + }, +}; + +use shade_protocol::multi_test::{ App }; + +use shade_multi_test::multi::{ + snip20::Snip20, + liability_mint::LiabilityMint, +}; + +fn test_liabilities( + mint_amount: Uint128, + limit: Uint128, + payback: Uint128, + expected_balance: Uint128, + expected_liabilities: Uint128, +) { + let mut app = App::default(); + + let admin = Addr::unchecked("admin"); + let viewing_key = "viewing_key".to_string(); + + let token = snip20::InstantiateMsg { + name: "token".into(), + admin: Some(admin.clone().into()), + symbol: "TKN".into(), + decimals: 6, + initial_balances: None, + prng_seed: to_binary("").ok().unwrap(), + config: Some(snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: None, + enable_redeem: None, + enable_mint: Some(true), + enable_burn: Some(true), + enable_transfer: Some(true), + }), + query_auth: None, + }.test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]).unwrap(); + + let liab_mint = liability_mint::InstantiateMsg { + admin: Some(admin.clone()), + token: Contract { + address: token.address.clone(), + code_hash: token.code_hash.clone(), + }, + limit, + }.test_init(LiabilityMint::default(), &mut app, admin.clone(), "liability_mint", &[]).unwrap(); + + // Setup liability minting + &snip20::ExecuteMsg::AddMinters { + minters: vec![liab_mint.address.to_string().clone()], + padding: None, + }.test_exec(&token, &mut app, admin.clone(), &[]).unwrap(); + + // add user to whitelist + &liability_mint::ExecuteMsg::AddWhitelist { + address: admin.clone(), + }.test_exec(&liab_mint, &mut app, admin.clone(), &[]).unwrap(); + + // Mint funds + &liability_mint::ExecuteMsg::Mint { + amount: mint_amount, + }.test_exec(&liab_mint, &mut app, admin.clone(), &[]).unwrap(); + + snip20::ExecuteMsg::SetViewingKey { + key: viewing_key.clone(), + padding: None, + }.test_exec(&token, &mut app, admin.clone(), &[]).unwrap(); + + // Check total supply + match (snip20::QueryMsg::TokenInfo { }).test_query(&token, &app).unwrap() { + snip20::QueryAnswer::TokenInfo { name, symbol, decimals, total_supply } => { + assert_eq!(total_supply.unwrap(), mint_amount, "total supply {} less than mint amount {}", total_supply.unwrap(), mint_amount); + }, + _ => { panic!("Query failed"); }, + } + // Check user balance + match (snip20::QueryMsg::Balance { + address: admin.to_string().clone(), + key: viewing_key.clone(), + }).test_query(&token, &app).unwrap() { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, mint_amount, "amount minted") + }, + _ => { panic!("Query failed"); }, + } + + // Check liabilities + match (liability_mint::QueryMsg::Liabilities { + }).test_query(&liab_mint, &app).unwrap() { + liability_mint::QueryAnswer::Liabilities { outstanding, limit } => { + assert_eq!(outstanding, mint_amount, "liabilities before payback") + }, + _ => { panic!("Query failed"); }, + } + + // Payback + snip20::ExecuteMsg::Send { + recipient: liab_mint.address.to_string().clone(), + recipient_code_hash: None, + amount: payback, + msg: None, + memo: None, + padding: None, + }.test_exec(&token, &mut app, admin.clone(), &[]).unwrap(); + + // Check total supply + match (snip20::QueryMsg::TokenInfo { }).test_query(&token, &app).unwrap() { + snip20::QueryAnswer::TokenInfo { name, symbol, decimals, total_supply } => { + assert_eq!(total_supply.unwrap(), mint_amount - payback, "total supply {} should be mint amount - payback {}", total_supply.unwrap(), mint_amount - payback); + }, + _ => { panic!("Query failed"); }, + } + // Check user balance + match (snip20::QueryMsg::Balance { + address: admin.to_string().clone(), + key: viewing_key.clone(), + }).test_query(&token, &app).unwrap() { + snip20::QueryAnswer::Balance { amount } => { + assert_eq!(amount, mint_amount - payback, "user balance after payback") + }, + _ => { panic!("Query failed"); }, + } + + // Check liabilities + match (liability_mint::QueryMsg::Liabilities { + }).test_query(&liab_mint, &app).unwrap() { + liability_mint::QueryAnswer::Liabilities { outstanding, limit } => { + assert_eq!(outstanding, mint_amount - payback, "liabilities after payback") + }, + _ => { panic!("Query failed"); }, + } +} + +macro_rules! liability_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (mint_amount, limit, payback, expected_balance, expected_liabilities) = $value; + test_liabilities(mint_amount, limit, payback, expected_balance, expected_liabilities); + } + )* + } +} +liability_tests! { + liability_half_payback: ( + Uint128::new(1_000_000), // mint amount + Uint128::new(1_000_000), // limit + Uint128::new( 500_000), // payback + Uint128::new( 500_000), // end balance + Uint128::new( 500_000), // end liabilities + ), + liability_full_payback: ( + Uint128::new(1_000_000), // mint amount + Uint128::new(1_000_000), // limit + Uint128::new(1_000_000), // payback + Uint128::new(0), // end balance + Uint128::new(0), // end liabilities + ), + liability_no_payback: ( + Uint128::new(1_000_000), // mint amount + Uint128::new(1_000_000), // limit + Uint128::new(0), // payback + Uint128::new(1_000_000), // end balance + Uint128::new(1_000_000), // end liabilities + ), +} diff --git a/tools/doc2book/Cargo.toml b/tools/doc2book/Cargo.toml new file mode 100644 index 0000000..8d02b99 --- /dev/null +++ b/tools/doc2book/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "doc2book" +version = "0.1.0" +edition = "2018" + +[[bin]] +name = "doc2book" +path = "src/doc2book.rs" + +[dev-dependencies] +pretty_assertions = "1.0.0" diff --git a/tools/doc2book/README.md b/tools/doc2book/README.md new file mode 100644 index 0000000..3da4ab3 --- /dev/null +++ b/tools/doc2book/README.md @@ -0,0 +1,59 @@ +# `doc2book` +A simple tool that scrapes rustdoc comments, `doc2book`-only comments and specified Rust code from a crate's source files +and outputs them as a single file (e.g. a Markdown book chapter). + +## Usage +The usage is simple, specify the crate and the output file path +``` +USAGE: doc2book CRATE_DIR OUT_FILE_PATH +``` + +Example from Shade workspace root directory: +``` +cargo r -p doc2book --release -- packages/shade_protocol doc/book/src/smart_contracts.md +``` + +### Comments +The following comments are scraped: +- `//! `: [rustdoc][1] crate-level (inner) doc comments +- `/// `: [rustdoc][1] code-level (outer) doc comments +- `//# `: `doc2book` comments, these will be ignored by rustdoc but picked up by `doc2book` + +[1]: https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html + +### Code +The comments `// book_include_code` and `// end_book_include_code` mark a section of code to be copied verbatim and place in a Rust code block (Markdown syntax). +This code: + +```rust +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +// book_include_code +pub struct Contract { + /// The address of the contract + pub address: Addr, + /// The hex encoded hash of the contract code + pub code_hash: String, +} +// end_book_include_code +``` + +Would result in this Markdown section: + +~~~ +```rust +pub struct Contract { + /// The address of the contract + pub address: Addr, + /// The hex encoded hash of the contract code + pub code_hash: String, +} +``` +~~~ + +## Limitations +As the tool is currently very simple it has a few limitations: +- The crate structure is expected to be one file per module at the same directory level as the lib.rs file. +- The tool outputs everything as a single file, it could be made to create a 'sub-chapter' file for each module. +- Due to it creating a single file, the ordering of comments, modules declarations and the code is the order it will appear output file. +- Adding whitespace between scraped sections needs to be explicit, i.e. an empty comment line needs to be inserted where you want a blank line `//! `. diff --git a/tools/doc2book/src/doc2book.rs b/tools/doc2book/src/doc2book.rs new file mode 100644 index 0000000..8a00192 --- /dev/null +++ b/tools/doc2book/src/doc2book.rs @@ -0,0 +1,326 @@ +use std::{ + fs::File, + io::{BufRead, BufReader, Error as IoError, Lines, Write}, +}; + +const USAGE: &str = "USAGE: doc2book CRATE_DIR OUT_FILE_PATH"; + +type Result = std::result::Result; + +#[derive(Debug)] +enum Error { + CrateSourceDirNotFound(String), + LibRsNotFound(String), + ModuleSourceNotFound(String), + Io(IoError), +} + +impl From for Error { + fn from(err: IoError) -> Self { + Self::Io(err) + } +} + +// source reading interface +trait ReadSource { + type Src: BufRead; + + fn lib_rs_path(&self) -> &str; + + fn module_path_from_name(&self, module: &str) -> String; + + fn lines_from_path(&self, src_path: &str) -> Result>; +} + +// source reading implementor for crates +struct CrateSource { + crate_src_dir: String, + lib_rs_path: String, +} + +impl CrateSource { + // ensure the crate is valid, + // i.e. it's src directory exists and contains a mod file + fn new(crate_dir: &str) -> Result { + let crate_src_dir = format!("{}/src", crate_dir); + let lib_rs_path = format!("{}/mod", crate_src_dir); + + if std::fs::metadata(&crate_src_dir).is_err() { + return Err(Error::CrateSourceDirNotFound(crate_src_dir)); + } + + if std::fs::metadata(&lib_rs_path).is_err() { + return Err(Error::LibRsNotFound(lib_rs_path)); + } + + Ok(CrateSource { + crate_src_dir, + lib_rs_path, + }) + } +} + +impl ReadSource for CrateSource { + type Src = BufReader; + + fn lib_rs_path(&self) -> &str { + &self.lib_rs_path + } + + fn module_path_from_name(&self, module: &str) -> String { + format!("{}/{}.rs", self.crate_src_dir, module) + } + + fn lines_from_path(&self, src_path: &str) -> Result> { + if std::fs::metadata(src_path).is_err() { + return Err(Error::ModuleSourceNotFound(src_path.to_string())); + } + + let file = File::open(src_path)?; + let lines = BufReader::new(file).lines(); + + Ok(lines) + } +} + +struct Output { + output: Vec, +} + +impl Output { + fn new() -> Output { + // allocate a decent chunk of memory at the start + let output = Vec::with_capacity(1_000_000_000); + Output { output } + } + + fn push_line(&mut self, line: &str) { + writeln!(&mut self.output, "{}", line).unwrap(); + } + + fn write_into(&self, w: &mut dyn Write) -> Result<()> { + w.write_all(&self.output)?; + Ok(()) + } +} + +fn parse_args() -> Option<(String, String)> { + let mut args = std::env::args().skip(1); + let crate_dir = args.next()?; + let out_path = args.next()?; + Some((crate_dir, out_path)) +} + +fn find_doc_comment(line: &str) -> Option<&str> { + line.strip_prefix("//! ") + .or_else(|| line.strip_prefix("//# ")) + .or_else(|| line.strip_prefix("/// ")) +} + +// scrape from code between pragmas and each type of doc comment +fn process_module(input: &I, output: &mut Output, module_name: &str) -> Result<()> +where + I: ReadSource, +{ + let module_path = input.module_path_from_name(module_name); + eprintln!("Processing module file: {}", module_path); + + let mut code_transcribe_enabled = false; + + for line in input.lines_from_path(&module_path)? { + let line = line?; + + if line.starts_with("// book_include_code") { + output.push_line("```rust"); + code_transcribe_enabled = true; + continue; + } + + if line.starts_with("// end_book_include_code") { + output.push_line("```"); + code_transcribe_enabled = false; + continue; + } + + if code_transcribe_enabled { + output.push_line(&line); + continue; + } + + if let Some(line) = find_doc_comment(&line) { + output.push_line(line); + } + } + + Ok(()) +} + +// start with the crate root, mod +// scrape code comments and with each public module inserted in the order they appear +fn process_crate(input: I, output: &mut Output) -> Result<()> +where + I: ReadSource, +{ + let lib_rs_path = input.lib_rs_path(); + eprintln!("Processing mod file: {}", lib_rs_path); + + for line in input.lines_from_path(lib_rs_path)? { + let line = line?; + + if let Some(doc) = find_doc_comment(&line) { + output.push_line(doc); + continue; + } + + if line.starts_with("pub mod ") && line.ends_with(';') { + let module = line + .strip_prefix("pub mod ") + .unwrap() + .strip_suffix(';') + .unwrap(); + + process_module(&input, output, module)?; + } + } + + Ok(()) +} + +fn main() { + let (crate_root_dir, out_path) = match parse_args() { + Some(args) => args, + _ => return eprintln!("{}", USAGE), + }; + + let input = match CrateSource::new(&crate_root_dir) { + Ok(input) => input, + Err(Error::CrateSourceDirNotFound(path)) => { + eprintln!( + "{} does not exist. Make sure the specified directory is a Rust project.", + path + ); + return eprintln!("{}", USAGE); + } + Err(Error::LibRsNotFound(path)) => { + eprintln!("{} not does not exist. The crate must be a library.", path); + return eprintln!("{}", USAGE); + } + Err(err) => panic!("unexpected err: {:?}", err), + }; + + let mut output = Output::new(); + + let result = process_crate(input, &mut output).and_then(|_| { + let mut out_file = File::create(out_path)?; + output.write_into(&mut out_file) + }); + + if let Err(err) = result { + match err { + Error::ModuleSourceNotFound(path) => { + eprintln!( + "Processing module file {} failed: file does not exist", + path + ) + } + Error::Io(err) => eprintln!("Unexpected I/O Error: {}", err), + err => panic!("unexpected err: {:?}", err), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + const LIB_RS: &str = r#"//! # Main Title +//! Some text +//! + +pub mod helper_mod; + +//# ## Common Types +//# Some more text +//# + +pub mod common_types; +"#; + + const COMMON_TYPES_RS: &str = r#"use some_dep::module; +use some_other_dep::module; + +//# ### `Contract` +/// Represents another contract on the network +/// + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +// book_include_code +pub struct Contract { + /// The address of the contract + pub address: Addr, + /// The hex encoded hash of the contract code + pub code_hash: String, +} +// end_book_include_code"#; + + const EXPECTED_OUTPUT: &str = r#"# Main Title +Some text + +## Common Types +Some more text + +### `Contract` +Represents another contract on the network + +```rust +pub struct Contract { + /// The address of the contract + pub address: Addr, + /// The hex encoded hash of the contract code + pub code_hash: String, +} +``` +"#; + + struct TestInput; + + impl ReadSource for TestInput { + type Src = BufReader<&'static [u8]>; + + fn lib_rs_path(&self) -> &str { + "src/mod" + } + + fn module_path_from_name(&self, module: &str) -> String { + format!("src/{}.rs", module) + } + + fn lines_from_path(&self, src_path: &str) -> Result> { + let s: &'static str = match src_path { + "src/mod" => LIB_RS, + "src/common_types.rs" => COMMON_TYPES_RS, + "src/helper_mod.rs" => "", + _ => panic!("Unexpected path: {}", src_path), + }; + + Ok(BufReader::new(s.as_bytes()).lines()) + } + } + + #[test] + fn it_works() { + let mut output = Output::new(); + + process_crate(TestInput, &mut output).unwrap(); + + let mut actual = Vec::new(); + + output.write_into(&mut actual).unwrap(); + + let actual = String::from_utf8(actual).unwrap(); + + assert_eq!(actual, EXPECTED_OUTPUT.to_owned()) + } +} diff --git a/tools/multisig/broadcast_multi.sh b/tools/multisig/broadcast_multi.sh new file mode 100755 index 0000000..97b2ecd --- /dev/null +++ b/tools/multisig/broadcast_multi.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# GUIDE: +# Organize all the signatures and the tx to be signed in one directory +# Pass that directory as $1 +# Pass the name of the file to be signed as $2 + + + +cd $1 +res=`ls` +signatures="" +for files in $res +do + if [ $files == signedMultiTx.json ] + then + rm signedMultiTx.json + fi + if [ $files == $2 ] + then + continue + else + signatures=$signatures" "$files + fi +done + +secretd tx multisign $2 ss_multisig $signatures --chain-id secret-4 > signedMultiTx.json +secretd tx broadcast signedMultiTx.json diff --git a/tools/multisig/sign_mutli.sh b/tools/multisig/sign_mutli.sh new file mode 100755 index 0000000..f0b059c --- /dev/null +++ b/tools/multisig/sign_mutli.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# example: ./multisig.sh secret1y277c499f44nxe7geeaqw8t6gpge68rcpla9lf ~/json/output.json jsledger + +secretd config node https://rpc.scrt.network:443 +secretd config chain-id secret-4 +res=`secretd q account $1` +eval sequence=`echo $res | jq ".sequence"` +eval acc_num=`echo $res | jq ".account_number"` +outputdoc="signature_$3.json" +secretd tx sign $2 --multisig ss_multisig --from $3 --output-document $outputdoc --chain-id secret-4 --offline --sequence $sequence --account-number $acc_num --sign-mode amino-json diff --git a/tools/multisig/sign_permit.py b/tools/multisig/sign_permit.py new file mode 100644 index 0000000..6cd8c57 --- /dev/null +++ b/tools/multisig/sign_permit.py @@ -0,0 +1,36 @@ +import argparse +import os + +parser = argparse.ArgumentParser(description="Create a cosmwasm msg for offline signing") + +parser.add_argument("msg", type=str, help="Permit data") +parser.add_argument("account", type=str, help="Permit signer") +parser.add_argument("--account_number", type=str, help="Account number", default="0") +parser.add_argument("--chain_id", type=str, help="Chain id to which this permit is written for", default="secret-4") +parser.add_argument("--memo", type=str, help="Memo for the permit", default="") +parser.add_argument("--msg_type", type=str, help="Msg type used on the signed msg", default="signature_proof") +parser.add_argument("--sequence", type=str, help="Signature sequence number", default="0") + +parser.add_argument("-o", "--output", type=str, help="Output message") +parser.add_argument("--use_old", action="store_true", help="Uses secretcli instead of secretd") +args = parser.parse_args() + +bin = "secretd" + +if args.use_old: + bin = "secretcli" + +output = "signed.json" + +if args.output: + output = args.output + +unsigned_permit = f'echo \' {{ "account_number": "{args.account_number}", ' \ + f'"chain_id": "{args.chain_id}", ' \ + f'"fee": {{ "amount": [{{ "amount": "0", "denom": "uscrt"}}], "gas": "1" }}, ' \ + f'"memo": "{args.memo}", "msgs": [{{ "type": "{args.msg_type}", "value": {args.msg} }}], ' \ + f'"sequence": "{args.sequence}"}} \'> unsigned.json' +os.system(unsigned_permit) + +command = f'{bin} tx sign-doc unsigned.json --from {args.account} > {output}' +os.system(command) \ No newline at end of file diff --git a/tools/multisig/wasm_msg.py b/tools/multisig/wasm_msg.py new file mode 100644 index 0000000..197461a --- /dev/null +++ b/tools/multisig/wasm_msg.py @@ -0,0 +1,25 @@ +import argparse +import os + +parser = argparse.ArgumentParser(description="Create a cosmwasm msg for offline signing") +parser.add_argument("contract_address", type=str, help="Smart contract's address") +parser.add_argument("contract_codehash", type=str, help="Smart contract's code hash") +parser.add_argument("msg", type=str, help="Smart contract's msg to execute") +parser.add_argument("sender", type=str, help="The msg sender") +parser.add_argument("key", type=str, help="Enclave key certificate") +parser.add_argument("-o", "--output", type=str, help="Output message") +parser.add_argument("--use_old", action="store_true", help="Uses secretcli instead of secretd") +args = parser.parse_args() + +bin = "secretd" + +if args.use_old: + bin = "secretcli" + +output = "output.json" + +if args.output: + output = args.output + +command = f"{bin} tx compute execute {args.contract_address} '{args.msg}' --from {args.sender} --generate-only --enclave-key {args.key} --code-hash {args.contract_codehash} --offline --sign-mode amino-json > {output}" +os.system(command) \ No newline at end of file From 5baf56ff0fb8f79df2afc693c955c639a08d0752 Mon Sep 17 00:00:00 2001 From: hard-nett Date: Sun, 4 Feb 2024 09:38:34 -0500 Subject: [PATCH 02/23] updates --- Cargo.lock | 594 +-- Cargo.toml | 27 - Makefile.toml | 2 +- README.md | 10 +- Shade_Protocol_Responsible_Disclosure.md | 286 - archived-contracts/bonds/.cargo/config | 5 - archived-contracts/bonds/.circleci/config.yml | 52 - archived-contracts/bonds/Cargo.toml | 47 - archived-contracts/bonds/Makefile | 68 - archived-contracts/bonds/README.md | 389 -- archived-contracts/bonds/src/contract.rs | 215 - archived-contracts/bonds/src/handle.rs | 845 --- archived-contracts/bonds/src/lib.rs | 44 - archived-contracts/bonds/src/query.rs | 155 - archived-contracts/bonds/src/state.rs | 99 - archived-contracts/bonds/src/test.rs | 65 - archived-contracts/bonds/src/tests/handle.rs | 500 -- archived-contracts/bonds/src/tests/mod.rs | 502 -- archived-contracts/bonds/src/tests/query.rs | 185 - .../dao/lp_shdswap/.cargo/config | 5 - .../dao/lp_shdswap/.circleci/config.yml | 52 - archived-contracts/dao/lp_shdswap/Cargo.toml | 38 - archived-contracts/dao/lp_shdswap/Makefile | 68 - archived-contracts/dao/lp_shdswap/README.md | 64 - .../dao/lp_shdswap/src/contract.rs | 198 - .../dao/lp_shdswap/src/execute.rs | 261 - archived-contracts/dao/lp_shdswap/src/lib.rs | 44 - .../dao/lp_shdswap/src/query.rs | 155 - .../dao/lp_shdswap/src/storage.rs | 10 - archived-contracts/dao/lp_shdswap/src/test.rs | 46 - .../dao/rewards_emission/.cargo/config | 5 - .../dao/rewards_emission/.circleci/config.yml | 52 - .../dao/rewards_emission/Cargo.toml | 29 - .../dao/rewards_emission/Makefile | 68 - .../dao/rewards_emission/README.md | 65 - .../dao/rewards_emission/src/contract.rs | 96 - .../dao/rewards_emission/src/execute.rs | 184 - .../dao/rewards_emission/src/lib.rs | 7 - .../dao/rewards_emission/src/query.rs | 57 - .../dao/rewards_emission/src/storage.rs | 14 - .../dao/rewards_emission/src/test.rs | 46 - archived-contracts/governance/.cargo/config | 5 - .../governance/.circleci/config.yml | 52 - archived-contracts/governance/Cargo.toml | 37 - archived-contracts/governance/Makefile | 68 - archived-contracts/governance/src/contract.rs | 482 -- .../governance/src/handle/assembly.rs | 256 - .../governance/src/handle/assembly_msg.rs | 105 - .../governance/src/handle/contract.rs | 133 - .../governance/src/handle/migration.rs | 217 - .../governance/src/handle/mod.rs | 135 - .../governance/src/handle/profile.rs | 77 - .../governance/src/handle/proposal.rs | 541 -- archived-contracts/governance/src/lib.rs | 8 - archived-contracts/governance/src/query.rs | 229 - .../governance/src/tests/handle/assembly.rs | 105 - .../src/tests/handle/assembly_msg.rs | 102 - .../governance/src/tests/handle/contract.rs | 301 -- .../governance/src/tests/handle/migration.rs | 310 -- .../governance/src/tests/handle/mod.rs | 127 - .../governance/src/tests/handle/profile.rs | 472 -- .../tests/handle/proposal/assembly_voting.rs | 844 --- .../src/tests/handle/proposal/funding.rs | 781 --- .../src/tests/handle/proposal/mod.rs | 293 - .../src/tests/handle/proposal/voting.rs | 1022 ---- .../governance/src/tests/handle/runstate.rs | 255 - .../governance/src/tests/mod.rs | 214 - .../governance/src/tests/query/mod.rs | 2 - .../governance/src/tests/query/public.rs | 180 - .../governance/src/tests/query/user.rs | 268 - archived-contracts/mint/.cargo/config | 5 - archived-contracts/mint/.circleci/config.yml | 52 - archived-contracts/mint/Cargo.toml | 33 - archived-contracts/mint/Makefile | 68 - archived-contracts/mint/README.md | 215 - archived-contracts/mint/src/contract.rs | 110 - archived-contracts/mint/src/handle.rs | 491 -- archived-contracts/mint/src/lib.rs | 4 - archived-contracts/mint/src/query.rs | 74 - archived-contracts/mint/src/state.rs | 107 - archived-contracts/mint/tests/integration.rs | 260 - archived-contracts/mint/tests/unit.rs | 113 - archived-contracts/mint_router/.cargo/config | 5 - .../mint_router/.circleci/config.yml | 52 - archived-contracts/mint_router/Cargo.toml | 34 - archived-contracts/mint_router/Makefile | 68 - archived-contracts/mint_router/README.md | 210 - .../mint_router/src/contract.rs | 80 - archived-contracts/mint_router/src/handle.rs | 230 - archived-contracts/mint_router/src/lib.rs | 49 - archived-contracts/mint_router/src/query.rs | 60 - archived-contracts/mint_router/src/state.rs | 76 - archived-contracts/mint_router/src/test.rs | 414 -- archived-contracts/mock_band/.cargo/config | 5 - .../mock_band/.circleci/config.yml | 52 - archived-contracts/mock_band/Cargo.toml | 34 - archived-contracts/mock_band/Makefile | 68 - archived-contracts/mock_band/README.md | 21 - archived-contracts/mock_band/src/contract.rs | 110 - archived-contracts/mock_band/src/execute.rs | 424 -- archived-contracts/mock_band/src/lib.rs | 1 - .../mock_secretswap_pair/.cargo/config | 5 - .../mock_secretswap_pair/.circleci/config.yml | 52 - .../mock_secretswap_pair/Cargo.toml | 31 - .../mock_secretswap_pair/Makefile | 68 - .../mock_secretswap_pair/README.md | 21 - .../mock_secretswap_pair/src/contract.rs | 176 - .../mock_secretswap_pair/src/lib.rs | 43 - .../mock_sienna_pair/Cargo.toml | 29 - .../mock_sienna_pair/src/contract.rs | 303 -- archived-contracts/oracle/.cargo/config | 5 - .../oracle/.circleci/config.yml | 52 - archived-contracts/oracle/Cargo.toml | 42 - archived-contracts/oracle/Makefile | 68 - archived-contracts/oracle/README.md | 142 - archived-contracts/oracle/src/contract.rs | 72 - archived-contracts/oracle/src/handle.rs | 313 -- archived-contracts/oracle/src/lib.rs | 49 - archived-contracts/oracle/src/query.rs | 177 - archived-contracts/oracle/src/state.rs | 45 - archived-contracts/oracle/src/test.rs | 48 - .../oracle/tests/integration.rs | 18 - archived-contracts/peg_stability/Cargo.toml | 37 - archived-contracts/peg_stability/Makefile | 68 - .../peg_stability/src/contract.rs | 94 - .../peg_stability/src/handle.rs | 233 - archived-contracts/peg_stability/src/lib.rs | 6 - archived-contracts/peg_stability/src/query.rs | 199 - .../peg_stability/src/tests/handle.rs | 59 - .../peg_stability/src/tests/mod.rs | 113 - .../peg_stability/src/tests/query.rs | 29 - archived-contracts/sky/.cargo/config | 5 - archived-contracts/sky/.circleci/config.yml | 52 - archived-contracts/sky/Cargo.toml | 33 - archived-contracts/sky/Makefile | 68 - archived-contracts/sky/src/contract.rs | 161 - archived-contracts/sky/src/execute.rs | 433 -- archived-contracts/sky/src/lib.rs | 3 - archived-contracts/sky/src/query.rs | 334 -- archived-contracts/sky/tests/integration.rs | 318 -- .../snip20_staking/.cargo/config | 4 - .../snip20_staking/.circleci/config.yml | 52 - archived-contracts/snip20_staking/Cargo.toml | 49 - archived-contracts/snip20_staking/README.md | 59 - .../snip20_staking/src/batch.rs | 55 - .../snip20_staking/src/contract.rs | 4738 ----------------- .../snip20_staking/src/distributors.rs | 91 - .../snip20_staking/src/expose_balance.rs | 150 - archived-contracts/snip20_staking/src/lib.rs | 56 - archived-contracts/snip20_staking/src/msg.rs | 556 -- archived-contracts/snip20_staking/src/rand.rs | 75 - .../snip20_staking/src/receiver.rs | 68 - .../snip20_staking/src/stake.rs | 1068 ---- .../snip20_staking/src/stake_queries.rs | 126 - .../snip20_staking/src/state.rs | 389 -- .../snip20_staking/src/state_staking.rs | 135 - .../snip20_staking/src/transaction_history.rs | 723 --- .../snip20_staking/src/utils.rs | 15 - .../snip20_staking/src/viewing_key.rs | 57 - .../src/testnet_airdrop.rs | 0 contractlib/oraclelib.py | 53 - contractlib/treasurylib.py | 72 - contracts/Staking Deep Dive | 1 - contracts/Staking Overview | 129 - contracts/admin/.cargo/config | 5 - contracts/admin/.circleci/config.yml | 52 - contracts/admin/Cargo.toml | 31 - contracts/admin/Makefile | 68 - contracts/admin/README.md | 127 - contracts/admin/src/contract.rs | 107 - contracts/admin/src/execute.rs | 167 - contracts/admin/src/lib.rs | 6 - contracts/admin/src/query.rs | 40 - contracts/admin/src/shared.rs | 35 - contracts/admin/src/test.rs | 351 -- contracts/airdrop/Cargo.toml | 9 +- contracts/airdrop/src/contract.rs | 98 +- contracts/airdrop/src/handle.rs | 789 +-- contracts/airdrop/src/query.rs | 96 +- contracts/airdrop/src/state.rs | 35 +- contracts/basic_staking/.cargo/config | 5 - contracts/basic_staking/.circleci/config.yml | 52 - contracts/basic_staking/Cargo.toml | 40 - contracts/basic_staking/Makefile | 68 - contracts/basic_staking/README.md | 83 - contracts/basic_staking/src/contract.rs | 212 - contracts/basic_staking/src/execute.rs | 938 ---- contracts/basic_staking/src/lib.rs | 4 - contracts/basic_staking/src/query.rs | 215 - contracts/basic_staking/src/storage.rs | 34 - .../basic_staking/tests/bad_stake_token.rs | 347 -- .../end_reward_pool_after_end_claimed.rs | 290 - .../end_reward_pool_after_end_unclaimed.rs | 288 - ...d_reward_pool_after_end_unclaimed_force.rs | 386 -- .../end_reward_pool_before_end_claimed.rs | 291 - .../end_reward_pool_before_end_unclaimed.rs | 291 - .../tests/end_reward_pool_before_start.rs | 273 - contracts/basic_staking/tests/multi_staker.rs | 523 -- .../basic_staking/tests/non_admin_access.rs | 186 - .../basic_staking/tests/non_stake_rewards.rs | 610 --- .../basic_staking/tests/single_staker.rs | 593 --- .../tests/single_staker_compounding.rs | 588 -- .../basic_staking/tests/stake_0_total_bug.rs | 445 -- .../basic_staking/tests/transfer_stake.rs | 454 -- .../tests/transfer_stake_compound.rs | 454 -- .../tests/transfer_stake_sender_no_stake.rs | 232 - .../tests/unbonding_withdrawals.rs | 316 -- .../tests/unstake_softlock_bug.rs | 340 -- .../basic_staking/tests/update_config.rs | 127 - contracts/dao/scrt_staking/.cargo/config | 5 - .../dao/scrt_staking/.circleci/config.yml | 52 - contracts/dao/scrt_staking/Cargo.toml | 40 - contracts/dao/scrt_staking/Makefile | 68 - contracts/dao/scrt_staking/README.md | 65 - contracts/dao/scrt_staking/src/contract.rs | 116 - contracts/dao/scrt_staking/src/execute.rs | 424 -- contracts/dao/scrt_staking/src/lib.rs | 7 - contracts/dao/scrt_staking/src/query.rs | 182 - contracts/dao/scrt_staking/src/storage.rs | 9 - contracts/dao/scrt_staking/src/test.rs | 46 - .../dao/scrt_staking/tests/integration.rs | 420 -- contracts/dao/scrt_staking/tests/unit.rs | 36 - contracts/dao/stkd_scrt/.cargo/config | 5 - contracts/dao/stkd_scrt/.circleci/config.yml | 52 - contracts/dao/stkd_scrt/Cargo.toml | 39 - contracts/dao/stkd_scrt/Makefile | 68 - contracts/dao/stkd_scrt/README.md | 65 - contracts/dao/stkd_scrt/src/contract.rs | 108 - contracts/dao/stkd_scrt/src/execute.rs | 158 - contracts/dao/stkd_scrt/src/lib.rs | 7 - contracts/dao/stkd_scrt/src/query.rs | 124 - contracts/dao/stkd_scrt/src/storage.rs | 7 - contracts/dao/stkd_scrt/src/test.rs | 46 - contracts/dao/stkd_scrt/tests/integration.rs | 331 -- contracts/dao/stkd_scrt/tests/unit.rs | 36 - contracts/dao/treasury/.cargo/config | 5 - contracts/dao/treasury/.circleci/config.yml | 52 - contracts/dao/treasury/Cargo.toml | 47 - contracts/dao/treasury/Makefile | 68 - contracts/dao/treasury/README.md | 172 - .../dao/treasury/Untitled Diagram.drawio | 1 - contracts/dao/treasury/src/contract.rs | 128 - contracts/dao/treasury/src/execute.rs | 795 --- contracts/dao/treasury/src/lib.rs | 4 - contracts/dao/treasury/src/query.rs | 173 - contracts/dao/treasury/src/storage.rs | 27 - .../dao/treasury/tests/dao/equilibrium.rs | 314 -- .../dao/treasury/tests/dao/gains_losses.rs | 1012 ---- contracts/dao/treasury/tests/dao/mod.rs | 322 -- .../treasury/tests/integration/allowance.rs | 389 -- .../integration/allowance_delay_refresh.rs | 335 -- .../dao/treasury/tests/integration/batch.rs | 95 - .../dao/treasury/tests/integration/config.rs | 80 - .../tests/integration/execute_errors.rs | 244 - .../treasury/tests/integration/migration.rs | 151 - .../dao/treasury/tests/integration/mod.rs | 12 - .../integration/non_manager_allowances.rs | 192 - .../dao/treasury/tests/integration/query.rs | 240 - .../tests/integration/scrt_staking.rs | 861 --- .../treasury/tests/integration/tolerance.rs | 436 -- .../treasury/tests/integration/treasury.rs | 557 -- .../dao/treasury/tests/integration/wrap.rs | 145 - contracts/dao/treasury/tests/mod.rs | 2 - contracts/dao/treasury_manager/.cargo/config | 5 - .../dao/treasury_manager/.circleci/config.yml | 52 - contracts/dao/treasury_manager/Cargo.toml | 48 - contracts/dao/treasury_manager/Makefile | 68 - contracts/dao/treasury_manager/README.md | 142 - .../dao/treasury_manager/src/contract.rs | 160 - contracts/dao/treasury_manager/src/execute.rs | 1446 ----- contracts/dao/treasury_manager/src/lib.rs | 4 - contracts/dao/treasury_manager/src/query.rs | 292 - contracts/dao/treasury_manager/src/storage.rs | 21 - .../tests/integration/batch.rs | 138 - .../tests/integration/config.rs | 86 - .../tests/integration/execute_error.rs | 183 - .../tests/integration/holder_integration.rs | 345 -- .../treasury_manager/tests/integration/mod.rs | 9 - .../tests/integration/multiple_holders.rs | 416 -- .../tests/integration/query.rs | 400 -- .../integration/scrt_staking_integration.rs | 548 -- .../tests/integration/tm_unbond.rs | 222 - .../tests/integration/tolerance.rs | 467 -- contracts/dao/treasury_manager/tests/mod.rs | 1 - contracts/mock/mock_adapter/.cargo/config | 5 - .../mock/mock_adapter/.circleci/config.yml | 52 - contracts/mock/mock_adapter/Cargo.toml | 41 - contracts/mock/mock_adapter/Makefile | 68 - contracts/mock/mock_adapter/README.md | 21 - contracts/mock/mock_adapter/src/contract.rs | 326 -- contracts/mock/mock_adapter/src/execute.rs | 200 - contracts/mock/mock_adapter/src/lib.rs | 1 - contracts/mock/mock_adapter/src/storage.rs | 23 - contracts/mock/mock_sienna_pair/.cargo/config | 5 - .../mock_sienna_pair/.circleci/config.yml | 52 - contracts/mock/mock_sienna_pair/Cargo.toml | 29 - contracts/mock/mock_sienna_pair/Makefile | 68 - contracts/mock/mock_sienna_pair/README.md | 21 - .../mock/mock_sienna_pair/src/contract.rs | 303 -- contracts/mock/mock_sienna_pair/src/lib.rs | 1 - .../mock/mock_stkd_derivative/Cargo.toml | 39 - contracts/mock/mock_stkd_derivative/Makefile | 68 - contracts/mock/mock_stkd_derivative/README.md | 1 - .../mock/mock_stkd_derivative/src/contract.rs | 386 -- .../mock/mock_stkd_derivative/src/lib.rs | 5 - .../mock/mock_stkd_derivative/src/tests.rs | 510 -- contracts/query_auth/.cargo/config | 5 - contracts/query_auth/.circleci/config.yml | 52 - contracts/query_auth/Cargo.toml | 34 - contracts/query_auth/README.md | 202 - contracts/query_auth/src/contract.rs | 130 - contracts/query_auth/src/handle.rs | 95 - contracts/query_auth/src/lib.rs | 6 - contracts/query_auth/src/query.rs | 33 - contracts/query_auth/src/tests/handle.rs | 274 - contracts/query_auth/src/tests/mod.rs | 119 - contracts/query_auth/src/tests/query.rs | 50 - contracts/snip20/.cargo/config | 5 - contracts/snip20/.circleci/config.yml | 52 - contracts/snip20/Cargo.toml | 37 - contracts/snip20/Makefile | 68 - contracts/snip20/src/batch.rs | 53 - contracts/snip20/src/contract.rs | 443 -- contracts/snip20/src/handle/allowance.rs | 203 - contracts/snip20/src/handle/burning.rs | 127 - contracts/snip20/src/handle/minting.rs | 158 - contracts/snip20/src/handle/mod.rs | 238 - contracts/snip20/src/handle/transfers.rs | 239 - contracts/snip20/src/lib.rs | 6 - contracts/snip20/src/query.rs | 143 - .../snip20/src/tests/handle/allowance.rs | 267 - contracts/snip20/src/tests/handle/burn.rs | 219 - contracts/snip20/src/tests/handle/mint.rs | 152 - contracts/snip20/src/tests/handle/mod.rs | 229 - contracts/snip20/src/tests/handle/transfer.rs | 134 - contracts/snip20/src/tests/handle/wrap.rs | 107 - contracts/snip20/src/tests/mod.rs | 116 - contracts/snip20/src/tests/query/mod.rs | 2 - contracts/snip20/src/tests/query/public.rs | 73 - contracts/snip20/src/tests/query/user.rs | 208 - contracts/snip20_derivative/.cargo/config | 8 - .../snip20_derivative/.circleci/config.yml | 52 - contracts/snip20_derivative/.editorconfig | 11 - .../.github/workflows/cicd.yaml | 26 - .../.images/engineering-diagram.png | Bin 63431 -> 0 bytes contracts/snip20_derivative/Cargo.toml | 52 - contracts/snip20_derivative/Developing.md | 153 - contracts/snip20_derivative/Importing.md | 62 - contracts/snip20_derivative/LICENSE | 202 - contracts/snip20_derivative/Makefile | 89 - contracts/snip20_derivative/NOTICE | 13 - contracts/snip20_derivative/Publishing.md | 115 - contracts/snip20_derivative/README.md | 901 ---- .../snip20_derivative/examples/schema.rs | 21 - contracts/snip20_derivative/src/contract.rs | 2660 --------- contracts/snip20_derivative/src/lib.rs | 4 - contracts/snip20_derivative/src/msg.rs | 277 - .../src/staking_interface.rs | 388 -- contracts/snip20_derivative/src/state.rs | 69 - .../snip20_derivative/tests/integration.rs | 3 - .../snip20_derivative/tests/integration.sh | 1699 ------ contracts/snip20_migration/Cargo.toml | 37 - contracts/snip20_migration/Makefile | 68 - contracts/snip20_migration/src/contract.rs | 175 - contracts/snip20_migration/src/lib.rs | 5 - contracts/snip20_migration/src/storage.rs | 10 - contracts/snip20_migration/src/test.rs | 271 - .../snip20_migration/tests/integration.rs | 111 - launch/Cargo.toml | 41 - makefile | 23 +- packages/multi_test/Cargo.toml | 43 +- packages/multi_test/src/interfaces/dao.rs | 490 -- packages/multi_test/src/interfaces/mod.rs | 32 +- .../multi_test/src/interfaces/scrt_staking.rs | 83 - packages/multi_test/src/interfaces/snip20.rs | 186 - .../multi_test/src/interfaces/treasury.rs | 469 -- .../src/interfaces/treasury_manager.rs | 667 --- packages/multi_test/src/interfaces/utils.rs | 5 - packages/multi_test/src/multi.rs | 252 +- packages/shade_protocol/Cargo.toml | 3 +- .../contract_interfaces/airdrop/account.rs | 20 +- .../contract_interfaces/airdrop/claim_info.rs | 14 +- .../src/contract_interfaces/airdrop/errors.rs | 53 +- .../src/contract_interfaces/airdrop/mod.rs | 106 +- packages/shade_protocol/src/schemas.rs | 11 +- temp-contracts/liability_mint/.cargo/config | 5 - .../liability_mint/.circleci/config.yml | 52 - temp-contracts/liability_mint/Cargo.toml | 39 - temp-contracts/liability_mint/Makefile | 68 - temp-contracts/liability_mint/README.md | 215 - temp-contracts/liability_mint/src/contract.rs | 71 - temp-contracts/liability_mint/src/execute.rs | 311 -- temp-contracts/liability_mint/src/lib.rs | 4 - temp-contracts/liability_mint/src/query.rs | 39 - temp-contracts/liability_mint/src/storage.rs | 12 - .../liability_mint/tests/integration.rs | 195 - tools/doc2book/Cargo.toml | 11 - tools/doc2book/README.md | 59 - tools/doc2book/src/doc2book.rs | 326 -- tools/multisig/broadcast_multi.sh | 28 - tools/multisig/sign_mutli.sh | 11 - tools/multisig/sign_permit.py | 36 - tools/multisig/wasm_msg.py | 25 - 403 files changed, 654 insertions(+), 71991 deletions(-) delete mode 100644 Shade_Protocol_Responsible_Disclosure.md delete mode 100644 archived-contracts/bonds/.cargo/config delete mode 100644 archived-contracts/bonds/.circleci/config.yml delete mode 100644 archived-contracts/bonds/Cargo.toml delete mode 100644 archived-contracts/bonds/Makefile delete mode 100644 archived-contracts/bonds/README.md delete mode 100644 archived-contracts/bonds/src/contract.rs delete mode 100644 archived-contracts/bonds/src/handle.rs delete mode 100644 archived-contracts/bonds/src/lib.rs delete mode 100644 archived-contracts/bonds/src/query.rs delete mode 100644 archived-contracts/bonds/src/state.rs delete mode 100644 archived-contracts/bonds/src/test.rs delete mode 100644 archived-contracts/bonds/src/tests/handle.rs delete mode 100644 archived-contracts/bonds/src/tests/mod.rs delete mode 100644 archived-contracts/bonds/src/tests/query.rs delete mode 100644 archived-contracts/dao/lp_shdswap/.cargo/config delete mode 100644 archived-contracts/dao/lp_shdswap/.circleci/config.yml delete mode 100644 archived-contracts/dao/lp_shdswap/Cargo.toml delete mode 100644 archived-contracts/dao/lp_shdswap/Makefile delete mode 100644 archived-contracts/dao/lp_shdswap/README.md delete mode 100644 archived-contracts/dao/lp_shdswap/src/contract.rs delete mode 100644 archived-contracts/dao/lp_shdswap/src/execute.rs delete mode 100644 archived-contracts/dao/lp_shdswap/src/lib.rs delete mode 100644 archived-contracts/dao/lp_shdswap/src/query.rs delete mode 100644 archived-contracts/dao/lp_shdswap/src/storage.rs delete mode 100644 archived-contracts/dao/lp_shdswap/src/test.rs delete mode 100644 archived-contracts/dao/rewards_emission/.cargo/config delete mode 100644 archived-contracts/dao/rewards_emission/.circleci/config.yml delete mode 100644 archived-contracts/dao/rewards_emission/Cargo.toml delete mode 100644 archived-contracts/dao/rewards_emission/Makefile delete mode 100644 archived-contracts/dao/rewards_emission/README.md delete mode 100644 archived-contracts/dao/rewards_emission/src/contract.rs delete mode 100644 archived-contracts/dao/rewards_emission/src/execute.rs delete mode 100644 archived-contracts/dao/rewards_emission/src/lib.rs delete mode 100644 archived-contracts/dao/rewards_emission/src/query.rs delete mode 100644 archived-contracts/dao/rewards_emission/src/storage.rs delete mode 100644 archived-contracts/dao/rewards_emission/src/test.rs delete mode 100644 archived-contracts/governance/.cargo/config delete mode 100644 archived-contracts/governance/.circleci/config.yml delete mode 100644 archived-contracts/governance/Cargo.toml delete mode 100644 archived-contracts/governance/Makefile delete mode 100644 archived-contracts/governance/src/contract.rs delete mode 100644 archived-contracts/governance/src/handle/assembly.rs delete mode 100644 archived-contracts/governance/src/handle/assembly_msg.rs delete mode 100644 archived-contracts/governance/src/handle/contract.rs delete mode 100644 archived-contracts/governance/src/handle/migration.rs delete mode 100644 archived-contracts/governance/src/handle/mod.rs delete mode 100644 archived-contracts/governance/src/handle/profile.rs delete mode 100644 archived-contracts/governance/src/handle/proposal.rs delete mode 100644 archived-contracts/governance/src/lib.rs delete mode 100644 archived-contracts/governance/src/query.rs delete mode 100644 archived-contracts/governance/src/tests/handle/assembly.rs delete mode 100644 archived-contracts/governance/src/tests/handle/assembly_msg.rs delete mode 100644 archived-contracts/governance/src/tests/handle/contract.rs delete mode 100644 archived-contracts/governance/src/tests/handle/migration.rs delete mode 100644 archived-contracts/governance/src/tests/handle/mod.rs delete mode 100644 archived-contracts/governance/src/tests/handle/profile.rs delete mode 100644 archived-contracts/governance/src/tests/handle/proposal/assembly_voting.rs delete mode 100644 archived-contracts/governance/src/tests/handle/proposal/funding.rs delete mode 100644 archived-contracts/governance/src/tests/handle/proposal/mod.rs delete mode 100644 archived-contracts/governance/src/tests/handle/proposal/voting.rs delete mode 100644 archived-contracts/governance/src/tests/handle/runstate.rs delete mode 100644 archived-contracts/governance/src/tests/mod.rs delete mode 100644 archived-contracts/governance/src/tests/query/mod.rs delete mode 100644 archived-contracts/governance/src/tests/query/public.rs delete mode 100644 archived-contracts/governance/src/tests/query/user.rs delete mode 100644 archived-contracts/mint/.cargo/config delete mode 100644 archived-contracts/mint/.circleci/config.yml delete mode 100644 archived-contracts/mint/Cargo.toml delete mode 100644 archived-contracts/mint/Makefile delete mode 100644 archived-contracts/mint/README.md delete mode 100644 archived-contracts/mint/src/contract.rs delete mode 100644 archived-contracts/mint/src/handle.rs delete mode 100644 archived-contracts/mint/src/lib.rs delete mode 100644 archived-contracts/mint/src/query.rs delete mode 100644 archived-contracts/mint/src/state.rs delete mode 100644 archived-contracts/mint/tests/integration.rs delete mode 100644 archived-contracts/mint/tests/unit.rs delete mode 100644 archived-contracts/mint_router/.cargo/config delete mode 100644 archived-contracts/mint_router/.circleci/config.yml delete mode 100644 archived-contracts/mint_router/Cargo.toml delete mode 100644 archived-contracts/mint_router/Makefile delete mode 100644 archived-contracts/mint_router/README.md delete mode 100644 archived-contracts/mint_router/src/contract.rs delete mode 100644 archived-contracts/mint_router/src/handle.rs delete mode 100644 archived-contracts/mint_router/src/lib.rs delete mode 100644 archived-contracts/mint_router/src/query.rs delete mode 100644 archived-contracts/mint_router/src/state.rs delete mode 100644 archived-contracts/mint_router/src/test.rs delete mode 100644 archived-contracts/mock_band/.cargo/config delete mode 100644 archived-contracts/mock_band/.circleci/config.yml delete mode 100644 archived-contracts/mock_band/Cargo.toml delete mode 100644 archived-contracts/mock_band/Makefile delete mode 100644 archived-contracts/mock_band/README.md delete mode 100644 archived-contracts/mock_band/src/contract.rs delete mode 100644 archived-contracts/mock_band/src/execute.rs delete mode 100644 archived-contracts/mock_band/src/lib.rs delete mode 100644 archived-contracts/mock_secretswap_pair/.cargo/config delete mode 100644 archived-contracts/mock_secretswap_pair/.circleci/config.yml delete mode 100644 archived-contracts/mock_secretswap_pair/Cargo.toml delete mode 100644 archived-contracts/mock_secretswap_pair/Makefile delete mode 100644 archived-contracts/mock_secretswap_pair/README.md delete mode 100644 archived-contracts/mock_secretswap_pair/src/contract.rs delete mode 100644 archived-contracts/mock_secretswap_pair/src/lib.rs delete mode 100644 archived-contracts/mock_sienna_pair/Cargo.toml delete mode 100644 archived-contracts/mock_sienna_pair/src/contract.rs delete mode 100644 archived-contracts/oracle/.cargo/config delete mode 100644 archived-contracts/oracle/.circleci/config.yml delete mode 100644 archived-contracts/oracle/Cargo.toml delete mode 100644 archived-contracts/oracle/Makefile delete mode 100644 archived-contracts/oracle/README.md delete mode 100644 archived-contracts/oracle/src/contract.rs delete mode 100644 archived-contracts/oracle/src/handle.rs delete mode 100644 archived-contracts/oracle/src/lib.rs delete mode 100644 archived-contracts/oracle/src/query.rs delete mode 100644 archived-contracts/oracle/src/state.rs delete mode 100644 archived-contracts/oracle/src/test.rs delete mode 100644 archived-contracts/oracle/tests/integration.rs delete mode 100644 archived-contracts/peg_stability/Cargo.toml delete mode 100644 archived-contracts/peg_stability/Makefile delete mode 100644 archived-contracts/peg_stability/src/contract.rs delete mode 100644 archived-contracts/peg_stability/src/handle.rs delete mode 100644 archived-contracts/peg_stability/src/lib.rs delete mode 100644 archived-contracts/peg_stability/src/query.rs delete mode 100644 archived-contracts/peg_stability/src/tests/handle.rs delete mode 100644 archived-contracts/peg_stability/src/tests/mod.rs delete mode 100644 archived-contracts/peg_stability/src/tests/query.rs delete mode 100644 archived-contracts/sky/.cargo/config delete mode 100644 archived-contracts/sky/.circleci/config.yml delete mode 100644 archived-contracts/sky/Cargo.toml delete mode 100644 archived-contracts/sky/Makefile delete mode 100644 archived-contracts/sky/src/contract.rs delete mode 100644 archived-contracts/sky/src/execute.rs delete mode 100644 archived-contracts/sky/src/lib.rs delete mode 100644 archived-contracts/sky/src/query.rs delete mode 100644 archived-contracts/sky/tests/integration.rs delete mode 100644 archived-contracts/snip20_staking/.cargo/config delete mode 100644 archived-contracts/snip20_staking/.circleci/config.yml delete mode 100644 archived-contracts/snip20_staking/Cargo.toml delete mode 100644 archived-contracts/snip20_staking/README.md delete mode 100644 archived-contracts/snip20_staking/src/batch.rs delete mode 100644 archived-contracts/snip20_staking/src/contract.rs delete mode 100644 archived-contracts/snip20_staking/src/distributors.rs delete mode 100644 archived-contracts/snip20_staking/src/expose_balance.rs delete mode 100644 archived-contracts/snip20_staking/src/lib.rs delete mode 100644 archived-contracts/snip20_staking/src/msg.rs delete mode 100644 archived-contracts/snip20_staking/src/rand.rs delete mode 100644 archived-contracts/snip20_staking/src/receiver.rs delete mode 100644 archived-contracts/snip20_staking/src/stake.rs delete mode 100644 archived-contracts/snip20_staking/src/stake_queries.rs delete mode 100644 archived-contracts/snip20_staking/src/state.rs delete mode 100644 archived-contracts/snip20_staking/src/state_staking.rs delete mode 100644 archived-contracts/snip20_staking/src/transaction_history.rs delete mode 100644 archived-contracts/snip20_staking/src/utils.rs delete mode 100644 archived-contracts/snip20_staking/src/viewing_key.rs delete mode 100644 archived_packages/network_integration/src/testnet_airdrop.rs delete mode 100644 contractlib/oraclelib.py delete mode 100644 contractlib/treasurylib.py delete mode 100644 contracts/Staking Deep Dive delete mode 100644 contracts/Staking Overview delete mode 100644 contracts/admin/.cargo/config delete mode 100644 contracts/admin/.circleci/config.yml delete mode 100644 contracts/admin/Cargo.toml delete mode 100644 contracts/admin/Makefile delete mode 100644 contracts/admin/README.md delete mode 100644 contracts/admin/src/contract.rs delete mode 100644 contracts/admin/src/execute.rs delete mode 100644 contracts/admin/src/lib.rs delete mode 100644 contracts/admin/src/query.rs delete mode 100644 contracts/admin/src/shared.rs delete mode 100644 contracts/admin/src/test.rs delete mode 100644 contracts/basic_staking/.cargo/config delete mode 100644 contracts/basic_staking/.circleci/config.yml delete mode 100644 contracts/basic_staking/Cargo.toml delete mode 100644 contracts/basic_staking/Makefile delete mode 100644 contracts/basic_staking/README.md delete mode 100644 contracts/basic_staking/src/contract.rs delete mode 100644 contracts/basic_staking/src/execute.rs delete mode 100644 contracts/basic_staking/src/lib.rs delete mode 100644 contracts/basic_staking/src/query.rs delete mode 100644 contracts/basic_staking/src/storage.rs delete mode 100644 contracts/basic_staking/tests/bad_stake_token.rs delete mode 100644 contracts/basic_staking/tests/end_reward_pool_after_end_claimed.rs delete mode 100644 contracts/basic_staking/tests/end_reward_pool_after_end_unclaimed.rs delete mode 100644 contracts/basic_staking/tests/end_reward_pool_after_end_unclaimed_force.rs delete mode 100644 contracts/basic_staking/tests/end_reward_pool_before_end_claimed.rs delete mode 100644 contracts/basic_staking/tests/end_reward_pool_before_end_unclaimed.rs delete mode 100644 contracts/basic_staking/tests/end_reward_pool_before_start.rs delete mode 100644 contracts/basic_staking/tests/multi_staker.rs delete mode 100644 contracts/basic_staking/tests/non_admin_access.rs delete mode 100644 contracts/basic_staking/tests/non_stake_rewards.rs delete mode 100644 contracts/basic_staking/tests/single_staker.rs delete mode 100644 contracts/basic_staking/tests/single_staker_compounding.rs delete mode 100644 contracts/basic_staking/tests/stake_0_total_bug.rs delete mode 100644 contracts/basic_staking/tests/transfer_stake.rs delete mode 100644 contracts/basic_staking/tests/transfer_stake_compound.rs delete mode 100644 contracts/basic_staking/tests/transfer_stake_sender_no_stake.rs delete mode 100644 contracts/basic_staking/tests/unbonding_withdrawals.rs delete mode 100644 contracts/basic_staking/tests/unstake_softlock_bug.rs delete mode 100644 contracts/basic_staking/tests/update_config.rs delete mode 100644 contracts/dao/scrt_staking/.cargo/config delete mode 100644 contracts/dao/scrt_staking/.circleci/config.yml delete mode 100644 contracts/dao/scrt_staking/Cargo.toml delete mode 100644 contracts/dao/scrt_staking/Makefile delete mode 100644 contracts/dao/scrt_staking/README.md delete mode 100644 contracts/dao/scrt_staking/src/contract.rs delete mode 100644 contracts/dao/scrt_staking/src/execute.rs delete mode 100644 contracts/dao/scrt_staking/src/lib.rs delete mode 100644 contracts/dao/scrt_staking/src/query.rs delete mode 100644 contracts/dao/scrt_staking/src/storage.rs delete mode 100644 contracts/dao/scrt_staking/src/test.rs delete mode 100644 contracts/dao/scrt_staking/tests/integration.rs delete mode 100644 contracts/dao/scrt_staking/tests/unit.rs delete mode 100644 contracts/dao/stkd_scrt/.cargo/config delete mode 100644 contracts/dao/stkd_scrt/.circleci/config.yml delete mode 100644 contracts/dao/stkd_scrt/Cargo.toml delete mode 100644 contracts/dao/stkd_scrt/Makefile delete mode 100644 contracts/dao/stkd_scrt/README.md delete mode 100644 contracts/dao/stkd_scrt/src/contract.rs delete mode 100644 contracts/dao/stkd_scrt/src/execute.rs delete mode 100644 contracts/dao/stkd_scrt/src/lib.rs delete mode 100644 contracts/dao/stkd_scrt/src/query.rs delete mode 100644 contracts/dao/stkd_scrt/src/storage.rs delete mode 100644 contracts/dao/stkd_scrt/src/test.rs delete mode 100644 contracts/dao/stkd_scrt/tests/integration.rs delete mode 100644 contracts/dao/stkd_scrt/tests/unit.rs delete mode 100644 contracts/dao/treasury/.cargo/config delete mode 100644 contracts/dao/treasury/.circleci/config.yml delete mode 100644 contracts/dao/treasury/Cargo.toml delete mode 100644 contracts/dao/treasury/Makefile delete mode 100644 contracts/dao/treasury/README.md delete mode 100644 contracts/dao/treasury/Untitled Diagram.drawio delete mode 100644 contracts/dao/treasury/src/contract.rs delete mode 100644 contracts/dao/treasury/src/execute.rs delete mode 100644 contracts/dao/treasury/src/lib.rs delete mode 100644 contracts/dao/treasury/src/query.rs delete mode 100644 contracts/dao/treasury/src/storage.rs delete mode 100644 contracts/dao/treasury/tests/dao/equilibrium.rs delete mode 100644 contracts/dao/treasury/tests/dao/gains_losses.rs delete mode 100644 contracts/dao/treasury/tests/dao/mod.rs delete mode 100644 contracts/dao/treasury/tests/integration/allowance.rs delete mode 100644 contracts/dao/treasury/tests/integration/allowance_delay_refresh.rs delete mode 100644 contracts/dao/treasury/tests/integration/batch.rs delete mode 100644 contracts/dao/treasury/tests/integration/config.rs delete mode 100644 contracts/dao/treasury/tests/integration/execute_errors.rs delete mode 100644 contracts/dao/treasury/tests/integration/migration.rs delete mode 100644 contracts/dao/treasury/tests/integration/mod.rs delete mode 100644 contracts/dao/treasury/tests/integration/non_manager_allowances.rs delete mode 100644 contracts/dao/treasury/tests/integration/query.rs delete mode 100644 contracts/dao/treasury/tests/integration/scrt_staking.rs delete mode 100644 contracts/dao/treasury/tests/integration/tolerance.rs delete mode 100644 contracts/dao/treasury/tests/integration/treasury.rs delete mode 100644 contracts/dao/treasury/tests/integration/wrap.rs delete mode 100644 contracts/dao/treasury/tests/mod.rs delete mode 100644 contracts/dao/treasury_manager/.cargo/config delete mode 100644 contracts/dao/treasury_manager/.circleci/config.yml delete mode 100644 contracts/dao/treasury_manager/Cargo.toml delete mode 100644 contracts/dao/treasury_manager/Makefile delete mode 100644 contracts/dao/treasury_manager/README.md delete mode 100644 contracts/dao/treasury_manager/src/contract.rs delete mode 100644 contracts/dao/treasury_manager/src/execute.rs delete mode 100644 contracts/dao/treasury_manager/src/lib.rs delete mode 100644 contracts/dao/treasury_manager/src/query.rs delete mode 100644 contracts/dao/treasury_manager/src/storage.rs delete mode 100644 contracts/dao/treasury_manager/tests/integration/batch.rs delete mode 100644 contracts/dao/treasury_manager/tests/integration/config.rs delete mode 100644 contracts/dao/treasury_manager/tests/integration/execute_error.rs delete mode 100644 contracts/dao/treasury_manager/tests/integration/holder_integration.rs delete mode 100644 contracts/dao/treasury_manager/tests/integration/mod.rs delete mode 100644 contracts/dao/treasury_manager/tests/integration/multiple_holders.rs delete mode 100644 contracts/dao/treasury_manager/tests/integration/query.rs delete mode 100644 contracts/dao/treasury_manager/tests/integration/scrt_staking_integration.rs delete mode 100644 contracts/dao/treasury_manager/tests/integration/tm_unbond.rs delete mode 100644 contracts/dao/treasury_manager/tests/integration/tolerance.rs delete mode 100644 contracts/dao/treasury_manager/tests/mod.rs delete mode 100644 contracts/mock/mock_adapter/.cargo/config delete mode 100644 contracts/mock/mock_adapter/.circleci/config.yml delete mode 100644 contracts/mock/mock_adapter/Cargo.toml delete mode 100644 contracts/mock/mock_adapter/Makefile delete mode 100644 contracts/mock/mock_adapter/README.md delete mode 100644 contracts/mock/mock_adapter/src/contract.rs delete mode 100644 contracts/mock/mock_adapter/src/execute.rs delete mode 100644 contracts/mock/mock_adapter/src/lib.rs delete mode 100644 contracts/mock/mock_adapter/src/storage.rs delete mode 100644 contracts/mock/mock_sienna_pair/.cargo/config delete mode 100644 contracts/mock/mock_sienna_pair/.circleci/config.yml delete mode 100644 contracts/mock/mock_sienna_pair/Cargo.toml delete mode 100644 contracts/mock/mock_sienna_pair/Makefile delete mode 100644 contracts/mock/mock_sienna_pair/README.md delete mode 100644 contracts/mock/mock_sienna_pair/src/contract.rs delete mode 100644 contracts/mock/mock_sienna_pair/src/lib.rs delete mode 100644 contracts/mock/mock_stkd_derivative/Cargo.toml delete mode 100644 contracts/mock/mock_stkd_derivative/Makefile delete mode 100644 contracts/mock/mock_stkd_derivative/README.md delete mode 100644 contracts/mock/mock_stkd_derivative/src/contract.rs delete mode 100644 contracts/mock/mock_stkd_derivative/src/lib.rs delete mode 100644 contracts/mock/mock_stkd_derivative/src/tests.rs delete mode 100644 contracts/query_auth/.cargo/config delete mode 100644 contracts/query_auth/.circleci/config.yml delete mode 100644 contracts/query_auth/Cargo.toml delete mode 100644 contracts/query_auth/README.md delete mode 100644 contracts/query_auth/src/contract.rs delete mode 100644 contracts/query_auth/src/handle.rs delete mode 100644 contracts/query_auth/src/lib.rs delete mode 100644 contracts/query_auth/src/query.rs delete mode 100644 contracts/query_auth/src/tests/handle.rs delete mode 100644 contracts/query_auth/src/tests/mod.rs delete mode 100644 contracts/query_auth/src/tests/query.rs delete mode 100644 contracts/snip20/.cargo/config delete mode 100644 contracts/snip20/.circleci/config.yml delete mode 100644 contracts/snip20/Cargo.toml delete mode 100644 contracts/snip20/Makefile delete mode 100644 contracts/snip20/src/batch.rs delete mode 100644 contracts/snip20/src/contract.rs delete mode 100644 contracts/snip20/src/handle/allowance.rs delete mode 100644 contracts/snip20/src/handle/burning.rs delete mode 100644 contracts/snip20/src/handle/minting.rs delete mode 100644 contracts/snip20/src/handle/mod.rs delete mode 100644 contracts/snip20/src/handle/transfers.rs delete mode 100644 contracts/snip20/src/lib.rs delete mode 100644 contracts/snip20/src/query.rs delete mode 100644 contracts/snip20/src/tests/handle/allowance.rs delete mode 100644 contracts/snip20/src/tests/handle/burn.rs delete mode 100644 contracts/snip20/src/tests/handle/mint.rs delete mode 100644 contracts/snip20/src/tests/handle/mod.rs delete mode 100644 contracts/snip20/src/tests/handle/transfer.rs delete mode 100644 contracts/snip20/src/tests/handle/wrap.rs delete mode 100644 contracts/snip20/src/tests/mod.rs delete mode 100644 contracts/snip20/src/tests/query/mod.rs delete mode 100644 contracts/snip20/src/tests/query/public.rs delete mode 100644 contracts/snip20/src/tests/query/user.rs delete mode 100644 contracts/snip20_derivative/.cargo/config delete mode 100644 contracts/snip20_derivative/.circleci/config.yml delete mode 100644 contracts/snip20_derivative/.editorconfig delete mode 100644 contracts/snip20_derivative/.github/workflows/cicd.yaml delete mode 100644 contracts/snip20_derivative/.images/engineering-diagram.png delete mode 100644 contracts/snip20_derivative/Cargo.toml delete mode 100644 contracts/snip20_derivative/Developing.md delete mode 100644 contracts/snip20_derivative/Importing.md delete mode 100644 contracts/snip20_derivative/LICENSE delete mode 100644 contracts/snip20_derivative/Makefile delete mode 100644 contracts/snip20_derivative/NOTICE delete mode 100644 contracts/snip20_derivative/Publishing.md delete mode 100644 contracts/snip20_derivative/README.md delete mode 100644 contracts/snip20_derivative/examples/schema.rs delete mode 100644 contracts/snip20_derivative/src/contract.rs delete mode 100644 contracts/snip20_derivative/src/lib.rs delete mode 100644 contracts/snip20_derivative/src/msg.rs delete mode 100644 contracts/snip20_derivative/src/staking_interface.rs delete mode 100644 contracts/snip20_derivative/src/state.rs delete mode 100644 contracts/snip20_derivative/tests/integration.rs delete mode 100755 contracts/snip20_derivative/tests/integration.sh delete mode 100644 contracts/snip20_migration/Cargo.toml delete mode 100644 contracts/snip20_migration/Makefile delete mode 100644 contracts/snip20_migration/src/contract.rs delete mode 100644 contracts/snip20_migration/src/lib.rs delete mode 100644 contracts/snip20_migration/src/storage.rs delete mode 100644 contracts/snip20_migration/src/test.rs delete mode 100644 contracts/snip20_migration/tests/integration.rs delete mode 100644 launch/Cargo.toml delete mode 100644 packages/multi_test/src/interfaces/dao.rs delete mode 100644 packages/multi_test/src/interfaces/scrt_staking.rs delete mode 100644 packages/multi_test/src/interfaces/snip20.rs delete mode 100644 packages/multi_test/src/interfaces/treasury.rs delete mode 100644 packages/multi_test/src/interfaces/treasury_manager.rs delete mode 100644 temp-contracts/liability_mint/.cargo/config delete mode 100644 temp-contracts/liability_mint/.circleci/config.yml delete mode 100644 temp-contracts/liability_mint/Cargo.toml delete mode 100644 temp-contracts/liability_mint/Makefile delete mode 100644 temp-contracts/liability_mint/README.md delete mode 100644 temp-contracts/liability_mint/src/contract.rs delete mode 100644 temp-contracts/liability_mint/src/execute.rs delete mode 100644 temp-contracts/liability_mint/src/lib.rs delete mode 100644 temp-contracts/liability_mint/src/query.rs delete mode 100644 temp-contracts/liability_mint/src/storage.rs delete mode 100644 temp-contracts/liability_mint/tests/integration.rs delete mode 100644 tools/doc2book/Cargo.toml delete mode 100644 tools/doc2book/README.md delete mode 100644 tools/doc2book/src/doc2book.rs delete mode 100755 tools/multisig/broadcast_multi.sh delete mode 100755 tools/multisig/sign_mutli.sh delete mode 100644 tools/multisig/sign_permit.py delete mode 100644 tools/multisig/wasm_msg.py diff --git a/Cargo.lock b/Cargo.lock index 2310520..96c951b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,16 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "admin" -version = "0.2.0" -dependencies = [ - "rstest", - "secret-cosmwasm-std", - "shade-multi-test", - "shade-protocol", -] - [[package]] name = "ahash" version = "0.7.7" @@ -25,19 +15,20 @@ dependencies = [ [[package]] name = "airdrop" -version = "0.1.0" +version = "0.1.1" dependencies = [ "ethereum-verify", "hex", "rs_merkle", + "sha2 0.10.8", "shade-protocol", ] [[package]] name = "anyhow" -version = "1.0.76" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355" +checksum = "ca87830a3e3fb156dc96cfbd31cb620265dd053be734723f22b760d6cc3c3051" [[package]] name = "autocfg" @@ -63,39 +54,18 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" -[[package]] -name = "base64" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" - [[package]] name = "base64ct" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "basic_staking" -version = "0.1.0" -dependencies = [ - "secret-cosmwasm-std", - "shade-multi-test", - "shade-protocol", -] - [[package]] name = "bech32" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" -[[package]] -name = "bech32" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" - [[package]] name = "bincode2" version = "2.0.1" @@ -213,20 +183,8 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0df41ea55f2946b6b43579659eec048cc2f66e8c8e2e3652fc5e5e476f673856" dependencies = [ - "cosmwasm-schema-derive 1.5.0", - "schemars 0.8.16", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "cosmwasm-schema" -version = "2.0.0-beta.0" -source = "git+https://github.com/CosmWasm/cosmwasm#1ce73c6b91466fc46e8b574e381cdaa1fb0b298e" -dependencies = [ - "cosmwasm-schema-derive 2.0.0-beta.0", - "schemars 0.8.16", + "cosmwasm-schema-derive", + "schemars", "serde", "serde_json", "thiserror", @@ -243,16 +201,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "cosmwasm-schema-derive" -version = "2.0.0-beta.0" -source = "git+https://github.com/CosmWasm/cosmwasm#1ce73c6b91466fc46e8b574e381cdaa1fb0b298e" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "cpufeatures" version = "0.2.11" @@ -407,7 +355,7 @@ dependencies = [ name = "ethereum-verify" version = "0.1.0" dependencies = [ - "cosmwasm-schema 1.5.0", + "cosmwasm-schema", "hex", "secret-cosmwasm-std", "sha2 0.10.8", @@ -430,101 +378,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" -[[package]] -name = "futures" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" - -[[package]] -name = "futures-executor" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" - -[[package]] -name = "futures-macro" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.42", -] - -[[package]] -name = "futures-sink" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" - -[[package]] -name = "futures-task" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" - -[[package]] -name = "futures-timer" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" - -[[package]] -name = "futures-util" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -629,41 +482,6 @@ version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" -[[package]] -name = "memchr" -version = "2.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" - -[[package]] -name = "mock_adapter" -version = "0.1.0" -dependencies = [ - "cosmwasm-schema 2.0.0-beta.0", - "schemars 0.8.16", - "serde", - "shade-multi-test", - "shade-protocol", -] - -[[package]] -name = "mock_sienna_pair" -version = "0.1.0" -dependencies = [ - "cosmwasm-schema 1.5.0", - "shade-protocol", -] - -[[package]] -name = "mock_stkd_derivative" -version = "0.1.0" -dependencies = [ - "cosmwasm-schema 1.5.0", - "mock_sienna_pair", - "shade-multi-test", - "shade-protocol", -] - [[package]] name = "multi-derive" version = "0.1.0" @@ -708,18 +526,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "pin-project-lite" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pkcs8" version = "0.9.0" @@ -738,9 +544,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.71" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +checksum = "2dd5e8a1f1029c43224ad5898e50140c2aebb1705f19e67c918ebf5b9e797fe1" dependencies = [ "unicode-ident", ] @@ -773,33 +579,23 @@ name = "query-authentication" version = "0.1.0" source = "git+https://github.com/securesecrets/query-authentication?branch=cosmwasm_v1_upgrade#473dae556a10a811a4a55563813df9107b18781a" dependencies = [ - "bech32 0.8.1", - "cosmwasm-schema 1.5.0", + "bech32", + "cosmwasm-schema", "remain", "ripemd160", - "schemars 0.8.16", - "secp256k1 0.20.3", + "schemars", + "secp256k1", "secret-cosmwasm-std", "serde", "sha2 0.9.9", "thiserror", ] -[[package]] -name = "query_auth" -version = "0.1.0" -dependencies = [ - "schemars 0.7.6", - "secret-cosmwasm-std", - "shade-multi-test", - "shade-protocol", -] - [[package]] name = "quote" -version = "1.0.33" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "22a37c9326af5ed140c86a46655b5278de879853be5573c01df185b6f49a580a" dependencies = [ "proc-macro2", ] @@ -858,7 +654,7 @@ checksum = "bce3a7139d2ee67d07538ee5dba997364fbc243e7e7143e96eb830c74bfaa082" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn 2.0.44", ] [[package]] @@ -872,15 +668,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ripemd" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" -dependencies = [ - "digest 0.10.7", -] - [[package]] name = "ripemd160" version = "0.9.1" @@ -900,40 +687,6 @@ dependencies = [ "sha2 0.9.9", ] -[[package]] -name = "rstest" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c9dc66cc29792b663ffb5269be669f1613664e69ad56441fdb895c2347b930" -dependencies = [ - "futures", - "futures-timer", - "rstest_macros", - "rustc_version", -] - -[[package]] -name = "rstest_macros" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5015e68a0685a95ade3eee617ff7101ab6a3fc689203101ca16ebc16f2b89c66" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "rustc_version", - "syn 1.0.109", -] - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "rustversion" version = "1.0.14" @@ -946,17 +699,6 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" -[[package]] -name = "schemars" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be77ed66abed6954aabf6a3e31a84706bedbf93750d267e92ef4a6d90bbd6a61" -dependencies = [ - "schemars_derive 0.7.6", - "serde", - "serde_json", -] - [[package]] name = "schemars" version = "0.8.16" @@ -964,23 +706,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" dependencies = [ "dyn-clone", - "schemars_derive 0.8.16", + "schemars_derive", "serde", "serde_json", ] -[[package]] -name = "schemars_derive" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11af7a475c9ee266cfaa9e303a47c830ebe072bf3101ab907a7b7b9d816fa01d" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals 0.25.0", - "syn 1.0.109", -] - [[package]] name = "schemars_derive" version = "0.8.16" @@ -989,19 +719,10 @@ checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" dependencies = [ "proc-macro2", "quote", - "serde_derive_internals 0.26.0", + "serde_derive_internals", "syn 1.0.109", ] -[[package]] -name = "scrt_staking" -version = "0.1.0" -dependencies = [ - "secret-cosmwasm-std", - "shade-multi-test", - "shade-protocol", -] - [[package]] name = "sec1" version = "0.3.0" @@ -1022,16 +743,7 @@ version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" dependencies = [ - "secp256k1-sys 0.4.2", -] - -[[package]] -name = "secp256k1" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" -dependencies = [ - "secp256k1-sys 0.8.1", + "secp256k1-sys", ] [[package]] @@ -1043,15 +755,6 @@ dependencies = [ "cc", ] -[[package]] -name = "secp256k1-sys" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" -dependencies = [ - "cc", -] - [[package]] name = "secret-cosmwasm-crypto" version = "1.1.11" @@ -1076,7 +779,7 @@ dependencies = [ "derivative", "forward_ref", "hex", - "schemars 0.8.16", + "schemars", "secret-cosmwasm-crypto", "serde", "serde-json-wasm", @@ -1104,7 +807,7 @@ dependencies = [ "itertools", "nanoid", "prost", - "schemars 0.8.16", + "schemars", "secret-cosmwasm-std", "secret-storage-plus 0.13.4 (git+https://github.com/securesecrets/secret-plus-utils)", "secret-utils", @@ -1118,7 +821,7 @@ version = "0.13.4" source = "git+https://github.com/securesecrets/secret-plus-utils?tag=v0.1.1#96438a5bf7f1fb0acc540fe3a43e934f01e6711f" dependencies = [ "bincode2", - "schemars 0.8.16", + "schemars", "secret-cosmwasm-std", "serde", ] @@ -1129,129 +832,9 @@ version = "0.13.4" source = "git+https://github.com/securesecrets/secret-plus-utils#96438a5bf7f1fb0acc540fe3a43e934f01e6711f" dependencies = [ "bincode2", - "schemars 0.8.16", - "secret-cosmwasm-std", - "serde", -] - -[[package]] -name = "secret-toolkit" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "338c972c0a98de51ccbb859312eb7672bc64b9050b086f058748ba26a509edbb" -dependencies = [ - "secret-toolkit-crypto", - "secret-toolkit-permit", - "secret-toolkit-serialization", - "secret-toolkit-snip20", - "secret-toolkit-snip721", - "secret-toolkit-storage", - "secret-toolkit-utils", - "secret-toolkit-viewing-key", -] - -[[package]] -name = "secret-toolkit-crypto" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003d7d5772c67f2240b7f298f96eb73a8a501916fe18c1d730ebfd591bf7e519" -dependencies = [ - "rand_chacha 0.3.1", - "rand_core 0.6.4", - "secp256k1 0.27.0", - "secret-cosmwasm-std", - "sha2 0.10.8", -] - -[[package]] -name = "secret-toolkit-permit" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4330571400b5959450fa37040609e6804a147d83f606783506bc2275f1527712" -dependencies = [ - "bech32 0.9.1", - "remain", - "ripemd", - "schemars 0.8.16", - "secret-cosmwasm-std", - "secret-toolkit-crypto", - "serde", -] - -[[package]] -name = "secret-toolkit-serialization" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "890adaeaa710f9f7068a807eb1553edc8c30ce9907290895c9097dd642fc613b" -dependencies = [ - "bincode2", - "schemars 0.8.16", - "secret-cosmwasm-std", - "serde", -] - -[[package]] -name = "secret-toolkit-snip20" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8144a11df9a75adf42acd645f938eb2c1ab508d6672033eb84b1e672247d2f05" -dependencies = [ - "schemars 0.8.16", - "secret-cosmwasm-std", - "secret-toolkit-utils", - "serde", -] - -[[package]] -name = "secret-toolkit-snip721" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2ab35fd2a52306f87ab8dceab75254cc1b87c95c43acf9e30c09372f0ee971" -dependencies = [ - "schemars 0.8.16", - "secret-cosmwasm-std", - "secret-toolkit-utils", - "serde", -] - -[[package]] -name = "secret-toolkit-storage" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55e8c5418af3e7ae1d1331c383b32d56c74a340dbc3b972d53555a768698f2a3" -dependencies = [ - "secret-cosmwasm-std", - "secret-cosmwasm-storage", - "secret-toolkit-serialization", - "serde", -] - -[[package]] -name = "secret-toolkit-utils" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f1cba2e70fd701e3dfc6072807c02eeeb9776bee49e346a9c7745d84ff40c8" -dependencies = [ - "schemars 0.8.16", - "secret-cosmwasm-std", - "secret-cosmwasm-storage", - "serde", -] - -[[package]] -name = "secret-toolkit-viewing-key" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d89a0b69fa9b12735a612fa30e6e7e48130943982f1783b7ddd5c46ed09e921" -dependencies = [ - "base64 0.21.5", - "schemars 0.8.16", + "schemars", "secret-cosmwasm-std", - "secret-cosmwasm-storage", - "secret-toolkit-crypto", - "secret-toolkit-utils", "serde", - "subtle", ] [[package]] @@ -1259,18 +842,12 @@ name = "secret-utils" version = "0.13.4" source = "git+https://github.com/securesecrets/secret-plus-utils#96438a5bf7f1fb0acc540fe3a43e934f01e6711f" dependencies = [ - "schemars 0.8.16", + "schemars", "secret-cosmwasm-std", "serde", "thiserror", ] -[[package]] -name = "semver" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" - [[package]] name = "serde" version = "1.0.193" @@ -1297,18 +874,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", -] - -[[package]] -name = "serde_derive_internals" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "syn 2.0.44", ] [[package]] @@ -1324,9 +890,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9" dependencies = [ "itoa", "ryu", @@ -1371,21 +937,9 @@ dependencies = [ name = "shade-multi-test" version = "0.1.0" dependencies = [ - "admin", "airdrop", - "basic_staking", - "mock_adapter", - "mock_sienna_pair", - "mock_stkd_derivative", "multi-derive", - "query_auth", - "scrt_staking", "shade-protocol", - "snip20", - "snip20_migration", - "stkd_scrt", - "treasury", - "treasury_manager", ] [[package]] @@ -1397,12 +951,12 @@ dependencies = [ "chrono", "const_format", "contract-derive", - "cosmwasm-schema 1.5.0", + "cosmwasm-schema", "query-authentication", "rand_chacha 0.2.2", "rand_core 0.5.1", "remain", - "schemars 0.8.16", + "schemars", "secret-cosmwasm-std", "secret-cosmwasm-storage", "secret-multi-test", @@ -1425,50 +979,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "snip20" -version = "0.1.0" -dependencies = [ - "secret-cosmwasm-std", - "shade-multi-test", - "shade-protocol", -] - -[[package]] -name = "snip20_derivative" -version = "1.0.0" -dependencies = [ - "base64 0.13.1", - "cosmwasm-schema 1.5.0", - "schemars 0.8.16", - "secret-cosmwasm-std", - "secret-cosmwasm-storage", - "secret-toolkit", - "secret-toolkit-crypto", - "serde", - "shade-protocol", -] - -[[package]] -name = "snip20_migration" -version = "0.1.0" -dependencies = [ - "rstest", - "schemars 0.7.6", - "serde_json", - "shade-multi-test", - "shade-protocol", -] - [[package]] name = "spki" version = "0.6.0" @@ -1485,14 +995,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "stkd_scrt" -version = "0.1.0" -dependencies = [ - "shade-multi-test", - "shade-protocol", -] - [[package]] name = "strum" version = "0.24.1" @@ -1531,9 +1033,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.42" +version = "2.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8" +checksum = "92d27c2c202598d05175a6dd3af46824b7f747f8d8e9b14c623f19fa5069735d" dependencies = [ "proc-macro2", "quote", @@ -1542,22 +1044,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.51" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +checksum = "b2cd5904763bad08ad5513ddbb12cf2ae273ca53fa9f68e843e236ec6dfccc09" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.51" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +checksum = "3dcf4a824cce0aeacd6f38ae6f24234c8e80d68632338ebaa1443b5df9e29e19" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn 2.0.44", ] [[package]] @@ -1571,30 +1073,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "treasury" -version = "0.1.0" -dependencies = [ - "mock_adapter", - "serde_json", - "shade-multi-test", - "shade-protocol", - "treasury", -] - -[[package]] -name = "treasury_manager" -version = "0.1.0" -dependencies = [ - "chrono", - "cosmwasm-schema 2.0.0-beta.0", - "itertools", - "mock_adapter", - "schemars 0.7.6", - "shade-multi-test", - "shade-protocol", -] - [[package]] name = "typenum" version = "1.17.0" diff --git a/Cargo.toml b/Cargo.toml index 5bbfb00..a1c05ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,33 +12,6 @@ members = [ # Network setups "contracts/airdrop", - # Protocol contracts - "contracts/snip20", - "contracts/query_auth", - "contracts/admin", - "contracts/basic_staking", - "contracts/snip20_migration", - - # Staking - "contracts/basic_staking", - "contracts/snip20_derivative", - - # DAO - # - Core - "contracts/dao/treasury", - "contracts/dao/treasury_manager", - # - Adapters - "contracts/dao/scrt_staking", - "contracts/dao/stkd_scrt", - # "contracts/dao/rewards_emission", - # "contracts/dao/lp_shdswap", - - # Mock contracts - # "contracts/mock/mock_band", //TODO: migrate to v1 - # "contracts/mock/mock_secretswap_pair", //TODO: migrate to v1 - "contracts/mock/mock_sienna_pair", - # "contracts/mock/mock_adapter", //TODO: migrate to v1 - "contracts/mock/mock_stkd_derivative", # Tools # "tools/doc2book", diff --git a/Makefile.toml b/Makefile.toml index ed806ac..c47498c 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -63,7 +63,7 @@ rm ./${CURRENT_CRATE}.wasm [tasks.schemas] workspace = false script = ''' -cargo run --bin schemas --features="snip20_migration admin airdrop bonds dao dex governance-impl mint oracles peg_stability query_auth sky snip20 staking mint_router" +cargo run --bin schemas --features=" airdrop" ''' # Testing diff --git a/README.md b/README.md index 856f068..b3bc8f7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ -# Private Headstash Distribution Claim +# Private Headstash Airdrop -This branch is a fork of the [Shade Protocol core contracts](https://github.com/securesecrets/shade). Our use case focuses only on a custmized version of the [Airdrop Contract](./contracts/airdrop/README.md), suited to our needs. Massive respect to the shade contributors. \ No newline at end of file +### InstantiateMsg + +### ExecuteMsg + +### QueryMsg + +## Unaudited fork of [Shade Protocol](https://shadeprotocol.io/) contracts. \ No newline at end of file diff --git a/Shade_Protocol_Responsible_Disclosure.md b/Shade_Protocol_Responsible_Disclosure.md deleted file mode 100644 index ab7303f..0000000 --- a/Shade_Protocol_Responsible_Disclosure.md +++ /dev/null @@ -1,286 +0,0 @@ -# Shade Protocol Responsible Disclosure Policy - -Shade Protocol is an interconnected suite of privacy preserving dApps built on the Secret Network whose smart contracts leverage the privacy preserving properties of secret contracts in order to empower private DeFi. The Shade Protocol Responsible Disclosure Framework establishes and defines the following for the Shade Security Policy: -- Vulnerability Severity Classification System (VSCS) -- Rewards By Threat Level -- Assets in Scope -- Out of Scope Work -- Proof of Concept (PoC) guidelines -- Rules of Engagement -- SLA Response Times -- Security Patch Policy and Procedure -- Rewards Payment Process - -For general information about reporting vulnerabilities, visit [security policy overview](./SECURITY.md). The Shade Protocol Responsible Disclosure Framework is subject to change at the discretion of the Shade Protocol core team. - - -## Bug Bounty Program Classes -- **Smart Contracts** -- **Website and Applications** -- **Infrastructure** - -## Vulnerability Severity Classification System - -### Smart Contracts - -#### **Critical** - -- Direct theft of any user funds -- Permanent Freezing of funds -- Protocol Insolvency -- Governance Voting Result Manipulation - -#### **High** - -- Theft of unclaimed yield -- Permanent freezing of unclaimed yield -- Temporary freezing of funds - -#### **Medium** -- Smart contract unable to operate due to lack of token funds -- Theft of gas -- Unbounded gas consumption -- Griefing (damage to users or protocol with no profit motive) - -#### **Low** -Contract fails to deliver promised returns, but doesn’t lose value - - -### Website and Applications - -#### **Critical** -- Taking down the website/application -- Direct theft of any user funds -- Execute arbitrary system commands -- Retrieve sensitive data from a server -- Taking state-modifying authenticated actions on behalf of other users with any interaction by that users. -- Subdomain takeover with already connected wallet interaction -- Malicious interactions with an already-connected wallet - -#### **High** -- Improperly disclosing confidential user information -- Changing sensitive details of other users (including modifying browser local storage) without already-connected wallet interaction. -- Injecting/modifying the static content on the target application without Javascript (Persistent) such as: - - HTML injection without Javascript - - Replacing existing text with arbitrary text - - Arbitrary file uploads, etc. - -#### **Medium** -- Injecting/modifying the static content on the target application without Javascript (Reflected) such as: - - Reflected HTML injection - - Loading external site data -- Redirecting users to malicious websites (Open Redirect) - -#### **Low** -- Taking over broken or expired outgoing links -- Disabling access to site for users -- Preventing connection to wallet -- Cookie bombing - - -### Infrastructure -#### **Critical** - - Access to any central keys address controlled by the project (e.g. private keys, seed phrases, etc.) - - Access to Central Database - -#### **High** - TBD - -#### **Medium** - TBD - -#### **Low** - TBD - -## Rewards by Threat Level - -### Smart Contract -| Vulnerability | Reward | Requirements | -| ------------ | ----------------- | --------------------- | -| Critical | ***Payout: Up to 40k SHD*** | PoC Requirement | -| High | Payout: Up to 5k SHD | PoC Requirement | -| Medium | Payout: Up to 1K SHD | PoC Requirement | -| Low | Payout: Up to 100 SHD | PoC Requirement | - - -### Website and applications - -| Vulnerability | Reward | Requirements | -| ------------ | ----------------- | --------------------- | -| Critical | ***Payout: Up to 10k SHD*** | PoC Requirement | -| High | Payout: Up to 1k SHD | PoC Requirement | -| Medium | Payout: Up to 100 SHD | PoC Requirement | -| Low | Payout: Up to 10 SHD | PoC Requirement | - -### Infrastructure - -| Vulnerability | Reward | Requirements | -| ------------ | ----------------- | --------------------- | -| Critical | ***Payout: Up to 10k SHD*** | PoC Requirement | -| High | Payout: Up to 1k SHD | PoC Requirement | -| Medium | Payout: Up to 100 SHD | PoC Requirement | -| Low | Payout: Up to 10 SHD | PoC Requirement | - - -Critical smart contract vulnerabilities are capped at 10% of economic damage, primarily taking into consideration the funds at risk. In cases of repeatable attacks, only the first attack is considered unless the smart contract cannot be upgraded or paused. High smart contract vulnerabilities will be capped at up to 100% of the funds affected. - -Critical website and application bug reports will be rewarded with 10k SHD only if the impact leads to a direct loss in funds or a manipulation of the votes or the voting result, as well as the modification of its display leading to a misrepresentation of the result or vote. All other impacts that would be classified as Critical would be rewarded 1K SHD. - -All calculations of the amount of funds at risk are done based on the time the bug report is submitted. - -### Response Times: - -| Action | Severity Level | Response Time | -| ------------ | ----------------- | --------------------- | -| Acknowledgment of report | Critical | 48 hours | -| | All severity levels except critical | 3-4 days (working days) | -| Processing of Report | Critical + High | Up to 14 days | -| | Medium + Low | Up to 14 days | -| Payout for valid reports | Critical + High | Within 14 days | -| | Medium + Low | Within 14 days | - - - -## Assets considered in scope: - -[Shade Protocol Audit Log](https://docs.shadeprotocol.io/shade-protocol/research/audit-log) - -### **Smart Contracts:** - -#### Shade Oracle - https://github.com/securesecrets/shade-oracle - -- https://github.com/securesecrets/shade-oracle/tree/release/contracts/oracle_router -- https://github.com/securesecrets/shade-oracle/tree/release/contracts/index_oracle -- https://github.com/securesecrets/shade-oracle/tree/release/contracts/shade_staking_derivatives_oracle -- https://github.com/securesecrets/shade-oracle/tree/release/contracts/shadeswap_market_oracle -- https://github.com/securesecrets/shade-oracle/tree/release/contracts/shadeswap_spot_oracle -- https://github.com/securesecrets/shade-oracle/tree/release/contracts/stride_staking_derivatives_oracle -- https://github.com/securesecrets/shade-oracle/tree/release/contracts/siennaswap_market_oracle - - - -#### ShadeSwap - https://github.com/securesecrets/shadeswap - -- https://github.com/securesecrets/shadeswap/tree/main/contracts/amm_pair -- https://github.com/securesecrets/shadeswap/tree/main/contracts/factory -- https://github.com/securesecrets/shadeswap/tree/main/contracts/lp_token -- https://github.com/securesecrets/shadeswap/tree/main/contracts/router -- https://github.com/securesecrets/shadeswap/tree/main/contracts/snip20 -- https://github.com/securesecrets/shadeswap/tree/main/contracts/staking - -#### Shade Protocol - https://github.com/securesecrets/shade - -- https://github.com/securesecrets/shade/blob/main/contracts/governance -- https://github.com/securesecrets/shade/blob/main/contracts/staking -- https://github.com/securesecrets/shade/blob/main/contracts/scrt_staking -- https://github.com/securesecrets/shade/blob/main/contracts/treasury -- https://github.com/securesecrets/shade/blob/main/contracts/mint -- https://github.com/securesecrets/shade/blob/main/contracts/oracle -- https://github.com/securesecrets/shade/blob/main/contracts/airdrop - -*In order to be eligible for a reward, the vulnerability must exist in both the deployed contract and its respective Github repository.* - -#### Website and Applications -- https://shadeprotocol.io -- https://app.shadeprotocol.io - - -## Out of Scope Work - -### Smart Contracts: - -- Basic Governance attacks -- Sybil attacks -- Centralization risks -- Disruption of price feeds from third party oracles (does not include oracle manipulation/flash loan attacks) - -### Website and Applications -- CSRF with no security impact -- Theoretical vulnerabilities without any proof or demonstration -- Missing HTTP Security Headers or cookie security flags -- Server-side information disclosure such as IPs, server names, and most stack traces -- URL Redirects (unless combined with another vulnerability to produce a more severe vulnerability) -- DDoS vulnerabilities -- Feature requests -- Best practices -- DNS Sabotage -- Self-XSS -- Captcha bypass using OCR -- Vulnerabilities used to enumerate or confirm the existence of users or tenants -- Vulnerabilities requiring unlikely user actions -- Lack of SSL/TLS best practices -- Attacks requiring privileged access from within the organization -- Vulnerabilities primarily caused by browser/plugin defects -- Any vulnerability exploit requiring CSP bypass resulting from a browser bug - - -### The following vulnerabilities are excluded from the rewards for this bug bounty program: - -- Previously-discovered bugs -- Attacks that the reporter has already exploited themselves, leading to damage -- Attacks requiring access to leaked keys/credentials -- Attacks requiring access to privileged addresses (governance, strategist) -- Attacks leveraging other DeFi protocols, unless the following are true: - - Losses or negative effects of the attack impact Shade Protocol ecosystem participants including SHD and SILK token holders - - Additional DeFi protocols used exist as smart contracts on the Secret Network mainnet and can reasonably be expected to have enough liquidity in various assets to allow the attack to succeed. - -### Proof of Concept (PoC) Guidelines: -- The smart contract PoC should always be made by forking the mainnet -- The PoC should contain runnable code for the exploit demonstration -- The PoC should mention all the dependencies, configuration files, and environmental variables that are required in order to run that PoC, as any other requirements to run the test. - - Add clear and descriptive replication steps so that the Shade Protocol Team can easily reproduce and validate your findings. -- PoCs should have clear print statements and or comments that detail each step of the attack and display relevant information, such as funds stolen/frozen etc. -- The PoC should ideally determine and provide data on the amount of funds at risk, which can be determined by calculating the total amount of tokens multiplied by the average price of the token at the time of the submission. -- The PoC must comply with any additional guidelines specified by the bug bounty program the whitehat is submitting a bug report to. - -## Rules of Engagement -*Violation of these rules can result in a temporary suspension or permanent ban from the Shade Protocol Bug Bounty Program, which may result in the forfeiture of potential payout and loss of access to all bug submissions. Shade Protocol has a zero tolerance policy for spam/incomplete bug reports and misrepresentation of bug severity.* - -The Shade Protocol team will take all reasonable actions to ensure the successful execution of and the maximum effectiveness of the Shade Protocol Bug Bounty Program. - -### Standard Program Rules: -- Unless otherwise noted, users should create accounts for testing purposes. -- Submissions must be made exclusively through the [Official Vulnerability Disclosure Portal](https://securesecrets.atlassian.net/servicedesk/customer/portal/3/group/11/create/37) to be considered for a reward. -- Communication regarding submissions must remain within Shade Protocol Bug Bounty support channels for the duration of the disclosure process. -- Users must submit a Proof of Concept (PoC) in order to receive bounties for bug reports. - - Example PoC can be found in the [Shade Security Advisories](https://github.com/securesecrets/shade/security/advisories). - -### Prohibited Behaviors: -- Any testing with mainnet contracts. Testing on mainnet is grounds for an immediate and permanent ban -- Misrepresenting assets in scope: claiming that a bug report impacts/targets an asset in scope when it does not -- Misrepresenting severity: claiming that a bug report is critical when it clearly is not -- Automated testing of services that generates significant amounts of traffic -- Attempting phishing or other social engineering attacks against Shade Protocol or its team members. -- Harassment, i.e., excessive, abusive, or bad faith communication -- Disputing a bug report in the dashboard once it has been paid or marked as closed -- Impersonation -- Threatening to publish or publishing personal information without consent -- Reporting a bug that has already been publicly disclosed -- Publicly disclosing a bug report--or even the existence of a bug report for a specific project--before it has been fixed and paid -- Publicly disclosing a bug report before 30 days have elapsed since the project closed the report as being out of scope or not requiring a fix -- Publicly disclosing a bug report deemed to be a duplicate or well-known to the project -- Submitting spam/very low-quality bug reports and submitting information through our platform that is not a bug report -- Submitting AI-generated/automated scanner bug reports -- Submitting fixes to a project's repository without their express consent -- Unauthorized disclosure or access of sensitive information beyond what is necessary to submit the report. - -## Security Patch Policy and Procedure - -All valid security bugs will be handled according to the following guidelines and will trigger an internal incident response process. We will keep you updated and work with you through the process. - -Even if you are not eligible for the bounty program you are recommend to follow all security patch procedures as highlighted in the [security policy overview](./SECURITY.md). - -Security patches are very sensitive by nature, and their exposure can provide a window of opportunity for a malicious actor to attack the protocol or any of its applications before it has been patched. As a result, Shade Protocol has adopted the following policies: - -- Due to the sensitive nature of security patches, the Shade Protocol team will **not** make the content of security patches public until the entire system has been patched. -- Users submitting bug reports are expected **not** to publicly disclose a bug report--or even the existence of a bug report for a specific project--before it has been fixed. -- Users submitting bug reports are expected **not** to publicly disclose a bug report deemed to be a duplicate or well-known to the Shade Protocol team. -- The details of the patch will be revealed after a holdover period of 10 days from the time after the successful application of patches to relevant systems. - - The holdover period provides reasonable time to ensure the stability of the contract or application before patch is revealed. - - The patch activity is considered successful if the applied patch completely mitigated the vulnerability as intended and the system remained stable. - -## Rewards Payment Process - -Users must have access to a Secret Network wallet address to receive any earned Bug Bounty rewards. Once a Bug Bounty report has been submitted, received, and verified, the Shade Protocol team will reconfirm the severity, reward payout amount, and wallet address to send bug bounty with the user who submitted the bug report. - -Once the bug report has been fully processed and the patch resulting from the Bug Bounty report has been successfully applied, the bug report will be considered “Closed”, at which time users will be notified via their provided email that they may begin the KYC process for the wallet receiving funds. diff --git a/archived-contracts/bonds/.cargo/config b/archived-contracts/bonds/.cargo/config deleted file mode 100644 index c1e7c50..0000000 --- a/archived-contracts/bonds/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" -schema = "run --example schema" \ No newline at end of file diff --git a/archived-contracts/bonds/.circleci/config.yml b/archived-contracts/bonds/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/archived-contracts/bonds/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/bonds/Cargo.toml b/archived-contracts/bonds/Cargo.toml deleted file mode 100644 index 65b87f4..0000000 --- a/archived-contracts/bonds/Cargo.toml +++ /dev/null @@ -1,47 +0,0 @@ -[package] -name = "bonds" -version = "0.1.0" -authors = [ - "Guy Garcia ", - "Jackson Swenson ", - "Kyle Wahlberg " -] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] -debug-print = ["shade-protocol/debug-print"] - -[dependencies] -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ - "bonds", - "math", - "query_auth_lib", - "chrono", -] } -schemars = "0.7" -time = "0.1.44" -admin = { git = "https://github.com/securesecrets/shadeadmin.git", tag = "v1.0" } -shade-oracles = { git = "https://github.com/securesecrets/shade-oracle.git", tag = "0.11"} - -[dev-dependencies] -fadroma = { branch = "v100", commit = 76867e0, git = "https://github.com/hackbg/fadroma.git", features= ["ensemble"] } -fadroma-platform-scrt = { branch = "v100", commit = 76867e0, git = "https://github.com/hackbg/fadroma.git" } -contract_harness = { version = "0.1.0", path = "../../packages/contract_harness", features = [ "snip20", "bonds", "oracle", "mock_band", "query_auth", "admin", "shade-oracles-ensemble" ] } -mock_band = { version = "0.1.0", path = "../../contracts/mock_band" } -shade-oracles-ensemble = { git = "https://github.com/securesecrets/shade-oracle.git", tag = "0.11"} - diff --git a/archived-contracts/bonds/Makefile b/archived-contracts/bonds/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/archived-contracts/bonds/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/archived-contracts/bonds/README.md b/archived-contracts/bonds/README.md deleted file mode 100644 index 6751ef6..0000000 --- a/archived-contracts/bonds/README.md +++ /dev/null @@ -1,389 +0,0 @@ - -# Bonds Contract -* [Introduction](#Introduction) -* [Sections](#Sections) - * [Init](#Init) - * [Admin](#Admin) - * Messages - * [UpdateConfig](#UpdateConfig) - * [OpenBond](#OpenBond) - * [CloseBond](#CloseBond) - * [Limit_Admin](#Limit_Admin) - * Messages - * [UpdateLimitConfig](#UpdateLimitConfig) - * [User](#User) - * Messages - * [Receive](#Receive) - * [Claim](#Claim) - * Queries - * [Config](#Config) - * [BondOpportunities](#BondOpportunities) - * [Account](#Account) - * [DepositAddresses](#DepositAddresses) - * [BondInfo](#BondInfo) - * [PriceCheck](#PriceCheck) - * [CheckAllowance](#CheckAllowance) - * [CheckBalance](#CheckBalance) - -# Introduction -Generic contract responsible for protocol and treasury bond opportunities -# Sections - -## Init -##### Request -| Name | Type | Description | optional | -|-----------------------------------|-----------|----------------------------------------------------------------------------|----------| -| limit_admin | Addr | Limit Assembly/Admin; SHOULD be a valid bech32 address | no | -| global_issuance_limit | Uint128 | Total number of tokens this contract can issue before limit reset | no | -| global_minimum_bonding_period | u64 | Minimum amount of time before any pending bonds can be claimed. | no | -| global_maximum_discount | Uint128 | Maximum allowed discount for any bond opportunities | no | -| admin | Addr | Bonds Assembly/Admin; SHOULD be a valid bech32 address | no | -| oracle | Contract | Oracle contract | no | -| treasury | Addr | Treasury address for allowance and deposit assets | no | -| issued_asset | Contract | Issued asset for this bonds contract | no | -| activated | bool | Turns entering opportunities contract-wide on/off | no | -| bond_issuance_limit | Uint128 | Default issuance limit for new bond opportunities | no | -| bonding_period | u64 | Default time for new opportunity before its pending bonds can be claimed | no | -| discount | Uint128 | Default percent discount on issued asset for new bond opportunities | no | -| global_min_accepted_issued_price | Uint128 | Min price for issued asset. Opps will never issue at lower price than this | no | -| global_err_issued_price | Uint128 | Asset price that will fail transaction due to risk | no | -| allowance_key | String | Entropy for generating snip20 viewing key for issued asset. Arbitrary. | no | -| airdrop | Contract | Airdrop contract for completing bond task and unlocking % of drop | yes | - -## Admin - -### Messages - -#### UpdateConfig -Updates the given values -##### Request -| Name | Type | Description | optional | -|-----------------------------------|-----------|-----------------------------------------------------------------------------------------------|-----------| -| admin | Addr | New contract admin; SHOULD be a valid bech32 address | yes | -| oracle | Contract | Oracle address | yes | -| treasury | Addr | Treasury address | yes | -| issued_asset | Contract | The asset this bond contract will issue to users | yes | -| activated | bool | If true, bond opportunities can be entered into | yes | -| minting_bond | bool | If true, bond is minting issued asset. If false, bond is spending on allowance from treasury | yes | -| bond_issuance_limit | Uint128 | Default issuance limit for any new opportunities | yes | -| bonding_period | Uint128 | Default bonding period in UNIX time for any new opportunities | yes | -| discount | Uint128 | Default discount % for any new opportunities | yes | -| global_min_accepted_issued_price | Uint128 | SMin price for issued asset. Opps will never issue at lower price than this | yes | -| global_err_issued_price | Uint128 | Asset price that will fail transaction due to risk | yes | -| airdrop | Contract | Airdrop contract for completing bond task and unlocking % of drop | yes | - -##### Response -``` json -{ - "update_config": { - "status": "success" - } -} -``` - -#### OpenBond -Opens new bond opportunity for a unique asset - -##### Request -| Name | Type | Description | optional | -|-------------------------------|-----------|---------------------------------------------------|-----------| -| deposit_asset | Contract | Contract for deposit asset | no | -| start_time | u64 | When the opportunity opens in UNIX time | no | -| end_time | u64 | When the opportunity closes in UNIX time | no | -| bond_issuance_limit | Uint128 | Issuance limit for this opportunity | yes | -| bonding_period | u64 | Bonding period for this opportunity in UNIX time | yes | -| discount | Uint128 | Discount % for this opportunity | yes | -| max_accepted_deposit_price | Uint128 | Maximum accepted price for deposit asset | no | -| err_deposit_price | Uint128 | Price for deposit asset that causes error | no | -| minting_bond | bool | True for minting from snip20, false for allowance | no | -##### Response -```json -{ - "open_bond": { - "status": "success", - "deposit_contract": "Contract", - "start_time": "u64 start in UNIX time", - "end_time": "u64 end in UNIX time", - "bond_issuance_limit": "opportunity limit Uint128", - "bonding_period": "u64 bonding period in UNIX time", - "discount": "opportunity discount percentage Uint128", - "max_accepted_deposit_price": "maximum price accepted for deposit asset Uint128", - "err_deposit_price": "error-causing price limit for deposit asset Uint128", - "minting_bond": "bool whether bond opp is a minting bond or not" - } -} -``` - -#### CloseBond -Closes bond opportunity for a given asset - -##### Request -| Name | Type | Description | optional | -|------------------|----------|-------------------------------|-----------| -| deposit_asset | Contract | Contract for deposit asset | no | - -##### Response -```json -{ - "close_bond": { - "status": "success", - "deposit_asset": "contract for asset who's opportunity was just closed" - } -} -``` - -## Limit Admin - -### Messages - -#### UpdateLimitConfig -Update the given limit config values -##### Request -| Name | Type | Description | optional | -|-------------------------------|-----------|-------------------------------------------------------------|-----------| -| limit_admin | Addr | New contract limit admin; SHOULD be a valid bech32 address | yes | -| global_isuance_limit | Uint128 | asset issuance limit, cumulative across all opportunities | yes | -| global_minimum_bonding_period | u64 | minimum bonding time for all opportunities, in UNIX time | yes | -| global_maximum_discount | Uint128 | maximum percent discount for all new opportunities | yes | -| reset_total_issued | bool | if true, resets global_total_issued to 0 | yes | -| reset_total_claimed | bool | if true, resets global_total_claimed to 0 | yes | - -##### Response -```json -{ - "update_limit_config": { - "status": "success" - } -} -``` - -## User - -### Messages - -#### Receive -To mint the user must use a supported asset's send function and send the amount over to the contract's address. The contract will take care of the rest. -In the msg field of a snip20 send command you must send a base64 encoded json like this one -```json -{"minimum_expected_amount": "Uint128" } -``` - -##### Response -```json -{ - "deposit": { - "status": "success", - "deposit_amount": "Deposit amount Uint128", - "pending_claim_amount": "Claim amount Uint128", - "end_date": "u64 end time of bonding period in UNIX time", - } -} -``` - -#### Claim -The user doesn't need to pass any parameters to claim. Claiming redeems all of a user's Pending Bonds. - -##### Response -```json -{ - "claim": { - "status": "success", - "amount": "claim amount Uint128", - } -} -``` - -### Queries - -#### Config -Gets the contract's config - -##### Response -```json -{ - "config": { - "config": "Contract's config" - } -} -``` - -#### BondOpportunities -Get the vector of bond opportunities currently available - -##### Response -```json -{ - "bond_opportunities": { - "bond_opportunities": "List of opportunities Vec", - } -} -``` - -#### DepositAddresses -Get the list of addresses for currently recognized deposit addresses, correlated to the open Bond Opportunities - -##### Response -```json -{ - "deposit_addresses": { - "deposit_addresses": "List of deposit addresses Vec", - } -} -``` - -#### BondInfo -Gets this contracts issuance and claimed totals, as well as the issued asset - -##### Response -```json -{ - "bond_info": { - "global_total_issued": "global total issued Uint128", - "global_total_claimed": "global total claimed Uint128", - "issued_asset": "native/issued asset Snip20Asset", - "global_min_accepted_issued_price": "global minimum accepted price for issued asset Uint128", - "global_err_issued_price": "global error limit price for issued asset Uint128" - } -} -``` - -#### PriceCheck -Gets the price for the passed asset by querying the oracle registered in the config - -##### Response -```json -{ - "price_check": { - "price": "price of passed asset in dollars Uint128", - } -} -``` - -#### CheckAllowance -Views this bond contract's allowance from its current Treasury address - -##### Response -```json -{ - "check_allowance": { - "allowance": "current queried allowance Uint128" - } -} -``` - -#### CheckBalance -Views this bond contract's current balance for its issued asset - -##### Response -```json -{ - "check_balance": { - "check_balance": "current balance Uint128" - } -} -``` - -## Account -User account, stores address - -### Structure -| Name | Type | Description | optional | -|-----------------|-------------------|-----------------------------------------------------------------------|---------- | -| address | Addr | User address | no | -| pending_bonds | Vec | Bond opportunities purchased by user that are unclaimed and maturing | no | - - -## PendingBond -Stored within user's pending_bonds vector. - -NOTE: The parameters must be in order -### Structure -| Name | Type | Description | optional | -|-----------------|-------------|-----------------------------------------------------------------------------------------|---------- | -| deposit_denom | Snip20Asset | Snip20 information for issued asset | no | -| end_time | u64 | Time that bond will be matured and claimable in UNIX time | no | -| deposit_amount | Uint128 | Amount of issued asset when opportunity was purchased | no | -| deposit_price | Uint128 | Price of deposit asset when opportunity was purchased | no | -| claim_amount | Uint128 | Amount of issued asset set to be claimed | no | -| claim_price | Uint128 | Price of issued asset when opportunity was purchased | no | -| discount | Uint128 | Discount of issued asset when opportunity was purchased | no | -| discount_price | Uint128 | Price of issued asset after discount was applied when opportunity was purchased | no | - - -## BondOpportunity -Stores information for bond opportunity - -NOTE: The parameters must be in order -### Structure -| Name | Type | Description | optional | -|-------------------------------|-------------|-----------------------------------------------------------------------|---------- | -| issuance_limit | Uint128 | Issuance limit for this bond opportunity | no | -| amount_issued | Uint128 | Amount of issued asset when opportunity was purchased | no | -| deposit_denom | Snip20Asset | Snip20 information for issued asset | no | -| start_time | u64 | Time that bond opportunity will be open in UNIX time | no | -| end_time | u64 | Time that bond opportunity will be closed in UNIX time | no | -| bonding_period | u64 | Time that users that enter the opportunity must wait before claiming | no | -| discount | Uint128 | Discount of issued asset when opportunity was purchased | no | -| max_accepted_deposit_price | Uint128 | Maximum accepted price for deposit asset | no | -| err_deposit_price | Uint128 | Error-causing limit price for deposit | no | -| minting_bond | bool | True for minting from snip20, false for allowance | no | - -## SlipMsg -Stores the user's slippage limit when entering bond opportunities - -```json -{ - "slip_msg": { - "minimum_expected_amount": "minimum expected amount to be issued Uint128" - } -} -``` - -## AccountProofMsg -The information inside permits that validate account ownership - -NOTE: The parameters must be in order -### Structure -| Name | Type | Description | optional | -|-----------|--------------|---------------------------------------------------------|----------| -| contracts | Vec | Bond contracts the permit is good for | no | -| key | String | Some permit key | no | - - -## PermitSignature -The signature that proves the validity of the data - -NOTE: The parameters must be in order -### Structure -| Name | Type | Description | optional | -|-----------|--------|---------------------------|----------| -| pub_key | pubkey | Signer's public key | no | -| signature | String | Base 64 encoded signature | no | - -## Pubkey -Public key - -NOTE: The parameters must be in order -### Structure -| Name | Type | Description | optional | -|-------|--------|------------------------------------|----------| -| type | String | Must be tendermint/PubKeySecp256k1 | no | -| value | String | The base 64 key | no | \ No newline at end of file diff --git a/archived-contracts/bonds/src/contract.rs b/archived-contracts/bonds/src/contract.rs deleted file mode 100644 index e0d5e36..0000000 --- a/archived-contracts/bonds/src/contract.rs +++ /dev/null @@ -1,215 +0,0 @@ -use shade_protocol::c_std::Uint128; -use shade_protocol::c_std::{ - to_binary, Api, Binary, Env, DepsMut, Response, Querier, StdResult, Storage, -}; - -use shade_protocol::snip20::helpers::{set_viewing_key_msg, token_info_query}; - -use shade_protocol::contract_interfaces::{ - bonds::{Config, ExecuteMsg, InstantiateMsg, QueryMsg, SnipViewingKey}, - snip20::helpers::Snip20Asset, -}; - -use shade_protocol::snip20::helpers::token_config; -use shade_protocol::utils::{pad_handle_result, pad_query_result}; - -use crate::{ - handle::{self, register_receive}, - query, - state::{ - allocated_allowance_w, allowance_key_w, deposit_assets_w, config_w, - global_total_claimed_w, global_total_issued_w, issued_asset_w, - }, -}; - -// Used to pad up responses for better privacy. -pub const RESPONSE_BLOCK_SIZE: usize = 256; - -pub fn init( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - let state = Config { - limit_admin: msg.limit_admin, - shade_admin: msg.shade_admin, - oracle: msg.oracle, - treasury: msg.treasury, - issued_asset: msg.issued_asset, - global_issuance_limit: msg.global_issuance_limit, - global_minimum_bonding_period: msg.global_minimum_bonding_period, - global_maximum_discount: msg.global_maximum_discount, - activated: msg.activated, - discount: msg.discount, - bond_issuance_limit: msg.bond_issuance_limit, - bonding_period: msg.bonding_period, - global_min_accepted_issued_price: msg.global_min_accepted_issued_price, - global_err_issued_price: msg.global_err_issued_price, - contract: env.contract.address.clone(), - airdrop: msg.airdrop, - query_auth: msg.query_auth, - }; - - config_w(deps.storage).save(&state)?; - - let mut messages = vec![]; - - let allowance_key: SnipViewingKey = - SnipViewingKey::new(&env, Default::default(), msg.allowance_key_entropy.as_ref()); - messages.push(set_viewing_key_msg( - allowance_key.0.clone(), - None, - 256, - state.issued_asset.code_hash.clone(), - state.issued_asset.address.clone(), - )?); - allowance_key_w(deps.storage).save(&allowance_key.0)?; - - let token_info = token_info_query( - &deps.querier, - 1, - state.issued_asset.code_hash.clone(), - state.issued_asset.address.clone(), - )?; - - let token_config = token_config( - &deps.querier, - 256, - state.issued_asset.code_hash.clone(), - state.issued_asset.address.clone(), - )?; - - issued_asset_w(deps.storage).save(&Snip20Asset { - contract: state.issued_asset.clone(), - token_info, - token_config: Option::from(token_config), - })?; - - messages.push(register_receive(&env, &state.issued_asset)?); - - // Write initial values to storage - global_total_issued_w(deps.storage).save(&Uint128::zero())?; - global_total_claimed_w(deps.storage).save(&Uint128::zero())?; - allocated_allowance_w(deps.storage).save(&Uint128::zero())?; - deposit_assets_w(deps.storage).save(&vec![])?; - - Ok(Response::new()) -} - -pub fn handle( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> StdResult { - pad_handle_result( - match msg { - ExecuteMsg::UpdateLimitConfig { - limit_admin, - shade_admin, - global_issuance_limit, - global_minimum_bonding_period, - global_maximum_discount, - reset_total_issued, - reset_total_claimed, - .. - } => handle::try_update_limit_config( - deps, - env, - limit_admin, - shade_admin, - global_issuance_limit, - global_minimum_bonding_period, - global_maximum_discount, - reset_total_issued, - reset_total_claimed, - ), - ExecuteMsg::UpdateConfig { - oracle, - treasury, - issued_asset, - activated, - bond_issuance_limit, - bonding_period, - discount, - global_min_accepted_issued_price, - global_err_issued_price, - allowance_key, - airdrop, - query_auth, - .. - } => handle::try_update_config( - deps, - env, - oracle, - treasury, - activated, - issued_asset, - bond_issuance_limit, - bonding_period, - discount, - global_min_accepted_issued_price, - global_err_issued_price, - allowance_key, - airdrop, - query_auth, - ), - ExecuteMsg::OpenBond { - deposit_asset, - start_time, - end_time, - bond_issuance_limit, - bonding_period, - discount, - max_accepted_deposit_price, - err_deposit_price, - minting_bond, - .. - } => handle::try_open_bond( - deps, - env, - deposit_asset, - start_time, - end_time, - bond_issuance_limit, - bonding_period, - discount, - max_accepted_deposit_price, - err_deposit_price, - minting_bond, - ), - ExecuteMsg::CloseBond { - deposit_asset, .. - } => handle::try_close_bond(deps, env, info, deposit_asset), - ExecuteMsg::Receive { - sender, - from, - amount, - msg, - .. - } => handle::try_deposit(deps, &env, sender, from, amount, msg), - ExecuteMsg::Claim { .. } => handle::try_claim(deps, env), - }, - RESPONSE_BLOCK_SIZE, - ) -} - -pub fn query( - deps: Deps, - msg: QueryMsg, -) -> StdResult { - pad_query_result( - match msg { - QueryMsg::Config {} => to_binary(&query::config(deps)?), - QueryMsg::BondOpportunities {} => to_binary(&query::bond_opportunities(deps)?), - QueryMsg::Account { permit } => to_binary(&query::account(deps, permit)?), - QueryMsg::DepositAddresses {} => to_binary(&query::list_deposit_addresses(deps)?), - QueryMsg::PriceCheck { asset } => to_binary(&query::price_check(asset, deps)?), - QueryMsg::BondInfo {} => to_binary(&query::bond_info(deps)?), - QueryMsg::CheckAllowance {} => to_binary(&query::check_allowance(deps)?), - QueryMsg::CheckBalance {} => to_binary(&query::check_balance(deps)?), - }, - RESPONSE_BLOCK_SIZE, - ) -} diff --git a/archived-contracts/bonds/src/handle.rs b/archived-contracts/bonds/src/handle.rs deleted file mode 100644 index c153944..0000000 --- a/archived-contracts/bonds/src/handle.rs +++ /dev/null @@ -1,845 +0,0 @@ -use shade_protocol::c_std::Uint128; -use shade_protocol::c_std::{ - from_binary, to_binary, Api, Binary, CosmosMsg, Env, DepsMut, Response, Addr, - Querier, StdError, StdResult, Storage, -}; - -use shade_protocol::snip20::helpers::{allowance_query, mint_msg, register_receive, send_msg, transfer_from_msg}; - -use shade_admin::admin::{QueryMsg, ValidateAdminPermissionResponse}; - -use shade_oracles::{common::OraclePrice, router::QueryMsg::GetPrice}; - -use shade_protocol::contract_interfaces::bonds::{ - errors::*, - BondOpportunity, SlipMsg, {Account, Config, HandleAnswer, PendingBond}, -}; -use shade_protocol::contract_interfaces::{ - airdrop::ExecuteMsg::CompleteTask, - snip20::helpers::{fetch_snip20, Snip20Asset}, -}; -use shade_protocol::utils::asset::Contract; -use shade_protocol::utils::generic_response::ResponseStatus; - -use std::{cmp::Ordering, convert::TryFrom}; - -use crate::state::{ - account_r, account_w, allocated_allowance_r, allocated_allowance_w, allowance_key_r, - allowance_key_w, bond_opportunity_r, bond_opportunity_w, deposit_assets_r, - deposit_assets_w, config_r, config_w, global_total_claimed_w, global_total_issued_r, - global_total_issued_w, issued_asset_r, -}; - -pub fn try_update_limit_config( - deps: DepsMut, - env: Env, - info: MessageInfo, - limit_admin: Option, - shade_admins: Option, - global_issuance_limit: Option, - global_minimum_bonding_period: Option, - global_maximum_discount: Option, - reset_total_issued: Option, - reset_total_claimed: Option, -) -> StdResult { - let cur_config = config_r(deps.storage).load()?; - - // Limit admin only - if info.sender != cur_config.limit_admin { - return Err(not_limit_admin()); - } - - let mut config = config_w(deps.storage); - config.update(|mut state| { - if let Some(limit_admin) = limit_admin { - state.limit_admin = limit_admin; - } - if let Some(shade_admins) = shade_admins { - state.shade_admin = shade_admins; - } - if let Some(global_issuance_limit) = global_issuance_limit { - state.global_issuance_limit = global_issuance_limit; - } - if let Some(global_minimum_bonding_period) = global_minimum_bonding_period { - state.global_minimum_bonding_period = global_minimum_bonding_period; - } - if let Some(global_maximum_discount) = global_maximum_discount { - state.global_maximum_discount = global_maximum_discount; - } - Ok(state) - })?; - - if let Some(reset_total_issued) = reset_total_issued { - if reset_total_issued { - global_total_issued_w(deps.storage).save(&Uint128::zero())?; - } - } - - if let Some(reset_total_claimed) = reset_total_claimed { - if reset_total_claimed { - global_total_claimed_w(deps.storage).save(&Uint128::zero())?; - } - } - - Ok(Response::new().set_data(to_binary(&HandleAnswer::UpdateLimitConfig { - status: ResponseStatus::Success, - })?)) -} - -pub fn try_update_config( - deps: DepsMut, - env: Env, - info: MessageInfo, - oracle: Option, - treasury: Option, - activated: Option, - issuance_asset: Option, - bond_issuance_limit: Option, - bonding_period: Option, - discount: Option, - global_min_accepted_issued_price: Option, - global_err_issued_price: Option, - allowance_key: Option, - airdrop: Option, - query_auth: Option, -) -> StdResult { - let cur_config = config_r(deps.storage).load()?; - - // Admin-only - let admin_response: ValidateAdminPermissionResponse = QueryMsg::ValidateAdminPermission { - contract_address: cur_config.contract.to_string(), - admin_address: info.sender.to_string(), - } - .query( - &deps.querier, - cur_config.shade_admin.code_hash, - cur_config.shade_admin.address, - )?; - - if admin_response.error_msg.is_some() { - return Err(not_admin()); - } - - if let Some(allowance_key) = allowance_key { - allowance_key_w(deps.storage).save(&allowance_key)?; - }; - - let mut config = config_w(deps.storage); - config.update(|mut state| { - if let Some(oracle) = oracle { - state.oracle = oracle; - } - if let Some(treasury) = treasury { - state.treasury = treasury; - } - if let Some(activated) = activated { - state.activated = activated; - } - if let Some(issuance_asset) = issuance_asset { - state.issued_asset = issuance_asset; - } - if let Some(bond_issuance_limit) = bond_issuance_limit { - state.bond_issuance_limit = bond_issuance_limit; - } - if let Some(bonding_period) = bonding_period { - state.bonding_period = bonding_period; - } - if let Some(discount) = discount { - state.discount = discount; - } - if let Some(global_min_accepted_issued_price) = global_min_accepted_issued_price { - state.global_min_accepted_issued_price = global_min_accepted_issued_price; - } - if let Some(global_err_issued_price) = global_err_issued_price { - state.global_err_issued_price = global_err_issued_price; - } - if let Some(airdrop) = airdrop { - state.airdrop = Some(airdrop); - } - if let Some(query_auth) = query_auth { - state.query_auth = query_auth; - } - Ok(state) - })?; - - Ok(Response::new().set_data(to_binary(&HandleAnswer::UpdateConfig { - status: ResponseStatus::Success, - })?)) -} - -pub fn try_deposit( - deps: DepsMut, - env: &Env, - sender: Addr, - _from: Addr, - deposit_amount: Uint128, - msg: Option, -) -> StdResult { - let config = config_r(deps.storage).load()?; - - // Check that sender isn't the treasury - if config.treasury == sender { - return Err(blacklisted(config.treasury)); - } - - if config.contract == sender { - return Err(blacklisted(config.contract)); - } - - // Check that sender isn't an admin - let admin_response: ValidateAdminPermissionResponse = QueryMsg::ValidateAdminPermission { - contract_address: config.contract.to_string(), - admin_address: sender.to_string(), - } - .query( - &deps.querier, - config.shade_admin.code_hash, - config.shade_admin.address, - )?; - - if admin_response.error_msg.is_none() { - return Err(blacklisted(sender)); - } - - // Check that sender isn't the minted asset - if config.issued_asset.address == info.sender { - return Err(issued_asset_deposit()); - } - - // Check that sender asset has an active bond opportunity - let bond_opportunity = match bond_opportunity_r(deps.storage) - .may_load(info.sender.to_string().as_bytes())? - { - Some(prev_opp) => { - bond_active(&env, &prev_opp)?; - prev_opp - } - None => { - return Err(no_bond_found(info.sender.as_str())); - } - }; - - let available = bond_opportunity - .issuance_limit - .checked_sub(bond_opportunity.amount_issued) - .unwrap(); - - // Load mint asset information - let issuance_asset = issued_asset_r(deps.storage).load()?; - - // Calculate conversion of deposit to SHD - let (amount_to_issue, deposit_price, claim_price, discount_price) = amount_to_issue( - &deps, - deposit_amount, - available, - bond_opportunity.deposit_denom.clone(), - issuance_asset, - bond_opportunity.discount, - bond_opportunity.max_accepted_deposit_price, - bond_opportunity.err_deposit_price, - config.global_min_accepted_issued_price, - config.global_err_issued_price, - )?; - - if let Some(message) = msg { - let msg: SlipMsg = from_binary(&message)?; - - // Check Slippage - if amount_to_issue.clone() < msg.minimum_expected_amount.clone() { - return Err(slippage_tolerance_exceeded( - amount_to_issue, - msg.minimum_expected_amount, - )); - } - }; - - let mut opp = - bond_opportunity_r(deps.storage).load(info.sender.to_string().as_bytes())?; - opp.amount_issued += amount_to_issue; - bond_opportunity_w(deps.storage).save(info.sender.to_string().as_bytes(), &opp)?; - - let mut messages = vec![]; - - // Deposit to treasury - messages.push(send_msg( - config.treasury.clone(), - deposit_amount.into(), - None, - None, - None, - 1, - bond_opportunity.deposit_denom.contract.code_hash.clone(), - bond_opportunity.deposit_denom.contract.address.clone(), - )?); - - // Format end date as String - let end: u64 = calculate_claim_date(env.block.time.seconds(), bond_opportunity.bonding_period); - - // Begin PendingBond - let new_bond = PendingBond { - claim_amount: amount_to_issue.clone(), - end_time: end, - deposit_denom: bond_opportunity.deposit_denom, - deposit_amount, - deposit_price, - claim_price, - discount: bond_opportunity.discount, - discount_price, - }; - - // Find user account, create if it doesn't exist - let mut account = match account_r(deps.storage).may_load(sender.as_str().as_bytes())? { - None => { - // Airdrop task - if let Some(airdrop) = config.airdrop { - let msg = CompleteTask { - address: sender.clone(), - padding: None, - }; - messages.push(msg.to_cosmos_msg(airdrop.code_hash, airdrop.address, None)?); - } - - Account { - address: sender, - pending_bonds: vec![], - } - } - Some(acc) => acc, - }; - - // Add new_bond to user's pending_bonds Vec - account.pending_bonds.push(new_bond.clone()); - - // Save account - account_w(deps.storage).save(account.address.as_str().as_bytes(), &account)?; - - if !bond_opportunity.minting_bond { - // Decrease AllocatedAllowance since user is claiming - allocated_allowance_w(deps.storage) - .update(|allocated| Ok(allocated.checked_sub(amount_to_issue.clone())?))?; - - // Transfer funds using allowance to bonds - messages.push(transfer_from_msg( - config.treasury.clone(), - env.contract.address.clone(), - amount_to_issue.into(), - None, - None, - 256, - config.issued_asset.code_hash.clone(), - config.issued_asset.address, - )?); - } else { - messages.push(mint_msg( - config.contract, - amount_to_issue.into(), - None, - None, - 256, - config.issued_asset.code_hash, - config.issued_asset.address, - )?); - } - - // Return Success response - Ok(Response::new().set_data(to_binary(&HandleAnswer::Deposit { - status: ResponseStatus::Success, - deposit_amount: new_bond.deposit_amount, - pending_claim_amount: new_bond.claim_amount, - end_date: new_bond.end_time, - })?)) -} - -pub fn try_claim( - deps: DepsMut, - env: Env, - info: MessageInfo, -) -> StdResult { - // Check if bonding period has elapsed and allow user to claim - // however much of the issuance asset they paid for with their deposit - let config = config_r(deps.storage).load()?; - - // Find user account, error out if DNE - let mut account = - match account_r(deps.storage).may_load(info.sender.as_str().as_bytes())? { - None => { - return Err(StdError::NotFound { - kind: info.sender.to_string(), - backtrace: None, - }); - } - Some(acc) => acc, - }; - - // Bring up pending bonds structure for user if account is found - let mut pending_bonds = account.pending_bonds; - if pending_bonds.is_empty() { - return Err(no_pending_bonds(account.address.as_str())); - } - - // Set up loop comparison values. - let now = env.block.time.seconds(); // Current time in seconds - let mut total = Uint128::zero(); - - // Iterate through pending bonds and compare one's end to current time - for bond in pending_bonds.iter() { - if bond.end_time <= now { - // Add claim amount to total - total = total.checked_add(bond.claim_amount).unwrap(); - } - } - - // Add case for if total is 0, error out - if total.is_zero() { - return Err(no_bonds_claimable()); - } - - // Remove claimed bonds from vector and save back to the account - pending_bonds.retain( - |bond| bond.end_time > now, // Retain only the bonds that end at a time greater than now - ); - - account.pending_bonds = pending_bonds; - account_w(deps.storage).save(info.sender.as_str().as_bytes(), &account)?; - - global_total_claimed_w(deps.storage) - .update(|global_total_claimed| Ok(global_total_claimed.checked_add(total.clone())?))?; - - //Set up empty message vec - let mut messages = vec![]; - - messages.push(send_msg( - info.sender, - total.into(), - None, - None, - None, - 256, - config.issued_asset.code_hash.clone(), - config.issued_asset.address, - )?); - - // Return Success response - Ok(Response::new().set_data(to_binary(&HandleAnswer::Claim { - status: ResponseStatus::Success, - amount: total, - })?)) -} - -pub fn try_open_bond( - deps: DepsMut, - env: Env, - info: MessageInfo, - deposit_asset: Contract, - start_time: u64, - end_time: u64, - bond_issuance_limit: Option, - bonding_period: Option, - discount: Option, - max_accepted_deposit_price: Uint128, - err_deposit_price: Uint128, - minting_bond: bool, -) -> StdResult { - let config = config_r(deps.storage).load()?; - - // Admin-only - let admin_response: ValidateAdminPermissionResponse = QueryMsg::ValidateAdminPermission { - contract_address: config.contract.to_string(), - admin_address: info.sender.to_string(), - } - .query( - &deps.querier, - config.shade_admin.code_hash, - config.shade_admin.address, - )?; - - if admin_response.error_msg.is_some() { - return Err(not_admin()); - } - - let mut messages = vec![]; - - // Check whether previous bond for this asset exists - match bond_opportunity_r(deps.storage) - .may_load(deposit_asset.address.as_str().as_bytes())? - { - Some(prev_opp) => { - let unspent = prev_opp - .issuance_limit - .checked_sub(prev_opp.amount_issued)?; - global_total_issued_w(deps.storage) - .update(|issued| Ok(issued.checked_sub(unspent.clone())?))?; - - if !prev_opp.minting_bond { - // Unallocate allowance that wasn't issued - - allocated_allowance_w(deps.storage) - .update(|allocated| Ok(allocated.checked_sub(unspent)?))?; - } - } - None => { - // Save to list of current deposit addresses - match deposit_assets_r(deps.storage).may_load()? { - None => { - let assets = vec![deposit_asset.address.clone()]; - deposit_assets_w(deps.storage).save(&assets)?; - } - Some(_assets) => { - deposit_assets_w(deps.storage).update(|mut assets| { - assets.push(deposit_asset.address.clone()); - Ok(assets) - })?; - } - }; - - // Prepare register_receive message for new asset - messages.push(register_receive(&env, &deposit_asset)?); - } - }; - - // Check optional fields, setting to config defaults if None - let limit = bond_issuance_limit.unwrap_or(config.bond_issuance_limit); - let period = bonding_period.unwrap_or(config.bonding_period); - let discount = discount.unwrap_or(config.discount); - - check_against_limits(&deps, limit, period, discount)?; - - if !minting_bond { - // Check bond issuance amount against snip20 allowance and allocated_allowance - let snip20_allowance = allowance_query( - &deps.querier, - config.treasury, - env.contract.address.clone(), - allowance_key_r(deps.storage).load()?.to_string(), - 1, - config.issued_asset.code_hash, - config.issued_asset.address, - )?; - - let allocated_allowance = allocated_allowance_r(deps.storage).load()?; - // Declaring again so 1.0 Uint128 works - let snip_allowance = Uint128::from(snip20_allowance.allowance); - - // Error out if allowance doesn't allow bond opportunity - if snip_allowance.checked_sub(allocated_allowance)? < limit { - return Err(bond_issuance_exceeds_allowance( - snip_allowance, - allocated_allowance, - limit, - )); - }; - - // Increase stored allocated_allowance by the opportunity's issuance limit - allocated_allowance_w(deps.storage) - .update(|allocated| Ok(allocated.checked_add(limit)?))?; - } - - let deposit_denom = fetch_snip20(&deposit_asset.clone(), &deps.querier)?; - - // Generate bond opportunity - let bond_opportunity = BondOpportunity { - issuance_limit: limit, - deposit_denom, - start_time, - end_time, - discount, - bonding_period: period, - amount_issued: Uint128::zero(), - max_accepted_deposit_price, - err_deposit_price, - minting_bond, - }; - - // Save bond opportunity - bond_opportunity_w(deps.storage).save( - deposit_asset.address.as_str().as_bytes(), - &bond_opportunity, - )?; - - // Increase global total issued by bond opportunity's issuance limit - global_total_issued_w(deps.storage).update(|global_total_issued| { - Ok(global_total_issued.checked_add(bond_opportunity.issuance_limit)?) - })?; - - // Return Success response - Ok(Response::new().set_data(to_binary(&HandleAnswer::OpenBond { - status: ResponseStatus::Success, - deposit_contract: bond_opportunity.deposit_denom.contract, - start_time: bond_opportunity.start_time, - end_time: bond_opportunity.end_time, - bond_issuance_limit: bond_opportunity.issuance_limit, - bonding_period: bond_opportunity.bonding_period, - discount: bond_opportunity.discount, - max_accepted_deposit_price: bond_opportunity.max_accepted_deposit_price, - err_deposit_price: bond_opportunity.err_deposit_price, - minting_bond: bond_opportunity.minting_bond, - })?)) -} - -pub fn try_close_bond( - deps: DepsMut, - env: Env, - info: MessageInfo, - deposit_asset: Contract, -) -> StdResult { - let config = config_r(deps.storage).load()?; - - // Admin-only - let admin_response: ValidateAdminPermissionResponse = QueryMsg::ValidateAdminPermission { - contract_address: config.contract.to_string(), - admin_address: info.sender.to_string(), - } - .query( - &deps.querier, - config.shade_admin.code_hash, - config.shade_admin.address, - )?; - - if admin_response.error_msg.is_some() { - return Err(not_admin()); - } - - // Check whether previous bond for this asset exists - - match bond_opportunity_r(deps.storage) - .may_load(deposit_asset.address.as_str().as_bytes())? - { - Some(prev_opp) => { - bond_opportunity_w(deps.storage) - .remove(deposit_asset.address.as_str().as_bytes()); - - // Remove asset from address list - deposit_assets_w(deps.storage).update(|mut assets| { - assets.retain(|address| *address != deposit_asset.address); - Ok(assets) - })?; - - let unspent = prev_opp - .issuance_limit - .checked_sub(prev_opp.amount_issued)?; - global_total_issued_w(deps.storage) - .update(|issued| Ok(issued.checked_sub(unspent.clone())?))?; - - if !prev_opp.minting_bond { - // Unallocate allowance that wasn't issued - - allocated_allowance_w(deps.storage) - .update(|allocated| Ok(allocated.checked_sub(unspent)?))?; - } - } - None => { - // Error out, no bond found with that deposit asset - return Err(no_bond_found(deposit_asset.address.as_str())); - } - } - - let messages = vec![]; - - // Return Success response - Ok(Response::new().set_data(to_binary(&HandleAnswer::ClosedBond { - status: ResponseStatus::Success, - deposit_asset, - })?)) -} - -fn bond_active(env: &Env, bond_opp: &BondOpportunity) -> StdResult<()> { - if bond_opp.amount_issued >= bond_opp.issuance_limit { - return Err(bond_limit_reached(bond_opp.issuance_limit)); - } - if bond_opp.start_time > env.block.time.seconds() { - return Err(bond_not_started(bond_opp.start_time, env.block.time.seconds())); - } - if bond_opp.end_time < env.block.time.seconds() { - return Err(bond_ended(bond_opp.end_time, env.block.time.seconds())); - } - Ok(()) -} - -fn check_against_limits( - deps: Deps, - bond_limit: Uint128, - bond_period: u64, - bond_discount: Uint128, -) -> StdResult { - let config = config_r(deps.storage).load()?; - // Check that global issuance limit won't be exceeded by this opportunity's limit - let global_total_issued = global_total_issued_r(deps.storage).load()?; - let global_issuance_limit = config.global_issuance_limit; - - active( - &config.activated, - &config.global_issuance_limit, - &global_total_issued, - )?; - - if global_total_issued.checked_add(bond_limit)? > global_issuance_limit { - return Err(bond_limit_exceeds_global_limit( - global_issuance_limit, - global_total_issued, - bond_limit, - )); - } else if bond_period < config.global_minimum_bonding_period { - return Err(bonding_period_below_minimum_time( - bond_period, - config.global_minimum_bonding_period, - )); - } else if bond_discount > config.global_maximum_discount { - return Err(bond_discount_above_maximum_rate( - bond_discount, - config.global_maximum_discount, - )); - } - Ok(true) -} - -pub fn active( - activated: &bool, - global_issuance_limit: &Uint128, - global_total_issued: &Uint128, -) -> StdResult<()> { - // Error out if bond contract isn't active - if !activated { - return Err(contract_not_active()); - } - - // Check whether mint limit has been reached - if global_total_issued >= global_issuance_limit { - return Err(global_limit_reached(*global_issuance_limit)); - } - - Ok(()) -} - -pub fn amount_to_issue( - deps: Deps, - deposit_amount: Uint128, - available: Uint128, - deposit_asset: Snip20Asset, - issuance_asset: Snip20Asset, - discount: Uint128, - max_accepted_deposit_price: Uint128, - err_deposit_price: Uint128, - min_accepted_issued_price: Uint128, - err_issued_price: Uint128, -) -> StdResult<(Uint128, Uint128, Uint128, Uint128)> { - let mut disc = discount; - let mut deposit_price = oracle(&deps, deposit_asset.token_info.symbol.clone())?; - if deposit_price > max_accepted_deposit_price { - if deposit_price > err_deposit_price { - return Err(deposit_price_exceeds_limit( - deposit_price.clone(), - err_deposit_price.clone(), - )); - } - deposit_price = max_accepted_deposit_price; - } - let mut issued_price = oracle(deps, issuance_asset.token_info.symbol.clone())?; - if issued_price < err_issued_price { - return Err(issued_price_below_minimum( - issued_price.clone(), - err_issued_price.clone(), - )); - } - if issued_price < min_accepted_issued_price { - disc = Uint128::zero(); - issued_price = min_accepted_issued_price; - } - let (issued_amount, discount_price) = calculate_issuance( - deposit_price.clone(), - deposit_amount, - deposit_asset.token_info.decimals, - issued_price, - issuance_asset.token_info.decimals, - disc, - min_accepted_issued_price, - ); - if issued_amount > available { - return Err(mint_exceeds_limit(issued_amount, available)); - } - Ok(( - issued_amount, - deposit_price, - issued_price, - discount_price, - )) -} - -pub fn calculate_issuance( - deposit_price: Uint128, - deposit_amount: Uint128, - deposit_decimals: u8, - issued_price: Uint128, - issued_decimals: u8, - discount: Uint128, - min_accepted_issued_price: Uint128, -) -> (Uint128, Uint128) { - // Math must be done in integers - // deposit_decimals = x - // issued_decimals = y - // deposit_price = p1 * 10^18 - // issued_price = p2 * 10^18 - // deposit_amount = a1 * 10^x - // issued_amount = a2 * 10^y - // discount = d1 * 10^18 - - // (a1 * 10^x) * (p1 * 10^18) = (a2 * 10^y) * (p2 * 10^18) * ((100 - d1) * 10^16) - - // (p1 * 10^18) - // (a1 * 10^x) * ------------------------------------ = (a2 * 10^y) - // (p2 * 10^18) * ((100 - d1)) - let percent_disc = Uint128::new(100_000).checked_sub(discount).unwrap(); // - discount.multiply_ratio(1000u128, 1_000_000_000_000_000_000u128).u128(); - let mut discount_price = issued_price.multiply_ratio(percent_disc, 100000u128); - if discount_price < min_accepted_issued_price { - discount_price = min_accepted_issued_price - } - let issued_amount = deposit_amount.multiply_ratio(deposit_price, discount_price); - let difference: i32 = i32::from(issued_decimals) - .checked_sub(i32::from(deposit_decimals)) - .unwrap(); - match difference.cmp(&0) { - Ordering::Greater => ( - issued_amount - .checked_mul(Uint128::new(10u128.pow(u32::try_from(difference).unwrap()))) - .unwrap(), - discount_price, - ), - Ordering::Less => ( - issued_amount - .multiply_ratio(1u128, 10u128.pow(u32::try_from(difference.abs()).unwrap())), - discount_price, - ), - Ordering::Equal => (issued_amount, discount_price), - } -} - -pub fn calculate_claim_date(env_time: u64, bonding_period: u64) -> u64 { - // Previously, translated the passed u64 as days and converted to seconds. - // Now, however, it treats the passed value as seconds, due to that being - // how the block environment tracks it. - let end = env_time.checked_add(bonding_period).unwrap(); - - end -} - -pub fn register_receive(env: &Env, contract: &Contract) -> StdResult { - register_receive( - env.contract.code_hash.clone(), - None, - contract - ) -} - -pub fn oracle( - deps: Deps, - key: String, -) -> StdResult { - let config: Config = config_r(deps.storage).load()?; - let answer: OraclePrice = GetPrice { key }.query( - &deps.querier, - config.oracle.code_hash, - config.oracle.address, - )?; - - // From wasn't working, so here's a fix - Ok(Uint128::new(answer.data.rate.u128())) -} diff --git a/archived-contracts/bonds/src/lib.rs b/archived-contracts/bonds/src/lib.rs deleted file mode 100644 index 7683a12..0000000 --- a/archived-contracts/bonds/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -pub mod contract; -pub mod handle; -pub mod query; -pub mod state; - -#[cfg(test)] -mod tests; - -#[cfg(target_arch = "wasm32")] -mod wasm { - use super::contract; - use shade_protocol::c_std::{ - do_handle, do_init, do_query, ExternalApi, ExternalQuerier, ExternalStorage, - }; - - #[no_mangle] - extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { - do_init( - &contract::init::, - env_ptr, - msg_ptr, - ) - } - - #[no_mangle] - extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { - do_handle( - &contract::handle::, - env_ptr, - msg_ptr, - ) - } - - #[no_mangle] - extern "C" fn query(msg_ptr: u32) -> u32 { - do_query( - &contract::query::, - msg_ptr, - ) - } - - // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available - // automatically because we `use cosmwasm_std`. -} diff --git a/archived-contracts/bonds/src/query.rs b/archived-contracts/bonds/src/query.rs deleted file mode 100644 index 16102c0..0000000 --- a/archived-contracts/bonds/src/query.rs +++ /dev/null @@ -1,155 +0,0 @@ -use crate::{ - handle::oracle, - state::{ - account_r, allowance_key_r, bond_opportunity_r, deposit_assets_r, config_r, - global_total_claimed_r, global_total_issued_r, issued_asset_r, - }, -}; - -use shade_protocol::c_std::Uint128; - -use shade_protocol::{ - snip20::helpers::{allowance_query, balance_query}, -}; - -use shade_protocol::c_std::{Api, DepsMut, Addr, Querier, StdResult, Storage}; -use shade_protocol::contract_interfaces::bonds::{ - errors::{permit_revoked, query_auth_bad_response}, - BondOpportunity, QueryAnswer, -}; - -use shade_protocol::contract_interfaces::query_auth::{ - self, QueryMsg::ValidatePermit, QueryPermit, -}; - -pub fn config(deps: Deps) -> StdResult { - Ok(QueryAnswer::Config { - config: config_r(deps.storage).load()?, - }) -} - -pub fn account( - deps: Deps, - permit: QueryPermit, -) -> StdResult { - let config = config_r(deps.storage).load()?; - // Validate address - let authorized: query_auth::QueryAnswer = ValidatePermit { permit }.query( - &deps.querier, - config.query_auth.code_hash, - config.query_auth.address, - )?; - match authorized { - query_auth::QueryAnswer::ValidatePermit { user, is_revoked } => { - if !is_revoked { - account_information(deps, user) - } else { - return Err(permit_revoked(user.as_str())); - } - } - _ => return Err(query_auth_bad_response()), - } -} - -fn account_information( - deps: Deps, - account_address: Addr, -) -> StdResult { - let account = account_r(deps.storage).load(account_address.as_str().as_bytes())?; - - // Return pending bonds - - Ok(QueryAnswer::Account { - pending_bonds: account.pending_bonds, - }) -} - -pub fn bond_opportunities( - deps: Deps, -) -> StdResult { - let deposit_assets = deposit_assets_r(deps.storage).load()?; - if deposit_assets.is_empty() { - return Ok(QueryAnswer::BondOpportunities { - bond_opportunities: vec![], - }); - } else { - let iter = deposit_assets.iter(); - let mut bond_opportunities: Vec = vec![]; - for asset in iter { - bond_opportunities - .push(bond_opportunity_r(deps.storage).load(asset.as_str().as_bytes())?); - } - return Ok(QueryAnswer::BondOpportunities { bond_opportunities }); - } -} - -pub fn bond_info(deps: Deps) -> StdResult { - let global_total_issued = global_total_issued_r(deps.storage).load()?; - let global_total_claimed = global_total_claimed_r(deps.storage).load()?; - let issued_asset = issued_asset_r(deps.storage).load()?; - let config = config_r(deps.storage).load()?; - Ok(QueryAnswer::BondInfo { - global_total_issued, - global_total_claimed, - issued_asset, - global_min_accepted_issued_price: config.global_min_accepted_issued_price, - global_err_issued_price: config.global_err_issued_price, - }) -} - -pub fn list_deposit_addresses( - deps: Deps, -) -> StdResult { - let deposit_addresses = deposit_assets_r(deps.storage).load()?; - Ok(QueryAnswer::DepositAddresses { - deposit_addresses, - }) -} - -pub fn price_check( - asset: String, - deps: Deps, -) -> StdResult { - let price = oracle(deps, asset)?; - Ok(QueryAnswer::PriceCheck { price }) -} - -pub fn check_allowance( - deps: Deps, -) -> StdResult { - let config = config_r(deps.storage).load()?; - - // Check bond issuance amount against snip20 allowance and allocated_allowance - let snip20_allowance = allowance_query( - &deps.querier, - config.treasury, - config.contract, - allowance_key_r(deps.storage).load()?.to_string(), - 1, - config.issued_asset.code_hash, - config.issued_asset.address, - )?; - - Ok(QueryAnswer::CheckAllowance { - allowance: Uint128::from(snip20_allowance.allowance), - }) -} - -pub fn check_balance( - deps: Deps, -) -> StdResult { - let config = config_r(deps.storage).load()?; - - let balance = balance_query( - &deps.querier, - config.contract, - allowance_key_r(deps.storage).load()?, - 256, - config.issued_asset.code_hash, - config.issued_asset.address, - )?; - - Ok(QueryAnswer::CheckBalance { - balance: Uint128::from(balance.amount), - }) -} diff --git a/archived-contracts/bonds/src/state.rs b/archived-contracts/bonds/src/state.rs deleted file mode 100644 index e0a0394..0000000 --- a/archived-contracts/bonds/src/state.rs +++ /dev/null @@ -1,99 +0,0 @@ -use shade_protocol::c_std::Uint128; -use shade_protocol::c_std::{Addr, Storage}; -use shade_protocol::storage::{ - bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton, - Singleton, -}; -use shade_protocol::contract_interfaces::{ - bonds::{Account, BondOpportunity, Config}, - snip20::helpers::Snip20Asset, -}; - -pub static CONFIG: &[u8] = b"config"; -pub static GLOBAL_TOTAL_ISSUED: &[u8] = b"global_total_issued"; -pub static GLOBAL_TOTAL_CLAIMED: &[u8] = b"global_total_claimed"; -pub static DEPOSIT_ASSETS: &[u8] = b"deposit_assets"; -pub static ISSUED_ASSET: &[u8] = b"issued_asset"; -pub static ACCOUNTS_KEY: &[u8] = b"accounts"; -pub static BOND_OPPORTUNITIES: &[u8] = b"bond_opportunities"; -pub static ALLOCATED_ALLOWANCE: &[u8] = b"allocated_allowance"; -pub static ALLOWANCE_VIEWING_KEY: &[u8] = b"allowance_viewing_key"; - -pub fn config_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, CONFIG) -} - -pub fn config_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, CONFIG) -} - -/* Global amount issued since last issuance reset */ -pub fn global_total_issued_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, GLOBAL_TOTAL_ISSUED) -} - -pub fn global_total_issued_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, GLOBAL_TOTAL_ISSUED) -} - -/* Global amount claimed since last issuance reset */ -pub fn global_total_claimed_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, GLOBAL_TOTAL_CLAIMED) -} - -pub fn global_total_claimed_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, GLOBAL_TOTAL_CLAIMED) -} - -/* List of assets that have bond opportunities stored */ -pub fn deposit_assets_w(storage: &mut dyn Storage) -> Singleton> { - singleton(storage, DEPOSIT_ASSETS) -} - -pub fn deposit_assets_r(storage: &dyn Storage) -> ReadonlySingleton> { - singleton_read(storage, DEPOSIT_ASSETS) -} - -/* Asset minted when user claims after bonding period */ -pub fn issued_asset_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, ISSUED_ASSET) -} - -pub fn issued_asset_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, ISSUED_ASSET) -} - -// Bond account -pub fn account_r(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, ACCOUNTS_KEY) -} - -pub fn account_w(storage: &mut dyn Storage) -> Bucket { - bucket(storage, ACCOUNTS_KEY) -} - -pub fn bond_opportunity_r(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, BOND_OPPORTUNITIES) -} - -pub fn bond_opportunity_w(storage: &mut dyn Storage) -> Bucket { - bucket(storage, BOND_OPPORTUNITIES) -} - -// The amount of allowance already allocated/unclaimed from opportunities -pub fn allocated_allowance_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, ALLOCATED_ALLOWANCE) -} - -pub fn allocated_allowance_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, ALLOCATED_ALLOWANCE) -} - -// Stores the bond contracts viewing key to see its own allowance -pub fn allowance_key_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, ALLOWANCE_VIEWING_KEY) -} - -pub fn allowance_key_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, ALLOWANCE_VIEWING_KEY) -} diff --git a/archived-contracts/bonds/src/test.rs b/archived-contracts/bonds/src/test.rs deleted file mode 100644 index 1951015..0000000 --- a/archived-contracts/bonds/src/test.rs +++ /dev/null @@ -1,65 +0,0 @@ -mod test { - use crate::handle::{active, calculate_claim_date, calculate_issuance}; - use shade_protocol::c_std::Uint128; - use shade_protocol::{ - contract_interfaces::{ - bonds::{errors::*}, - }, - }; - - #[test] - fn checking_limits() {} - - #[test] - fn check_active() { - assert_eq!(active(&true, &Uint128::new(10), &Uint128::new(9)), Ok(())); - assert_eq!( - active(&false, &Uint128::new(10), &Uint128::new(9)), - Err(contract_not_active()) - ); - assert_eq!( - active(&true, &Uint128::new(10), &Uint128::new(10)), - Err(global_limit_reached(Uint128::new(10))) - ); - } - - #[test] - fn claim_date() { - assert_eq!(calculate_claim_date(0, 1), 1); - assert_eq!(calculate_claim_date(100_000_000, 7), 100_000_007); - } - - #[test] - fn calc_mint() { - let result = calculate_issuance( - Uint128::new(7_000_000_000_000_000_000), - Uint128::new(10_000_000), - 6, - Uint128::new(5_000_000_000_000_000_000), - 6, - Uint128::new(7_000), - Uint128::new(0), - ); - assert_eq!(result.0, Uint128::new(15_053_763)); - let result2 = calculate_issuance( - Uint128::new(10_000_000_000_000_000_000), - Uint128::new(50_000_000), - 6, - Uint128::new(50_000_000_000_000_000_000), - 8, - Uint128::new(9_000), - Uint128::new(0), - ); - assert_eq!(result2.0, Uint128::new(1_098_901_000)); - let result3 = calculate_issuance( - Uint128::new(10_000_000_000_000_000_000), - Uint128::new(5_000_000_000), - 8, - Uint128::new(50_000_000_000_000_000_000), - 6, - Uint128::new(9_000), - Uint128::new(0), - ); - assert_eq!(result3.0, Uint128::new(10989010)); - } -} diff --git a/archived-contracts/bonds/src/tests/handle.rs b/archived-contracts/bonds/src/tests/handle.rs deleted file mode 100644 index 8f2e342..0000000 --- a/archived-contracts/bonds/src/tests/handle.rs +++ /dev/null @@ -1,500 +0,0 @@ -use crate::tests::{ - check_balances, init_contracts, - query::{query_no_opps, query_opp_parameters}, - set_prices, -}; -use shade_protocol::c_std::Uint128; -use shade_protocol::c_std::Addr; -use shade_protocol::fadroma::core::ContractLink; -use shade_protocol::fadroma::ensemble::{ContractEnsemble, MockEnv}; -use shade_protocol::contract_interfaces::{bonds, query_auth, snip20}; -use shade_protocol::utils::asset::Contract; - -use super::{increase_allowance, query::query_acccount_parameters, setup_admin}; - -#[test] -pub fn test_bonds() { - let (mut chain, bonds, issu, depo, atom, band, _oracle, query_auth, shade_admins) = - init_contracts().unwrap(); - - set_prices( - &mut chain, - &band, - Uint128::new(10_000_000_000_000_000_000), - Uint128::new(5_000_000_000_000_000_000), - Uint128::new(20_000_000_000_000_000_000), - ) - .unwrap(); - - setup_admin(&mut chain, &shade_admins, &bonds); - - increase_allowance(&mut chain, &bonds, &issu); - - // No bond, so fail - buy_opp_fail(&mut chain, &bonds, &depo); - - open_opp( - &mut chain, - &bonds, - &depo, - "admin", - Some(100), - Some(Uint128::new(10_000_000_000)), - Some(0), - Some(Uint128::new(1000)), - Uint128::new(10_000_000_000_000_000_000_000_000), - Uint128::new(10_000_000_000_000_000_000_000_000), - false, - ); - - buy_opp(&mut chain, &bonds, &depo, Uint128::new(2_000_000_000)); - - query_acccount_parameters( - &mut chain, - &bonds.clone(), - &query_auth.clone(), - "secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq", - None, - None, - Some(Uint128::new(2_000_000_000)), - None, - None, - None, - None, - None, - ); - - query_opp_parameters( - &mut chain, - &bonds, - None, - Some(Uint128::new(1000000000)), - None, - None, - None, - None, - None, - None, - None, - None, - ); - - update_config( - &mut chain, - &bonds, - "admin", - None, - None, - None, - None, - None, - None, - None, - Some(Uint128::new(9_000_000_000_000_000_000)), - None, - None, - None, - None, - ); - - buy_opp(&mut chain, &bonds, &depo, Uint128::new(2_000_000_000)); - - query_opp_parameters( - &mut chain, - &bonds, - None, - Some(Uint128::new(2010101010)), - None, - None, - None, - None, - None, - None, - None, - None, - ); - - let msg = query_auth::ExecuteMsg::CreateViewingKey { - entropy: "random".to_string(), - padding: None, - }; - - chain - .execute( - &msg, - MockEnv::new( - "secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq", - query_auth.clone(), - ), - ) - .unwrap(); - - claim(&mut chain, &bonds); - - check_balances( - &mut chain, - &issu, - &depo, - Uint128::new(2010101010), - Uint128::new(4_000_000_000), - ) - .unwrap(); - - close_opp(&mut chain, &bonds, &depo, "admin"); - - query_no_opps(&mut chain, &bonds); - - open_opp( - &mut chain, - &bonds, - &depo, - "admin", - None, - None, - None, - None, - Uint128::new(1), - Uint128::new(1), - false, - ); - open_opp_fail( - &mut chain, - &bonds, - &depo, - "secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq", - None, - None, - None, - None, - Uint128::new(1), - Uint128::new(1), - false, - ); - open_opp_fail( - &mut chain, - &bonds, - &depo, - "admin", - None, - None, - None, - Some(Uint128::new(10000000000000000000)), - Uint128::new(1), - Uint128::new(1), - false, - ); - open_opp( - &mut chain, - &bonds, - &depo, - "admin", - None, - None, - None, - Some(Uint128::new(4_347)), - Uint128::new(1_000_000_000_000_000_000), - Uint128::new(950_000_000_000_000_000), - false, - ); - - set_prices( - &mut chain, - &band, - Uint128::new(7_500_000_000_000_000_000), - Uint128::new(980_000_000_000_000_000), - Uint128::new(20_000_000_000_000_000_000), - ) - .unwrap(); - - buy_opp(&mut chain, &bonds, &depo, Uint128::new(5)); - open_opp( - &mut chain, - &bonds, - &depo, - "admin", - None, - None, - None, - Some(Uint128::new(4_347)), - Uint128::new(1_000_000_000_000_000_000), - Uint128::new(950_000_000_000_000_000), - false, - ); - buy_opp(&mut chain, &bonds, &depo, Uint128::new(500_000_000)); // 5 units - // 4.9/9 for amount purchased, due to config issu_limit of $9 and current depo price of $.98 - query_opp_parameters( - &mut chain, - &bonds, - None, - Some(Uint128::new(54444444)), - None, - None, - None, - None, - None, - None, - None, - None, - ); - - open_opp_fail( - &mut chain, - &bonds, - &atom, - "admin", - None, - Some(Uint128::new(1000000000000000000)), - None, - None, - Uint128::new(1), - Uint128::new(1), - false, - ); - open_opp( - &mut chain, - &bonds, - &atom, - "admin", - None, - Some(Uint128::new(1000000000050)), - None, - None, - Uint128::new(1), - Uint128::new(1), - false, - ); - open_opp( - &mut chain, - &bonds, - &depo, - "admin", - None, - None, - None, - Some(Uint128::new(4_347)), - Uint128::new(1_000_000_000_000_000_000), - Uint128::new(950_000_000_000_000_000), - false, - ); - close_opp(&mut chain, &bonds, &depo, "admin"); - query_opp_parameters( - &mut chain, - &bonds, - Some(Uint128::new(1000000000050)), - None, - None, - None, - None, - None, - None, - None, - None, - None, - ); -} - -fn claim(chain: &mut ContractEnsemble, bonds: &ContractLink) -> () { - let msg = bonds::ExecuteMsg::Claim { padding: None }; - - chain - .execute( - &msg, - MockEnv::new( - "secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq", - bonds.clone(), - ), - ) - .unwrap(); -} - -fn buy_opp( - chain: &mut ContractEnsemble, - bonds: &ContractLink, - depo: &ContractLink, - amount: Uint128, -) -> () { - let msg = snip20::ExecuteMsg::Send { - recipient: bonds.address.clone(), - recipient_code_hash: Some(bonds.code_hash.clone()), - amount, - msg: None, - memo: None, - padding: None, - }; - - chain - .execute( - &msg, - MockEnv::new( - "secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq", - depo.clone(), - ), - ) - .unwrap(); -} - -fn buy_opp_fail( - chain: &mut ContractEnsemble, - bonds: &ContractLink, - depo: &ContractLink, -) -> () { - let msg = snip20::ExecuteMsg::Send { - recipient: bonds.address.clone(), - recipient_code_hash: Some(bonds.code_hash.clone()), - amount: Uint128::new(2_000_000_000), //20 - msg: None, - memo: None, - padding: None, - }; - - match chain.execute( - &msg, - MockEnv::new( - "secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq", - depo.clone(), - ), - ) { - Ok(_) => assert!(false), - Err(_) => assert!(true), - } -} - -fn open_opp( - chain: &mut ContractEnsemble, - bonds: &ContractLink, - depo: &ContractLink, - sender: &str, - time_till_opp_end: Option, - bond_issuance_limit: Option, - bonding_period: Option, - discount: Option, - max_accepted_deposit_price: Uint128, - err_deposit_price: Uint128, - minting_bond: bool, -) -> () { - let mut add: u64 = 50; - if time_till_opp_end.is_some() { - add = time_till_opp_end.unwrap(); - } - - let msg = bonds::ExecuteMsg::OpenBond { - deposit_asset: Contract { - address: depo.address.clone(), - code_hash: depo.code_hash.clone(), - }, - start_time: chain.block().time, - end_time: (chain.block().time + add), - bond_issuance_limit, - bonding_period, - discount, - max_accepted_deposit_price, - err_deposit_price, - minting_bond, - padding: None, - }; - - chain - .execute(&msg, MockEnv::new(sender, bonds.clone())) - .unwrap(); -} - -fn open_opp_fail( - chain: &mut ContractEnsemble, - bonds: &ContractLink, - depo: &ContractLink, - sender: &str, - time_till_opp_end: Option, - bond_issuance_limit: Option, - bonding_period: Option, - discount: Option, - max_accepted_deposit_price: Uint128, - err_deposit_price: Uint128, - minting_bond: bool, -) -> () { - let mut add: u64 = 0; - if time_till_opp_end.is_some() { - add = time_till_opp_end.unwrap(); - } - - let msg = bonds::ExecuteMsg::OpenBond { - deposit_asset: Contract { - address: depo.address.clone(), - code_hash: depo.code_hash.clone(), - }, - start_time: chain.block().time, - end_time: (chain.block().time + add), - bond_issuance_limit, - bonding_period, - discount, - max_accepted_deposit_price, - err_deposit_price, - minting_bond, - padding: None, - }; - - match chain.execute(&msg, MockEnv::new(sender, bonds.clone())) { - Ok(_) => { - assert!(false) - } - Err(_) => { - assert!(true) - } - } -} - -fn close_opp( - chain: &mut ContractEnsemble, - bonds: &ContractLink, - depo: &ContractLink, - sender: &str, -) -> () { - let msg = bonds::ExecuteMsg::CloseBond { - deposit_asset: Contract { - address: depo.address.clone(), - code_hash: depo.code_hash.clone(), - }, - padding: None, - }; - - chain - .execute(&msg, MockEnv::new(sender, bonds.clone())) - .unwrap(); -} - -fn update_config( - chain: &mut ContractEnsemble, - bonds: &ContractLink, - sender: &str, - oracle: Option, - treasury: Option, - issued_asset: Option, - activated: Option, - bond_issuance_limit: Option, - bonding_period: Option, - discount: Option, - global_min_accepted_issued_price: Option, - global_err_issued_price: Option, - allowance_key: Option, - airdrop: Option, - query_auth: Option, -) -> () { - let msg = bonds::ExecuteMsg::UpdateConfig { - oracle, - treasury, - issued_asset, - activated, - bond_issuance_limit, - bonding_period, - discount, - global_min_accepted_issued_price, - global_err_issued_price, - allowance_key, - airdrop, - query_auth, - padding: None, - }; - - chain - .execute(&msg, MockEnv::new(sender, bonds.clone())) - .unwrap(); -} diff --git a/archived-contracts/bonds/src/tests/mod.rs b/archived-contracts/bonds/src/tests/mod.rs deleted file mode 100644 index 6152bd1..0000000 --- a/archived-contracts/bonds/src/tests/mod.rs +++ /dev/null @@ -1,502 +0,0 @@ -pub mod handle; -pub mod query; - -use contract_harness::harness::{ - admin::Admin, bonds::Bonds, query_auth::QueryAuth, snip20::Snip20, -}; -use shade_protocol::c_std::{Addr, StdResult}; -use shade_protocol::fadroma::core::ContractLink; -use shade_protocol::fadroma::ensemble::{ContractEnsemble, MockEnv}; -use shade_oracles_ensemble::harness::{MockBand, OracleRouter, ProxyBandOracle}; -use shade_protocol::contract_interfaces::{ - bonds, query_auth, - snip20::{self, InitialBalance}, -}; -use shade_protocol::utils::asset::Contract; - -use shade_protocol::c_std::Uint128; -use shade_admin::admin; -use shade_oracles::{ - band::{self, proxy::InstantiateMsg, ExecuteMsg::UpdateSymbolPrice}, - router, -}; - -pub fn init_contracts() -> StdResult<( - ContractEnsemble, - ContractLink, - ContractLink, - ContractLink, - ContractLink, - ContractLink, - ContractLink, - ContractLink, - ContractLink, -)> { - let mut chain = ContractEnsemble::new(50); - - // Register shade_admin - let shade_admin = chain.register(Box::new(Admin)); - let shade_admin = chain - .instantiate( - shade_admin.id, - &admin::InstantiateMsg {}, - MockEnv::new( - "admin", - ContractLink { - address: "shade_admin".into(), - code_hash: shade_admin.code_hash, - }, - ), - )? - .instance; - - // Register snip20s - let issu = chain.register(Box::new(Snip20)); - let issu = chain - .instantiate( - issu.id, - &snip20::InstantiateMsg { - name: "Issued".into(), - admin: Some(Addr::unchecked("admin")), - symbol: "ISSU".into(), - decimals: 8, - initial_balances: Some(vec![InitialBalance { - address: Addr::unchecked("admin"), - amount: Uint128::new(1_000_000_000_000_000), - }]), - prng_seed: Default::default(), - config: None, - }, - MockEnv::new( - "admin", - ContractLink { - address: "issu".into(), - code_hash: issu.code_hash, - }, - ), - )? - .instance; - - let msg = snip20::ExecuteMsg::SetViewingKey { - key: "key".to_string(), - padding: None, - }; - chain - .execute( - &msg, - MockEnv::new( - "secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq", - issu.clone(), - ), - ) - .unwrap(); - - let depo = chain.register(Box::new(Snip20)); - let depo = chain - .instantiate( - depo.id, - &snip20::InstantiateMsg { - name: "Deposit".into(), - admin: Some(Addr::unchecked("admin")), - symbol: "DEPO".into(), - decimals: 8, - initial_balances: Some(vec![InitialBalance { - address: Addr::unchecked("secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq"), - amount: Uint128::new(1_000_000_000_000_000), - }]), - prng_seed: Default::default(), - config: None, - }, - MockEnv::new( - "admin", - ContractLink { - address: "depo".into(), - code_hash: depo.code_hash, - }, - ), - )? - .instance; - - let msg = snip20::ExecuteMsg::SetViewingKey { - key: "key".to_string(), - padding: None, - }; - chain - .execute(&msg, MockEnv::new("admin", depo.clone())) - .unwrap(); - - let atom = chain.register(Box::new(Snip20)); - let atom = chain - .instantiate( - atom.id, - &snip20::InstantiateMsg { - name: "Atom".into(), - admin: Some(Addr::unchecked("admin")), - symbol: "ATOM".into(), - decimals: 6, - initial_balances: Some(vec![InitialBalance { - address: Addr::unchecked("other_user"), - amount: Uint128::new(1_000_000_000_000_000), - }]), - prng_seed: Default::default(), - config: None, - }, - MockEnv::new( - "admin", - ContractLink { - address: "atom".into(), - code_hash: atom.code_hash, - }, - ), - )? - .instance; - - let msg = snip20::ExecuteMsg::SetViewingKey { - key: "key".to_string(), - padding: None, - }; - chain - .execute(&msg, MockEnv::new("admin", atom.clone())) - .unwrap(); - - // Register mockband - let band = chain.register(Box::new(MockBand)); - let band = chain - .instantiate( - band.id, - &band::InstantiateMsg {}, - MockEnv::new( - "admin", - ContractLink { - address: "band".into(), - code_hash: band.code_hash, - }, - ), - )? - .instance; - - // Register oracles - let issu_oracle = chain.register(Box::new(ProxyBandOracle)); - let issu_oracle = chain - .instantiate( - issu_oracle.id, - &InstantiateMsg { - admin_auth: shade_oracles::common::Contract { - address: shade_admin.address.clone(), - code_hash: shade_admin.code_hash.clone(), - }, - band: shade_oracles::common::Contract { - address: band.address.clone(), - code_hash: band.code_hash.clone(), - }, - quote_symbol: "ISSU".to_string(), - }, - MockEnv::new( - "admin", - ContractLink { - address: "issu_oracle".into(), - code_hash: issu_oracle.code_hash, - }, - ), - )? - .instance; - - // Depo oracles - let depo_oracle = chain.register(Box::new(ProxyBandOracle)); - let depo_oracle = chain - .instantiate( - depo_oracle.id, - &InstantiateMsg { - admin_auth: shade_oracles::common::Contract { - address: shade_admin.address.clone(), - code_hash: shade_admin.code_hash.clone(), - }, - band: shade_oracles::common::Contract { - address: band.address.clone(), - code_hash: band.code_hash.clone(), - }, - quote_symbol: "DEPO".to_string(), - }, - MockEnv::new( - "admin", - ContractLink { - address: "depo_oracle".into(), - code_hash: depo_oracle.code_hash, - }, - ), - )? - .instance; - - // Atom oracle - let atom_oracle = chain.register(Box::new(ProxyBandOracle)); - let atom_oracle = chain - .instantiate( - atom_oracle.id, - &InstantiateMsg { - admin_auth: shade_oracles::common::Contract { - address: shade_admin.address.clone(), - code_hash: shade_admin.code_hash.clone(), - }, - band: shade_oracles::common::Contract { - address: band.address.clone(), - code_hash: band.code_hash.clone(), - }, - quote_symbol: "ATOM".to_string(), - }, - MockEnv::new( - "admin", - ContractLink { - address: "atom_oracle".into(), - code_hash: atom_oracle.code_hash, - }, - ), - )? - .instance; - - // Oracle Router - let router = chain.register(Box::new(OracleRouter)); - let router = chain - .instantiate( - router.id, - &router::InstantiateMsg { - admin_auth: shade_oracles::common::Contract { - address: shade_admin.address.clone(), - code_hash: shade_admin.code_hash.clone(), - }, - default_oracle: shade_oracles::common::Contract { - address: depo_oracle.address.clone(), - code_hash: depo_oracle.code_hash.clone(), - }, - band: shade_oracles::common::Contract { - address: band.address.clone(), - code_hash: band.code_hash.clone(), - }, - quote_symbol: "DEPO".to_string(), - }, - MockEnv::new( - "admin", - ContractLink { - address: "router".into(), - code_hash: router.code_hash, - }, - ), - )? - .instance; - - let msg = router::ExecuteMsg::UpdateRegistry { - operation: router::RegistryOperation::Add { - oracle: shade_oracles::common::Contract { - address: issu_oracle.address.clone(), - code_hash: issu_oracle.code_hash.clone(), - }, - key: "ISSU".to_string(), - }, - }; - - assert!(chain - .execute(&msg, MockEnv::new("admin", router.clone())) - .is_ok()); - - let msg = router::ExecuteMsg::UpdateRegistry { - operation: router::RegistryOperation::Add { - oracle: shade_oracles::common::Contract { - address: atom_oracle.address.clone(), - code_hash: atom_oracle.code_hash.clone(), - }, - key: "ATOM".to_string(), - }, - }; - - assert!(chain - .execute(&msg, MockEnv::new("admin", router.clone())) - .is_ok()); - - // Register query_auth - let query_auth = chain.register(Box::new(QueryAuth)); - let query_auth = chain - .instantiate( - query_auth.id, - &query_auth::InstantiateMsg { - admin_auth: Contract { - address: shade_admin.address.clone(), - code_hash: shade_admin.code_hash.clone(), - }, - prng_seed: Default::default(), - }, - MockEnv::new( - "admin", - ContractLink { - address: "query_auth".into(), - code_hash: query_auth.code_hash, - }, - ), - )? - .instance; - - // Register bonds - let bonds = chain.register(Box::new(Bonds)); - let bonds = chain - .instantiate( - bonds.id, - &bonds::InstantiateMsg { - limit_admin: Addr::unchecked("limit_admin"), - global_issuance_limit: Uint128::new(100_000_000_000_000_000), - global_minimum_bonding_period: 0, - global_maximum_discount: Uint128::new(10_000), - oracle: Contract { - address: router.address.clone(), - code_hash: router.code_hash.clone(), - }, - treasury: Addr::unchecked("admin"), - issued_asset: Contract { - address: issu.address.clone(), - code_hash: issu.code_hash.clone(), - }, - activated: true, - bond_issuance_limit: Uint128::new(100_000_000_000_000), - bonding_period: 0, - discount: Uint128::new(10_000), - global_min_accepted_issued_price: Uint128::new(10_000_000_000_000_000_000), - global_err_issued_price: Uint128::new(5_000_000_000_000_000_000), - allowance_key_entropy: "".into(), - airdrop: None, - shade_admin: Contract { - address: shade_admin.address.clone(), - code_hash: shade_admin.code_hash.clone(), - }, - query_auth: Contract { - address: query_auth.address.clone(), - code_hash: query_auth.code_hash.clone(), - }, - }, - MockEnv::new( - "admin", - ContractLink { - address: "bonds".into(), - code_hash: bonds.code_hash, - }, - ), - )? - .instance; - - Ok(( - chain, - bonds, - issu, - depo, - atom, - band, - router, - query_auth, - shade_admin, - )) -} - -pub fn set_prices( - chain: &mut ContractEnsemble, - band: &ContractLink, - issu_price: Uint128, - depo_price: Uint128, - atom_price: Uint128, -) -> StdResult<()> { - let msg = UpdateSymbolPrice { - base_symbol: "ISSU".to_string(), - quote_symbol: "ISSU".to_string(), - rate: issu_price.u128().into(), - last_updated: None, - }; - chain - .execute(&msg, MockEnv::new("admin", band.clone())) - .unwrap(); - - let msg = UpdateSymbolPrice { - base_symbol: "DEPO".to_string(), - rate: depo_price.u128().into(), - quote_symbol: "DEPO".to_string(), - last_updated: None, - }; - chain - .execute(&msg, MockEnv::new("admin", band.clone())) - .unwrap(); - - let msg = UpdateSymbolPrice { - base_symbol: "ATOM".to_string(), - rate: atom_price.u128().into(), - quote_symbol: "ATOM".to_string(), - last_updated: None, - }; - chain - .execute(&msg, MockEnv::new("admin", band.clone())) - .unwrap(); - - Ok(()) -} - -pub fn check_balances( - chain: &mut ContractEnsemble, - issu: &ContractLink, - depo: &ContractLink, - user_expected_issu: Uint128, - admin_expected_depo: Uint128, -) -> StdResult<()> { - let msg = snip20::QueryMsg::Balance { - address: Addr::unchecked("admin".to_string()), - key: "key".to_string(), - }; - - let query: snip20::QueryAnswer = chain.query(depo.address.clone(), &msg).unwrap(); - - match query { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, admin_expected_depo); - } - _ => assert!(false), - } - - let msg = snip20::QueryMsg::Balance { - address: Addr::unchecked("secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq".to_string()), - key: "key".to_string(), - }; - - let query: snip20::QueryAnswer = chain.query(issu.address.clone(), &msg).unwrap(); - - match query { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, user_expected_issu); - } - _ => assert!(false), - }; - - Ok(()) -} - -pub fn setup_admin( - chain: &mut ContractEnsemble, - shade_admins: &ContractLink, - bonds: &ContractLink, -) -> () { - let msg = admin::ExecuteMsg::AddContract { - contract_address: bonds.address.clone().to_string(), - }; - - assert!(chain - .execute(&msg, MockEnv::new("admin", shade_admins.clone())) - .is_ok()); -} - -pub fn increase_allowance( - chain: &mut ContractEnsemble, - bonds: &ContractLink, - issu: &ContractLink, -) -> () { - let msg = snip20::ExecuteMsg::IncreaseAllowance { - spender: bonds.address.clone(), - amount: Uint128::new(9_999_999_999_999_999), - expiration: None, - padding: None, - }; - - assert!(chain - .execute(&msg, MockEnv::new("admin", issu.clone())) - .is_ok()); -} diff --git a/archived-contracts/bonds/src/tests/query.rs b/archived-contracts/bonds/src/tests/query.rs deleted file mode 100644 index a210bbf..0000000 --- a/archived-contracts/bonds/src/tests/query.rs +++ /dev/null @@ -1,185 +0,0 @@ -use shade_protocol::c_std::{testing::*, Binary, Addr}; -use shade_protocol::fadroma::core::ContractLink; -use shade_protocol::fadroma::ensemble::ContractEnsemble; -use shade_protocol::contract_interfaces::{ - bonds, - query_auth::{self, PermitData, QueryPermit}, - snip20::helpers::Snip20Asset, -}; - -use shade_protocol::query_authentication::transaction::{PermitSignature, PubKey}; - -use shade_protocol::c_std::Uint128; - -pub fn query_no_opps(chain: &mut ContractEnsemble, bonds: &ContractLink) -> () { - let msg = bonds::QueryMsg::BondOpportunities {}; - - let query: bonds::QueryAnswer = chain.query(bonds.address.clone(), &msg).unwrap(); - - match query { - bonds::QueryAnswer::BondOpportunities { bond_opportunities } => { - assert_eq!(bond_opportunities, vec![]); - } - _ => assert!(false), - } -} - -pub fn query_opp_parameters( - chain: &mut ContractEnsemble, - bonds: &ContractLink, - issuance_limit: Option, - amount_issued: Option, - deposit_denom: Option, - start_time: Option, - end_time: Option, - bonding_period: Option, - discount: Option, - max_accepted_deposit_price: Option, - err_deposit_price: Option, - minting_bond: Option, -) -> () { - let query: bonds::QueryAnswer = chain - .query( - bonds.address.clone(), - &bonds::QueryMsg::BondOpportunities {}, - ) - .unwrap(); - - match query { - bonds::QueryAnswer::BondOpportunities { - bond_opportunities, .. - } => { - if issuance_limit.is_some() { - assert_eq!( - bond_opportunities[0].issuance_limit, - issuance_limit.unwrap() - ) - } - if amount_issued.is_some() { - assert_eq!(bond_opportunities[0].amount_issued, amount_issued.unwrap()) - } - if deposit_denom.is_some() { - assert_eq!(bond_opportunities[0].deposit_denom, deposit_denom.unwrap()) - } - if start_time.is_some() { - assert_eq!(bond_opportunities[0].start_time, start_time.unwrap()) - } - if end_time.is_some() { - assert_eq!(bond_opportunities[0].end_time, end_time.unwrap()) - } - if bonding_period.is_some() { - assert_eq!( - bond_opportunities[0].bonding_period, - bonding_period.unwrap() - ) - } - if discount.is_some() { - assert_eq!(bond_opportunities[0].discount, discount.unwrap()) - } - if max_accepted_deposit_price.is_some() { - assert_eq!( - bond_opportunities[0].max_accepted_deposit_price, - max_accepted_deposit_price.unwrap() - ) - } - if err_deposit_price.is_some() { - assert_eq!( - bond_opportunities[0].err_deposit_price, - err_deposit_price.unwrap() - ) - } - if minting_bond.is_some() { - assert_eq!(bond_opportunities[0].minting_bond, minting_bond.unwrap()) - } - } - _ => assert!(false), - }; -} - -pub fn query_acccount_parameters( - chain: &mut ContractEnsemble, - bonds: &ContractLink, - query_auth: &ContractLink, - _sender: &str, - deposit_denom: Option, - end_time: Option, - deposit_amount: Option, - deposit_price: Option, - claim_amount: Option, - claim_price: Option, - discount: Option, - discount_price: Option, -) -> () { - let permit = get_permit(); - - let deps = mock_dependencies(20, &[]); - - // Confirm that the permit is valid - assert!(permit.clone().validate(&deps.api, None).is_ok()); - - let _query: query_auth::QueryAnswer = chain - .query( - query_auth.address.clone(), - &query_auth::QueryMsg::ValidatePermit { - permit: permit.clone(), - }, - ) - .unwrap(); - - let query: bonds::QueryAnswer = chain - .query(bonds.address.clone(), &bonds::QueryMsg::Account { permit }) - .unwrap(); - - match query { - bonds::QueryAnswer::Account { pending_bonds, .. } => { - if deposit_denom.is_some() { - assert_eq!(pending_bonds[0].deposit_denom, deposit_denom.unwrap()) - } - if end_time.is_some() { - assert_eq!(pending_bonds[0].end_time, end_time.unwrap()) - } - if deposit_price.is_some() { - assert_eq!(pending_bonds[0].deposit_price, deposit_price.unwrap()) - } - if deposit_amount.is_some() { - assert_eq!(pending_bonds[0].deposit_amount, deposit_amount.unwrap()) - } - if claim_amount.is_some() { - assert_eq!(pending_bonds[0].claim_amount, claim_amount.unwrap()) - } - if claim_price.is_some() { - assert_eq!(pending_bonds[0].claim_price, claim_price.unwrap()) - } - if discount.is_some() { - assert_eq!(pending_bonds[0].discount, discount.unwrap()) - } - if discount_price.is_some() { - assert_eq!(pending_bonds[0].discount_price, discount_price.unwrap()) - } - } - _ => assert!(false), - }; -} - -fn get_permit() -> QueryPermit { - QueryPermit { - params: PermitData { - key: "key".to_string(), - data: Binary::from_base64("c29tZSBzdHJpbmc=").unwrap() - }, - signature: PermitSignature { - pub_key: PubKey::new( - Binary::from_base64( - "A9NjbriiP7OXCpoTov9ox/35+h5k0y1K0qCY/B09YzAP" - ).unwrap() - ), - signature: Binary::from_base64( - "XRzykrPmMs0ZhksNXX+eU0TM21fYBZXZogr5wYZGGy11t2ntfySuQNQJEw6D4QKvPsiU9gYMsQ259dOzMZNAEg==" - ).unwrap() - }, - account_number: None, - chain_id: Some(String::from("chain")), - sequence: None, - memo: None - } -} diff --git a/archived-contracts/dao/lp_shdswap/.cargo/config b/archived-contracts/dao/lp_shdswap/.cargo/config deleted file mode 100644 index 882fe08..0000000 --- a/archived-contracts/dao/lp_shdswap/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[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/archived-contracts/dao/lp_shdswap/.circleci/config.yml b/archived-contracts/dao/lp_shdswap/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/archived-contracts/dao/lp_shdswap/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/dao/lp_shdswap/Cargo.toml b/archived-contracts/dao/lp_shdswap/Cargo.toml deleted file mode 100644 index 7c1cd10..0000000 --- a/archived-contracts/dao/lp_shdswap/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "lp_shdswap" -version = "0.1.0" -authors = ["Jack Swenson ", "Jack Sisson "] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] - -[dependencies] -shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ - "adapter", - "dao", - "dex", - "treasury", - "lp_shdswap", - "math", - "storage_plus", -] } - -[dev-dependencies] -shade-multi-test = { path = "../../../packages/multi_test", features = [ - "admin" -] } diff --git a/archived-contracts/dao/lp_shdswap/Makefile b/archived-contracts/dao/lp_shdswap/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/archived-contracts/dao/lp_shdswap/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/archived-contracts/dao/lp_shdswap/README.md b/archived-contracts/dao/lp_shdswap/README.md deleted file mode 100644 index 4f9ceb1..0000000 --- a/archived-contracts/dao/lp_shdswap/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# Shade Swap LP Providing and Bonding -* [Introduction](#Introduction) -* [Sections](#Sections) - * [Init](#Init) - * [DAO Adapter](/packages/shade_protocol/src/DAO_ADAPTER.md) - * [Interface](#Interface) - * Messages - * [Receive](#Receive) - * [UpdateConfig](#UpdateConfig) - * Queries - * [Config](#Config) - * [Delegations](#Delegations) - -# Introduction -The sSCRT Staking contract receives sSCRT, redeems it for SCRT, then stakes it with a validator that falls within the criteria it has been configured with. The configured `treasury` will receive all funds from claiming rewards/unbonding. - -# Sections - -## Init -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|admin | Addr | contract owner/admin; a valid bech32 address; -|treasury | Addr | contract designated to receive all outgoing funds -|viewing_key | String | Viewing Key to be set for any relevant SNIP-20 -|token_a | Contract | One token to be provided to the pool -|token_b | Contract | Other token to be provided to the pool -|pool | Contract | Pool contract to provide LP to -|bonding | Contract | Contract to bond LP for rewards - -## Interface - -### Messages -#### UpdateConfig -Updates the given values -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|config | Config | contract designated to receive all outgoing funds - -##### Response -```json -{ - "update_config": { - "status": "success" - } -} -``` - - -### Queries - -#### Config -Gets the contract's configuration variables -##### Response -```json -{ - "config": { - "config": { - "owner": "Owner address", - } - } -} -``` diff --git a/archived-contracts/dao/lp_shdswap/src/contract.rs b/archived-contracts/dao/lp_shdswap/src/contract.rs deleted file mode 100644 index 118dd7c..0000000 --- a/archived-contracts/dao/lp_shdswap/src/contract.rs +++ /dev/null @@ -1,198 +0,0 @@ -use shade_protocol::{ - c_std::{ - entry_point, - to_binary, - Binary, - Deps, - DepsMut, - Env, - MessageInfo, - Response, - StdError, - StdResult, - Uint128, - }, - contract_interfaces::{ - dao::{ - adapter, - lp_shdswap::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}, - }, - dex::shadeswap, - }, - snip20::helpers::{register_receive, set_viewing_key_msg}, - utils::{asset::Contract, Query}, -}; - -use crate::{execute, query, storage::*}; - -#[entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - SELF_ADDRESS.save(deps.storage, &env.contract.address)?; - VIEWING_KEY.save(deps.storage, &msg.viewing_key)?; - - let pair_info: shadeswap::PairInfoResponse = - match (shadeswap::PairQuery::GetPairInfo {}.query(&deps.querier, &msg.pair)) { - Ok(info) => info, - Err(_) => { - return Err(StdError::generic_err("Failed to query pair")); - } - }; - - let token_a = match pair_info.pair.token_0 { - shadeswap::TokenType::CustomToken { - contract_addr, - token_code_hash, - } => Contract { - address: contract_addr, - code_hash: token_code_hash, - }, - _ => { - return Err(StdError::generic_err("Unsupported token type")); - } - }; - - let token_b = match pair_info.pair.token_1 { - shadeswap::TokenType::CustomToken { - contract_addr, - token_code_hash, - } => Contract { - address: contract_addr, - code_hash: token_code_hash, - }, - _ => { - return Err(StdError::generic_err("Unsupported token type")); - } - }; - - /*let staking_info: amm_pair::QueryMsgResponse::StakingContractInfo = - amm_pair::QueryMsg::GetStakingContractInfo.query( - &deps.querier, - msg.pair.code_hash.clone(), - msg.pair.address.clone(), - )?; - - //TODO need this query - let reward_token: Contract = Contract { - address: Addr::unchecked(""), - code_hash: "".into(), - };*/ - - let config = Config { - admin: match msg.admin { - None => info.sender.clone(), - Some(admin) => admin, - }, - treasury: msg.treasury, - pair: msg.pair.clone(), - token_a: token_a.clone(), - token_b: token_b.clone(), - liquidity_token: pair_info.liquidity_token.clone(), - staking_contract: Some(Contract::default()), - //staking_info.staking_contract.clone(), - // TODO: query reward token from staking contract - reward_token: None, - //TODO: add this - split: None, - }; - // TODO verify split contract - let mut assets = vec![ - token_a.clone(), - token_b.clone(), - pair_info.liquidity_token.clone(), - ]; - - if let Some(token) = config.reward_token.clone() { - assets.push(token); - } - - let mut messages = vec![]; - - // Init unbondings & msgs - for token in assets { - UNBONDING.save(deps.storage, token.address.clone(), &Uint128::zero())?; - - messages.append(&mut vec![ - set_viewing_key_msg(msg.viewing_key.clone(), None, &token)?, - register_receive(env.contract.code_hash.clone(), None, &token)?, - ]); - } - - // Init approvals to max - /* - for token in vec![token_a, token_b] { - set_allowance(&deps, &env, - config.pair.clone(), - Uint128(9_000_000_000_000_000_000_000_000), - msg.viewing_key.clone(), - token.clone(), - ); - } - */ - - CONFIG.save(deps.storage, &config)?; - - Ok(Response::new()) -} - -#[entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - ExecuteMsg::Receive { - sender, - from, - amount, - msg, - .. - } => execute::receive(deps, env, info, sender, from, amount, msg), - ExecuteMsg::UpdateConfig { config } => execute::try_update_config(deps, env, info, config), - ExecuteMsg::RefreshApprovals => execute::refesh_allowances(deps, env, info), - ExecuteMsg::Adapter(adapter) => match adapter { - adapter::SubExecuteMsg::Unbond { asset, amount } => { - let asset = deps.api.addr_validate(&asset)?; - execute::unbond(deps, env, info, asset, amount) - } - adapter::SubExecuteMsg::Claim { asset } => { - let asset = deps.api.addr_validate(&asset)?; - execute::claim(deps, env, info, asset) - } - adapter::SubExecuteMsg::Update { asset } => { - let asset = deps.api.addr_validate(&asset)?; - execute::update(deps, env, info, asset) - } - }, - } -} - -#[entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query::config(deps)?), - QueryMsg::Adapter(adapter) => match adapter { - adapter::SubQueryMsg::Balance { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::balance(deps, asset)?) - } - adapter::SubQueryMsg::Claimable { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::claimable(deps, asset)?) - } - adapter::SubQueryMsg::Unbonding { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::unbonding(deps, asset)?) - } - adapter::SubQueryMsg::Unbondable { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::unbondable(deps, asset)?) - } - adapter::SubQueryMsg::Reserves { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::reserves(deps, asset)?) - } - }, - } -} diff --git a/archived-contracts/dao/lp_shdswap/src/execute.rs b/archived-contracts/dao/lp_shdswap/src/execute.rs deleted file mode 100644 index 1a77b3a..0000000 --- a/archived-contracts/dao/lp_shdswap/src/execute.rs +++ /dev/null @@ -1,261 +0,0 @@ -use crate::storage::*; -use shade_protocol::{ - c_std::{ - to_binary, - Addr, - Binary, - DepsMut, - Env, - MessageInfo, - Response, - StdError, - StdResult, - Uint128, - }, - contract_interfaces::dao::{ - adapter, - lp_shdswap::{get_supported_asset, is_supported_asset, Config, ExecuteAnswer, SplitMethod}, - }, - snip20::helpers::balance_query, - utils::{asset::Contract, generic_response::ResponseStatus}, -}; - -pub fn receive( - deps: DepsMut, - _env: Env, - info: MessageInfo, - _sender: Addr, - _from: Addr, - _amount: Uint128, - _msg: Option, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if !is_supported_asset(&config, &info.sender) { - return Err(StdError::generic_err("Unrecognized Asset")); - } - - /* Base tokens in pair - * - * max out how much LP you can provide - * bond LP token into rewards - */ - - let desired_token: Contract; - - if info.sender == config.token_a.address { - desired_token = config.token_a; - println!("{}", desired_token.address); - } else if info.sender == config.token_b.address { - desired_token = config.token_b; - println!("{}", desired_token.address); - } else if info.sender == config.liquidity_token.address { - // TODO: stake lp tokens & exit - } else { - // TODO: send to treasury, non-pair rewards token - } - - // get exchange rate &dyn Storageplit tokens - match config.split { - Some(split) => { - match split { - SplitMethod::Conversion { contract: _ } => {} /* - SplitMethod::Conversion { mint } => { - // TODO: get exchange rate - mint::QueryMsg::Mint { - offer_asset: desired_token.address.clone(), - amount: Uint128(1u128.pow(desired_token.decimals)), - }.query( - ); - }, - */ - //SplitMethod::Market { contract } => { } - //SplitMethod::Lend { contract } => { } - } - } - None => {} - } - - /*let pair_info: shadeswap::PairInfoResponse = - match (shadeswap::PairQuery::GetPairInfo {}.query(&deps.querier, &msg.pair)) { - Ok(info) => info, - Err(_) => { - return Err(StdError::generic_err("Failed to query pair")); - } - }; - - if desired_token.address == pair_info.token_0.address { - denominator = pair_info.amount_0; - } else if desired_token.address == pair_info.token_1.address { - denominator = pair_info.amount_1; - } else { - return Err(StdError::generic_err(format!( - "Asset configuration conflict, pair info missing: {}", - desired_token.address.to_string() - ))); - }*/ - - let _provide_amounts: (Uint128, Uint128); - // TODO math with exchange_rate & pool ratio & received amount - - // Can be added with a trigger if too slow - //let mut messages = vec![]; - /*messages.append(set_allowance( - &deps, - &env, - config.pair.clone(), - provide_amounts.0, - msg.viewing_key.clone(), - config.token_a.clone(), - )?); - messages.append(set_allowance( - &deps, - &env, - config.pair.clone(), - provide_amounts.0, - msg.viewing_key.clone(), - config.token_b.clone(), - )?);*/ - - /* TODO - * - add LP to pair - * - stake LP tokens in staking_contract (auto complete from pair?) - */ - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Receive { - status: ResponseStatus::Success, - })?)) -} - -pub fn try_update_config( - deps: DepsMut, - _env: Env, - info: MessageInfo, - config: Config, -) -> StdResult { - let cur_config = CONFIG.load(deps.storage)?; - - if info.sender != cur_config.admin { - return Err(StdError::generic_err("unauthorized")); - } - - // Save new info - CONFIG.save(deps.storage, &config)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { - status: ResponseStatus::Success, - config, - })?), - ) -} - -pub fn refesh_allowances(_deps: DepsMut, _env: Env, _info: MessageInfo) -> StdResult { - Ok(Response::new()) -} - -/* Claim rewards and restake, hold enough for pending unbondings - * Send available unbonded funds to treasury - */ -pub fn update(deps: DepsMut, _env: Env, _info: MessageInfo, asset: Addr) -> StdResult { - //let mut messages = vec![]; - - let config = CONFIG.load(deps.storage)?; - - if !is_supported_asset(&config, &asset) { - return Err(StdError::generic_err("Unrecognized Asset")); - } - - /* Claim Rewards - * - * If rewards is an LP denom, try to re-add LP based on balances - * e.g. SILK/SHD w/ SHD rewards - * pair/split the new SHD with SILK and provide - * - * Else send direct to treasury e.g. sSCRT/sETH w/ SHD rewards - */ - Ok( - Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Update { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn unbond( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - asset: Addr, - amount: Uint128, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - //TODO: needs treasury & manager as admin, maybe just manager? - /* - if info.sender != config.admin && info.sender != config.treasury { - return Err(StdError::generic_err("unauthorized")); - } - */ - - //let mut messages = vec![]; - - if asset == config.liquidity_token.address { - /* Pull LP token out of rewards contract - * Hold for claiming - */ - } else if vec![config.token_a.address, config.token_b.address].contains(&asset) { - /* Pull LP from rewards - * Split LP into tokens A & B - * Mark requested token for claim - */ - } else { - return Err(StdError::generic_err("Unrecognized Asset")); - } - - UNBONDING.update(deps.storage, asset.clone(), |u| -> StdResult { - Ok(u.unwrap_or_else(|| Uint128::zero()) + amount) - })?; - - Ok( - Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Unbond { - status: ResponseStatus::Success, - amount, - })?), - ) -} - -pub fn claim(deps: DepsMut, env: Env, _info: MessageInfo, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if !is_supported_asset(&config, &asset) { - return Err(StdError::generic_err("Unrecognized Asset")); - } - - let asset_contract = get_supported_asset(&config, &asset); - - //let mut messages = vec![]; - - let balance = balance_query( - &deps.querier, - env.contract.address, - VIEWING_KEY.load(deps.storage)?, - &asset_contract, - )?; - - let mut claim_amount = UNBONDING.load(deps.storage, asset.clone())?; - - if balance < claim_amount { - claim_amount = balance; - } - - UNBONDING.update(deps.storage, asset.clone(), |u| -> StdResult { - Ok(u.unwrap_or_else(|| Uint128::zero()) - claim_amount) - })?; - - Ok( - Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Claim { - status: ResponseStatus::Success, - amount: claim_amount, - })?), - ) -} diff --git a/archived-contracts/dao/lp_shdswap/src/lib.rs b/archived-contracts/dao/lp_shdswap/src/lib.rs deleted file mode 100644 index d4a766c..0000000 --- a/archived-contracts/dao/lp_shdswap/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -pub mod contract; -pub mod execute; -pub mod query; -pub mod storage; - -#[cfg(test)] -mod test; - -/*#[cfg(target_arch = "wasm32")] -mod wasm { - use super::contract; - use shade_protocol::c_std::{ - do_handle, do_init, do_query, ExternalApi, ExternalQuerier, ExternalStorage, - }; - - #[no_mangle] - extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { - do_init( - &contract::init::, - env_ptr, - msg_ptr, - ) - } - - #[no_mangle] - extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { - do_handle( - &contract::handle::, - env_ptr, - msg_ptr, - ) - } - - #[no_mangle] - extern "C" fn query(msg_ptr: u32) -> u32 { - do_query( - &contract::query::, - msg_ptr, - ) - } - - // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available - // automatically because we `use cosmwasm_std`. -}*/ diff --git a/archived-contracts/dao/lp_shdswap/src/query.rs b/archived-contracts/dao/lp_shdswap/src/query.rs deleted file mode 100644 index 6dc5b04..0000000 --- a/archived-contracts/dao/lp_shdswap/src/query.rs +++ /dev/null @@ -1,155 +0,0 @@ -use shade_protocol::c_std::{ - Addr, - Deps, - StdError, - StdResult, - Uint128, -}; - -use shade_protocol::{ - contract_interfaces::dao::{ - adapter, - lp_shdswap::{get_supported_asset, is_supported_asset, QueryAnswer}, - }, -}; - -use shade_protocol::snip20::helpers::balance_query; - -use crate::storage::*; - -pub fn config(deps: Deps) -> StdResult { - Ok(QueryAnswer::Config { - config: CONFIG.load(deps.storage)?, - }) -} - -pub fn rewards(_deps: Deps) -> StdResult { - //TODO: query pending rewards from rewards contract - Ok(Uint128::zero()) -} - -pub fn balance(deps: Deps, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if !is_supported_asset(&config, &asset) { - return Err(StdError::generic_err(format!( - "Unrecognized Asset {}", - asset - ))); - } - - let balance = Uint128::zero(); - - if vec![config.token_a.address, config.token_b.address].contains(&asset) { - // Determine balance of LP, determine redemption value - } else if config.liquidity_token.address == asset { - // Check LP tokens in rewards contract + balance - } - - Ok(adapter::QueryAnswer::Balance { amount: balance }) -} - -pub fn claimable(deps: Deps, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if !is_supported_asset(&config, &asset) { - return Err(StdError::generic_err(format!( - "Unrecognized Asset {}", - asset - ))); - } - - let asset_contract = get_supported_asset(&config, &asset); - - let balance = balance_query( - &deps.querier, - SELF_ADDRESS.load(deps.storage)?, - VIEWING_KEY.load(deps.storage)?, - &asset_contract, - )?; - - let mut claimable = UNBONDING.load(deps.storage, asset.clone())?; - - if balance < claimable { - claimable = balance; - } - - Ok(adapter::QueryAnswer::Claimable { amount: claimable }) -} - -pub fn unbonding(deps: Deps, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if !is_supported_asset(&config, &asset) { - return Err(StdError::generic_err(format!( - "Unrecognized Asset {}", - asset - ))); - } - - Ok(adapter::QueryAnswer::Unbonding { - amount: UNBONDING.load(deps.storage, asset.clone())?, - }) -} - -pub fn unbondable(deps: Deps, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if !is_supported_asset(&config, &asset) { - return Err(StdError::generic_err(format!( - "Unrecognized Asset {}", - asset - ))); - } - - let unbonding = UNBONDING.load(deps.storage, asset.clone())?; - - /* Need to check LP token redemption value - */ - let unbondable = match balance(deps, asset)? { - adapter::QueryAnswer::Balance { amount } => { - if amount < unbonding { - Uint128::zero() - } else { - amount - unbonding - } - } - _ => { - return Err(StdError::generic_err("Failed to query balance")); - } - }; - - Ok(adapter::QueryAnswer::Unbondable { amount: unbondable }) -} - -pub fn reserves(deps: Deps, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if !is_supported_asset(&config, &asset) { - return Err(StdError::generic_err(format!( - "Unrecognized Asset {}", - asset - ))); - } - - let asset_contract = get_supported_asset(&config, &asset); - - let unbonding = UNBONDING.load(deps.storage, asset.clone())?; - - let balance = balance_query( - &deps.querier, - SELF_ADDRESS.load(deps.storage)?, - VIEWING_KEY.load(deps.storage)?, - &asset_contract, - )?; - - if unbonding >= balance { - return Ok(adapter::QueryAnswer::Reserves { - amount: Uint128::zero(), - }); - } else { - return Ok(adapter::QueryAnswer::Reserves { - amount: balance - unbonding, - }); - } -} diff --git a/archived-contracts/dao/lp_shdswap/src/storage.rs b/archived-contracts/dao/lp_shdswap/src/storage.rs deleted file mode 100644 index 3a88bc1..0000000 --- a/archived-contracts/dao/lp_shdswap/src/storage.rs +++ /dev/null @@ -1,10 +0,0 @@ -use shade_protocol::{ - c_std::{Addr, Uint128}, - contract_interfaces::dao::lp_shdswap, - secret_storage_plus::{Item, Map}, -}; - -pub const CONFIG: Item = Item::new("config"); -pub const VIEWING_KEY: Item = Item::new("viewing_key"); -pub const SELF_ADDRESS: Item = Item::new("self_address"); -pub const UNBONDING: Map = Map::new("unbonding"); diff --git a/archived-contracts/dao/lp_shdswap/src/test.rs b/archived-contracts/dao/lp_shdswap/src/test.rs deleted file mode 100644 index 04225b9..0000000 --- a/archived-contracts/dao/lp_shdswap/src/test.rs +++ /dev/null @@ -1,46 +0,0 @@ -/* -#[cfg(test)] -pub mod tests { - use shade_protocol::c_std::{ - testing::{ - mock_dependencies, mock_env, MockStorage, MockApi, MockQuerier - }, - Addr, - coins, from_binary, StdError, Uint128, - DepsMut, - }; - use shade_protocol::{ - treasury::{ - QueryAnswer, InstantiateMsg, ExecuteMsg, - QueryMsg, - }, - asset::Contract, - }; - - use crate::{ - contract::{ - init, handle, query, - }, - }; - - fn create_contract(address: &str, code_hash: &str) -> Contract { - let env = mock_env(address.to_string(), &[]); - return Contract{ - address: info.sender, - code_hash: code_hash.to_string() - } - } - - fn dummy_init(admin: String, viewing_key: String) -> Extern { - let mut deps = mock_dependencies(20, &[]); - let msg = InstantiateMsg { - admin: Option::from(Addr(admin.clone())), - viewing_key, - }; - let env = mock_env(admin, &coins(1000, "earth")); - let _res = init(&mut deps, env, info, msg).unwrap(); - - return deps - } -} -*/ diff --git a/archived-contracts/dao/rewards_emission/.cargo/config b/archived-contracts/dao/rewards_emission/.cargo/config deleted file mode 100644 index 882fe08..0000000 --- a/archived-contracts/dao/rewards_emission/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[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/archived-contracts/dao/rewards_emission/.circleci/config.yml b/archived-contracts/dao/rewards_emission/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/archived-contracts/dao/rewards_emission/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/dao/rewards_emission/Cargo.toml b/archived-contracts/dao/rewards_emission/Cargo.toml deleted file mode 100644 index 24091b0..0000000 --- a/archived-contracts/dao/rewards_emission/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "rewards_emission" -version = "0.1.0" -authors = ["Jack Swenson "] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] -debug-print = ["shade-protocol/debug-print"] - -[dependencies] -cosmwasm-schema = { git = "https://github.com/CosmWasm/cosmwasm", commit = "1e05e7e" } - -shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = ["rewards_emission", "snip20", "dao", "utils", "dao-utils", "chrono"] } -schemars = "0.7" diff --git a/archived-contracts/dao/rewards_emission/Makefile b/archived-contracts/dao/rewards_emission/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/archived-contracts/dao/rewards_emission/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/archived-contracts/dao/rewards_emission/README.md b/archived-contracts/dao/rewards_emission/README.md deleted file mode 100644 index 55ef630..0000000 --- a/archived-contracts/dao/rewards_emission/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# sSCRT Staking Contract -* [Introduction](#Introduction) -* [Sections](#Sections) - * [Init](#Init) - * [Admin](#Admin) - * Messages - * [UpdateConfig](#UpdateConfig) - * [Receive](#Receive) - * [Unbond](#Unbond) - * [Claim](#Claim) - * Queries - * [GetConfig](#GetConfig) - * [Delegations](#Delegations) - * [Delegation](#Delegation) -# Introduction -The sSCRT Staking contract receives sSCRT, redeems it for SCRT, then stakes it with a validator that falls within the criteria it has been configured with. The configured `treasury` will receive all funds from claiming rewards/unbonding. - -# Sections - -## Init -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|owner | Addr | contract owner/admin; a valid bech32 address; -|treasury | Addre | contract designated to receive all outgoing funds -|sscrt | Contract | sSCRT Snip-20 contract to accept for redemption/staking, all other funds will error -|validator_bounds | ValidatorBounds | criteria defining an acceptable validator to stake with - -## Admin - -### Messages -#### UpdateConfig -Updates the given values -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|owner | Addr | contract owner/admin; a valid bech32 address; -|treasury | Addre | contract designated to receive all outgoing funds -|sscrt | Contract | sSCRT Snip-20 contract to accept for redemption/staking, all other funds will error -|validator_bounds | ValidatorBounds | criteria defining an acceptable validator to stake with - -##### Response -```json -{ - "update_config": { - "status": "success" - } -} -``` - - -### Queries - -#### GetConfig -Gets the contract's configuration variables -##### Response -```json -{ - "config": { - "config": { - "owner": "Owner address", - } - } -} -``` diff --git a/archived-contracts/dao/rewards_emission/src/contract.rs b/archived-contracts/dao/rewards_emission/src/contract.rs deleted file mode 100644 index 1af1c0e..0000000 --- a/archived-contracts/dao/rewards_emission/src/contract.rs +++ /dev/null @@ -1,96 +0,0 @@ -use shade_protocol::c_std::{ - shd_entry_point, - to_binary, - Addr, - Binary, - Deps, - DepsMut, - Env, - MessageInfo, - Response, - StdResult, -}; - -use shade_protocol::contract_interfaces::dao::rewards_emission::{ - Config, - ExecuteMsg, - InstantiateMsg, - QueryMsg, -}; - -use shade_protocol::snip20::helpers::fetch_snip20; -//use shade_protocol::contract_interfaces::dao::adapter; - -use crate::{execute, query, storage::*}; - -#[shd_entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - let mut admins: Vec = msg.admins - .iter() - //TODO change unwrap - .map(|a| deps.api.addr_validate(&a).ok().unwrap()) - .collect(); - - if !admins.contains(&info.sender) { - admins.push(info.sender); - } - - let config = Config { - admins, - treasury: deps.api.addr_validate(&msg.treasury)?, - }; - - CONFIG.save(deps.storage, &config)?; - SELF_ADDRESS.save(deps.storage, &env.contract.address)?; - VIEWING_KEY.save(deps.storage, &msg.viewing_key)?; - TOKEN.save( - deps.storage, - &fetch_snip20(&msg.token.into_valid(deps.api)?, &deps.querier)?, - )?; - - Ok(Response::new()) -} - -#[shd_entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - ExecuteMsg::Receive { - sender, - from, - amount, - msg, - .. - } => execute::receive(deps, env, info, sender, from, amount, msg), - ExecuteMsg::UpdateConfig { config } => execute::try_update_config(deps, env, info, config), - ExecuteMsg::RegisterRewards { - token, - distributor, - amount, - cycle, - expiration, - } => execute::register_rewards( - deps, - env, - info, - token, - distributor, - amount, - cycle, - expiration, - ), - ExecuteMsg::RefillRewards {} => execute::refill_rewards(deps, env, info), - } -} - -#[shd_entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query::config(deps)?), - //QueryMsg::PendingAllowance { asset } => to_binary(&query::pending_allowance(deps, asset)?), - } -} diff --git a/archived-contracts/dao/rewards_emission/src/execute.rs b/archived-contracts/dao/rewards_emission/src/execute.rs deleted file mode 100644 index 12322c3..0000000 --- a/archived-contracts/dao/rewards_emission/src/execute.rs +++ /dev/null @@ -1,184 +0,0 @@ -use shade_protocol::c_std::{ - to_binary, - MessageInfo, - Binary, - Env, - DepsMut, - Response, - Addr, - StdError, - StdResult, - Uint128, -}; - -use shade_protocol::snip20::helpers::{ - send_from_msg, -}; - -use shade_protocol::{ - contract_interfaces::{ - dao::{ - rewards_emission::{Config, ExecuteAnswer, Reward}, - }, - }, - utils::{ - asset::{Contract}, - generic_response::ResponseStatus, - cycle::{Cycle, exceeds_cycle, utc_now, parse_utc_datetime}, - }, -}; - -use crate::{ - storage::*, -}; - -pub fn receive( - _deps: DepsMut, - _env: Env, - _info: MessageInfo, - _sender: Addr, - _from: Addr, - _amount: Uint128, - _msg: Option, -) -> StdResult { - //TODO: forward to distributor (quick fix mechanism) - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Receive { - status: ResponseStatus::Success, - })?)) -} - -pub fn try_update_config( - deps: DepsMut, - _env: Env, - info: MessageInfo, - config: Config, -) -> StdResult { - let cur_config = CONFIG.load(deps.storage)?; - - if !cur_config.admins.contains(&info.sender) { - return Err(StdError::generic_err("unauthorized")); - } - - CONFIG.save(deps.storage, &config)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { - status: ResponseStatus::Success, - })?)) -} - -pub fn refill_rewards( - deps: DepsMut, - env: Env, - info: MessageInfo, -) -> StdResult { - - let config = CONFIG.load(deps.storage)?; - let mut messages = vec![]; - - if let Some(mut reward) = REWARD.may_load(deps.storage, info.sender.clone())? { - - let token = TOKEN.load(deps.storage)?; - let now = utc_now(&env); - - // Check expiration - if let Some(expiry) = reward.expiration.clone() { - if now > parse_utc_datetime(&expiry)? { - return Err(StdError::generic_err(format!("Rewards expired on {}", expiry))); - } - } - - if exceeds_cycle(&now, &parse_utc_datetime(&reward.last_refresh.clone())?, reward.cycle.clone()) { - reward.last_refresh = now.to_rfc3339(); - REWARD.save(deps.storage, info.sender, &reward)?; - // Send from treasury - messages.push(send_from_msg( - config.treasury.clone(), - reward.distributor.address.clone(), - reward.amount, - None, - None, - None, - &token.contract.clone(), - )?); - } - else { - return Err(StdError::generic_err(format!("Last rewards were requested on {}", reward.last_refresh))); - } - } - else { - return Err(StdError::generic_err("No rewards for you")); - } - - Ok(Response::new() - .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::RefillRewards { - status: ResponseStatus::Success, - })?) - ) -} - -pub fn register_rewards( - deps: DepsMut, - env: Env, - info: MessageInfo, - token: Addr, - distributor: Contract, - amount: Uint128, - cycle: Cycle, - expiration: Option, -) -> StdResult { - - if token != TOKEN.load(deps.storage)?.contract.address { - return Err(StdError::generic_err("Invalid token")); - } - - REWARD.save(deps.storage, info.sender, &Reward { - distributor, - amount, - cycle, - //TODO change to null/zero for first refresh - last_refresh: utc_now(&env).to_rfc3339(), - expiration, - })?; - - Ok(Response::new() - .set_data(to_binary(&ExecuteAnswer::RegisterReward{ - status: ResponseStatus::Success, - })?) - ) -} - -/* -pub fn update( - deps: DepsMut, - env: Env, - info: MessageInfo, - asset: Addr, -) -> StdResult { - Ok(Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Update { - status: ResponseStatus::Success, - })?)) -} - -pub fn claim( - deps: DepsMut, - _env: Env, - asset: Addr, -) -> StdResult { - match asset_r(deps.storage).may_load(&asset.as_str().as_bytes())? { - Some(_) => Ok(Response { - messages: vec![], - log: vec![], - data: Some(to_binary(&adapter::ExecuteAnswer::Claim { - status: ResponseStatus::Success, - amount: Uint128::zero(), - })?), - }), - None => Err(StdError::generic_err(format!( - "Unrecognized Asset {}", - asset - ))), - } -} -*/ diff --git a/archived-contracts/dao/rewards_emission/src/lib.rs b/archived-contracts/dao/rewards_emission/src/lib.rs deleted file mode 100644 index cce0227..0000000 --- a/archived-contracts/dao/rewards_emission/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod contract; -pub mod execute; -pub mod query; -pub mod storage; - -#[cfg(test)] -mod test; diff --git a/archived-contracts/dao/rewards_emission/src/query.rs b/archived-contracts/dao/rewards_emission/src/query.rs deleted file mode 100644 index 08b727c..0000000 --- a/archived-contracts/dao/rewards_emission/src/query.rs +++ /dev/null @@ -1,57 +0,0 @@ -use shade_protocol::c_std::{ - Deps, - StdResult, -}; - -use shade_protocol::{ - contract_interfaces::dao::rewards_emission::QueryAnswer, -}; - - - - -use crate::storage::*; - -pub fn config(deps: Deps) -> StdResult { - Ok(QueryAnswer::Config { - config: CONFIG.load(deps.storage)?, - }) -} - -/* -pub fn pending_allowance( - deps: Deps, - asset: Addr, -) -> StdResult { - let token = TOKEN.load(deps.storage)?; - let config = CONFIG.load(deps.storage)?; - - let allowance = allowance_query( - &deps.querier, - config.treasury, - SELF_ADDRESS.load(deps.storage)?, - VIEWING_KEY.load(deps.storage)?, - &token.contract.clone(), - )? - .allowance; - - Ok(QueryAnswer::PendingAllowance { amount: allowance }) -} - -pub fn balance( - deps: Deps, - asset: Addr, -) -> StdResult { - let token = TOKEN.may_load(deps.storage)?; - - let balance = balance_query( - &deps.querier, - SELF_ADDRESS.load(deps.storage)?, - VIEWING_KEY.load(deps.storage)?, - &token.contract.clone(), - )? - .amount; - - Ok(adapter::QueryAnswer::Balance { amount: balance }) -} -*/ diff --git a/archived-contracts/dao/rewards_emission/src/storage.rs b/archived-contracts/dao/rewards_emission/src/storage.rs deleted file mode 100644 index 53e8510..0000000 --- a/archived-contracts/dao/rewards_emission/src/storage.rs +++ /dev/null @@ -1,14 +0,0 @@ -use shade_protocol::c_std::{Addr}; - -use shade_protocol::contract_interfaces::{dao::rewards_emission, snip20::helpers::Snip20Asset}; - -use shade_protocol::{ - secret_storage_plus::{Map, Item}, -}; - -pub const CONFIG: Item = Item::new("config"); -pub const SELF_ADDRESS: Item = Item::new("self_address"); -pub const VIEWING_KEY: Item = Item::new("viewing_key"); -pub const TOKEN: Item = Item::new("token"); -pub const REWARD: Map = Map::new("rewards"); - diff --git a/archived-contracts/dao/rewards_emission/src/test.rs b/archived-contracts/dao/rewards_emission/src/test.rs deleted file mode 100644 index 04225b9..0000000 --- a/archived-contracts/dao/rewards_emission/src/test.rs +++ /dev/null @@ -1,46 +0,0 @@ -/* -#[cfg(test)] -pub mod tests { - use shade_protocol::c_std::{ - testing::{ - mock_dependencies, mock_env, MockStorage, MockApi, MockQuerier - }, - Addr, - coins, from_binary, StdError, Uint128, - DepsMut, - }; - use shade_protocol::{ - treasury::{ - QueryAnswer, InstantiateMsg, ExecuteMsg, - QueryMsg, - }, - asset::Contract, - }; - - use crate::{ - contract::{ - init, handle, query, - }, - }; - - fn create_contract(address: &str, code_hash: &str) -> Contract { - let env = mock_env(address.to_string(), &[]); - return Contract{ - address: info.sender, - code_hash: code_hash.to_string() - } - } - - fn dummy_init(admin: String, viewing_key: String) -> Extern { - let mut deps = mock_dependencies(20, &[]); - let msg = InstantiateMsg { - admin: Option::from(Addr(admin.clone())), - viewing_key, - }; - let env = mock_env(admin, &coins(1000, "earth")); - let _res = init(&mut deps, env, info, msg).unwrap(); - - return deps - } -} -*/ diff --git a/archived-contracts/governance/.cargo/config b/archived-contracts/governance/.cargo/config deleted file mode 100644 index 882fe08..0000000 --- a/archived-contracts/governance/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[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/archived-contracts/governance/.circleci/config.yml b/archived-contracts/governance/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/archived-contracts/governance/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/governance/Cargo.toml b/archived-contracts/governance/Cargo.toml deleted file mode 100644 index 06fdeba..0000000 --- a/archived-contracts/governance/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "governance" -version = "0.1.0" -authors = ["Guy Garcia "] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] -debug-print = ["shade-protocol/debug-print"] - -[dependencies] -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ - "governance-impl", - "snip20_staking", - "query_auth" -] } -schemars = "0.7" - -[dev-dependencies] -rstest = "0.15" -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["multi-test", "admin"] } -serde_json = { version = "1.0.67" } -shade-multi-test = { version = "0.1.0", path = "../../packages/multi_test", features = [ "governance", "snip20", "query_auth", "admin" ] } diff --git a/archived-contracts/governance/Makefile b/archived-contracts/governance/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/archived-contracts/governance/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/archived-contracts/governance/src/contract.rs b/archived-contracts/governance/src/contract.rs deleted file mode 100644 index 0c20214..0000000 --- a/archived-contracts/governance/src/contract.rs +++ /dev/null @@ -1,482 +0,0 @@ -use crate::{ - handle::{ - assembly::{try_add_assembly, try_assembly_proposal, try_assembly_vote, try_set_assembly}, - assembly_msg::{ - try_add_assembly_msg, - try_add_assembly_msg_assemblies, - try_set_assembly_msg, - }, - authorized, - contract::{try_add_contract, try_add_contract_assemblies, try_set_contract}, - migration::{try_migrate, try_migrate_data, try_receive_migration_data}, - profile::{try_add_profile, try_set_profile}, - proposal::{ - try_cancel, - try_claim_funding, - try_receive_funding, - try_receive_vote, - try_trigger, - try_update, - }, - try_set_config, - try_set_runtime_state, - }, - query, -}; -use shade_protocol::{ - c_std::{ - shd_entry_point, - to_binary, - Addr, - Binary, - Deps, - DepsMut, - Env, - MessageInfo, - Reply, - Response, - StdResult, - SubMsg, - }, - contract_interfaces::governance::{ - assembly::{Assembly, AssemblyMsg}, - contract::AllowedContract, - stored_id::ID, - Config, - ExecuteMsg, - InstantiateMsg, - QueryMsg, - MSG_VARIABLE, - }, - governance::{errors::Error, AuthQuery, QueryData, RuntimeState}, - query_auth::helpers::{authenticate_permit, authenticate_vk, PermitAuthentication}, - snip20::helpers::register_receive, - utils::{ - asset::Contract, - flexible_msg::FlexibleMsg, - pad_handle_result, - pad_query_result, - storage::plus::ItemStorage, - }, -}; - -// Used to pad up responses for better privacy. -pub const RESPONSE_BLOCK_SIZE: usize = 256; - -#[shd_entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - let self_contract = Contract { - address: env.contract.address, - code_hash: env.contract.code_hash.clone(), - }; - - let migrated_from: Option; - - if let Some(migrator) = msg.migrator { - ID::set_assembly(deps.storage, migrator.assembly)?; - ID::set_profile(deps.storage, migrator.profile)?; - ID::set_assembly_msg(deps.storage, migrator.assembly_msg)?; - ID::set_contract(deps.storage, migrator.contract)?; - migrated_from = Some(migrator.source); - } else { - // Setups IDs - ID::set_assembly(deps.storage, 1)?; - ID::set_profile(deps.storage, 1)?; - ID::set_assembly_msg(deps.storage, 0)?; - ID::set_contract(deps.storage, 0)?; - migrated_from = None; - } - - // Setup config - Config { - query: msg.query_auth, - treasury: msg.treasury, - vote_token: msg.vote_token.clone(), - funding_token: msg.funding_token.clone(), - migrated_from, - migrated_to: None, - } - .save(deps.storage)?; - - let mut messages = vec![]; - if let Some(vote_token) = msg.vote_token.clone() { - messages.push(SubMsg::new(register_receive( - env.contract.code_hash.clone(), - None, - &vote_token, - )?)); - } - if let Some(funding_token) = msg.funding_token.clone() { - messages.push(SubMsg::new(register_receive( - env.contract.code_hash.clone(), - None, - &funding_token, - )?)); - } - - // Only initialize the data if not migrating - if let Some(assemblies) = msg.assemblies { - // Setup public profile - assemblies.public_profile.save(deps.storage, 0)?; - - if assemblies.public_profile.funding.is_some() { - if msg.funding_token.is_none() { - return Err(Error::missing_funding_token(vec![])); - } - } - - if assemblies.public_profile.token.is_some() { - if msg.vote_token.is_none() { - return Err(Error::missing_voting_token(vec![])); - } - } - - // Setup public assembly - Assembly { - name: "public".to_string(), - metadata: "All inclusive assembly, acts like traditional governance".to_string(), - members: vec![], - profile: 0, - } - .save(deps.storage, 0)?; - - // Setup admin profile - assemblies.admin_profile.save(deps.storage, 1)?; - - if assemblies.admin_profile.funding.is_some() { - if msg.funding_token.is_none() { - return Err(Error::missing_funding_token(vec![])); - } - } - - if assemblies.admin_profile.token.is_some() { - if msg.vote_token.is_none() { - return Err(Error::missing_voting_token(vec![])); - } - } - - // Setup admin assembly - Assembly { - name: "admin".to_string(), - metadata: "Assembly of DAO admins.".to_string(), - members: assemblies.admin_members, - profile: 1, - } - .save(deps.storage, 1)?; - - // Setup generic command - AssemblyMsg { - name: "blank message".to_string(), - assemblies: vec![0, 1], - msg: FlexibleMsg { - msg: MSG_VARIABLE.to_string(), - arguments: 1, - }, - } - .save(deps.storage, 0)?; - - // Setup self contract - AllowedContract { - name: "Governance".to_string(), - metadata: "Current governance contract, this one".to_string(), - assemblies: None, - contract: self_contract.clone(), - } - .save(deps.storage, 0)?; - } - - // Set runtime - RuntimeState::Normal.save(deps.storage)?; - - Ok(Response::new() - .add_submessages(messages) - .add_attributes(vec![ - (ADDRESS_ATTRIBUTE, self_contract.address.to_string()), - (CODE_HASH_ATTRIBUTE, self_contract.code_hash), - ])) -} - -#[shd_entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - ExecuteMsg::Trigger { .. } // Will be deprecated - | ExecuteMsg::Cancel { .. } // Will also be deprecated - | ExecuteMsg::Update { .. } // Gets halted - | ExecuteMsg::Receive { .. } // Gets halted - | ExecuteMsg::ClaimFunding { .. } // Gets halted - | ExecuteMsg::AssemblyVote { .. } // Gets halted - | ExecuteMsg::ReceiveBalance { .. } // Gets halted - | ExecuteMsg::AssemblyProposal { .. } // Gets halted with special permissions - | ExecuteMsg::MigrateData { .. } - | ExecuteMsg::ReceiveMigrationData { .. } => {} - // Only callable by itself - _ => authorized(deps.storage, &env, &info)?, - } - - pad_handle_result( - match msg { - // State setups - ExecuteMsg::SetConfig { - query_auth, - treasury, - vote_token, - funding_token, - .. - } => try_set_config( - deps, - env, - info, - query_auth, - treasury, - vote_token, - funding_token, - ), - - ExecuteMsg::SetRuntimeState { state, .. } => { - try_set_runtime_state(deps, env, info, state) - } - - // Proposals - ExecuteMsg::Trigger { proposal, .. } => try_trigger(deps, env, info, proposal), - ExecuteMsg::Cancel { proposal, .. } => try_cancel(deps, env, info, proposal), - ExecuteMsg::Update { proposal, .. } => try_update(deps, env, info, proposal), - ExecuteMsg::Receive { - sender, - from, - amount, - msg, - memo, - .. - } => try_receive_funding(deps, env, info, sender, from, amount, msg, memo), - ExecuteMsg::ClaimFunding { id } => try_claim_funding(deps, env, info, id), - - ExecuteMsg::ReceiveBalance { - sender, - msg, - balance, - memo, - } => try_receive_vote(deps, env, info, sender, msg, balance, memo), - - // Assemblies - ExecuteMsg::AssemblyVote { proposal, vote, .. } => { - try_assembly_vote(deps, env, info, proposal, vote) - } - - ExecuteMsg::AssemblyProposal { - assembly, - title, - metadata, - msgs, - .. - } => try_assembly_proposal(deps, env, info, assembly, title, metadata, msgs), - - ExecuteMsg::AddAssembly { - name, - metadata, - members, - profile, - .. - } => try_add_assembly(deps, env, info, name, metadata, members, profile), - - ExecuteMsg::SetAssembly { - id, - name, - metadata, - members, - profile, - .. - } => try_set_assembly(deps, env, info, id, name, metadata, members, profile), - - // Assembly Msgs - ExecuteMsg::AddAssemblyMsg { - name, - msg, - assemblies, - .. - } => try_add_assembly_msg(deps, env, info, name, msg, assemblies), - - ExecuteMsg::SetAssemblyMsg { - id, - name, - msg, - assemblies, - .. - } => try_set_assembly_msg(deps, env, info, id, name, msg, assemblies), - - ExecuteMsg::AddAssemblyMsgAssemblies { id, assemblies } => { - try_add_assembly_msg_assemblies(deps, env, info, id, assemblies) - } - - // Profiles - ExecuteMsg::AddProfile { profile, .. } => try_add_profile(deps, env, info, profile), - - ExecuteMsg::SetProfile { id, profile, .. } => { - try_set_profile(deps, env, info, id, profile) - } - - // Contracts - ExecuteMsg::AddContract { - name, - metadata, - contract, - assemblies, - .. - } => try_add_contract(deps, env, info, name, metadata, contract, assemblies), - - ExecuteMsg::SetContract { - id, - name, - metadata, - contract, - disable_assemblies, - assemblies, - .. - } => try_set_contract( - deps, - env, - info, - id, - name, - metadata, - contract, - disable_assemblies, - assemblies, - ), - - ExecuteMsg::AddContractAssemblies { id, assemblies } => { - try_add_contract_assemblies(deps, env, info, id, assemblies) - } - - // Migration - ExecuteMsg::Migrate { - id, - label, - code_hash, - } => try_migrate(deps, env, info, id, label, code_hash), - - ExecuteMsg::MigrateData { data, total } => { - try_migrate_data(deps, env, info, data, total) - } - - ExecuteMsg::ReceiveMigrationData { data } => { - try_receive_migration_data(deps, env, info, data) - } - }, - RESPONSE_BLOCK_SIZE, - ) -} - -#[shd_entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - pad_query_result( - match msg { - QueryMsg::TotalProposals {} => to_binary(&query::total_proposals(deps)?), - - QueryMsg::Proposals { start, end } => to_binary(&query::proposals(deps, start, end)?), - - QueryMsg::TotalAssemblies {} => to_binary(&query::total_assemblies(deps)?), - - QueryMsg::Assemblies { start, end } => to_binary(&query::assemblies(deps, start, end)?), - - QueryMsg::TotalAssemblyMsgs {} => to_binary(&query::total_assembly_msgs(deps)?), - - QueryMsg::AssemblyMsgs { start, end } => { - to_binary(&query::assembly_msgs(deps, start, end)?) - } - - QueryMsg::TotalProfiles {} => to_binary(&query::total_profiles(deps)?), - - QueryMsg::Profiles { start, end } => to_binary(&query::profiles(deps, start, end)?), - - QueryMsg::TotalContracts {} => to_binary(&query::total_contracts(deps)?), - - QueryMsg::Contracts { start, end } => to_binary(&query::contracts(deps, start, end)?), - - QueryMsg::Config {} => to_binary(&query::config(deps)?), - - QueryMsg::WithVK { user, key, query } => { - // Query VK info - let authenticator = Config::load(deps.storage)?.query; - if !authenticate_vk(user.clone(), key, &deps.querier, &authenticator)? { - return Err(Error::bad_vk(vec![])); - } - - auth_queries(deps, query, user) - } - - QueryMsg::WithPermit { permit, query } => { - // Query Permit info - let authenticator = Config::load(deps.storage)?.query; - let res: PermitAuthentication = - authenticate_permit(permit, &deps.querier, authenticator)?; - - if res.revoked { - return Err(Error::bad_pkey(vec![])); - } - - auth_queries(deps, query, res.sender) - } - }, - RESPONSE_BLOCK_SIZE, - ) -} - -pub fn auth_queries(deps: Deps, msg: AuthQuery, user: Addr) -> StdResult { - to_binary(&match msg { - AuthQuery::Proposals { pagination } => query::user_proposals(deps, user, pagination)?, - AuthQuery::AssemblyVotes { pagination } => { - query::user_assembly_votes(deps, user, pagination)? - } - AuthQuery::Funding { pagination } => query::user_funding(deps, user, pagination)?, - AuthQuery::Votes { pagination } => query::user_votes(deps, user, pagination)?, - }) -} - -const MIGRATION_REPLY: u64 = 0; -// const PROPOSAL_REPLY: u64 = 1; -const ADDRESS_ATTRIBUTE: &str = "instantiated-address"; -const CODE_HASH_ATTRIBUTE: &str = "instantiated-code-hash"; -#[shd_entry_point] -pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> StdResult { - match msg.id { - MIGRATION_REPLY => { - // Get the returned address and code_hash - let res = msg.result.unwrap(); - let wasm = res - .events - .iter() - .find(|event| event.ty == "wasm") - .ok_or_else(|| Error::bad_event(vec![]))?; - let address = deps.api.addr_validate( - &wasm - .attributes - .iter() - .find(|attribute| attribute.key == ADDRESS_ATTRIBUTE) - .ok_or_else(|| Error::missing_migration_event(vec!["address"]))? - .value, - )?; - let code_hash = &wasm - .attributes - .iter() - .find(|attribute| attribute.key == CODE_HASH_ATTRIBUTE) - .ok_or_else(|| Error::missing_migration_event(vec!["code-hash"]))? - .value; - - let mut config = Config::load(deps.storage)?; - config.migrated_to = Some(Contract { - address, - code_hash: code_hash.to_string(), - }); - config.save(deps.storage)?; - } - // TODO: on receiving a response, subtract 1 and update that proposals status to failed - _ => return Err(Error::wrong_reply(vec![&msg.id.to_string()])), - } - - Ok(Response::new()) -} diff --git a/archived-contracts/governance/src/handle/assembly.rs b/archived-contracts/governance/src/handle/assembly.rs deleted file mode 100644 index 9314fcf..0000000 --- a/archived-contracts/governance/src/handle/assembly.rs +++ /dev/null @@ -1,256 +0,0 @@ -use crate::handle::authorize_assembly; -use shade_protocol::{ - c_std::{ - from_binary, - to_binary, - Addr, - Binary, - DepsMut, - Env, - MessageInfo, - Response, - StdResult, - Uint128, - }, - contract_interfaces::governance::{ - assembly::{Assembly, AssemblyMsg}, - contract::AllowedContract, - profile::Profile, - proposal::{Proposal, ProposalMsg, Status}, - stored_id::{UserID, ID}, - vote::Vote, - ExecuteAnswer, - MSG_VARIABLE, - }, - governance::errors::Error, - utils::generic_response::ResponseStatus, -}; - -pub fn try_assembly_vote( - deps: DepsMut, - env: Env, - info: MessageInfo, - proposal: u32, - vote: Vote, -) -> StdResult { - authorize_assembly( - deps.storage, - &info, - Proposal::assembly(deps.storage, proposal)?, - )?; - - let sender = info.sender; - - // Check if proposal in assembly voting - if let Status::AssemblyVote { end, .. } = Proposal::status(deps.storage, proposal)? { - if end <= env.block.time.seconds() { - return Err(Error::voting_ended(vec![&end.to_string()])); - } - } else { - return Err(Error::not_assembly_voting(vec![])); - } - - let mut tally = Proposal::assembly_votes(deps.storage, proposal)?; - - // Assembly votes can only be = 1 uint - if vote.total_count()? != Uint128::new(1) { - return Err(Error::assembly_vote_qty(vec![])); - } - - // Check if user voted - if let Some(old_vote) = Proposal::assembly_vote(deps.storage, proposal, &sender)? { - tally = tally.checked_sub(&old_vote)?; - } - - Proposal::save_assembly_vote(deps.storage, proposal, &sender, &vote)?; - Proposal::save_assembly_votes(deps.storage, proposal, &tally.checked_add(&vote)?)?; - - // Save data for user queries - UserID::add_assembly_vote(deps.storage, sender.clone(), proposal.clone())?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::AssemblyVote { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn try_assembly_proposal( - deps: DepsMut, - env: Env, - info: MessageInfo, - assembly_id: u16, - title: String, - metadata: String, - msgs: Option>, -) -> StdResult { - // Get assembly - let assembly_data = authorize_assembly(deps.storage, &info, assembly_id)?; - - // Get profile - // Check if assembly is enabled - let profile = Profile::data(deps.storage, assembly_data.profile)?; - - let status: Status; - - // Check if assembly voting - if let Some(vote_settings) = Profile::assembly_voting(deps.storage, assembly_data.profile)? { - status = Status::AssemblyVote { - start: env.block.time.seconds(), - end: env.block.time.seconds() + vote_settings.deadline, - } - } - // Check if funding - else if let Some(fund_settings) = Profile::funding(deps.storage, assembly_data.profile)? { - status = Status::Funding { - amount: Uint128::zero(), - start: env.block.time.seconds(), - end: env.block.time.seconds() + fund_settings.deadline, - } - } - // Check if token voting - else if let Some(vote_settings) = Profile::public_voting(deps.storage, assembly_data.profile)? - { - status = Status::Voting { - start: env.block.time.seconds(), - end: env.block.time.seconds() + vote_settings.deadline, - } - } - // Else push directly to passed - else { - status = Status::Passed { - start: env.block.time.seconds(), - end: env.block.time.seconds() + profile.cancel_deadline, - } - } - - let processed_msgs: Option>; - if let Some(msgs) = msgs.clone() { - let mut new_msgs = vec![]; - for msg in msgs.iter() { - // Check if msg is allowed in assembly - let assembly_msg = AssemblyMsg::data(deps.storage, msg.assembly_msg)?; - if !assembly_msg.assemblies.contains(&assembly_id) { - return Err(Error::msg_not_in_assembly(vec![])); - } - - // Check if msg is allowed in contract - let contract = AllowedContract::data(deps.storage, msg.target)?; - if let Some(assemblies) = contract.assemblies { - if !assemblies.contains(&msg.target) { - return Err(Error::msg_not_in_contract(vec![])); - } - } - - let vars: Vec = from_binary(&msg.msg)?; - let binary_msg = - Binary::from(assembly_msg.msg.create_msg(vars, MSG_VARIABLE)?.as_bytes()); - - new_msgs.push(ProposalMsg { - target: msg.target, - assembly_msg: msg.assembly_msg, - msg: binary_msg, - send: msg.send.clone(), - }); - } - processed_msgs = Some(new_msgs); - } else { - processed_msgs = None; - } - - let prop = Proposal { - proposer: info.sender, - title, - metadata, - msgs: processed_msgs, - assembly: assembly_id, - assembly_vote_tally: None, - public_vote_tally: None, - status, - status_history: vec![], - funders: None, - }; - - prop.save(deps.storage)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::AssemblyProposal { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn try_add_assembly( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - name: String, - metadata: String, - members: Vec, - profile: u16, -) -> StdResult { - let id = ID::add_assembly(deps.storage)?; - - // Check that profile exists - if profile > ID::profile(deps.storage)? { - return Err(Error::item_not_found(vec![&profile.to_string(), "Profile"])); - } - - Assembly { - name, - metadata, - members, - profile, - } - .save(deps.storage, id)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::AddAssembly { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn try_set_assembly( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - id: u16, - name: Option, - metadata: Option, - members: Option>, - profile: Option, -) -> StdResult { - let mut assembly = match Assembly::may_load(deps.storage, id)? { - None => return Err(Error::item_not_found(vec![&id.to_string(), "Assembly"])), - Some(c) => c, - }; - - if let Some(name) = name { - assembly.name = name; - } - - if let Some(metadata) = metadata { - assembly.metadata = metadata - } - - if let Some(members) = members { - assembly.members = members - } - - if let Some(profile) = profile { - // Check that profile exists - if profile > ID::profile(deps.storage)? { - return Err(Error::item_not_found(vec![&profile.to_string(), "Profile"])); - } - assembly.profile = profile - } - - assembly.save(deps.storage, id)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::SetAssembly { - status: ResponseStatus::Success, - })?), - ) -} diff --git a/archived-contracts/governance/src/handle/assembly_msg.rs b/archived-contracts/governance/src/handle/assembly_msg.rs deleted file mode 100644 index a74d2c1..0000000 --- a/archived-contracts/governance/src/handle/assembly_msg.rs +++ /dev/null @@ -1,105 +0,0 @@ -use shade_protocol::{ - c_std::{to_binary, DepsMut, Env, MessageInfo, Response, StdResult}, - contract_interfaces::governance::{ - assembly::AssemblyMsg, - stored_id::ID, - ExecuteAnswer, - MSG_VARIABLE, - }, - governance::errors::Error, - utils::{flexible_msg::FlexibleMsg, generic_response::ResponseStatus}, -}; - -pub fn try_add_assembly_msg( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - name: String, - msg: String, - assemblies: Vec, -) -> StdResult { - let id = ID::add_assembly_msg(deps.storage)?; - - // Check that assemblys exist - for assembly in assemblies.iter() { - if *assembly > ID::assembly(deps.storage)? { - return Err(Error::item_not_found(vec![ - &assembly.to_string(), - "Assembly", - ])); - } - } - - AssemblyMsg { - name, - assemblies, - msg: FlexibleMsg::new(msg, MSG_VARIABLE), - } - .save(deps.storage, id)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::AddAssemblyMsg { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn try_set_assembly_msg( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - id: u16, - name: Option, - msg: Option, - assemblies: Option>, -) -> StdResult { - let mut assembly_msg = match AssemblyMsg::may_load(deps.storage, id)? { - None => return Err(Error::item_not_found(vec![&id.to_string(), "AssemblyMsg"])), - Some(c) => c, - }; - - if let Some(name) = name { - assembly_msg.name = name; - } - - if let Some(msg) = msg { - assembly_msg.msg = FlexibleMsg::new(msg, MSG_VARIABLE); - } - - if let Some(assemblies) = assemblies { - assembly_msg.assemblies = assemblies; - } - - assembly_msg.save(deps.storage, id)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::SetAssemblyMsg { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn try_add_assembly_msg_assemblies( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - id: u16, - assemblies: Vec, -) -> StdResult { - let mut assembly_msg = AssemblyMsg::data(deps.storage, id)?; - - let assembly_id = ID::assembly(deps.storage)?; - for assembly in assemblies.iter() { - if assembly < &assembly_id && !assembly_msg.assemblies.contains(assembly) { - assembly_msg.assemblies.push(assembly.clone()); - } - } - - AssemblyMsg::save_data(deps.storage, id, assembly_msg)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::SetAssemblyMsg { - status: ResponseStatus::Success, - })?), - ) -} diff --git a/archived-contracts/governance/src/handle/contract.rs b/archived-contracts/governance/src/handle/contract.rs deleted file mode 100644 index c6749b3..0000000 --- a/archived-contracts/governance/src/handle/contract.rs +++ /dev/null @@ -1,133 +0,0 @@ -use shade_protocol::{ - c_std::{to_binary, DepsMut, Env, MessageInfo, Response, StdResult}, - contract_interfaces::governance::{contract::AllowedContract, stored_id::ID, ExecuteAnswer}, - governance::errors::Error, - utils::{asset::Contract, generic_response::ResponseStatus}, -}; - -pub fn try_add_contract( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - name: String, - metadata: String, - contract: Contract, - assemblies: Option>, -) -> StdResult { - let id = ID::add_contract(deps.storage)?; - - if let Some(ref assemblies) = assemblies { - let assembly_id = ID::assembly(deps.storage)?; - for assembly in assemblies.iter() { - if assembly > &assembly_id { - return Err(Error::item_not_found(vec![ - &assembly.to_string(), - "Assembly", - ])); - } - } - } - - AllowedContract { - name, - metadata, - contract, - assemblies, - } - .save(deps.storage, id)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::AddContract { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn try_set_contract( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - id: u16, - name: Option, - metadata: Option, - contract: Option, - disable_assemblies: bool, - assemblies: Option>, -) -> StdResult { - if id > ID::contract(deps.storage)? { - return Err(Error::item_not_found(vec![&id.to_string(), "Contract"])); - } - - let mut allowed_contract = AllowedContract::load(deps.storage, id)?; - - if let Some(name) = name { - allowed_contract.name = name; - } - - if let Some(metadata) = metadata { - allowed_contract.metadata = metadata; - } - - if let Some(contract) = contract { - allowed_contract.contract = contract; - } - - if disable_assemblies { - allowed_contract.assemblies = None; - } else { - if let Some(assemblies) = assemblies { - let assembly_id = ID::assembly(deps.storage)?; - for assembly in assemblies.iter() { - if assembly > &assembly_id { - return Err(Error::item_not_found(vec![ - &assembly.to_string(), - "Assembly", - ])); - } - } - allowed_contract.assemblies = Some(assemblies); - } - } - - allowed_contract.save(deps.storage, id)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::AddContract { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn try_add_contract_assemblies( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - id: u16, - assemblies: Vec, -) -> StdResult { - if id > ID::contract(deps.storage)? { - return Err(Error::item_not_found(vec![&id.to_string(), "Contract"])); - } - - let mut allowed_contract = AllowedContract::data(deps.storage, id)?; - - if let Some(mut old_assemblies) = allowed_contract.assemblies { - let assembly_id = ID::assembly(deps.storage)?; - for assembly in assemblies.iter() { - if assembly <= &assembly_id && !old_assemblies.contains(assembly) { - old_assemblies.push(assembly.clone()); - } - } - allowed_contract.assemblies = Some(old_assemblies); - } else { - return Err(Error::contract_disabled(vec![])); - } - - AllowedContract::save_data(deps.storage, id, allowed_contract)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::AddContractAssemblies { - status: ResponseStatus::Success, - })?), - ) -} diff --git a/archived-contracts/governance/src/handle/migration.rs b/archived-contracts/governance/src/handle/migration.rs deleted file mode 100644 index 5657634..0000000 --- a/archived-contracts/governance/src/handle/migration.rs +++ /dev/null @@ -1,217 +0,0 @@ -use shade_protocol::{ - c_std::{to_binary, DepsMut, Env, MessageInfo, Response, StdResult, SubMsg}, - governance::{ - assembly::{Assembly, AssemblyMsg}, - contract::AllowedContract, - errors::Error, - profile::Profile, - stored_id::ID, - Config, - ExecuteAnswer, - ExecuteMsg::ReceiveMigrationData, - InstantiateMsg, - MigrationData, - MigrationDataAsk, - MigrationInit, - RuntimeState, - }, - utils::{ - generic_response::ResponseStatus, - storage::plus::ItemStorage, - ExecuteCallback, - InstantiateCallback, - }, - Contract, -}; -use std::cmp::min; - -pub fn try_migrate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - id: u64, - mut label: String, - code_hash: String, -) -> StdResult { - // TODO: maybe randomly generate migration label - ID::init_migration(deps.storage)?; - - let config = Config::load(deps.storage)?; - - RuntimeState::Migrated {}.save(deps.storage)?; - - label.push_str(&env.block.time.nanos().to_string()); - - let res = Response::new(); - Ok(res - .add_submessage(SubMsg::reply_on_success( - InstantiateMsg { - treasury: config.treasury, - query_auth: config.query, - assemblies: None, - funding_token: config.funding_token, - vote_token: config.vote_token, - migrator: Some(MigrationInit { - source: Contract { - address: env.contract.address, - code_hash: env.contract.code_hash, - }, - assembly: ID::assembly(deps.storage)?, - assembly_msg: ID::assembly_msg(deps.storage)?, - profile: ID::profile(deps.storage)?, - contract: ID::contract(deps.storage)?, - }), - } - .to_cosmos_msg(label, id, code_hash, vec![])?, - 0, - )) - .set_data(to_binary(&ExecuteAnswer::Migrate { - status: ResponseStatus::Success, - })?)) -} - -pub fn try_migrate_data( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - data: MigrationDataAsk, - total: u16, -) -> StdResult { - let res = Response::new(); - - let config = Config::load(deps.storage)?; - - match RuntimeState::load(deps.storage)? { - // Fail if not migrating - RuntimeState::Normal | RuntimeState::SpecificAssemblies { .. } => { - return Err(Error::migration_not_started(vec![])); - } - RuntimeState::Migrated => { - if let Some(target) = config.migrated_to { - let res_data: MigrationData; - - // Go over the migration data asks, there might be a cleaner way to do this - match data { - MigrationDataAsk::Assembly => { - let mut assemblies = vec![]; - - // Get the next id to pick - let current_id = ID::assembly_migration(deps.storage)?; - let last_id = min(current_id + total, ID::assembly(deps.storage)?); - - // iterate from next over to last - for i in current_id..=last_id { - assemblies.push((i, Assembly::load(deps.storage, i)?)); - } - - ID::set_assembly_migration(deps.storage, last_id)?; - - res_data = MigrationData::Assembly { data: assemblies }; - } - MigrationDataAsk::AssemblyMsg => { - let mut assembly_msgs = vec![]; - - let current_id = ID::assembly_msg_migration(deps.storage)?; - let last_id = min(current_id + total, ID::assembly_msg(deps.storage)?); - - for i in current_id..=last_id { - assembly_msgs.push((i, AssemblyMsg::load(deps.storage, i)?)); - } - - ID::set_assembly_msg_migration(deps.storage, last_id)?; - - res_data = MigrationData::AssemblyMsg { - data: assembly_msgs, - } - } - MigrationDataAsk::Profile => { - let mut profiles = vec![]; - - let current_id = ID::profile_migration(deps.storage)?; - let last_id = min(current_id + total, ID::profile(deps.storage)?); - - for i in current_id..=last_id { - profiles.push((i, Profile::load(deps.storage, i)?)); - } - - ID::set_profile_migration(deps.storage, last_id)?; - - res_data = MigrationData::Profile { data: profiles }; - } - MigrationDataAsk::Contract => { - let mut contracts = vec![]; - - let current_id = ID::contract_migration(deps.storage)?; - let last_id = min(current_id + total, ID::contract(deps.storage)?); - - for i in current_id..=last_id { - contracts.push((i, AllowedContract::load(deps.storage, i)?)); - } - - ID::set_contract_migration(deps.storage, last_id)?; - - res_data = MigrationData::Contract { data: contracts }; - } - }; - - return Ok(res - .add_message( - ReceiveMigrationData { data: res_data }.to_cosmos_msg(&target, vec![])?, - ) - .set_data(to_binary(&ExecuteAnswer::MigrateData { - status: ResponseStatus::Success, - })?)); - } else { - return Err(Error::migration_tartet(vec![])); - } - } - }; -} - -pub fn try_receive_migration_data( - deps: DepsMut, - _env: Env, - info: MessageInfo, - data: MigrationData, -) -> StdResult { - let res = Response::new(); - - let config = Config::load(deps.storage)?; - - if let Some(from) = config.migrated_from { - if from.address != info.sender { - return Err(Error::no_migrator(vec![])); - } - - match data { - MigrationData::Assembly { data } => { - for item in data { - item.1.save(deps.storage, item.0)?; - } - } - MigrationData::AssemblyMsg { data } => { - for item in data { - item.1.save(deps.storage, item.0)?; - } - } - MigrationData::Profile { data } => { - for item in data { - item.1.save(deps.storage, item.0)?; - } - } - MigrationData::Contract { data } => { - for item in data { - item.1.save(deps.storage, item.0)?; - } - } - } - } else { - return Err(Error::migration_tartet(vec![])); - } - - Ok( - res.set_data(to_binary(&ExecuteAnswer::ReceiveMigrationData { - status: ResponseStatus::Success, - })?), - ) -} diff --git a/archived-contracts/governance/src/handle/mod.rs b/archived-contracts/governance/src/handle/mod.rs deleted file mode 100644 index 59b65fe..0000000 --- a/archived-contracts/governance/src/handle/mod.rs +++ /dev/null @@ -1,135 +0,0 @@ -use shade_protocol::{ - c_std::{to_binary, Addr, DepsMut, Env, MessageInfo, Response, StdResult, Storage, SubMsg}, - contract_interfaces::governance::{Config, ExecuteAnswer, RuntimeState}, - governance::{ - assembly::{Assembly, AssemblyData}, - errors::Error, - profile::Profile, - }, - snip20::helpers::register_receive, - utils::{asset::Contract, generic_response::ResponseStatus, storage::plus::ItemStorage}, -}; - -pub mod assembly; -pub mod assembly_msg; -pub mod contract; -pub mod migration; -pub mod profile; -pub mod proposal; - -/// Checks that state can be updated -pub fn assembly_state_valid(storage: &dyn Storage, assembly: u16) -> StdResult<()> { - match RuntimeState::load(storage)? { - RuntimeState::Normal => {} - RuntimeState::SpecificAssemblies { assemblies } => { - if !assemblies.contains(&assembly) { - return Err(Error::assemblies_limited(vec![])); - } - } - RuntimeState::Migrated { .. } => return Err(Error::migrated(vec![])), - }; - - Ok(()) -} - -/// Authorizes the assembly, returns assembly data to avoid redundant loading -pub fn authorize_assembly( - storage: &dyn Storage, - info: &MessageInfo, - assembly: u16, -) -> StdResult { - assembly_state_valid(storage, assembly)?; - - let data = Assembly::data(storage, assembly)?; - - // Check that the user is in the non-public assembly - if data.profile != 0 && !data.members.contains(&info.sender) { - return Err(Error::not_in_assembly(vec![ - info.sender.as_str(), - &assembly.to_string(), - ])); - }; - - // Check if enabled - if !Profile::data(storage, data.profile)?.enabled { - return Err(Error::profile_disabled(vec![&data.profile.to_string()])); - } - - Ok(data) -} - -/// Checks that the message sender is self and also not migrated -pub fn authorized(storage: &dyn Storage, env: &Env, info: &MessageInfo) -> StdResult<()> { - if info.sender != env.contract.address { - return Err(Error::not_self(vec![])); - } else if let RuntimeState::Migrated { .. } = RuntimeState::load(storage)? { - return Err(Error::migrated(vec![])); - } - - Ok(()) -} - -pub fn try_set_config( - deps: DepsMut, - env: Env, - _info: MessageInfo, - query_auth: Option, - treasury: Option, - vote_token: Option, - funding_token: Option, -) -> StdResult { - let mut messages = vec![]; - let mut config = Config::load(deps.storage)?; - - // Vote and funding tokens cannot be set to none after being set - if let Some(vote_token) = vote_token { - config.vote_token = Some(vote_token.clone()); - messages.push(SubMsg::new(register_receive( - env.contract.code_hash.clone(), - None, - &vote_token, - )?)); - } - - if let Some(funding_token) = funding_token { - config.funding_token = Some(funding_token.clone()); - messages.push(SubMsg::new(register_receive( - env.contract.code_hash.clone(), - None, - &funding_token, - )?)); - } - - if let Some(treasury) = treasury { - config.treasury = treasury; - } - - if let Some(query_auth) = query_auth { - config.query = query_auth; - } - - config.save(deps.storage)?; - Ok(Response::new() - .set_data(to_binary(&ExecuteAnswer::SetConfig { - status: ResponseStatus::Success, - })?) - .add_submessages(messages)) -} - -pub fn try_set_runtime_state( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - state: RuntimeState, -) -> StdResult { - if let RuntimeState::Migrated { .. } = state { - return Err(Error::migrated(vec![])); - } - - state.save(deps.storage)?; - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::SetRuntimeState { - status: ResponseStatus::Success, - })?), - ) -} diff --git a/archived-contracts/governance/src/handle/profile.rs b/archived-contracts/governance/src/handle/profile.rs deleted file mode 100644 index 6c8bc7b..0000000 --- a/archived-contracts/governance/src/handle/profile.rs +++ /dev/null @@ -1,77 +0,0 @@ -use shade_protocol::{ - c_std::{to_binary, DepsMut, Env, MessageInfo, Response, StdResult}, - contract_interfaces::governance::{ - profile::{Profile, UpdateProfile}, - stored_id::ID, - ExecuteAnswer, - }, - governance::errors::Error, - utils::generic_response::ResponseStatus, -}; - -pub fn try_add_profile( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - profile: Profile, -) -> StdResult { - let id = ID::add_profile(deps.storage)?; - profile.save(deps.storage, id)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::AddProfile { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn try_set_profile( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - id: u16, - new_profile: UpdateProfile, -) -> StdResult { - let mut profile = match Profile::may_load(deps.storage, id)? { - None => return Err(Error::item_not_found(vec![&id.to_string(), "Profile"])), - Some(p) => p, - }; - - if let Some(name) = new_profile.name { - profile.name = name; - } - - if let Some(enabled) = new_profile.enabled { - profile.enabled = enabled; - } - - if new_profile.disable_assembly { - profile.assembly = None; - } else if let Some(assembly) = new_profile.assembly { - profile.assembly = Some(assembly.update_profile(&profile.assembly)?) - } - - if new_profile.disable_funding { - profile.funding = None; - } else if let Some(funding) = new_profile.funding { - profile.funding = Some(funding.update_profile(&profile.funding)?) - } - - if new_profile.disable_token { - profile.token = None; - } else if let Some(token) = new_profile.token { - profile.token = Some(token.update_profile(&profile.token)?) - } - - if let Some(cancel_deadline) = new_profile.cancel_deadline { - profile.cancel_deadline = cancel_deadline; - } - - profile.save(deps.storage, id)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::SetProfile { - status: ResponseStatus::Success, - })?), - ) -} diff --git a/archived-contracts/governance/src/handle/proposal.rs b/archived-contracts/governance/src/handle/proposal.rs deleted file mode 100644 index bf4c30c..0000000 --- a/archived-contracts/governance/src/handle/proposal.rs +++ /dev/null @@ -1,541 +0,0 @@ -use crate::handle::assembly_state_valid; -use shade_protocol::{ - c_std::{ - from_binary, - to_binary, - Addr, - Binary, - DepsMut, - Env, - MessageInfo, - Response, - StdResult, - SubMsg, - Uint128, - WasmMsg, - }, - contract_interfaces::{ - governance::{ - assembly::Assembly, - contract::AllowedContract, - profile::{Count, Profile, VoteProfile}, - proposal::{Funding, Proposal, Status}, - stored_id::UserID, - vote::{ReceiveBalanceMsg, TalliedVotes, Vote}, - Config, - ExecuteAnswer, - }, - staking::snip20_staking, - }, - governance::errors::Error, - snip20::helpers::send_msg, - utils::{asset::Contract, generic_response::ResponseStatus, storage::plus::ItemStorage, Query}, -}; - -pub fn try_trigger( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - proposal: u32, -) -> StdResult { - let mut messages = vec![]; - - let status = Proposal::status(deps.storage, proposal)?; - if let Status::Passed { .. } = status { - let mut history = Proposal::status_history(deps.storage, proposal)?; - history.push(status); - Proposal::save_status_history(deps.storage, proposal, history)?; - Proposal::save_status(deps.storage, proposal, Status::Success)?; - - // Trigger the msg - let proposal_msg = Proposal::msg(deps.storage, proposal)?; - if let Some(prop_msgs) = proposal_msg { - for (_i, prop_msg) in prop_msgs.iter().enumerate() { - let contract = AllowedContract::data(deps.storage, prop_msg.target)?.contract; - let msg = WasmMsg::Execute { - contract_addr: contract.address.into(), - code_hash: contract.code_hash, - msg: prop_msg.msg.clone(), - funds: prop_msg.send.clone(), - }; - // TODO: set to reply on error where ID is propID + 1 - // TODO: set proposal status to success - messages.push(SubMsg::new(msg)); - } - } - } else { - return Err(Error::not_passed(vec![])); - } - - Ok(Response::new() - .add_submessages(messages) - .set_data(to_binary(&ExecuteAnswer::Trigger { - status: ResponseStatus::Success, - })?)) -} - -pub fn try_cancel( - deps: DepsMut, - env: Env, - _info: MessageInfo, - proposal: u32, -) -> StdResult { - // Check if passed, and check if current time > cancel time - let status = Proposal::status(deps.storage, proposal)?; - if let Status::Passed { start: _, end } = status { - if env.block.time.seconds() < end { - return Err(Error::cannot_cancel(vec![&end.to_string()])); - } - let mut history = Proposal::status_history(deps.storage, proposal)?; - history.push(status); - Proposal::save_status_history(deps.storage, proposal, history)?; - Proposal::save_status(deps.storage, proposal, Status::Canceled)?; - } else { - return Err(Error::cannot_cancel(vec![&(-1).to_string()])); - } - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Cancel { - status: ResponseStatus::Success, - })?)) -} - -fn validate_votes(votes: Vote, total_power: Uint128, settings: VoteProfile) -> Status { - let tally = TalliedVotes::tally(votes); - - let threshold = match settings.threshold { - Count::Percentage { percent } => total_power.multiply_ratio(percent, Uint128::new(10000)), - Count::LiteralCount { count } => count, - }; - - let yes_threshold = match settings.yes_threshold { - Count::Percentage { percent } => { - (tally.yes + tally.no).multiply_ratio(percent, Uint128::new(10000)) - } - Count::LiteralCount { count } => count, - }; - - let veto_threshold = match settings.veto_threshold { - Count::Percentage { percent } => { - (tally.yes + tally.no).multiply_ratio(percent, Uint128::new(10000)) - } - Count::LiteralCount { count } => count, - }; - - let new_status: Status; - - if tally.total < threshold { - new_status = Status::Expired; - } else if tally.veto >= veto_threshold { - new_status = Status::Vetoed { - slash_percent: Uint128::zero(), - }; - } else if tally.yes < yes_threshold { - new_status = Status::Rejected; - } else { - new_status = Status::Success; - } - - return new_status; -} - -pub fn try_update( - deps: DepsMut, - env: Env, - _info: MessageInfo, - proposal: u32, -) -> StdResult { - // TODO: see if this can get cleaned up - - let mut history = Proposal::status_history(deps.storage, proposal)?; - let status = Proposal::status(deps.storage, proposal)?; - let mut new_status: Status; - - let assembly = Proposal::assembly(deps.storage, proposal)?; - let profile = Assembly::data(deps.storage, assembly)?.profile; - - // Halt all proposal updates - assembly_state_valid(deps.storage, assembly)?; - - let mut messages = vec![]; - - match status.clone() { - Status::AssemblyVote { start: _, end } => { - if end > env.block.time.seconds() { - return Err(Error::cannot_update(vec!["AssemblyVote", &end.to_string()])); - } - - let votes = Proposal::assembly_votes(deps.storage, proposal)?; - - // Total power is equal to the total amount of assembly members - let total_power = - Uint128::new(Assembly::data(deps.storage, assembly)?.members.len() as u128); - - // Try to load, if not then assume it was updated after proposal creation but before section end - let mut vote_conclusion: Status; - if let Some(settings) = Profile::assembly_voting(deps.storage, profile)? { - vote_conclusion = validate_votes(votes, total_power, settings); - } else { - vote_conclusion = Status::Success - } - - if let Status::Vetoed { .. } = vote_conclusion { - // Cant veto an assembly vote - vote_conclusion = Status::Rejected; - } - - // Try to load the next steps, if all are none then pass - if let Status::Success = vote_conclusion { - if let Some(setting) = Profile::funding(deps.storage, profile)? { - vote_conclusion = Status::Funding { - amount: Uint128::zero(), - start: env.block.time.seconds(), - end: env.block.time.seconds() + setting.deadline, - } - } else if let Some(setting) = Profile::public_voting(deps.storage, profile)? { - vote_conclusion = Status::Voting { - start: env.block.time.seconds(), - end: env.block.time.seconds() + setting.deadline, - } - } else { - vote_conclusion = Status::Passed { - start: env.block.time.seconds(), - end: env.block.time.seconds() - + Profile::data(deps.storage, profile)?.cancel_deadline, - } - } - } - - new_status = vote_conclusion; - } - Status::Funding { amount, end, .. } => { - // This helps combat the possibility of the profile changing - // before another proposal is finished - if let Some(setting) = Profile::funding(deps.storage, profile)? { - // Check if deadline or funding limit reached - if amount >= setting.required { - new_status = Status::Passed { - start: env.block.time.seconds(), - end: env.block.time.seconds() - + Profile::data(deps.storage, profile)?.cancel_deadline, - } - } else if end > env.block.time.seconds() { - return Err(Error::cannot_update(vec!["Funding", &end.to_string()])); - } else { - new_status = Status::Expired; - } - } else { - new_status = Status::Passed { - start: env.block.time.seconds(), - end: env.block.time.seconds() - + Profile::data(deps.storage, profile)?.cancel_deadline, - } - } - - if let Status::Passed { .. } = new_status { - if let Some(setting) = Profile::public_voting(deps.storage, profile)? { - new_status = Status::Voting { - start: env.block.time.seconds(), - end: env.block.time.seconds() + setting.deadline, - } - } - } - } - Status::Voting { start: _, end } => { - if end > env.block.time.seconds() { - return Err(Error::cannot_update(vec!["Voting", &end.to_string()])); - } - - let config = Config::load(deps.storage)?; - let votes = Proposal::public_votes(deps.storage, proposal)?; - - let query: snip20_staking::QueryAnswer = snip20_staking::QueryMsg::TotalStaked {} - .query(&deps.querier, &config.vote_token.unwrap())?; - - // Get total staking power - let total_power = match query { - snip20_staking::QueryAnswer::TotalStaked { tokens, .. } => tokens.into(), - _ => return Err(Error::unexpected_query_response(vec![])), - }; - - let mut vote_conclusion: Status; - - if let Some(settings) = Profile::public_voting(deps.storage, profile)? { - vote_conclusion = validate_votes(votes, total_power, settings); - } else { - vote_conclusion = Status::Success - } - - if let Status::Vetoed { .. } = vote_conclusion { - // Send the funding amount to the treasury - if let Some(profile) = Profile::funding(deps.storage, profile)? { - // Look for the history and find funding - for s in history.iter() { - // Check if it has funding history - if let Status::Funding { amount, .. } = s { - let loss = profile.veto_deposit_loss.clone(); - vote_conclusion = Status::Vetoed { - slash_percent: loss, - }; - - let send_amount = amount.multiply_ratio(100000u128, loss); - if send_amount != Uint128::zero() { - let config = Config::load(deps.storage)?; - // Update slash amount - messages.push(send_msg( - config.treasury.into(), - Uint128::new(send_amount.u128()), - None, - None, - None, - &config.funding_token.unwrap(), - )?); - } - break; - } - } - } - } else if let Status::Success = vote_conclusion { - vote_conclusion = Status::Passed { - start: env.block.time.seconds(), - end: env.block.time.seconds() - + Profile::data(deps.storage, profile)?.cancel_deadline, - } - } - - new_status = vote_conclusion; - } - _ => return Err(Error::state_update(vec![])), - } - - // Add old status to history - history.push(status); - Proposal::save_status_history(deps.storage, proposal, history)?; - // Save new status - Proposal::save_status(deps.storage, proposal, new_status.clone())?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Update { - status: ResponseStatus::Success, - })?)) -} - -pub fn try_receive_funding( - deps: DepsMut, - env: Env, - info: MessageInfo, - _sender: Addr, - from: Addr, - amount: Uint128, - msg: Option, - _memo: Option, -) -> StdResult { - // Check if sent token is the funding token - let funding_token: Contract; - if let Some(token) = Config::load(deps.storage)?.funding_token { - funding_token = token.clone(); - if info.sender != token.address { - return Err(Error::missing_funding_token(vec![])); - } - } else { - return Err(Error::missing_funding_token(vec![])); - } - - // Check if msg contains the proposal information - let proposal: u32; - if let Some(msg) = msg { - proposal = from_binary(&msg)?; - } else { - return Err(Error::funding_msg_not_set(vec![])); - } - - // Check if proposal is in funding stage - let mut return_amount = Uint128::zero(); - - let status = Proposal::status(deps.storage, proposal)?; - if let Status::Funding { - amount: funded, - start, - end, - } = status - { - // Check if proposal funding stage is set or funding limit already set - if env.block.time.seconds() >= end { - return Err(Error::funding_limit_reached(vec![])); - } - - let mut new_fund = amount + funded; - - let assembly = Proposal::assembly(deps.storage, proposal)?; - - // Validate that this action is possible - assembly_state_valid(deps.storage, assembly)?; - - let profile = Assembly::data(deps.storage, assembly)?.profile; - if let Some(funding_profile) = Profile::funding(deps.storage, profile)? { - if funding_profile.required == funded { - return Err(Error::completely_funded(vec![])); - } - - if funding_profile.required < new_fund { - return_amount = new_fund.checked_sub(funding_profile.required)?; - new_fund = funding_profile.required; - } - } else { - return Err(Error::no_funding_profile(vec![])); - } - - // Store the funder information and update the current funding data - Proposal::save_status(deps.storage, proposal, Status::Funding { - amount: new_fund, - start, - end, - })?; - - // Either add or update funder - let mut funder_amount = amount.checked_sub(return_amount)?; - let mut funders = Proposal::funders(deps.storage, proposal)?; - if funders.contains(&from) { - funder_amount += Proposal::funding(deps.storage, proposal, &from)?.amount; - } else { - funders.push(from.clone()); - Proposal::save_funders(deps.storage, proposal, funders)?; - } - Proposal::save_funding(deps.storage, proposal, &from, Funding { - amount: funder_amount, - claimed: false, - })?; - - // Add funding info to cross search - UserID::add_funding(deps.storage, from.clone(), proposal.clone())?; - } else { - return Err(Error::no_funding_state(vec![])); - } - - let mut messages = vec![]; - if return_amount != Uint128::zero() { - messages.push(send_msg( - from.into(), - return_amount.into(), - None, - None, - None, - &funding_token, - )?); - } - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Receive { - status: ResponseStatus::Success, - })?)) -} - -pub fn try_claim_funding( - deps: DepsMut, - _env: Env, - info: MessageInfo, - id: u32, -) -> StdResult { - let reduction = match Proposal::status(deps.storage, id)? { - Status::AssemblyVote { .. } | Status::Funding { .. } | Status::Voting { .. } => { - return Err(Error::funding_not_claimable(vec![])); - } - Status::Vetoed { slash_percent } => slash_percent, - _ => Uint128::zero(), - }; - - let funding = Proposal::funding(deps.storage, id, &info.sender)?; - - if funding.claimed { - return Err(Error::funding_claimed(vec![])); - } - - let return_amount = funding.amount.checked_sub( - funding - .amount - .multiply_ratio(reduction, Uint128::new(10000)), - )?; - - if return_amount == Uint128::zero() { - return Err(Error::funding_nothing(vec![])); - } - - let funding_token = match Config::load(deps.storage)?.funding_token { - None => return Err(Error::missing_funding_token(vec![])), - Some(token) => token, - }; - - Ok(Response::new() - .add_message(send_msg( - info.sender.into(), - return_amount.into(), - None, - None, - None, - &funding_token, - )?) - .set_data(to_binary(&ExecuteAnswer::ClaimFunding { - status: ResponseStatus::Success, - })?)) -} - -pub fn try_receive_vote( - deps: DepsMut, - env: Env, - info: MessageInfo, - sender: Addr, - msg: Option, - balance: Uint128, - _memo: Option, -) -> StdResult { - if let Some(token) = Config::load(deps.storage)?.vote_token { - if info.sender != token.address { - return Err(Error::sender_funding(vec![])); - } - } else { - return Err(Error::missing_funding_token(vec![])); - } - - let vote: Vote; - let proposal: u32; - if let Some(msg) = msg { - let decoded_msg: ReceiveBalanceMsg = from_binary(&msg)?; - vote = decoded_msg.vote; - proposal = decoded_msg.proposal; - - // Verify that total does not exceed balance - let total_votes = vote.yes.checked_add( - vote.no - .checked_add(vote.abstain.checked_add(vote.no_with_veto)?)?, - )?; - - if total_votes > balance { - return Err(Error::voting_balance(vec![])); - } - } else { - return Err(Error::voting_msg(vec![])); - } - - // Check if proposal in assembly voting - if let Status::Voting { end, .. } = Proposal::status(deps.storage, proposal)? { - if end <= env.block.time.seconds() { - return Err(Error::voting_time(vec![&end.to_string()])); - } - } else { - return Err(Error::voting_not_state(vec![])); - } - - let mut tally = Proposal::public_votes(deps.storage, proposal)?; - - // Check if user voted - if let Some(old_vote) = Proposal::public_vote(deps.storage, proposal, &sender)? { - tally = tally.checked_sub(&old_vote)?; - } - - Proposal::save_public_vote(deps.storage, proposal, &sender, &vote)?; - Proposal::save_public_votes(deps.storage, proposal, &tally.checked_add(&vote)?)?; - UserID::add_vote(deps.storage, sender.clone(), proposal)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::ReceiveBalance { - status: ResponseStatus::Success, - })?), - ) -} diff --git a/archived-contracts/governance/src/lib.rs b/archived-contracts/governance/src/lib.rs deleted file mode 100644 index dda1443..0000000 --- a/archived-contracts/governance/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod contract; -pub struct QueryAuth; - -pub mod handle; -pub mod query; - -#[cfg(test)] -pub mod tests; diff --git a/archived-contracts/governance/src/query.rs b/archived-contracts/governance/src/query.rs deleted file mode 100644 index 824d6a5..0000000 --- a/archived-contracts/governance/src/query.rs +++ /dev/null @@ -1,229 +0,0 @@ -use shade_protocol::{ - c_std::{Addr, Deps, StdResult}, - contract_interfaces::governance::{ - assembly::{Assembly, AssemblyMsg}, - contract::AllowedContract, - profile::Profile, - proposal::Proposal, - stored_id::ID, - Config, - QueryAnswer, - }, - governance::{errors::Error, stored_id::UserID, Pagination, ResponseWithID}, - utils::storage::plus::ItemStorage, -}; -use std::cmp::min; - -pub fn config(deps: Deps) -> StdResult { - Ok(QueryAnswer::Config { - config: Config::load(deps.storage)?, - }) -} - -pub fn total_proposals(deps: Deps) -> StdResult { - Ok(QueryAnswer::Total { - total: ID::proposal(deps.storage)?.checked_add(1).unwrap(), - }) -} - -pub fn proposals(deps: Deps, start: u32, end: u32) -> StdResult { - let mut items = vec![]; - let total = ID::proposal(deps.storage)?; - - if start > total { - return Err(Error::item_not_found(vec![&start.to_string(), "Proposal"])); - } - - for i in start..=min(end, total) { - items.push(Proposal::load(deps.storage, i)?); - } - - Ok(QueryAnswer::Proposals { props: items }) -} - -pub fn total_profiles(deps: Deps) -> StdResult { - Ok(QueryAnswer::Total { - total: ID::profile(deps.storage)?.checked_add(1).unwrap() as u32, - }) -} - -pub fn profiles(deps: Deps, start: u16, end: u16) -> StdResult { - let mut items = vec![]; - let total = ID::profile(deps.storage)?; - - if start > total { - return Err(Error::item_not_found(vec![&start.to_string(), "Profile"])); - } - - for i in start..=min(end, total) { - items.push(Profile::load(deps.storage, i)?); - } - - Ok(QueryAnswer::Profiles { profiles: items }) -} - -pub fn total_assemblies(deps: Deps) -> StdResult { - Ok(QueryAnswer::Total { - total: ID::assembly(deps.storage)?.checked_add(1).unwrap() as u32, - }) -} - -pub fn assemblies(deps: Deps, start: u16, end: u16) -> StdResult { - let mut items = vec![]; - let total = ID::assembly(deps.storage)?; - - if start > total { - return Err(Error::item_not_found(vec![&start.to_string(), "Assembly"])); - } - - for i in start..=min(end, total) { - items.push(Assembly::load(deps.storage, i)?); - } - - Ok(QueryAnswer::Assemblies { assemblies: items }) -} - -pub fn total_assembly_msgs(deps: Deps) -> StdResult { - Ok(QueryAnswer::Total { - total: ID::assembly_msg(deps.storage)?.checked_add(1).unwrap() as u32, - }) -} - -pub fn assembly_msgs(deps: Deps, start: u16, end: u16) -> StdResult { - let mut items = vec![]; - let total = ID::assembly_msg(deps.storage)?; - - if start > total { - return Err(Error::item_not_found(vec![ - &start.to_string(), - "AssemblyMsg", - ])); - } - - for i in start..=min(end, total) { - items.push(AssemblyMsg::load(deps.storage, i)?); - } - - Ok(QueryAnswer::AssemblyMsgs { msgs: items }) -} - -pub fn total_contracts(deps: Deps) -> StdResult { - Ok(QueryAnswer::Total { - total: ID::contract(deps.storage)?.checked_add(1).unwrap() as u32, - }) -} - -pub fn contracts(deps: Deps, start: u16, end: u16) -> StdResult { - let mut items = vec![]; - let total = ID::contract(deps.storage)?; - - if start > total { - return Err(Error::item_not_found(vec![&start.to_string(), "Contract"])); - } - - for i in start..=min(end, total) { - items.push(AllowedContract::load(deps.storage, i)?); - } - - Ok(QueryAnswer::Contracts { contracts: items }) -} - -pub fn user_proposals(deps: Deps, user: Addr, pagination: Pagination) -> StdResult { - let total = UserID::total_proposals(deps.storage, user.clone())?; - - let start = pagination - .amount - .checked_mul(pagination.page as u32) - .unwrap(); - let mut props = vec![]; - - for i in start..start + pagination.amount { - let id = match UserID::proposal(deps.storage, user.clone(), i) { - Ok(id) => id, - Err(_) => break, - }; - - props.push(ResponseWithID { - prop_id: id, - data: Proposal::load(deps.storage, id)?, - }); - } - - Ok(QueryAnswer::UserProposals { props, total }) -} - -pub fn user_assembly_votes( - deps: Deps, - user: Addr, - pagination: Pagination, -) -> StdResult { - let total = UserID::total_assembly_votes(deps.storage, user.clone())?; - - let start = pagination - .amount - .checked_mul(pagination.page as u32) - .unwrap(); - let mut votes = vec![]; - - for i in start..start + pagination.amount { - let id = match UserID::assembly_vote(deps.storage, user.clone(), i) { - Ok(id) => id, - Err(_) => break, - }; - - votes.push(ResponseWithID { - prop_id: id, - data: Proposal::assembly_vote(deps.storage, id, &user)?.unwrap(), - }); - } - - Ok(QueryAnswer::UserAssemblyVotes { votes, total }) -} - -pub fn user_funding(deps: Deps, user: Addr, pagination: Pagination) -> StdResult { - let total = UserID::total_funding(deps.storage, user.clone())?; - - let start = pagination - .amount - .checked_mul(pagination.page as u32) - .unwrap(); - let mut funds = vec![]; - - for i in start..start + pagination.amount { - let id = match UserID::funding(deps.storage, user.clone(), i as u32) { - Ok(id) => id, - Err(_) => break, - }; - - funds.push(ResponseWithID { - prop_id: id, - data: Proposal::funding(deps.storage, id, &user)?, - }); - } - - Ok(QueryAnswer::UserFunding { funds, total }) -} - -pub fn user_votes(deps: Deps, user: Addr, pagination: Pagination) -> StdResult { - let total = UserID::total_votes(deps.storage, user.clone())?; - - let start = pagination - .amount - .checked_mul(pagination.page as u32) - .unwrap(); - let mut votes = vec![]; - - for i in start..start + pagination.amount { - let id = match UserID::votes(deps.storage, user.clone(), i) { - Ok(id) => id, - Err(_) => break, - }; - - votes.push(ResponseWithID { - prop_id: id, - data: Proposal::public_vote(deps.storage, id, &user)?.unwrap(), - }); - } - - Ok(QueryAnswer::UserVotes { votes, total }) -} diff --git a/archived-contracts/governance/src/tests/handle/assembly.rs b/archived-contracts/governance/src/tests/handle/assembly.rs deleted file mode 100644 index 9dae98f..0000000 --- a/archived-contracts/governance/src/tests/handle/assembly.rs +++ /dev/null @@ -1,105 +0,0 @@ -use crate::tests::{admin_only_governance, get_assemblies}; -use shade_protocol::{c_std::Addr, contract_interfaces::governance, utils::ExecuteCallback}; - -#[test] -fn add_assembly() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - governance::ExecuteMsg::AddAssembly { - name: "Other assembly".to_string(), - metadata: "some data".to_string(), - members: vec![], - profile: 1, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let assemblies = get_assemblies(&mut chain, &gov, 0, 2).unwrap(); - - assert_eq!(assemblies.len(), 3); -} - -#[test] -fn unauthorised_add_assembly() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - assert!( - governance::ExecuteMsg::AddAssembly { - name: "Other assembly".to_string(), - metadata: "some data".to_string(), - members: vec![], - profile: 1, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - Addr::unchecked("random"), - &[] - ) - .is_err() - ) -} - -#[test] -fn set_assembly() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - let old_assembly = get_assemblies(&mut chain, &gov, 1, 2).unwrap()[0].clone(); - - governance::ExecuteMsg::SetAssembly { - id: 1, - name: Some("Random name".to_string()), - metadata: Some("data".to_string()), - members: None, - profile: None, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let new_assembly = get_assemblies(&mut chain, &gov, 1, 2).unwrap()[0].clone(); - - assert_ne!(new_assembly.name, old_assembly.name); - assert_ne!(new_assembly.metadata, old_assembly.metadata); - assert_eq!(new_assembly.members, old_assembly.members); - assert_eq!(new_assembly.profile, old_assembly.profile); -} - -#[test] -fn unauthorised_set_assembly() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - assert!( - governance::ExecuteMsg::SetAssembly { - id: 1, - name: Some("Random name".to_string()), - metadata: Some("data".to_string()), - members: None, - profile: None, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - Addr::unchecked("random"), - &[] - ) - .is_err() - ) -} diff --git a/archived-contracts/governance/src/tests/handle/assembly_msg.rs b/archived-contracts/governance/src/tests/handle/assembly_msg.rs deleted file mode 100644 index 013a371..0000000 --- a/archived-contracts/governance/src/tests/handle/assembly_msg.rs +++ /dev/null @@ -1,102 +0,0 @@ -use crate::tests::{admin_only_governance, get_assembly_msgs}; -use shade_protocol::{c_std::Addr, contract_interfaces::governance, utils::ExecuteCallback}; - -#[test] -fn add_assembly_msg() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - governance::ExecuteMsg::AddAssemblyMsg { - name: "Some Assembly name".to_string(), - msg: "{}".to_string(), - assemblies: vec![0], - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let assemblies = get_assembly_msgs(&mut chain, &gov, 0, 1).unwrap(); - - assert_eq!(assemblies.len(), 2); -} - -#[test] -fn unauthorised_add_assembly_msg() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - assert!( - governance::ExecuteMsg::AddAssemblyMsg { - name: "Some Assembly name".to_string(), - msg: "{}".to_string(), - assemblies: vec![0], - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - Addr::unchecked("random"), - &[] - ) - .is_err() - ); -} - -#[test] -fn set_assembly_msg() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - let original_msg = get_assembly_msgs(&mut chain, &gov, 0, 1).unwrap()[0].clone(); - - governance::ExecuteMsg::SetAssemblyMsg { - id: 0, - name: Some("New name".to_string()), - msg: None, - assemblies: None, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let assemblies = get_assembly_msgs(&mut chain, &gov, 0, 1).unwrap(); - - assert_eq!(assemblies.len(), 1); - - assert_ne!(original_msg.name, assemblies[0].name); - assert_eq!(original_msg.assemblies, assemblies[0].assemblies); - assert_eq!(original_msg.msg, assemblies[0].msg); -} - -#[test] -fn unauthorised_set_assembly_msg() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - assert!( - governance::ExecuteMsg::SetAssemblyMsg { - id: 0, - name: Some("New name".to_string()), - msg: None, - assemblies: None, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - Addr::unchecked("random"), - &[] - ) - .is_err() - ); -} diff --git a/archived-contracts/governance/src/tests/handle/contract.rs b/archived-contracts/governance/src/tests/handle/contract.rs deleted file mode 100644 index 46c4a7e..0000000 --- a/archived-contracts/governance/src/tests/handle/contract.rs +++ /dev/null @@ -1,301 +0,0 @@ -use crate::tests::{admin_only_governance, get_contract}; -use shade_protocol::{ - c_std::Addr, - contract_interfaces::governance, - utils::{asset::Contract, ExecuteCallback}, -}; - -#[test] -fn add_contract() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - governance::ExecuteMsg::AddContract { - name: "Contract".to_string(), - metadata: "some description".to_string(), - contract: Contract { - address: Addr::unchecked("contract"), - code_hash: "hash".to_string(), - }, - assemblies: None, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let contracts = get_contract(&mut chain, &gov, 0, 1).unwrap(); - - assert_eq!(contracts.len(), 2); -} -#[test] -fn unauthorised_add_contract() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - assert!( - governance::ExecuteMsg::AddContract { - name: "Contract".to_string(), - metadata: "some description".to_string(), - contract: Contract { - address: Addr::unchecked("contract"), - code_hash: "hash".to_string(), - }, - assemblies: None, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - Addr::unchecked("random"), - &[] - ) - .is_err() - ); -} -#[test] -fn set_contract() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - governance::ExecuteMsg::AddContract { - name: "Contract".to_string(), - metadata: "some description".to_string(), - contract: Contract { - address: Addr::unchecked("contract"), - code_hash: "hash".to_string(), - }, - assemblies: None, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let old_contract = get_contract(&mut chain, &gov, 1, 1).unwrap()[0].clone(); - - governance::ExecuteMsg::SetContract { - id: 1, - name: Some("New name".to_string()), - metadata: Some("New desc".to_string()), - contract: Some(Contract { - address: Addr::unchecked("new contract"), - code_hash: "other hash".to_string(), - }), - disable_assemblies: false, - assemblies: None, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let new_contract = get_contract(&mut chain, &gov, 1, 1).unwrap()[0].clone(); - - assert_ne!(old_contract.name, new_contract.name); - assert_ne!(old_contract.metadata, new_contract.metadata); - assert_ne!(old_contract.contract.address, new_contract.contract.address); - assert_ne!( - old_contract.contract.code_hash, - new_contract.contract.code_hash - ); -} - -#[test] -fn disable_contract_assemblies() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - governance::ExecuteMsg::AddContract { - name: "Contract".to_string(), - metadata: "some description".to_string(), - contract: Contract { - address: Addr::unchecked("contract"), - code_hash: "hash".to_string(), - }, - assemblies: Some(vec![0]), - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let old_contract = get_contract(&mut chain, &gov, 1, 1).unwrap()[0].clone(); - - governance::ExecuteMsg::SetContract { - id: 1, - name: Some("New name".to_string()), - metadata: Some("New desc".to_string()), - contract: Some(Contract { - address: Addr::unchecked("new contract"), - code_hash: "other hash".to_string(), - }), - disable_assemblies: true, - assemblies: None, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let new_contract = get_contract(&mut chain, &gov, 1, 1).unwrap()[0].clone(); - - assert_ne!(old_contract.name, new_contract.name); - assert_ne!(old_contract.metadata, new_contract.metadata); - assert_ne!(old_contract.contract.address, new_contract.contract.address); - assert_ne!( - old_contract.contract.code_hash, - new_contract.contract.code_hash - ); - assert_ne!(old_contract.assemblies, new_contract.assemblies); -} - -#[test] -fn enable_contract_assemblies() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - governance::ExecuteMsg::AddContract { - name: "Contract".to_string(), - metadata: "some description".to_string(), - contract: Contract { - address: Addr::unchecked("contract"), - code_hash: "hash".to_string(), - }, - assemblies: None, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let old_contract = get_contract(&mut chain, &gov, 1, 1).unwrap()[0].clone(); - - governance::ExecuteMsg::SetContract { - id: 1, - name: Some("New name".to_string()), - metadata: Some("New desc".to_string()), - contract: Some(Contract { - address: Addr::unchecked("new contract"), - code_hash: "other hash".to_string(), - }), - disable_assemblies: false, - assemblies: Some(vec![0]), - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let new_contract = get_contract(&mut chain, &gov, 1, 1).unwrap()[0].clone(); - - assert_ne!(old_contract.name, new_contract.name); - assert_ne!(old_contract.metadata, new_contract.metadata); - assert_ne!(old_contract.contract.address, new_contract.contract.address); - assert_ne!( - old_contract.contract.code_hash, - new_contract.contract.code_hash - ); - assert_ne!(old_contract.assemblies, new_contract.assemblies); -} - -#[test] -fn unauthorised_set_contract() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - assert!( - governance::ExecuteMsg::SetContract { - id: 1, - name: Some("New name".to_string()), - metadata: Some("New desc".to_string()), - contract: Some(Contract { - address: Addr::unchecked("new contract"), - code_hash: "other hash".to_string(), - }), - disable_assemblies: false, - assemblies: None, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - Addr::unchecked("random"), - &[] - ) - .is_err() - ); -} -#[test] -fn add_contract_assemblies() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - governance::ExecuteMsg::AddContract { - name: "Contract".to_string(), - metadata: "some description".to_string(), - contract: Contract { - address: Addr::unchecked("contract"), - code_hash: "hash".to_string(), - }, - assemblies: Some(vec![0]), - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let old_contract = get_contract(&mut chain, &gov, 1, 1).unwrap()[0].clone(); - - governance::ExecuteMsg::AddContractAssemblies { - id: 1, - assemblies: vec![1], - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let new_contract = get_contract(&mut chain, &gov, 1, 1).unwrap()[0].clone(); - - assert_ne!(old_contract.assemblies, new_contract.assemblies); -} diff --git a/archived-contracts/governance/src/tests/handle/migration.rs b/archived-contracts/governance/src/tests/handle/migration.rs deleted file mode 100644 index 55b8c0e..0000000 --- a/archived-contracts/governance/src/tests/handle/migration.rs +++ /dev/null @@ -1,310 +0,0 @@ -// Migrate to new contract -// Send data -// Receive data - -use crate::tests::handle::runstate::init_gov; -use shade_protocol::{ - c_std::{Addr, ContractInfo}, - governance, - governance::{profile::Profile, ExecuteMsg, MigrationDataAsk, QueryAnswer, QueryMsg}, - multi_test::App, - utils::{ExecuteCallback, Query}, -}; - -#[test] -fn migrate() { - let (mut chain, gov, _snip20, gov_id) = init_gov().unwrap(); - - for i in 0..20 { - // Generate multiple assemblies to migrate - governance::ExecuteMsg::AddAssembly { - name: format!("Assembly {}", i), - metadata: "some data".to_string(), - members: vec![ - Addr::unchecked("alpha"), - Addr::unchecked("beta"), - Addr::unchecked("charlie"), - ], - profile: 1, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - // Generate multiple profiles - governance::ExecuteMsg::AddProfile { - profile: Profile { - name: format!("Profile {}", i), - enabled: false, - assembly: None, - funding: None, - token: None, - cancel_deadline: 0, - }, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - // Generate multiple assembly msgs - governance::ExecuteMsg::AddAssemblyMsg { - name: format!("AssemblyMsg {}", i), - msg: "{}".to_string(), - assemblies: vec![], - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - // Generate multiple contracts - governance::ExecuteMsg::AddContract { - name: format!("Contract {}", i), - metadata: "".to_string(), - contract: Default::default(), - assemblies: None, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - } - - governance::ExecuteMsg::Migrate { - id: gov_id, - label: "new_governance".to_string(), - code_hash: gov.code_hash.clone(), - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let answer: governance::QueryAnswer = governance::QueryMsg::Config {} - .test_query(&gov, &chain) - .unwrap(); - - let new_gov = match answer { - QueryAnswer::Config { config } => { - if let Some(contract) = config.migrated_to { - ContractInfo { - address: contract.address, - code_hash: contract.code_hash, - } - } else { - panic!("No migration target") - } - } - _ => panic!("Not the expected response"), - }; - - // Check that totals are well initialized - assert_query_totals(QueryMsg::TotalAssemblies {}, &chain, &gov, &new_gov); - assert_query_totals(QueryMsg::TotalAssemblyMsgs {}, &chain, &gov, &new_gov); - assert_query_totals(QueryMsg::TotalContracts {}, &chain, &gov, &new_gov); - assert_query_totals(QueryMsg::TotalProfiles {}, &chain, &gov, &new_gov); - - // Check that gov is not well initialized - assert_migrated_items(&chain, &gov, &new_gov, false); - - // Make sure that it can handle exact migration - ExecuteMsg::MigrateData { - data: MigrationDataAsk::Assembly, - total: 22, - } - .test_exec(&gov, &mut chain, Addr::unchecked("anyone"), &[]) - .unwrap(); - - // Handle fractional migrations - ExecuteMsg::MigrateData { - data: MigrationDataAsk::Profile, - total: 11, - } - .test_exec(&gov, &mut chain, Addr::unchecked("anyone"), &[]) - .unwrap(); - ExecuteMsg::MigrateData { - data: MigrationDataAsk::Profile, - total: 11, - } - .test_exec(&gov, &mut chain, Addr::unchecked("anyone"), &[]) - .unwrap(); - - // Handle amount overflow - ExecuteMsg::MigrateData { - data: MigrationDataAsk::AssemblyMsg, - total: 40, - } - .test_exec(&gov, &mut chain, Addr::unchecked("anyone"), &[]) - .unwrap(); - - ExecuteMsg::MigrateData { - data: MigrationDataAsk::Contract, - total: 25, - } - .test_exec(&gov, &mut chain, Addr::unchecked("anyone"), &[]) - .unwrap(); - - // Finally assert that everything is fine - assert_migrated_items(&chain, &gov, &new_gov, true); -} - -fn assert_query_totals(query: QueryMsg, chain: &App, gov1: &ContractInfo, gov2: &ContractInfo) { - let query1 = query.test_query(&gov1, &chain).unwrap(); - let query2 = query.test_query(&gov2, &chain).unwrap(); - if let QueryAnswer::Total { total: total1 } = query1 { - if let QueryAnswer::Total { total: total2 } = query2 { - assert_eq!(total1, total2); - } else { - panic!("Expected something") - } - } else { - panic!("Expected something") - } -} - -fn assert_migrated_items( - chain: &App, - gov1: &ContractInfo, - gov2: &ContractInfo, - should_equal: bool, -) { - ///////// ASSEMBLIES - let query = QueryMsg::Assemblies { start: 0, end: 25 }; - let query1 = query.test_query(&gov1, &chain).unwrap(); - let query2_try = query.test_query(&gov2, &chain); - - // Should error out cause item is not found - if !should_equal { - assert!(query2_try.is_err()); - } else if let QueryAnswer::Assemblies { - assemblies: assemblies1, - } = query1 - { - let query2 = query2_try.unwrap(); - if let QueryAnswer::Assemblies { - assemblies: assemblies2, - } = query2 - { - assert_eq!(assemblies1.len(), assemblies2.len()); - if should_equal { - for (i, assembly) in assemblies1.iter().enumerate() { - assert_eq!(assembly.clone(), assemblies2[i]); - } - } - } else { - panic!("Expected something") - } - } else { - panic!("Expected something") - } - - ///////// ASSEMBLY MSGS - let query = QueryMsg::AssemblyMsgs { start: 0, end: 25 }; - let query1 = query.test_query(&gov1, &chain).unwrap(); - let query2_try = query.test_query(&gov2, &chain); - - // Should error out cause item is not found - if !should_equal { - assert!(query2_try.is_err()); - } else if let QueryAnswer::AssemblyMsgs { msgs: msgs1 } = query1 { - let query2 = query2_try.unwrap(); - if let QueryAnswer::AssemblyMsgs { msgs: msgs2 } = query2 { - assert_eq!(msgs1.len(), msgs2.len()); - if should_equal { - for (i, msg) in msgs1.iter().enumerate() { - assert_eq!(msg.clone(), msgs2[i]); - } - } - } else { - panic!("Expected something") - } - } else { - panic!("Expected something") - } - - ///////// PROFILES - let query = QueryMsg::Profiles { start: 0, end: 25 }; - let query1 = query.test_query(&gov1, &chain).unwrap(); - let query2_try = query.test_query(&gov2, &chain); - - // Should error out cause item is not found - if !should_equal { - assert!(query2_try.is_err()); - } else if let QueryAnswer::Profiles { - profiles: profiles1, - } = query1 - { - let query2 = query2_try.unwrap(); - if let QueryAnswer::Profiles { - profiles: profiles2, - } = query2 - { - assert_eq!(profiles1.len(), profiles2.len()); - if should_equal { - for (i, profile) in profiles1.iter().enumerate() { - assert_eq!(profile.clone(), profiles2[i]); - } - } - } else { - panic!("Expected something") - } - } else { - panic!("Expected something") - } - - ///////// CONTRACTS - let query = QueryMsg::Contracts { start: 0, end: 25 }; - let query1 = query.test_query(&gov1, &chain).unwrap(); - let query2_try = query.test_query(&gov2, &chain); - - // Should error out cause item is not found - if !should_equal { - assert!(query2_try.is_err()); - } else if let QueryAnswer::Contracts { - contracts: contracts1, - } = query1 - { - let query2 = query2_try.unwrap(); - if let QueryAnswer::Contracts { - contracts: contracts2, - } = query2 - { - assert_eq!(contracts1.len(), contracts2.len()); - if should_equal { - for (i, contract) in contracts1.iter().enumerate() { - assert_eq!(contract.clone(), contracts2[i]); - } - } - } else { - panic!("Expected something") - } - } else { - panic!("Expected something") - } -} diff --git a/archived-contracts/governance/src/tests/handle/mod.rs b/archived-contracts/governance/src/tests/handle/mod.rs deleted file mode 100644 index 5347087..0000000 --- a/archived-contracts/governance/src/tests/handle/mod.rs +++ /dev/null @@ -1,127 +0,0 @@ -pub mod assembly; -pub mod assembly_msg; -pub mod contract; -pub mod migration; -pub mod profile; -pub mod proposal; -pub mod runstate; - -use crate::tests::{admin_only_governance, get_config, handle::proposal::init_funding_token}; -use shade_protocol::{ - c_std::Addr, - contract_interfaces::governance, - utils::{asset::Contract, ExecuteCallback}, -}; - -#[test] -fn set_config_msg() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - let old_config = get_config(&mut chain, &gov).unwrap(); - - let snip20 = init_funding_token(&mut chain, None, None).unwrap(); - - governance::ExecuteMsg::SetConfig { - query_auth: None, - treasury: Some(Addr::unchecked("random")), - funding_token: Some(Contract { - address: snip20.address.clone(), - code_hash: snip20.code_hash.clone(), - }), - vote_token: Some(Contract { - address: snip20.address, - code_hash: snip20.code_hash, - }), - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let new_config = get_config(&mut chain, &gov).unwrap(); - - assert_ne!(old_config.treasury, new_config.treasury); - assert_ne!(old_config.funding_token, new_config.funding_token); - assert_ne!(old_config.vote_token, new_config.vote_token); -} - -#[test] -fn unauthorised_set_config_msg() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - assert!( - governance::ExecuteMsg::SetConfig { - query_auth: None, - treasury: None, - funding_token: None, - vote_token: None, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - Addr::unchecked("random"), - &[] - ) - .is_err() - ); -} - -#[test] -fn reject_disable_config_tokens() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - let snip20 = init_funding_token(&mut chain, None, None).unwrap(); - - governance::ExecuteMsg::SetConfig { - query_auth: None, - treasury: Some(Addr::unchecked("random")), - funding_token: Some(Contract { - address: snip20.address.clone(), - code_hash: snip20.code_hash.clone(), - }), - vote_token: Some(Contract { - address: snip20.address, - code_hash: snip20.code_hash, - }), - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let old_config = get_config(&mut chain, &gov).unwrap(); - - governance::ExecuteMsg::SetConfig { - query_auth: None, - treasury: None, - funding_token: None, - vote_token: None, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let new_config = get_config(&mut chain, &gov).unwrap(); - - assert_eq!(old_config.treasury, new_config.treasury); - assert_eq!(old_config.funding_token, new_config.funding_token); - assert_eq!(old_config.vote_token, new_config.vote_token); -} diff --git a/archived-contracts/governance/src/tests/handle/profile.rs b/archived-contracts/governance/src/tests/handle/profile.rs deleted file mode 100644 index ad09a3c..0000000 --- a/archived-contracts/governance/src/tests/handle/profile.rs +++ /dev/null @@ -1,472 +0,0 @@ -use crate::tests::{admin_only_governance, get_profiles}; -use shade_protocol::{ - c_std::{Addr, Uint128}, - contract_interfaces::{ - governance, - governance::profile::{ - Count, - Profile, - UpdateFundProfile, - UpdateProfile, - UpdateVoteProfile, - }, - }, - utils::ExecuteCallback, -}; - -#[test] -fn add_profile() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - governance::ExecuteMsg::AddProfile { - profile: Profile { - name: "Other Profile".to_string(), - enabled: false, - assembly: None, - funding: None, - token: None, - cancel_deadline: 0, - }, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let profiles = get_profiles(&mut chain, &gov, 0, 10).unwrap(); - - assert_eq!(profiles.len(), 3); -} -#[test] -fn unauthorised_add_profile() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - assert!( - governance::ExecuteMsg::AddProfile { - profile: Profile { - name: "Other Profile".to_string(), - enabled: false, - assembly: None, - funding: None, - token: None, - cancel_deadline: 0, - }, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - Addr::unchecked("random"), - &[] - ) - .is_err() - ); -} - -#[test] -fn set_profile() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - let old_profile = get_profiles(&mut chain, &gov, 1, 1).unwrap()[0].clone(); - - governance::ExecuteMsg::SetProfile { - id: 1, - profile: UpdateProfile { - name: Some("New Name".to_string()), - enabled: None, - disable_assembly: false, - assembly: None, - disable_funding: false, - funding: None, - disable_token: false, - token: None, - cancel_deadline: None, - }, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let new_profile = get_profiles(&mut chain, &gov, 1, 1).unwrap()[0].clone(); - - assert_ne!(new_profile.name, old_profile.name); - assert_eq!(new_profile.assembly, old_profile.assembly); - assert_eq!(new_profile.funding, old_profile.funding); - assert_eq!(new_profile.token, old_profile.token); - assert_eq!(new_profile.enabled, old_profile.enabled); - assert_eq!(new_profile.cancel_deadline, old_profile.cancel_deadline); -} - -#[test] -fn unauthorised_set_profile() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - assert!( - governance::ExecuteMsg::SetProfile { - id: 1, - profile: UpdateProfile { - name: Some("New Name".to_string()), - enabled: None, - disable_assembly: false, - assembly: None, - disable_funding: false, - funding: None, - disable_token: false, - token: None, - cancel_deadline: None, - }, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - Addr::unchecked("random"), - &[] - ) - .is_err() - ); -} - -#[test] -fn set_profile_disable_assembly() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - governance::ExecuteMsg::SetProfile { - id: 1, - profile: UpdateProfile { - name: None, - enabled: None, - disable_assembly: false, - assembly: Some(UpdateVoteProfile { - deadline: Some(0), - threshold: Some(Count::LiteralCount { - count: Uint128::zero(), - }), - yes_threshold: Some(Count::LiteralCount { - count: Uint128::zero(), - }), - veto_threshold: Some(Count::LiteralCount { - count: Uint128::zero(), - }), - }), - disable_funding: false, - funding: None, - disable_token: false, - token: None, - cancel_deadline: None, - }, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let old_profile = get_profiles(&mut chain, &gov, 1, 1).unwrap()[0].clone(); - - governance::ExecuteMsg::SetProfile { - id: 1, - profile: UpdateProfile { - name: None, - enabled: None, - disable_assembly: true, - assembly: None, - disable_funding: false, - funding: None, - disable_token: false, - token: None, - cancel_deadline: None, - }, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let new_profile = get_profiles(&mut chain, &gov, 1, 1).unwrap()[0].clone(); - - assert_eq!(new_profile.name, old_profile.name); - assert_ne!(new_profile.assembly, old_profile.assembly); - assert_eq!(new_profile.funding, old_profile.funding); - assert_eq!(new_profile.token, old_profile.token); - assert_eq!(new_profile.enabled, old_profile.enabled); - assert_eq!(new_profile.cancel_deadline, old_profile.cancel_deadline); -} - -#[test] -fn set_profile_set_incomplete_assembly() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - assert!( - governance::ExecuteMsg::SetProfile { - id: 1, - profile: UpdateProfile { - name: None, - enabled: None, - disable_assembly: false, - assembly: Some(UpdateVoteProfile { - deadline: Some(0), - threshold: None, - yes_threshold: None, - veto_threshold: Some(Count::LiteralCount { - count: Uint128::zero(), - }), - }), - disable_funding: false, - funding: None, - disable_token: false, - token: None, - cancel_deadline: None, - }, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[] - ) - .is_err() - ); -} - -#[test] -fn set_profile_disable_token() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - governance::ExecuteMsg::SetProfile { - id: 1, - profile: UpdateProfile { - name: None, - enabled: None, - disable_assembly: false, - assembly: None, - disable_funding: false, - funding: None, - disable_token: false, - token: Some(UpdateVoteProfile { - deadline: Some(0), - threshold: Some(Count::LiteralCount { - count: Uint128::zero(), - }), - yes_threshold: Some(Count::LiteralCount { - count: Uint128::zero(), - }), - veto_threshold: Some(Count::LiteralCount { - count: Uint128::zero(), - }), - }), - cancel_deadline: None, - }, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let old_profile = get_profiles(&mut chain, &gov, 1, 1).unwrap()[0].clone(); - - governance::ExecuteMsg::SetProfile { - id: 1, - profile: UpdateProfile { - name: None, - enabled: None, - disable_assembly: false, - assembly: None, - disable_funding: false, - funding: None, - disable_token: true, - token: None, - cancel_deadline: None, - }, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let new_profile = get_profiles(&mut chain, &gov, 1, 1).unwrap()[0].clone(); - - assert_eq!(new_profile.name, old_profile.name); - assert_eq!(new_profile.assembly, old_profile.assembly); - assert_eq!(new_profile.funding, old_profile.funding); - assert_ne!(new_profile.token, old_profile.token); - assert_eq!(new_profile.enabled, old_profile.enabled); - assert_eq!(new_profile.cancel_deadline, old_profile.cancel_deadline); -} - -#[test] -fn set_profile_set_incomplete_token() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - assert!( - governance::ExecuteMsg::SetProfile { - id: 1, - profile: UpdateProfile { - name: None, - enabled: None, - disable_assembly: false, - assembly: None, - disable_funding: false, - funding: None, - disable_token: false, - token: Some(UpdateVoteProfile { - deadline: Some(0), - threshold: None, - yes_threshold: None, - veto_threshold: Some(Count::LiteralCount { - count: Uint128::zero(), - }), - }), - cancel_deadline: None, - }, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[] - ) - .is_err() - ); -} - -#[test] -fn set_profile_disable_funding() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - governance::ExecuteMsg::SetProfile { - id: 1, - profile: UpdateProfile { - name: None, - enabled: None, - disable_assembly: false, - assembly: None, - disable_funding: false, - funding: Some(UpdateFundProfile { - deadline: Some(0), - required: Some(Uint128::zero()), - privacy: Some(true), - veto_deposit_loss: Some(Uint128::zero()), - }), - disable_token: false, - token: None, - cancel_deadline: None, - }, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let old_profile = get_profiles(&mut chain, &gov, 1, 1).unwrap()[0].clone(); - - governance::ExecuteMsg::SetProfile { - id: 1, - profile: UpdateProfile { - name: None, - enabled: None, - disable_assembly: false, - assembly: None, - disable_funding: true, - funding: None, - disable_token: false, - token: None, - cancel_deadline: None, - }, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - let new_profile = get_profiles(&mut chain, &gov, 1, 1).unwrap()[0].clone(); - - assert_eq!(new_profile.name, old_profile.name); - assert_eq!(new_profile.assembly, old_profile.assembly); - assert_ne!(new_profile.funding, old_profile.funding); - assert_eq!(new_profile.token, old_profile.token); - assert_eq!(new_profile.enabled, old_profile.enabled); - assert_eq!(new_profile.cancel_deadline, old_profile.cancel_deadline); -} - -#[test] -fn set_profile_set_incomplete_fuding() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - assert!( - governance::ExecuteMsg::SetProfile { - id: 1, - profile: UpdateProfile { - name: None, - enabled: None, - disable_assembly: false, - assembly: None, - disable_funding: false, - funding: Some(UpdateFundProfile { - deadline: Some(0), - required: None, - privacy: Some(true), - veto_deposit_loss: None, - }), - disable_token: false, - token: None, - cancel_deadline: None, - }, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[] - ) - .is_err() - ); -} diff --git a/archived-contracts/governance/src/tests/handle/proposal/assembly_voting.rs b/archived-contracts/governance/src/tests/handle/proposal/assembly_voting.rs deleted file mode 100644 index 2ac8fdd..0000000 --- a/archived-contracts/governance/src/tests/handle/proposal/assembly_voting.rs +++ /dev/null @@ -1,844 +0,0 @@ -use crate::tests::{get_proposals, init_chain}; -use shade_multi_test::multi::governance::Governance; -use shade_protocol::{ - c_std::{Addr, ContractInfo, StdResult, Uint128}, - contract_interfaces::{ - governance, - governance::{ - profile::{Count, Profile, VoteProfile}, - proposal::Status, - vote::Vote, - InstantiateMsg, - }, - query_auth, - }, - governance::AssemblyInit, - multi_test::App, - utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable}, -}; - -pub fn init_assembly_governance_with_proposal() -> StdResult<(App, ContractInfo)> { - let (mut chain, auth) = init_chain(); - - query_auth::ExecuteMsg::SetViewingKey { - key: "password".to_string(), - padding: None, - } - .test_exec(&auth, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - query_auth::ExecuteMsg::SetViewingKey { - key: "password".to_string(), - padding: None, - } - .test_exec(&auth, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - query_auth::ExecuteMsg::SetViewingKey { - key: "password".to_string(), - padding: None, - } - .test_exec(&auth, &mut chain, Addr::unchecked("charlie"), &[]) - .unwrap(); - - let gov = InstantiateMsg { - treasury: Addr::unchecked("treasury"), - query_auth: Contract { - address: auth.address, - code_hash: auth.code_hash, - }, - funding_token: None, - vote_token: None, - assemblies: Some(AssemblyInit { - admin_members: vec![ - Addr::unchecked("alpha"), - Addr::unchecked("beta"), - Addr::unchecked("charlie"), - ], - admin_profile: Profile { - name: "admin".to_string(), - enabled: true, - assembly: Some(VoteProfile { - deadline: 10000, - threshold: Count::LiteralCount { - count: Uint128::new(2), - }, - yes_threshold: Count::LiteralCount { - count: Uint128::new(2), - }, - veto_threshold: Count::LiteralCount { - count: Uint128::new(3), - }, - }), - funding: None, - token: None, - cancel_deadline: 0, - }, - public_profile: Profile { - name: "public".to_string(), - enabled: false, - assembly: None, - funding: None, - token: None, - cancel_deadline: 0, - }, - }), - migrator: None, - } - .test_init( - Governance::default(), - &mut chain, - Addr::unchecked("admin"), - "governance", - &[], - ) - .unwrap(); - - governance::ExecuteMsg::AssemblyProposal { - assembly: 1, - title: "Title".to_string(), - metadata: "Text only proposal".to_string(), - msgs: None, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - Ok((chain, gov)) -} - -#[test] -fn assembly_voting() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - assert_eq!(prop.title, "Title".to_string()); - assert_eq!(prop.metadata, "Text only proposal".to_string()); - assert_eq!(prop.proposer, Addr::unchecked("alpha")); - assert_eq!(prop.assembly, 1); - - match prop.status { - Status::AssemblyVote { .. } => assert!(true), - _ => assert!(false), - }; -} - -#[test] -fn update_before_deadline() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - assert!( - governance::ExecuteMsg::Update { - proposal: 0, - padding: None - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .is_err() - ); -} - -#[test] -fn update_after_deadline() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - assert!( - governance::ExecuteMsg::Update { - proposal: 0, - padding: None - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .is_ok() - ); -} - -#[test] -fn invalid_vote() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - assert!( - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::new(1), - no: Uint128::new(1), - no_with_veto: Default::default(), - abstain: Default::default() - }, - padding: None - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .is_err() - ); -} - -#[test] -fn unauthorised_vote() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - assert!( - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::zero(), - no: Uint128::new(1), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }, - padding: None - } - .test_exec(&gov, &mut chain, Addr::unchecked("foxtrot"), &[]) - .is_err() - ); -} - -#[test] -fn vote_after_deadline() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - assert!( - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::zero(), - no: Uint128::new(1), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }, - padding: None - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .is_err() - ); -} - -#[test] -fn vote_yes() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::new(1), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::AssemblyVote { .. } => assert!(true), - _ => assert!(false), - }; - - assert_eq!( - prop.assembly_vote_tally, - Some(Vote { - yes: Uint128::new(1), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }) - ) -} - -#[test] -fn vote_abstain() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::new(1), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::AssemblyVote { .. } => assert!(true), - _ => assert!(false), - }; - - assert_eq!( - prop.assembly_vote_tally, - Some(Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::new(1) - }) - ) -} - -#[test] -fn vote_no() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::zero(), - no: Uint128::new(1), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::AssemblyVote { .. } => assert!(true), - _ => assert!(false), - }; - - assert_eq!( - prop.assembly_vote_tally, - Some(Vote { - yes: Uint128::zero(), - no: Uint128::new(1), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }) - ) -} - -#[test] -fn vote_veto() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::new(1), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::AssemblyVote { .. } => assert!(true), - _ => assert!(false), - }; - - assert_eq!( - prop.assembly_vote_tally, - Some(Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::new(1), - abstain: Uint128::zero() - }) - ) -} - -#[test] -fn vote_passed() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::new(1), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::new(1), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Passed { .. } => assert!(true), - _ => assert!(false), - }; -} - -#[test] -fn vote_abstained() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::new(1), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::new(1), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Rejected { .. } => assert!(true), - _ => assert!(false), - }; -} - -#[test] -fn vote_rejected() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::zero(), - no: Uint128::new(1), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::zero(), - no: Uint128::new(1), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Rejected { .. } => assert!(true), - _ => assert!(false), - }; -} - -#[test] -fn vote_vetoed() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::new(1), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::new(1), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - // NOTE: assembly votes cannot be vetoed - Status::Rejected { .. } => assert!(true), - _ => assert!(false), - }; -} - -#[test] -fn vote_no_quorum() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::new(1), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - assert_eq!(prop.status, Status::Expired); -} - -#[test] -fn vote_total() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::new(1), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::new(1), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::new(1), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("charlie"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::AssemblyVote { .. } => assert!(true), - _ => assert!(false), - }; - - assert_eq!( - prop.assembly_vote_tally, - Some(Vote { - yes: Uint128::new(2), - no: Uint128::zero(), - no_with_veto: Uint128::new(1), - abstain: Uint128::zero() - }) - ) -} - -#[test] -fn update_vote() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::new(1), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - assert_eq!( - prop.assembly_vote_tally, - Some(Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::new(1), - abstain: Uint128::zero() - }) - ); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::new(1), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - assert_eq!( - prop.assembly_vote_tally, - Some(Vote { - yes: Uint128::new(1), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }) - ); -} - -#[test] -fn vote_count() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::new(1), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::new(1), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Passed { .. } => assert!(true), - _ => assert!(false), - }; -} - -#[test] -fn vote_count_percentage() { - let (mut chain, auth) = init_chain(); - - let gov = InstantiateMsg { - treasury: Addr::unchecked("treasury"), - query_auth: Contract { - address: auth.address, - code_hash: auth.code_hash, - }, - funding_token: None, - vote_token: None, - assemblies: Some(AssemblyInit { - admin_members: vec![ - Addr::unchecked("alpha"), - Addr::unchecked("beta"), - Addr::unchecked("charlie"), - ], - admin_profile: Profile { - name: "admin".to_string(), - enabled: true, - assembly: Some(VoteProfile { - deadline: 10000, - threshold: Count::Percentage { percent: 6500 }, - yes_threshold: Count::Percentage { percent: 6500 }, - veto_threshold: Count::Percentage { percent: 6500 }, - }), - funding: None, - token: None, - cancel_deadline: 0, - }, - public_profile: Profile { - name: "public".to_string(), - enabled: false, - assembly: None, - funding: None, - token: None, - cancel_deadline: 0, - }, - }), - migrator: None, - } - .test_init( - Governance::default(), - &mut chain, - Addr::unchecked("admin"), - "governance", - &[], - ) - .unwrap(); - - governance::ExecuteMsg::AssemblyProposal { - assembly: 1, - title: "Title".to_string(), - metadata: "Text only proposal".to_string(), - msgs: None, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::new(1), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::new(1), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Passed { .. } => assert!(true), - _ => assert!(false), - }; -} diff --git a/archived-contracts/governance/src/tests/handle/proposal/funding.rs b/archived-contracts/governance/src/tests/handle/proposal/funding.rs deleted file mode 100644 index 6e79fb2..0000000 --- a/archived-contracts/governance/src/tests/handle/proposal/funding.rs +++ /dev/null @@ -1,781 +0,0 @@ -use crate::tests::{get_proposals, handle::proposal::init_funding_token, init_chain}; -use shade_multi_test::multi::{governance::Governance, snip20::Snip20}; -use shade_protocol::{ - c_std::{to_binary, Addr, ContractInfo, StdResult, Uint128}, - contract_interfaces::{ - governance, - governance::{ - profile::{Count, FundProfile, Profile, UpdateProfile, UpdateVoteProfile}, - proposal::Status, - vote::Vote, - InstantiateMsg, - }, - query_auth, - snip20, - }, - governance::AssemblyInit, - multi_test::App, - utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -pub fn init_funding_governance_with_proposal() --> StdResult<(App, ContractInfo, ContractInfo, ContractInfo)> { - let (mut chain, auth) = init_chain(); - - // Register snip20 - let snip20 = snip20::InstantiateMsg { - name: "funding_token".to_string(), - admin: None, - symbol: "FND".to_string(), - decimals: 6, - initial_balances: Some(vec![ - snip20::InitialBalance { - address: "alpha".into(), - amount: Uint128::new(10000), - }, - snip20::InitialBalance { - address: "beta".into(), - amount: Uint128::new(10000), - }, - snip20::InitialBalance { - address: "charlie".into(), - amount: Uint128::new(10000), - }, - ]), - prng_seed: Default::default(), - config: None, - query_auth: None, - } - .test_init( - Snip20::default(), - &mut chain, - Addr::unchecked("admin"), - "funding_token", - &[], - ) - .unwrap(); - - let gov = InstantiateMsg { - treasury: Addr::unchecked("treasury"), - query_auth: Contract { - address: auth.address.clone(), - code_hash: auth.code_hash.clone(), - }, - - assemblies: Some(AssemblyInit { - admin_members: vec![ - Addr::unchecked("alpha"), - Addr::unchecked("beta"), - Addr::unchecked("charlie"), - ], - admin_profile: Profile { - name: "admin".to_string(), - enabled: true, - assembly: None, - funding: Some(FundProfile { - deadline: 1000, - required: Uint128::new(2000), - privacy: false, - veto_deposit_loss: Default::default(), - }), - token: None, - cancel_deadline: 0, - }, - public_profile: Profile { - name: "public".to_string(), - enabled: false, - assembly: None, - funding: None, - token: None, - cancel_deadline: 0, - }, - }), - funding_token: Some(Contract { - address: snip20.address.clone(), - code_hash: snip20.code_hash.clone(), - }), - vote_token: None, - migrator: None, - } - .test_init( - Governance::default(), - &mut chain, - Addr::unchecked("admin"), - "governance", - &[], - ) - .unwrap(); - - governance::ExecuteMsg::AssemblyProposal { - assembly: 1, - title: "Title".to_string(), - metadata: "Text only proposal".to_string(), - msgs: None, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - snip20::ExecuteMsg::SetViewingKey { - key: "password".to_string(), - padding: None, - } - .test_exec(&snip20, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - query_auth::ExecuteMsg::SetViewingKey { - key: "password".to_string(), - padding: None, - } - .test_exec(&auth, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - snip20::ExecuteMsg::SetViewingKey { - key: "password".to_string(), - padding: None, - } - .test_exec(&snip20, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - snip20::ExecuteMsg::SetViewingKey { - key: "password".to_string(), - padding: None, - } - .test_exec(&snip20, &mut chain, Addr::unchecked("charlie"), &[]) - .unwrap(); - - Ok((chain, gov, snip20, auth)) -} - -#[test] -fn assembly_to_funding_transition() { - let (mut chain, gov, _snip20, _auth) = init_funding_governance_with_proposal().unwrap(); - - governance::ExecuteMsg::SetProfile { - id: 1, - profile: UpdateProfile { - name: None, - enabled: None, - disable_assembly: false, - assembly: Some(UpdateVoteProfile { - deadline: Some(1000), - threshold: Some(Count::LiteralCount { - count: Uint128::new(1), - }), - yes_threshold: Some(Count::LiteralCount { - count: Uint128::new(1), - }), - veto_threshold: Some(Count::LiteralCount { - count: Uint128::new(1), - }), - }), - disable_funding: false, - funding: None, - disable_token: false, - token: None, - cancel_deadline: None, - }, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - governance::ExecuteMsg::AssemblyProposal { - assembly: 1, - title: "Title".to_string(), - metadata: "Text only proposal".to_string(), - msgs: None, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 1, - vote: Vote { - yes: Uint128::new(1), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 1, - vote: Vote { - yes: Uint128::new(1), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - governance::ExecuteMsg::Update { - proposal: 1, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 1, 2).unwrap()[0].clone(); - - assert_eq!(prop.title, "Title".to_string()); - assert_eq!(prop.metadata, "Text only proposal".to_string()); - assert_eq!(prop.proposer, Addr::unchecked("alpha")); - assert_eq!(prop.assembly, 1); - - // Check that history works - match prop.status_history[0] { - Status::AssemblyVote { .. } => assert!(true), - _ => assert!(false), - } - - match prop.status { - Status::Funding { .. } => assert!(true), - _ => assert!(false), - }; -} -#[test] -fn fake_funding_token() { - let (mut chain, gov, snip20, _) = init_funding_governance_with_proposal().unwrap(); - - let other = snip20::InstantiateMsg { - name: "funding_token".to_string(), - admin: None, - symbol: "FND".to_string(), - decimals: 6, - initial_balances: Some(vec![ - snip20::InitialBalance { - address: "alpha".into(), - amount: Uint128::new(10000), - }, - snip20::InitialBalance { - address: "beta".into(), - amount: Uint128::new(10000), - }, - snip20::InitialBalance { - address: "charlie".into(), - amount: Uint128::new(10000), - }, - ]), - prng_seed: Default::default(), - config: None, - query_auth: None, - } - .test_init( - Snip20::default(), - &mut chain, - Addr::unchecked("admin"), - "other_snip20", - &[], - ) - .unwrap(); - - governance::ExecuteMsg::SetConfig { - query_auth: None, - treasury: None, - funding_token: Some(Contract { - address: other.address.clone(), - code_hash: other.code_hash, - }), - vote_token: None, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - assert!( - snip20::ExecuteMsg::Send { - recipient: gov.address.into(), - recipient_code_hash: None, - amount: Uint128::new(100), - msg: None, - memo: None, - padding: None - } - .test_exec( - // Sender is self - &snip20, - &mut chain, - Addr::unchecked("alpha"), - &[] - ) - .is_err() - ); -} -#[test] -fn funding_proposal_without_msg() { - let (mut chain, gov, snip20, _auth) = init_funding_governance_with_proposal().unwrap(); - - assert!( - snip20::ExecuteMsg::Send { - recipient: gov.address.into(), - recipient_code_hash: None, - amount: Uint128::new(100), - msg: None, - memo: None, - padding: None - } - .test_exec( - // Sender is self - &snip20, - &mut chain, - Addr::unchecked("alpha"), - &[] - ) - .is_err() - ); -} -#[test] -fn funding_proposal() { - let (mut chain, gov, snip20, _auth) = init_funding_governance_with_proposal().unwrap(); - - snip20::ExecuteMsg::Send { - recipient: gov.address.clone().into(), - recipient_code_hash: None, - amount: Uint128::new(100), - msg: Some(to_binary(&0).unwrap()), - memo: None, - padding: None, - } - .test_exec( - // Sender is self - &snip20, - &mut chain, - Addr::unchecked("alpha"), - &[], - ) - .unwrap(); - - snip20::ExecuteMsg::Send { - recipient: gov.address.clone().into(), - recipient_code_hash: None, - amount: Uint128::new(100), - msg: Some(to_binary(&0).unwrap()), - memo: None, - padding: None, - } - .test_exec( - // Sender is self - &snip20, - &mut chain, - Addr::unchecked("beta"), - &[], - ) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Funding { amount, .. } => assert_eq!(amount, Uint128::new(200)), - _ => assert!(false), - }; -} -#[test] -fn funding_proposal_after_deadline() { - let (mut chain, gov, snip20, _auth) = init_funding_governance_with_proposal().unwrap(); - - chain.update_block(|block| block.time = block.time.plus_seconds(10000)); - - assert!( - snip20::ExecuteMsg::Send { - recipient: gov.address.into(), - recipient_code_hash: None, - amount: Uint128::new(100), - msg: Some(to_binary(&0).unwrap()), - memo: None, - padding: None - } - .test_exec( - // Sender is self - &snip20, - &mut chain, - Addr::unchecked("alpha"), - &[] - ) - .is_err() - ) -} -#[test] -fn update_while_funding() { - let (mut chain, gov, _snip20, _auth) = init_funding_governance_with_proposal().unwrap(); - - assert!( - governance::ExecuteMsg::Update { - proposal: 0, - padding: None - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .is_err() - ); -} -#[test] -fn update_when_fully_funded() { - let (mut chain, gov, snip20, _auth) = init_funding_governance_with_proposal().unwrap(); - - snip20::ExecuteMsg::Send { - recipient: gov.address.clone().into(), - recipient_code_hash: None, - amount: Uint128::new(1000), - msg: Some(to_binary(&0).unwrap()), - memo: None, - padding: None, - } - .test_exec( - // Sender is self - &snip20, - &mut chain, - Addr::unchecked("alpha"), - &[], - ) - .unwrap(); - - snip20::ExecuteMsg::Send { - recipient: gov.address.clone().into(), - recipient_code_hash: None, - amount: Uint128::new(1000), - msg: Some(to_binary(&0).unwrap()), - memo: None, - padding: None, - } - .test_exec( - // Sender is self - &snip20, - &mut chain, - Addr::unchecked("beta"), - &[], - ) - .unwrap(); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Passed { .. } => assert!(true), - _ => assert!(false), - }; -} -#[test] -fn update_after_failed_funding() { - let (mut chain, gov, snip20, _auth) = init_funding_governance_with_proposal().unwrap(); - - snip20::ExecuteMsg::Send { - recipient: gov.address.clone().into(), - recipient_code_hash: None, - amount: Uint128::new(1000), - msg: Some(to_binary(&0).unwrap()), - memo: None, - padding: None, - } - .test_exec( - // Sender is self - &snip20, - &mut chain, - Addr::unchecked("alpha"), - &[], - ) - .unwrap(); - - chain.update_block(|block| block.time = block.time.plus_seconds(10000)); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Expired {} => assert!(true), - _ => assert!(false), - }; -} -#[test] -fn claim_when_not_finished() { - let (mut chain, gov, snip20, _auth) = init_funding_governance_with_proposal().unwrap(); - - snip20::ExecuteMsg::Send { - recipient: gov.address.into(), - recipient_code_hash: None, - amount: Uint128::new(1000), - msg: Some(to_binary(&0).unwrap()), - memo: None, - padding: None, - } - .test_exec( - // Sender is self - &snip20, - &mut chain, - Addr::unchecked("alpha"), - &[], - ) - .unwrap(); - - assert!( - governance::ExecuteMsg::ClaimFunding { id: 0 } - .test_exec( - // Sender is self - &snip20, - &mut chain, - Addr::unchecked("alpha"), - &[] - ) - .is_err() - ); -} -#[test] -fn claim_after_failing() { - let (mut chain, gov, snip20, _auth) = init_funding_governance_with_proposal().unwrap(); - - snip20::ExecuteMsg::Send { - recipient: gov.address.clone().into(), - recipient_code_hash: None, - amount: Uint128::new(1000), - msg: Some(to_binary(&0).unwrap()), - memo: None, - padding: None, - } - .test_exec( - // Sender is self - &snip20, - &mut chain, - Addr::unchecked("alpha"), - &[], - ) - .unwrap(); - - chain.update_block(|block| block.time = block.time.plus_seconds(10000)); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - governance::ExecuteMsg::ClaimFunding { id: 0 } - .test_exec( - // Sender is self - &gov, - &mut chain, - Addr::unchecked("alpha"), - &[], - ) - .unwrap(); - - let query: snip20::QueryAnswer = snip20::QueryMsg::Balance { - address: "alpha".into(), - key: "password".to_string(), - } - .test_query(&snip20, &chain) - .unwrap(); - - match query { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, Uint128::new(10000)) - } - _ => assert!(false), - }; -} -#[test] -fn claim_after_passing() { - let (mut chain, gov, snip20, _auth) = init_funding_governance_with_proposal().unwrap(); - - snip20::ExecuteMsg::Send { - recipient: gov.address.clone().into(), - recipient_code_hash: None, - amount: Uint128::new(2000), - msg: Some(to_binary(&0).unwrap()), - memo: None, - padding: None, - } - .test_exec( - // Sender is self - &snip20, - &mut chain, - Addr::unchecked("alpha"), - &[], - ) - .unwrap(); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - governance::ExecuteMsg::ClaimFunding { id: 0 } - .test_exec( - // Sender is self - &gov, - &mut chain, - Addr::unchecked("alpha"), - &[], - ) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - assert_eq!( - prop.funders.unwrap()[0], - (Addr::unchecked("alpha"), Uint128::new(2000)) - ); - - let query: snip20::QueryAnswer = snip20::QueryMsg::Balance { - address: "alpha".into(), - key: "password".to_string(), - } - .test_query(&snip20, &chain) - .unwrap(); - - match query { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, Uint128::new(10000)) - } - _ => assert!(false), - }; -} - -fn init_funding_governance_with_proposal_with_privacy() --> StdResult<(App, ContractInfo, ContractInfo, ContractInfo)> { - let (mut chain, auth) = init_chain(); - - // Register snip20 - let snip20 = init_funding_token( - &mut chain, - Some(vec![ - snip20::InitialBalance { - address: "alpha".into(), - amount: Uint128::new(10000), - }, - snip20::InitialBalance { - address: "beta".into(), - amount: Uint128::new(10000), - }, - snip20::InitialBalance { - address: "charlie".into(), - amount: Uint128::new(10000), - }, - ]), - Some(&auth), - )?; - - // Register governance - let gov = InstantiateMsg { - treasury: Addr::unchecked("treasury"), - query_auth: Contract { - address: auth.address.clone(), - code_hash: auth.code_hash.clone(), - }, - assemblies: Some(AssemblyInit { - admin_members: vec![ - Addr::unchecked("alpha"), - Addr::unchecked("beta"), - Addr::unchecked("charlie"), - ], - admin_profile: Profile { - name: "admin".to_string(), - enabled: true, - assembly: None, - funding: Some(FundProfile { - deadline: 1000, - required: Uint128::new(2000), - privacy: true, - veto_deposit_loss: Default::default(), - }), - token: None, - cancel_deadline: 0, - }, - public_profile: Profile { - name: "public".to_string(), - enabled: false, - assembly: None, - funding: None, - token: None, - cancel_deadline: 0, - }, - }), - funding_token: Some(Contract { - address: snip20.address.clone(), - code_hash: snip20.code_hash.clone(), - }), - vote_token: None, - migrator: None, - } - .test_init( - Governance::default(), - &mut chain, - Addr::unchecked("admin"), - "governance", - &[], - ) - .unwrap(); - - governance::ExecuteMsg::AssemblyProposal { - assembly: 1, - title: "Title".to_string(), - metadata: "Text only proposal".to_string(), - msgs: None, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - Ok((chain, gov, snip20, auth)) -} - -#[test] -fn funding_privacy() { - let (mut chain, gov, snip20, _auth) = - init_funding_governance_with_proposal_with_privacy().unwrap(); - - snip20::ExecuteMsg::Send { - recipient: gov.address.clone().into(), - recipient_code_hash: None, - amount: Uint128::new(2000), - msg: Some(to_binary(&0).unwrap()), - memo: None, - padding: None, - } - .test_exec( - // Sender is self - &snip20, - &mut chain, - Addr::unchecked("alpha"), - &[], - ) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - assert!(prop.funders.is_none()); -} diff --git a/archived-contracts/governance/src/tests/handle/proposal/mod.rs b/archived-contracts/governance/src/tests/handle/proposal/mod.rs deleted file mode 100644 index fbf1360..0000000 --- a/archived-contracts/governance/src/tests/handle/proposal/mod.rs +++ /dev/null @@ -1,293 +0,0 @@ -pub mod assembly_voting; -pub mod funding; -pub mod voting; - -use crate::tests::{ - admin_only_governance, - get_assemblies, - get_proposals, - gov_generic_proposal, - gov_msg_proposal, -}; -use shade_multi_test::multi::snip20::Snip20; -use shade_protocol::{ - c_std::{to_binary, Addr, ContractInfo, StdResult}, - contract_interfaces::{ - governance, - governance::proposal::{ProposalMsg, Status}, - }, - multi_test::App, - query_auth, - snip20::{self, InitialBalance}, - utils::{ExecuteCallback, InstantiateCallback, MultiTestable}, -}; - -pub fn init_funding_token( - chain: &mut App, - initial_balances: Option>, - query_auth: Option<&ContractInfo>, -) -> StdResult { - let snip20 = snip20::InstantiateMsg { - name: "funding_token".to_string(), - admin: None, - symbol: "FND".to_string(), - decimals: 6, - initial_balances: initial_balances.clone(), - prng_seed: Default::default(), - config: None, - query_auth: None, - } - .test_init( - Snip20::default(), - chain, - Addr::unchecked("admin"), - "funding_token", - &[], - ) - .unwrap(); - - if let Some(balances) = initial_balances { - if let Some(auth) = query_auth { - for balance in balances { - query_auth::ExecuteMsg::SetViewingKey { - key: "password".to_string(), - padding: None, - } - .test_exec(&auth, chain, Addr::unchecked(balance.address), &[]) - .unwrap(); - } - } - } - - Ok(snip20) -} - -#[test] -fn trigger_admin_command() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - governance::ExecuteMsg::AssemblyProposal { - assembly: 1, - title: "Title".to_string(), - metadata: "Proposal metadata".to_string(), - msgs: None, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) - .unwrap(); -} - -#[test] -fn unauthorized_trigger_admin_command() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - assert!( - governance::ExecuteMsg::AssemblyProposal { - assembly: 1, - title: "Title".to_string(), - metadata: "Proposal metadata".to_string(), - msgs: None, - padding: None - } - .test_exec(&gov, &mut chain, Addr::unchecked("random"), &[]) - .is_err() - ); -} - -#[test] -fn text_only_proposal() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - governance::ExecuteMsg::AssemblyProposal { - assembly: 1, - title: "Title".to_string(), - metadata: "Text only proposal".to_string(), - msgs: None, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - assert_eq!(prop.proposer, Addr::unchecked("admin")); - assert_eq!(prop.title, "Title".to_string()); - assert_eq!(prop.metadata, "Text only proposal".to_string()); - assert_eq!(prop.msgs, None); - assert_eq!(prop.assembly, 1); - assert_eq!(prop.assembly_vote_tally, None); - assert_eq!(prop.public_vote_tally, None); - match prop.status { - Status::Passed { .. } => assert!(true), - _ => assert!(false), - }; - assert_eq!(prop.status_history.len(), 0); - assert_eq!(prop.funders, None); - - governance::ExecuteMsg::Trigger { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - assert_eq!(prop.status, Status::Success); - assert_eq!(prop.status_history.len(), 1); -} - -#[test] -fn msg_proposal() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - gov_generic_proposal( - &mut chain, - &gov, - "admin", - governance::ExecuteMsg::SetAssembly { - id: 1, - name: Some("Random name".to_string()), - metadata: None, - members: None, - profile: None, - padding: None, - }, - ) - .unwrap(); - - let old_assembly = get_assemblies(&mut chain, &gov, 1, 2).unwrap()[0].clone(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Passed { .. } => assert!(true), - _ => assert!(false), - }; - - governance::ExecuteMsg::Trigger { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - assert!(prop.msgs.is_some()); - assert_eq!(prop.status, Status::Success); - - let new_assembly = get_assemblies(&mut chain, &gov, 1, 2).unwrap()[0].clone(); - - assert_ne!(new_assembly.name, old_assembly.name); -} - -#[test] -fn multi_msg_proposal() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - gov_msg_proposal(&mut chain, &gov, "admin", vec![ - ProposalMsg { - target: 0, - assembly_msg: 0, - msg: to_binary(&vec![ - serde_json::to_string(&governance::ExecuteMsg::SetAssembly { - id: 1, - name: Some("Random name".to_string()), - metadata: None, - members: None, - profile: None, - padding: None, - }) - .unwrap(), - ]) - .unwrap(), - send: vec![], - }, - ProposalMsg { - target: 0, - assembly_msg: 0, - msg: to_binary(&vec![ - serde_json::to_string(&governance::ExecuteMsg::SetAssembly { - id: 1, - name: None, - metadata: Some("Random name".to_string()), - members: None, - profile: None, - padding: None, - }) - .unwrap(), - ]) - .unwrap(), - send: vec![], - }, - ]) - .unwrap(); - - let old_assembly = get_assemblies(&mut chain, &gov, 1, 2).unwrap()[0].clone(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Passed { .. } => assert!(true), - _ => assert!(false), - }; - - governance::ExecuteMsg::Trigger { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - assert_eq!(prop.status, Status::Success); - - let new_assembly = get_assemblies(&mut chain, &gov, 1, 2).unwrap()[0].clone(); - - assert_ne!(new_assembly.name, old_assembly.name); - assert_ne!(new_assembly.metadata, old_assembly.metadata); -} - -#[test] -fn msg_proposal_invalid_msg() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - gov_generic_proposal( - &mut chain, - &gov, - "admin", - governance::ExecuteMsg::SetAssembly { - id: 3, - name: Some("Random name".to_string()), - metadata: None, - members: None, - profile: None, - padding: None, - }, - ) - .unwrap(); - - assert!( - governance::ExecuteMsg::Trigger { - proposal: 0, - padding: None - } - .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) - .is_err() - ); - - chain.update_block(|block| block.time = block.time.plus_seconds(100000)); - - governance::ExecuteMsg::Cancel { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - assert_eq!(prop.status, Status::Canceled); -} diff --git a/archived-contracts/governance/src/tests/handle/proposal/voting.rs b/archived-contracts/governance/src/tests/handle/proposal/voting.rs deleted file mode 100644 index ecc484b..0000000 --- a/archived-contracts/governance/src/tests/handle/proposal/voting.rs +++ /dev/null @@ -1,1022 +0,0 @@ -use crate::tests::{get_proposals, handle::proposal::init_funding_token, init_chain}; -use shade_multi_test::multi::{governance::Governance, snip20::Snip20}; -use shade_protocol::{ - c_std::{to_binary, Addr, ContractInfo, StdResult, Uint128}, - contract_interfaces::{ - governance, - governance::{ - profile::{Count, Profile, VoteProfile}, - proposal::Status, - vote::Vote, - InstantiateMsg, - }, - snip20, - }, - governance::AssemblyInit, - multi_test::{App, AppResponse}, - query_auth, - utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable}, - AnyResult, -}; - -pub fn init_voting_governance_with_proposal() -> StdResult<(App, ContractInfo, String, ContractInfo)> -{ - let (mut chain, auth) = init_chain(); - - // Register snip20 - let _snip20 = init_funding_token( - &mut chain, - Some(vec![ - snip20::InitialBalance { - address: "alpha".into(), - amount: Uint128::new(20_000_000), - }, - snip20::InitialBalance { - address: "beta".into(), - amount: Uint128::new(20_000_000), - }, - snip20::InitialBalance { - address: "charlie".into(), - amount: Uint128::new(20_000_000), - }, - ]), - Some(&auth), - ) - .unwrap(); - - // Fake init token so it has a valid codehash - let stkd_tkn = snip20::InstantiateMsg { - name: "token".to_string(), - admin: None, - symbol: "TKN".to_string(), - decimals: 6, - initial_balances: None, - prng_seed: to_binary("some seed").unwrap(), - config: None, - query_auth: None, - } - .test_init( - Snip20::default(), - &mut chain, - Addr::unchecked("admin"), - "staked_token", - &[], - ) - .unwrap(); - // Assume they got 20_000_000 total staked - - // Register governance - query_auth::ExecuteMsg::SetViewingKey { - key: "password".to_string(), - padding: None, - } - .test_exec(&auth, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - query_auth::ExecuteMsg::SetViewingKey { - key: "password".to_string(), - padding: None, - } - .test_exec(&auth, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - query_auth::ExecuteMsg::SetViewingKey { - key: "password".to_string(), - padding: None, - } - .test_exec(&auth, &mut chain, Addr::unchecked("charlie"), &[]) - .unwrap(); - - let gov = InstantiateMsg { - treasury: Addr::unchecked("treasury"), - query_auth: Contract { - address: auth.address.clone(), - code_hash: auth.code_hash.clone(), - }, - assemblies: Some(AssemblyInit { - admin_members: vec![ - Addr::unchecked("alpha"), - Addr::unchecked("beta"), - Addr::unchecked("charlie"), - ], - admin_profile: Profile { - name: "admin".to_string(), - enabled: true, - assembly: None, - funding: None, - token: Some(VoteProfile { - deadline: 10000, - threshold: Count::LiteralCount { - count: Uint128::new(10_000_000), - }, - yes_threshold: Count::LiteralCount { - count: Uint128::new(15_000_000), - }, - veto_threshold: Count::LiteralCount { - count: Uint128::new(15_000_000), - }, - }), - cancel_deadline: 0, - }, - public_profile: Profile { - name: "public".to_string(), - enabled: false, - assembly: None, - funding: None, - token: None, - cancel_deadline: 0, - }, - }), - funding_token: None, - vote_token: Some(Contract { - address: stkd_tkn.address.clone(), - code_hash: stkd_tkn.code_hash.clone(), - }), - migrator: None, - } - .test_init( - Governance::default(), - &mut chain, - Addr::unchecked("admin"), - "governance", - &[], - ) - .unwrap(); - - governance::ExecuteMsg::AssemblyProposal { - assembly: 1, - title: "Title".to_string(), - metadata: "Text only proposal".to_string(), - msgs: None, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - Ok((chain, gov, stkd_tkn.address.to_string(), auth)) -} - -pub fn vote( - gov: &ContractInfo, - chain: &mut App, - stkd: &str, - voter: &str, - vote: governance::vote::ReceiveBalanceMsg, - balance: Uint128, -) -> AnyResult { - governance::ExecuteMsg::ReceiveBalance { - sender: Addr::unchecked(voter), - msg: Some(to_binary(&vote).unwrap()), - balance, - memo: None, - } - .test_exec(gov, chain, Addr::unchecked(stkd), &[]) -} - -#[test] -fn voting() { - let (mut chain, gov, _, _auth) = init_voting_governance_with_proposal().unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - assert_eq!(prop.title, "Title".to_string()); - assert_eq!(prop.metadata, "Text only proposal".to_string()); - assert_eq!(prop.proposer, Addr::unchecked("alpha")); - assert_eq!(prop.assembly, 1); - - match prop.status { - Status::Voting { .. } => assert!(true), - _ => assert!(false), - }; -} - -#[test] -fn update_before_deadline() { - let (mut chain, _gov, _, auth) = init_voting_governance_with_proposal().unwrap(); - - assert!( - governance::ExecuteMsg::Update { - proposal: 0, - padding: None - } - .test_exec(&auth, &mut chain, Addr::unchecked("alpha"), &[]) - .is_err() - ); -} - -// TODO -/*#[test] -fn update_after_deadline() { - let (mut chain, gov, _, _auth) = init_voting_governance_with_proposal().unwrap(); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - // TODO: will crash until i get staking back up - assert!( - governance::ExecuteMsg::Update { - proposal: 0, - padding: None - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .is_ok() - ); -}*/ - -#[test] -fn invalid_vote() { - let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "alpha", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::new(25_000_000), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_err() - ); -} - -#[test] -fn vote_after_deadline() { - let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "alpha", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::new(10_000_000), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_err() - ); -} - -#[test] -fn vote_yes() { - let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "alpha", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::new(1_000_000), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Voting { .. } => assert!(true), - _ => assert!(false), - }; - - assert_eq!( - prop.public_vote_tally, - Some(Vote { - yes: Uint128::new(1_000_000), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }) - ) -} - -#[test] -fn vote_abstain() { - let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "alpha", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::new(1_000_000) - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Voting { .. } => assert!(true), - _ => assert!(false), - }; - - assert_eq!( - prop.public_vote_tally, - Some(Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::new(1_000_000) - }) - ) -} - -#[test] -fn vote_no() { - let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "alpha", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::zero(), - no: Uint128::new(1_000_000), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Voting { .. } => assert!(true), - _ => assert!(false), - }; - - assert_eq!( - prop.public_vote_tally, - Some(Vote { - yes: Uint128::zero(), - no: Uint128::new(1_000_000), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }) - ) -} - -#[test] -fn vote_veto() { - let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "alpha", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::new(1_000_000), - abstain: Uint128::zero() - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Voting { .. } => assert!(true), - _ => assert!(false), - }; - - assert_eq!( - prop.public_vote_tally, - Some(Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::new(1_000_000), - abstain: Uint128::zero() - }) - ) -} - -// TODO -/*#[test] -fn vote_passed() { - let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "alpha", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::new(10_000_000), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "beta", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::new(10_000_000), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - // Check that history works - match prop.status_history[0] { - Status::Voting { .. } => assert!(true), - _ => assert!(false), - } - - match prop.status { - Status::Passed { .. } => assert!(true), - _ => assert!(false), - }; -} - -#[test] -fn vote_abstained() { - let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "alpha", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::new(10_000_000) - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "beta", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::new(10_000_000) - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Rejected { .. } => assert!(true), - _ => assert!(false), - }; -} - -#[test] -fn vote_rejected() { - let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "alpha", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::zero(), - no: Uint128::new(10_000_000), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "beta", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::zero(), - no: Uint128::new(10_000_000), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Rejected { .. } => assert!(true), - _ => assert!(false), - }; -} - -#[test] -fn vote_vetoed() { - let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "alpha", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::new(10_000_000), - abstain: Uint128::zero() - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "beta", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::new(10_000_000), - abstain: Uint128::zero() - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Vetoed { .. } => assert!(true), - _ => assert!(false), - }; -} - -#[test] -fn vote_no_quorum() { - let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "alpha", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::new(10), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "beta", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::new(10), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Expired { .. } => assert!(true), - _ => assert!(false), - }; -} - -#[test] -fn vote_total() { - let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "alpha", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::new(10), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "beta", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::new(10), - no: Uint128::zero(), - no_with_veto: Uint128::new(10_000), - abstain: Uint128::zero() - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "charlie", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::zero(), - no: Uint128::new(23_000), - no_with_veto: Uint128::zero(), - abstain: Uint128::new(10_000), - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Voting { .. } => assert!(true), - _ => assert!(false), - }; - - assert_eq!( - prop.public_vote_tally, - Some(Vote { - yes: Uint128::new(20), - no: Uint128::new(23_000), - no_with_veto: Uint128::new(10_000), - abstain: Uint128::new(10_000) - }) - ) -}*/ - -#[test] -fn update_vote() { - let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "alpha", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::new(22_000), - abstain: Uint128::zero(), - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - assert_eq!( - prop.public_vote_tally, - Some(Vote { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::new(22_000), - abstain: Uint128::zero() - }) - ); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "alpha", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::new(10_000), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - assert_eq!( - prop.public_vote_tally, - Some(Vote { - yes: Uint128::new(10_000), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero() - }) - ); -} - -// TODO -/*#[test] -fn vote_count() { - let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "alpha", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::new(10_000_000), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "beta", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::new(10_000_000), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Passed { .. } => assert!(true), - _ => assert!(false), - }; -} - -#[test] -fn vote_count_percentage() { - let (mut chain, gov, stkd_tkn, _auth) = init_voting_governance_with_proposal().unwrap(); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "alpha", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::new(10_000_000), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "beta", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::new(10_000_000), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - - chain.update_block(|block| block.time = block.time.plus_seconds(30000)); - - governance::ExecuteMsg::Update { - proposal: 0, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("beta"), &[]) - .unwrap(); - - let prop = get_proposals(&mut chain, &gov, 0, 2).unwrap()[0].clone(); - - match prop.status { - Status::Passed { .. } => assert!(true), - _ => assert!(false), - }; -}*/ diff --git a/archived-contracts/governance/src/tests/handle/runstate.rs b/archived-contracts/governance/src/tests/handle/runstate.rs deleted file mode 100644 index 8efd9aa..0000000 --- a/archived-contracts/governance/src/tests/handle/runstate.rs +++ /dev/null @@ -1,255 +0,0 @@ -use crate::tests::{handle::init_funding_token, init_chain}; -use rstest::*; -use shade_multi_test::multi::governance::Governance; -use shade_protocol::{ - c_std::{to_binary, Addr, ContractInfo, StdResult, Uint128}, - governance, - governance::{ - profile::{Count, FundProfile, Profile, VoteProfile}, - vote::Vote, - AssemblyInit, - InstantiateMsg, - RuntimeState, - }, - multi_test::{App, AppResponse, Executor}, - snip20, - utils::{asset::Contract, ExecuteCallback, MultiTestable}, - AnyResult, -}; - -pub fn init_gov() -> StdResult<(App, ContractInfo, ContractInfo, u64)> { - let (mut chain, auth) = init_chain(); - - // Register snip20 - let snip20 = init_funding_token( - &mut chain, - Some(vec![ - snip20::InitialBalance { - address: "alpha".into(), - amount: Uint128::new(10000), - }, - snip20::InitialBalance { - address: "beta".into(), - amount: Uint128::new(10000), - }, - snip20::InitialBalance { - address: "charlie".into(), - amount: Uint128::new(10000), - }, - ]), - Some(&auth), - )?; - - // Register governance - let stored_code = chain.store_code(Governance::default().contract()); - let gov = chain - .instantiate_contract( - stored_code.clone(), - Addr::unchecked("admin"), - &InstantiateMsg { - treasury: Addr::unchecked("treasury"), - query_auth: Contract { - address: auth.address.clone(), - code_hash: auth.code_hash.clone(), - }, - assemblies: Some(AssemblyInit { - admin_members: vec![ - Addr::unchecked("alpha"), - Addr::unchecked("beta"), - Addr::unchecked("charlie"), - ], - admin_profile: Profile { - name: "admin".to_string(), - enabled: true, - assembly: Some(VoteProfile { - deadline: 1000, - threshold: Count::LiteralCount { - count: Uint128::new(1), - }, - yes_threshold: Count::LiteralCount { - count: Uint128::new(1), - }, - veto_threshold: Count::LiteralCount { - count: Uint128::new(1), - }, - }), - funding: Some(FundProfile { - deadline: 1000, - required: Uint128::new(1000), - privacy: true, - veto_deposit_loss: Default::default(), - }), - token: None, - cancel_deadline: 0, - }, - public_profile: Profile { - name: "public".to_string(), - enabled: false, - assembly: None, - funding: None, - token: None, - cancel_deadline: 0, - }, - }), - funding_token: Some(Contract { - address: snip20.address.clone(), - code_hash: snip20.code_hash.clone(), - }), - vote_token: None, - migrator: None, - }, - &vec![], - "governance", - None, - ) - .unwrap(); - - Ok((chain, gov, snip20, stored_code.code_id)) -} - -fn update_proposal(chain: &mut App, gov: &ContractInfo, proposal: u32) -> AnyResult { - governance::ExecuteMsg::Update { - proposal, - padding: None, - } - .test_exec(&gov, chain, Addr::unchecked("beta"), &[]) -} - -fn create_proposal(chain: &mut App, gov: &ContractInfo, assembly: u16) -> AnyResult { - governance::ExecuteMsg::AssemblyProposal { - assembly, - title: "Title".to_string(), - metadata: "Text only proposal".to_string(), - msgs: None, - padding: None, - } - .test_exec(&gov, chain, Addr::unchecked("alpha"), &[]) -} - -fn assembly_vote(chain: &mut App, gov: &ContractInfo, proposal: u32) -> AnyResult { - governance::ExecuteMsg::AssemblyVote { - proposal, - vote: Vote { - yes: Uint128::new(1), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, chain, Addr::unchecked("alpha"), &[]) -} - -fn fund_proposal( - chain: &mut App, - gov: &ContractInfo, - snip20: &ContractInfo, - proposal: u32, -) -> AnyResult { - snip20::ExecuteMsg::Send { - recipient: gov.address.clone().into(), - recipient_code_hash: None, - amount: Uint128::new(100), - msg: Some(to_binary(&proposal).unwrap()), - memo: None, - padding: None, - } - .test_exec( - // Sender is self - &snip20, - chain, - Addr::unchecked("alpha"), - &[], - ) -} - -// Use RS test to run all the expected functions under all of the states -#[rstest] -#[case(RuntimeState::Normal, 1, true)] -#[case(RuntimeState::SpecificAssemblies { assemblies: vec![1] }, 2, false)] -#[case(RuntimeState::SpecificAssemblies { assemblies: vec![1] }, 1, true)] -#[case(RuntimeState::Migrated, 1, false)] -fn runstate_states(#[case] state: RuntimeState, #[case] assembly: u16, #[case] expect: bool) { - let (mut chain, gov, snip20, gov_id) = init_gov().unwrap(); - - governance::ExecuteMsg::AddAssembly { - name: "Other assembly".to_string(), - metadata: "some data".to_string(), - members: vec![ - Addr::unchecked("alpha"), - Addr::unchecked("beta"), - Addr::unchecked("charlie"), - ], - profile: 1, - padding: None, - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - - // Proposal for claiming funding - create_proposal(&mut chain, &gov, assembly).unwrap(); - assembly_vote(&mut chain, &gov, 0).unwrap(); - chain.update_block(|block| block.time = block.time.plus_seconds(10000)); - update_proposal(&mut chain, &gov, 0).unwrap(); - fund_proposal(&mut chain, &gov, &snip20, 0).unwrap(); - chain.update_block(|block| block.time = block.time.plus_seconds(10000)); - update_proposal(&mut chain, &gov, 0).unwrap(); - - // Proposal for updating state - create_proposal(&mut chain, &gov, assembly).unwrap(); - assembly_vote(&mut chain, &gov, 1).unwrap(); - chain.update_block(|block| block.time = block.time.plus_seconds(10000)); - - // Proposal for voting - create_proposal(&mut chain, &gov, assembly).unwrap(); - - match state { - RuntimeState::Normal => {} - RuntimeState::SpecificAssemblies { assemblies } => { - governance::ExecuteMsg::SetRuntimeState { - state: RuntimeState::SpecificAssemblies { assemblies }, - padding: None, - } - .test_exec(&gov, &mut chain, gov.address.clone(), &[]) - .unwrap(); - } - RuntimeState::Migrated => { - governance::ExecuteMsg::Migrate { - id: gov_id, - label: "migrated".to_string(), - code_hash: gov.code_hash.clone(), - } - .test_exec( - // Sender is self - &gov, - &mut chain, - gov.address.clone(), - &[], - ) - .unwrap(); - } - } - - // try to create proposal - assert_eq!(expect, create_proposal(&mut chain, &gov, assembly).is_ok()); - - // Claim funding TODO: should this get halted? - assert_eq!( - true, - governance::ExecuteMsg::ClaimFunding { id: 0 } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .is_ok() - ); - - assert_eq!(expect, update_proposal(&mut chain, &gov, 1).is_ok()); - - assert_eq!(expect, assembly_vote(&mut chain, &gov, 2).is_ok()); - - // TODO: not working but progress to user voting -} diff --git a/archived-contracts/governance/src/tests/mod.rs b/archived-contracts/governance/src/tests/mod.rs deleted file mode 100644 index af5b492..0000000 --- a/archived-contracts/governance/src/tests/mod.rs +++ /dev/null @@ -1,214 +0,0 @@ -pub mod handle; -pub mod query; - -use shade_multi_test::multi::{ - admin::init_admin_auth, - governance::Governance, - query_auth::QueryAuth, -}; -use shade_protocol::{ - c_std::{to_binary, Addr, Binary, ContractInfo, StdError, StdResult}, - contract_interfaces::{ - governance, - governance::{ - assembly::{Assembly, AssemblyMsg}, - contract::AllowedContract, - profile::Profile, - proposal::{Proposal, ProposalMsg}, - Config, - }, - }, - governance::AssemblyInit, - multi_test::App, - query_auth, - utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -pub fn init_chain() -> (App, ContractInfo) { - let mut chain = App::default(); - - let admin = init_admin_auth(&mut chain, &Addr::unchecked("admin")); - - let auth = query_auth::InstantiateMsg { - admin_auth: Contract { - address: admin.address.clone(), - code_hash: admin.code_hash.clone(), - }, - prng_seed: Binary::from("random".as_bytes()), - } - .test_init( - QueryAuth::default(), - &mut chain, - Addr::unchecked("admin"), - "query_auth", - &[], - ) - .unwrap(); - - (chain, auth) -} - -pub fn admin_only_governance() -> StdResult<(App, ContractInfo)> { - let (mut chain, auth) = init_chain(); - - let gov = governance::InstantiateMsg { - treasury: Addr::unchecked("treasury".to_string()), - query_auth: Contract { - address: auth.address, - code_hash: auth.code_hash, - }, - assemblies: Some(AssemblyInit { - admin_members: vec![Addr::unchecked("admin".to_string())], - admin_profile: Profile { - name: "admin".to_string(), - enabled: true, - assembly: None, - funding: None, - token: None, - cancel_deadline: 0, - }, - public_profile: Profile { - name: "public".to_string(), - enabled: false, - assembly: None, - funding: None, - token: None, - cancel_deadline: 0, - }, - }), - funding_token: None, - vote_token: None, - migrator: None, - } - .test_init( - Governance::default(), - &mut chain, - Addr::unchecked("admin"), - "gov", - &[], - ) - .unwrap(); - - Ok((chain, gov)) -} - -pub fn gov_generic_proposal( - chain: &mut App, - gov: &ContractInfo, - sender: &str, - msg: governance::ExecuteMsg, -) -> StdResult<()> { - gov_msg_proposal(chain, gov, sender, vec![ProposalMsg { - target: 0, - assembly_msg: 0, - msg: to_binary(&vec![serde_json::to_string(&msg).unwrap()]).unwrap(), - send: vec![], - }]) -} - -pub fn gov_msg_proposal( - chain: &mut App, - gov: &ContractInfo, - sender: &str, - msgs: Vec, -) -> StdResult<()> { - governance::ExecuteMsg::AssemblyProposal { - assembly: 1, - title: "Title".to_string(), - metadata: "Proposal metadata".to_string(), - msgs: Some(msgs), - padding: None, - } - .test_exec(gov, chain, Addr::unchecked(sender), &[]) - .unwrap(); - - Ok(()) -} - -pub fn get_assembly_msgs( - chain: &mut App, - gov: &ContractInfo, - start: u16, - end: u16, -) -> StdResult> { - let query: governance::QueryAnswer = - governance::QueryMsg::AssemblyMsgs { start, end }.test_query(&gov, &chain)?; - - let msgs = match query { - governance::QueryAnswer::AssemblyMsgs { msgs } => msgs, - _ => return Err(StdError::generic_err("Returned wrong enum")), - }; - - Ok(msgs) -} - -pub fn get_contract( - chain: &mut App, - gov: &ContractInfo, - start: u16, - end: u16, -) -> StdResult> { - let query: governance::QueryAnswer = - governance::QueryMsg::Contracts { start, end }.test_query(&gov, &chain)?; - - match query { - governance::QueryAnswer::Contracts { contracts } => Ok(contracts), - _ => return Err(StdError::generic_err("Returned wrong enum")), - } -} - -pub fn get_profiles( - chain: &mut App, - gov: &ContractInfo, - start: u16, - end: u16, -) -> StdResult> { - let query: governance::QueryAnswer = - governance::QueryMsg::Profiles { start, end }.test_query(&gov, &chain)?; - - match query { - governance::QueryAnswer::Profiles { profiles } => Ok(profiles), - _ => return Err(StdError::generic_err("Returned wrong enum")), - } -} - -pub fn get_assemblies( - chain: &mut App, - gov: &ContractInfo, - start: u16, - end: u16, -) -> StdResult> { - let query: governance::QueryAnswer = - governance::QueryMsg::Assemblies { start, end }.test_query(&gov, &chain)?; - - match query { - governance::QueryAnswer::Assemblies { assemblies } => Ok(assemblies), - _ => return Err(StdError::generic_err("Returned wrong enum")), - } -} - -pub fn get_proposals( - chain: &mut App, - gov: &ContractInfo, - start: u32, - end: u32, -) -> StdResult> { - let query: governance::QueryAnswer = - governance::QueryMsg::Proposals { start, end }.test_query(&gov, &chain)?; - - match query { - governance::QueryAnswer::Proposals { props } => Ok(props), - _ => return Err(StdError::generic_err("Returned wrong enum")), - } -} - -pub fn get_config(chain: &mut App, gov: &ContractInfo) -> StdResult { - let query: governance::QueryAnswer = governance::QueryMsg::Config {} - .test_query(&gov, &chain) - .unwrap(); - - match query { - governance::QueryAnswer::Config { config } => Ok(config), - _ => return Err(StdError::generic_err("Returned wrong enum")), - } -} diff --git a/archived-contracts/governance/src/tests/query/mod.rs b/archived-contracts/governance/src/tests/query/mod.rs deleted file mode 100644 index ec85dc4..0000000 --- a/archived-contracts/governance/src/tests/query/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod public; -pub mod user; diff --git a/archived-contracts/governance/src/tests/query/public.rs b/archived-contracts/governance/src/tests/query/public.rs deleted file mode 100644 index 8b8e9d3..0000000 --- a/archived-contracts/governance/src/tests/query/public.rs +++ /dev/null @@ -1,180 +0,0 @@ -use crate::tests::{ - admin_only_governance, - get_assemblies, - get_assembly_msgs, - get_config, - get_contract, - get_profiles, -}; -use shade_protocol::{contract_interfaces::governance, utils::Query}; - -#[test] -fn query_total_assembly_msg() { - let (chain, gov) = admin_only_governance().unwrap(); - - let query: governance::QueryAnswer = governance::QueryMsg::TotalAssemblyMsgs {} - .test_query(&gov, &chain) - .unwrap(); - - let total = match query { - governance::QueryAnswer::Total { total } => total, - _ => 0, - }; - - assert_eq!(total, 1); -} - -#[test] -fn query_assembly_msg() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - let assemblies = get_assembly_msgs(&mut chain, &gov, 0, 0).unwrap(); - - assert_eq!(assemblies.len(), 1); -} - -#[test] -fn query_assembly_msg_large_end() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - let assemblies = get_assembly_msgs(&mut chain, &gov, 0, 10).unwrap(); - - assert_eq!(assemblies.len(), 1); -} - -#[test] -fn query_assembly_msg_wrong_index() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - assert!(get_assembly_msgs(&mut chain, &gov, 5, 10).is_err()); -} - -#[test] -fn query_total_contracts() { - let (chain, gov) = admin_only_governance().unwrap(); - - let query: governance::QueryAnswer = governance::QueryMsg::TotalContracts {} - .test_query(&gov, &chain) - .unwrap(); - - let total = match query { - governance::QueryAnswer::Total { total } => total, - _ => 0, - }; - - assert_eq!(total, 1); -} - -#[test] -fn query_contracts() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - let contracts = get_contract(&mut chain, &gov, 0, 0).unwrap(); - - assert_eq!(contracts.len(), 1); -} - -#[test] -fn query_contracts_large_end() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - let contracts = get_contract(&mut chain, &gov, 0, 10).unwrap(); - - assert_eq!(contracts.len(), 1); -} - -#[test] -fn query_contracts_wrong_index() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - assert!(get_contract(&mut chain, &gov, 5, 10).is_err()); -} - -#[test] -fn query_total_profiles() { - let (chain, gov) = admin_only_governance().unwrap(); - - let query: governance::QueryAnswer = governance::QueryMsg::TotalProfiles {} - .test_query(&gov, &chain) - .unwrap(); - - let total = match query { - governance::QueryAnswer::Total { total } => total, - _ => 0, - }; - - assert_eq!(total, 2); -} - -#[test] -fn query_profiles() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - let profiles = get_profiles(&mut chain, &gov, 0, 0).unwrap(); - - assert_eq!(profiles.len(), 1); -} - -#[test] -fn query_profiles_large_end() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - let profiles = get_profiles(&mut chain, &gov, 0, 10).unwrap(); - - assert_eq!(profiles.len(), 2); -} - -#[test] -fn query_profiles_wrong_index() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - assert!(get_profiles(&mut chain, &gov, 5, 10).is_err()); -} - -#[test] -fn query_total_assemblies() { - let (chain, gov) = admin_only_governance().unwrap(); - - let query: governance::QueryAnswer = governance::QueryMsg::TotalAssemblies {} - .test_query(&gov, &chain) - .unwrap(); - - let total = match query { - governance::QueryAnswer::Total { total } => total, - _ => 0, - }; - - assert_eq!(total, 2); -} - -#[test] -fn query_assemblies() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - let assemblies = get_assemblies(&mut chain, &gov, 0, 0).unwrap(); - - assert_eq!(assemblies.len(), 1); -} - -#[test] -fn query_assemblies_large_end() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - let assemblies = get_assemblies(&mut chain, &gov, 0, 10).unwrap(); - - assert_eq!(assemblies.len(), 2); -} - -#[test] -fn query_assemblies_wrong_index() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - assert!(get_assemblies(&mut chain, &gov, 5, 10).is_err()); -} - -#[test] -fn query_config() { - let (mut chain, gov) = admin_only_governance().unwrap(); - - get_config(&mut chain, &gov).unwrap(); -} diff --git a/archived-contracts/governance/src/tests/query/user.rs b/archived-contracts/governance/src/tests/query/user.rs deleted file mode 100644 index 3cfc65f..0000000 --- a/archived-contracts/governance/src/tests/query/user.rs +++ /dev/null @@ -1,268 +0,0 @@ -use crate::tests::{ - handle::proposal::{ - assembly_voting::init_assembly_governance_with_proposal, - funding::init_funding_governance_with_proposal, - voting::{init_voting_governance_with_proposal, vote}, - }, - init_chain, -}; -use shade_multi_test::multi::governance::Governance; -use shade_protocol::{ - c_std::{to_binary, Addr, StdResult, Uint128}, - contract_interfaces::{ - governance::{self, profile::Profile, vote::Vote, AuthQuery, Pagination, QueryAnswer}, - query_auth, - snip20, - }, - governance::AssemblyInit, - utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -#[test] -fn proposals() { - let (mut chain, auth) = init_chain(); - - query_auth::ExecuteMsg::SetViewingKey { - key: "password".to_string(), - padding: None, - } - .test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) - .unwrap(); - - let msg = governance::InstantiateMsg { - treasury: Addr::unchecked("treasury".to_string()), - query_auth: Contract { - address: auth.address, - code_hash: auth.code_hash, - }, - assemblies: Some(AssemblyInit { - admin_members: vec![Addr::unchecked("admin".to_string())], - admin_profile: Profile { - name: "admin".to_string(), - enabled: true, - assembly: None, - funding: None, - token: None, - cancel_deadline: 0, - }, - public_profile: Profile { - name: "public".to_string(), - enabled: false, - assembly: None, - funding: None, - token: None, - cancel_deadline: 0, - }, - }), - funding_token: None, - vote_token: None, - migrator: None, - }; - - let gov = msg - .test_init( - Governance::default(), - &mut chain, - Addr::unchecked("admin"), - "governance", - &[], - ) - .unwrap(); - - governance::ExecuteMsg::AssemblyProposal { - assembly: 1, - title: "Title".to_string(), - metadata: "Text".to_string(), - msgs: None, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) - .unwrap(); - - let query: governance::QueryAnswer = governance::QueryMsg::WithVK { - user: Addr::unchecked("admin"), - key: "password".to_string(), - query: AuthQuery::Proposals { - pagination: Pagination { - page: 0, - amount: 10, - }, - }, - } - .test_query(&gov, &chain) - .unwrap(); - - match query { - QueryAnswer::UserProposals { props, total } => { - assert_eq!(total, 0); - assert_eq!(props.len(), 1); - } - _ => assert!(false), - } - - governance::ExecuteMsg::AssemblyProposal { - assembly: 1, - title: "Title".to_string(), - metadata: "Text".to_string(), - msgs: None, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("admin"), &[]) - .unwrap(); - - let query: governance::QueryAnswer = governance::QueryMsg::WithVK { - user: Addr::unchecked("admin"), - key: "password".to_string(), - query: AuthQuery::Proposals { - pagination: Pagination { - page: 0, - amount: 10, - }, - }, - } - .test_query(&gov, &chain) - .unwrap(); - - match query { - QueryAnswer::UserProposals { props, total } => { - assert_eq!(total, 1); - assert_eq!(props.len(), 2); - } - _ => assert!(false), - } - - let query: StdResult = governance::QueryMsg::WithVK { - user: Addr::unchecked("admin"), - key: "not_password".to_string(), - query: AuthQuery::Proposals { - pagination: Pagination { - page: 0, - amount: 10, - }, - }, - } - .test_query(&gov, &chain); - assert!(query.is_err()) -} - -#[test] -fn assembly_votes() { - let (mut chain, gov) = init_assembly_governance_with_proposal().unwrap(); - - governance::ExecuteMsg::AssemblyVote { - proposal: 0, - vote: Vote { - yes: Uint128::new(1), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - }, - padding: None, - } - .test_exec(&gov, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - let query: governance::QueryAnswer = governance::QueryMsg::WithVK { - user: Addr::unchecked("alpha"), - key: "password".to_string(), - query: AuthQuery::AssemblyVotes { - pagination: Pagination { - page: 0, - amount: 10, - }, - }, - } - .test_query(&gov, &chain) - .unwrap(); - - match query { - QueryAnswer::UserAssemblyVotes { votes, total } => { - assert_eq!(total, 0); - assert_eq!(votes.len(), 1); - } - _ => assert!(false), - } -} - -#[test] -fn funding() { - let (mut chain, gov, snip20, _) = init_funding_governance_with_proposal().unwrap(); - - snip20::ExecuteMsg::Send { - recipient: gov.address.clone().into(), - recipient_code_hash: None, - amount: Uint128::new(100), - msg: Some(to_binary(&0).unwrap()), - memo: None, - padding: None, - } - .test_exec(&snip20, &mut chain, Addr::unchecked("alpha"), &[]) - .unwrap(); - - let query: governance::QueryAnswer = governance::QueryMsg::WithVK { - user: Addr::unchecked("alpha"), - key: "password".to_string(), - query: AuthQuery::Funding { - pagination: Pagination { - page: 0, - amount: 10, - }, - }, - } - .test_query(&gov, &chain) - .unwrap(); - - match query { - QueryAnswer::UserFunding { funds, total } => { - assert_eq!(total, 0); - assert_eq!(funds.len(), 1); - } - _ => assert!(false), - } -} - -#[test] -fn votes() { - let (mut chain, gov, stkd_tkn, _) = init_voting_governance_with_proposal().unwrap(); - - assert!( - vote( - &gov, - &mut chain, - stkd_tkn.as_str(), - "alpha", - governance::vote::ReceiveBalanceMsg { - vote: Vote { - yes: Uint128::new(1_000_000), - no: Default::default(), - no_with_veto: Default::default(), - abstain: Default::default(), - }, - proposal: 0 - }, - Uint128::new(20_000_000) - ) - .is_ok() - ); - - let query: governance::QueryAnswer = governance::QueryMsg::WithVK { - user: Addr::unchecked("alpha"), - key: "password".to_string(), - query: AuthQuery::Votes { - pagination: Pagination { - page: 0, - amount: 10, - }, - }, - } - .test_query(&gov, &chain) - .unwrap(); - - match query { - QueryAnswer::UserVotes { votes, total } => { - assert_eq!(total, 0); - assert_eq!(votes.len(), 1); - } - _ => assert!(false), - } -} diff --git a/archived-contracts/mint/.cargo/config b/archived-contracts/mint/.cargo/config deleted file mode 100644 index 882fe08..0000000 --- a/archived-contracts/mint/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[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/archived-contracts/mint/.circleci/config.yml b/archived-contracts/mint/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/archived-contracts/mint/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/mint/Cargo.toml b/archived-contracts/mint/Cargo.toml deleted file mode 100644 index c19b32a..0000000 --- a/archived-contracts/mint/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "mint" -version = "0.1.0" -authors = [ - "Guy Garcia ", - "Jackson Swenson ", -] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] -debug-print = ["shade-protocol/debug-print"] - -[dependencies] -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ "mint", "oracles", "band", "dex", "snip20", "ensemble", "chrono" ] } -schemars = "0.7" - -[dev-dependencies] -contract_harness = { version = "0.1.0", path = "../../packages/contract_harness", features = ["mint", "snip20", "mock_band", "oracle"] } diff --git a/archived-contracts/mint/Makefile b/archived-contracts/mint/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/archived-contracts/mint/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/archived-contracts/mint/README.md b/archived-contracts/mint/README.md deleted file mode 100644 index e7d2c66..0000000 --- a/archived-contracts/mint/README.md +++ /dev/null @@ -1,215 +0,0 @@ - -# Mint Contract -* [Introduction](#Introduction) -* [Sections](#Sections) - * [Init](#Init) - * [Admin](#Admin) - * Messages - * [UpdateConfig](#UpdateConfig) - * [UpdateMintLimit](#UpdateMintLimit) - * [RegisterAsset](#RegisterAsset) - * [RemoveAsset](#RemoveAsset) - * [User](#User) - * Messages - * [Receive](#Receive) - * Queries - * [GetNativeAsset](#GetNativeAsset) - * [GetConfig](#GetConfig) - * [GetMintLimit](#GetMintLimit) - * [GetSupportedAssets](#GetSupportedAssets) - * [GetAsset](#GetAsset) -# Introduction -Contract responsible to mint a paired snip20 asset - -# Sections - -## Init -##### Request -|Name |Type |Description | optional | -|-----------------|------------|-------------------------------------------------------------------------------|----------| -|admin | string | New contract owner; SHOULD be a valid bech32 address | yes | -|native_asset | Contract | Asset to mint | no | -|oracle | Contract | Oracle contract | no | -|peg | String | Symbol to peg to when querying oracle (defaults to native_asset symbol) | yes | -|treasury | Contract | Treasury contract | yes | -|secondary_burn | Addrr | Where non-burnable assets will go | yes | -|start_epoch | String | The starting epoch | yes | -|epoch_frequency | String | The frequency in which the mint limit resets, if 0 then no limit is enforced | yes | -|epoch_mint_limit | String | The limit of uTokens to mint per epoch | yes | -## Admin - -### Messages -#### UpdateConfig -Updates the given values -##### Request -|Name |Type |Description | optional | -|---------------|------------|-------------------------------------------------------|----------| -|admin | string | New contract admin; SHOULD be a valid bech32 address | yes | -|oracle | Contract | Oracle contract | yes | -|treasury | Contract | Treasury contract | yes | -|secondary_burn | Addrr | Where non-burnable assets will go | yes | -##### Response -```json -{ - "update_config": { - "status": "success" - } -} -``` - -#### UpdateMintLimit -Updates the mint limit and epoch time -##### Request -|Name |Type |Description | optional | -|-----------------|----------|--------------------------------------------------------------------------------|----------| -|start_epoch | String | The starting epoch | yes | -|epoch_frequency | String | The frequency in which the mint limit resets, if 0 then no limit is enforced | yes | -|epoch_mint_limit | String | The limit of uTokens to mint per epoch | yes | -##### Response -```json -{ - "update_mint_limit": { - "status": "success" - } -} -``` - -#### RegisterAsset -Registers a supported asset. The asset must be SNIP-20 compliant since [RegisterReceive](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md#RegisterReceive) is called. - -##### Request -|Name |Type |Description | optional | -|------------|--------|-------------------------------------|----------| -|contract | Contract | Type explained [here](#Contract) | no | -##### Response -```json -{ - "register_asset": { - "status": "success" - } -} -``` - -#### RemoveAsset -Remove a registered asset. -##### Request -|Name |Type |Description | optional | -|------------|--------|--------------------------------|----------| -|address | String | The asset to remove's address | no | -##### Response -```json -{ - "remove_asset": { - "status": "success" - } -} -``` - -##User - -### Messages - -#### Receive -To mint the user must use a supported asset's send function and send the amount over to the contract's address. The contract will take care of the rest. - -In the msg field of a snip20 send command you must send a base64 encoded json like this one -```json -{"minimum_expected_amount": "Uint128" } -``` - -### Queries - -#### GetNativeAsset -Gets the contract's minted asset -#### Response -```json -{ - "native_asset": { - "asset": "Snip20Asset Object", - "peg": "Pegged symbol" - } -} -``` - -#### GetConfig -Gets the contract's configuration variables -##### Response -```json -{ - "config": { - "config": { - "admin": "Owner address", - "oracle": { - "address": "Asset contract address", - "code_hash": "Asset callback code hash" - }, - "treasury": { - "address": "Asset contract address", - "code_hash": "Asset callback code hash" - }, - "secondary_burn": "Optional burn address", - "activated": "Boolean of contract's actviation status" - } - } -} -``` - -#### GetMintLimit -Gets the contract's configuration variables -##### Response -```json -{ - "limit": { - "mint_limit": { - "frequency": "Frequency per epoch reset", - "mint_capacity": "Mint capacity per epoch", - "total_minted": "Total minted in current epoch", - "next_epoch": "Timestamp for the next epoch" - } - } -} -``` - -#### GetSupportedAssets -Get all the contract's supported assets. -##### Response -```json -{ - "supported_assets": { - "assets": ["asset address"] - } -} -``` - -#### GetAsset -Get specific information on a supported asset. -##### Request -|Name |Type |Description | optional | -|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| -|contract | string | Snip20 contract address; SHOULD be a valid bech32 address, but contracts may use a different naming scheme as well | no | -##### Response -```json -{ - "asset": { - "asset": { - "Snip20Asset": { - "contract": "Asset contract", - "token_info": "Token info as per Snip20", - "token_config": "Optional information about the config if the Snip20 supports it" - }, - "burned": "Total burned on this contract" - } - } -} -``` - -## Contract -Type used in many of the admin commands -```json -{ - "config": { - "address": "Asset contract address", - "code_hash": "Asset callback code hash" - } -} -``` \ No newline at end of file diff --git a/archived-contracts/mint/src/contract.rs b/archived-contracts/mint/src/contract.rs deleted file mode 100644 index adb5cb7..0000000 --- a/archived-contracts/mint/src/contract.rs +++ /dev/null @@ -1,110 +0,0 @@ -use shade_protocol::{ - c_std::{ - shd_entry_point, - to_binary, - Api, - Binary, - Deps, - DepsMut, - Env, - MessageInfo, - Querier, - Response, - StdResult, - Storage, - }, - snip20::helpers::{token_config, token_info}, -}; - -use shade_protocol::contract_interfaces::{ - mint::mint::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}, - snip20::helpers::Snip20Asset, -}; - -use crate::{ - handle, - query, - state::{asset_list_w, asset_peg_w, config_w, limit_w, native_asset_w}, -}; - -#[shd_entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - let state = Config { - admin: match msg.admin { - None => info.sender.clone(), - Some(admin) => admin, - }, - oracle: msg.oracle, - treasury: msg.treasury, - secondary_burn: msg.secondary_burn, - limit: msg.limit, - activated: true, - }; - - config_w(deps.storage).save(&state)?; - - let token_info = token_info(&deps.querier, &msg.native_asset)?; - - let token_config = token_config(&deps.querier, &msg.native_asset)?; - - let peg = match msg.peg { - Some(p) => p, - None => token_info.symbol.clone(), - }; - asset_peg_w(deps.storage).save(&peg)?; - - deps.api.debug("Setting native asset"); - native_asset_w(deps.storage).save(&Snip20Asset { - contract: msg.native_asset.clone(), - token_info, - token_config: Option::from(token_config), - })?; - - asset_list_w(deps.storage).save(&vec![])?; - - deps.api - .debug(&format!("Contract was initialized by {}", info.sender)); - - Ok(Response::new()) -} - -#[shd_entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - ExecuteMsg::UpdateConfig { config } => handle::try_update_config(deps, env, info, config), - ExecuteMsg::RegisterAsset { - contract, - capture, - fee, - unlimited, - } => handle::try_register_asset(deps, &env, info, &contract, capture, fee, unlimited), - ExecuteMsg::RemoveAsset { address } => handle::try_remove_asset(deps, &env, address), - ExecuteMsg::Receive { - sender, - from, - amount, - msg, - .. - } => handle::try_burn(deps, env, info, sender, from, amount, msg), - } -} - -#[shd_entry_point] -pub fn query(deps: Deps, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::NativeAsset {} => to_binary(&query::native_asset(deps)?), - QueryMsg::SupportedAssets {} => to_binary(&query::supported_assets(deps)?), - QueryMsg::Asset { contract } => to_binary(&query::asset(deps, contract)?), - QueryMsg::Config {} => to_binary(&query::config(deps)?), - QueryMsg::Limit {} => to_binary(&query::limit(deps)?), - QueryMsg::Mint { - offer_asset, - amount, - } => to_binary(&query::mint(deps, offer_asset, amount)?), - } -} diff --git a/archived-contracts/mint/src/handle.rs b/archived-contracts/mint/src/handle.rs deleted file mode 100644 index cf4b35c..0000000 --- a/archived-contracts/mint/src/handle.rs +++ /dev/null @@ -1,491 +0,0 @@ -use shade_protocol::{ - c_std::{ - from_binary, - to_binary, - Addr, - Api, - Binary, - CosmosMsg, - Deps, - DepsMut, - Env, - MessageInfo, - Querier, - QuerierWrapper, - Response, - StdError, - StdResult, - Storage, - Uint128, - }, - chrono::prelude::*, - contract_interfaces::{ - mint::mint::{Config, HandleAnswer, Limit, MintMsgHook, SupportedAsset}, - oracles::{band::ReferenceData, oracle::QueryMsg::Price}, - snip20::helpers::Snip20Asset, - }, - snip20::helpers::{self, burn_msg, mint_msg, send_msg, token_config, token_info, TokenConfig}, - utils::{asset::Contract, generic_response::ResponseStatus, Query}, -}; -use std::{borrow::BorrowMut, cmp::Ordering, convert::TryFrom, fmt::format}; - -use crate::state::{ - asset_list_w, asset_peg_r, assets_r, assets_w, config_r, config_w, limit_r, limit_refresh_r, - limit_refresh_w, limit_w, minted_r, minted_w, native_asset_r, total_burned_w, -}; - -pub fn try_burn( - deps: DepsMut, - env: Env, - info: MessageInfo, - _sender: Addr, - from: Addr, - amount: Uint128, - msg: Option, -) -> StdResult { - let config = config_r(deps.storage).load()?; - // Check if contract enabled - if !config.activated { - return Err(StdError::generic_err("unauthorized")); - } - - let mint_asset = native_asset_r(deps.storage).load()?; - - // Prevent sender to be native asset - if mint_asset.contract.address == info.sender { - return Err(StdError::generic_err( - "Sender cannot be the same as the native asset.", - )); - } - - // Check that sender is a supported snip20 asset - let burn_asset = match assets_r(deps.storage).may_load(info.sender.to_string().as_bytes())? { - Some(supported_asset) => { - deps.api.debug(&format!( - "Found Burn Asset: {} {}", - &supported_asset.asset.token_info.symbol, - info.sender.to_string() - )); - supported_asset - } - None => { - return Err(StdError::not_found(info.sender)); - } - }; - - let mut input_amount = amount; - let mut messages = vec![]; - - if burn_asset.fee > Uint128::zero() { - let fee_amount = calculate_portion(input_amount, burn_asset.fee); - // Reduce input by fee - input_amount = input_amount.checked_sub(fee_amount)?; - - // Fee to treasury - messages.push(send_msg( - config.treasury.clone(), - fee_amount.into(), - None, - None, - None, - &burn_asset.asset.contract, - )?); - } - - // This will calculate the total mint value - let amount_to_mint: Uint128 = - mint_amount(deps.as_ref(), input_amount, &burn_asset, &mint_asset)?; - - if let Some(limit) = config.limit { - // Limit Refresh Check - try_limit_refresh(deps.storage, &deps.querier, env, info, limit)?; - - // Check & adjust limit if a limited asset - if !burn_asset.unlimited { - let minted = minted_r(deps.storage).load()?; - if (amount_to_mint + minted) > limit_r(deps.storage).load()? { - return Err(StdError::generic_err("Limit Exceeded")); - } - - minted_w(deps.storage).save(&(amount_to_mint + minted))?; - } - } - - let mut burn_amount = input_amount; - - // Ignore capture if the set capture is 0 - if burn_asset.capture > Uint128::zero() { - let capture_amount = calculate_portion(amount, burn_asset.capture); - - // Capture to treasury - messages.push(send_msg( - config.treasury.into(), - capture_amount.into(), - None, - None, - None, - &burn_asset.asset.contract, - )?); - - burn_amount = input_amount.checked_sub(capture_amount)?; - } - - if burn_amount > Uint128::zero() { - // Try to burn - if let Some(token_config) = &burn_asset.asset.token_config { - if token_config.burn_enabled { - messages.push(burn_msg( - burn_amount.into(), - None, - None, - &burn_asset.asset.contract, - )?); - } else if let Some(recipient) = config.secondary_burn { - messages.push(send_msg( - recipient, - burn_amount.into(), - None, - None, - None, - &burn_asset.asset.contract, - )?); - } - } else if let Some(recipient) = config.secondary_burn { - messages.push(send_msg( - recipient, - burn_amount.into(), - None, - None, - None, - &burn_asset.asset.contract, - )?); - } - } - - total_burned_w(deps.storage).update( - burn_asset.asset.contract.address.to_string().as_bytes(), - |burned| -> StdResult { - match burned { - Some(burned) => Ok(burned + burn_amount), - None => Ok(burn_amount), - } - }, - )?; - - if let Some(message) = msg { - let msg: MintMsgHook = from_binary(&message)?; - - // Check Slippage - if amount_to_mint < msg.minimum_expected_amount { - return Err(StdError::generic_err( - "Mint amount is less than the minimum expected.", - )); - } - }; - - messages.push(mint_msg( - from, - amount_to_mint.into(), - None, - None, - &mint_asset.contract, - )?); - - Ok(Response::new().set_data(to_binary(&HandleAnswer::Mint { - status: ResponseStatus::Success, - amount: amount_to_mint, - })?)) -} - -pub fn try_limit_refresh( - storage: &mut dyn Storage, - querier: &QuerierWrapper, - env: Env, - info: MessageInfo, - limit: Limit, -) -> StdResult { - match DateTime::parse_from_rfc3339(&limit_refresh_r(storage).load()?) { - Ok(parsed) => { - let naive = NaiveDateTime::from_timestamp(env.block.time.seconds() as i64, 0); - let now: DateTime = DateTime::from_utc(naive, Utc); - let last_refresh: DateTime = parsed.with_timezone(&Utc); - - let mut fresh_amount = Uint128::zero(); - - let native_asset = native_asset_r(storage).load()?; - - let token_info = token_info(querier, &native_asset.contract)?; - - let supply = match token_info.total_supply { - Some(s) => s.into(), - None => return Err(StdError::generic_err("Could not get native token supply")), - }; - - // get amount to add, 0 if not in need of refresh - match limit { - Limit::Daily { - supply_portion, - days, - } => { - // Slight error in annual limit if (days / 365) is not a whole number - if now.num_days_from_ce() as u128 - days.u128() - >= last_refresh.num_days_from_ce() as u128 - { - fresh_amount = calculate_portion(supply, supply_portion); - } - } - Limit::Monthly { - supply_portion, - months, - } => { - if now.year() > last_refresh.year() || now.month() > last_refresh.month() { - /* If its a new year or new month, add (year_diff * 12) to the later (now) month - * 12-2021 <-> 1-2022 becomes a comparison between 12 <-> (1 + 12) - * resulting in a difference of 1 month - */ - let year_diff = now.year() - last_refresh.year(); - - if (now.month() + (year_diff * 12) as u32) - last_refresh.month() - >= months.u128() as u32 - { - fresh_amount = calculate_portion(supply, supply_portion); - } - } - } - } - - if fresh_amount > Uint128::zero() { - let minted = minted_r(storage).load()?; - - limit_w(storage).update(|state| -> StdResult { - // Stack with previous unminted limit - Ok(state.checked_sub(minted)? + fresh_amount) - })?; - limit_refresh_w(storage).save(&now.to_rfc3339())?; - minted_w(storage).save(&Uint128::zero())?; - } - - Ok(fresh_amount) - } - Err(e) => return Err(StdError::generic_err("Failed to parse previous datetime")), - } -} - -pub fn try_update_config( - deps: DepsMut, - env: Env, - info: MessageInfo, - config: Config, -) -> StdResult { - let cur_config = config_r(deps.storage).load()?; - - // Admin-only - if info.sender != cur_config.admin { - return Err(StdError::generic_err("unauthorized")); - } - - config_w(deps.storage).save(&config)?; - - Ok( - Response::new().set_data(to_binary(&HandleAnswer::UpdateConfig { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn try_register_asset( - deps: DepsMut, - env: &Env, - info: MessageInfo, - contract: &Contract, - capture: Option, - fee: Option, - unlimited: Option, -) -> StdResult { - let config = config_r(deps.storage).load()?; - // Check if admin - if info.sender != config.admin { - return Err(StdError::generic_err("unauthorized")); - } - // Check if contract enabled - if !config.activated { - return Err(StdError::generic_err("unauthorized")); - } - - let contract_str = contract.address.to_string(); - - // Add the new asset - let asset_info = token_info(&deps.querier, &contract)?; - - let asset_config: Option = match token_config(&deps.querier, &contract) { - Ok(c) => Option::from(c), - Err(_) => None, - }; - - deps.api - .debug(&format!("Registering {}", asset_info.symbol)); - assets_w(deps.storage).save(contract_str.as_bytes(), &SupportedAsset { - asset: Snip20Asset { - contract: contract.clone(), - token_info: asset_info, - token_config: asset_config, - }, - // If capture is not set then default to 0 - capture: match capture { - None => Uint128::zero(), - Some(value) => value, - }, - fee: match fee { - None => Uint128::zero(), - Some(value) => value, - }, - unlimited: match unlimited { - None => false, - Some(u) => u, - }, - )?; - - total_burned_w(deps.storage).save(contract_str.as_bytes(), &Uint128::zero())?; - - // Add the asset to list - asset_list_w(deps.storage).update(|mut state| -> StdResult> { - state.push(contract.clone()); - Ok(state) - })?; - - // Register contract in asset - let messages = vec![register_receive(env, contract)?]; - - Ok( - Response::new().set_data(to_binary(&HandleAnswer::RegisterAsset { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn try_remove_asset(deps: DepsMut, _env: &Env, address: Addr) -> StdResult { - let address_str = address.to_string(); - - // Remove asset from the array - asset_list_w(deps.storage).update(|mut state| -> StdResult> { - state.retain(|value| value.address != address); - Ok(state) - })?; - - // Remove supported asset - assets_w(deps.storage).remove(address_str.as_bytes()); - - // We wont remove the total burned since we want to keep track of all the burned assets - - Ok( - Response::new().set_data(to_binary(&HandleAnswer::RemoveAsset { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn register_receive(env: &Env, contract: &Contract) -> StdResult { - helpers::register_receive(env.contract.code_hash.clone(), None, contract) -} - -pub fn mint_amount( - deps: Deps, - burn_amount: Uint128, - burn_asset: &SupportedAsset, - mint_asset: &Snip20Asset, -) -> StdResult { - deps.api.debug(&format!( - "Burning {} {} for {}", - burn_amount, burn_asset.asset.token_info.symbol, mint_asset.token_info.symbol - )); - - let burn_price = oracle(deps, burn_asset.asset.token_info.symbol.clone())?; - deps.api.debug(&format!("Burn Price: {}", burn_price)); - - let mint_price = oracle(deps, asset_peg_r(deps.storage).load()?)?; - deps.api.debug(&format!("Mint Price: {}", mint_price)); - - Ok(calculate_mint( - burn_price, - burn_amount, - burn_asset.asset.token_info.decimals, - mint_price, - mint_asset.token_info.decimals, - )) -} - -pub fn calculate_mint( - burn_price: Uint128, - burn_amount: Uint128, - burn_decimals: u8, - mint_price: Uint128, - mint_decimals: u8, -) -> Uint128 { - // Math must only be made in integers - // in_decimals = x - // target_decimals = y - // in_price = p1 * 10^18 - // target_price = p2 * 10^18 - // in_amount = a1 * 10^x - // return = a2 * 10^y - - // (a1 * 10^x) * (p1 * 10^18) = (a2 * 10^y) * (p2 * 10^18) - - // (p1 * 10^18) - // (a1 * 10^x) * -------------- = (a2 * 10^y) - // (p2 * 10^18) - - let burn_value = burn_amount.multiply_ratio(burn_price, mint_price); - - // burn_value * 10^(y - x) = (a2 * 10^y) - let difference: i32 = mint_decimals as i32 - burn_decimals as i32; - - // To avoid a mess of different types doing math - match difference.cmp(&0) { - Ordering::Greater => { - Uint128::new(burn_value.u128() * 10u128.pow(u32::try_from(difference).unwrap())) - } - Ordering::Less => { - burn_value.multiply_ratio(1u128, 10u128.pow(u32::try_from(difference.abs()).unwrap())) - } - Ordering::Equal => burn_value, - } -} - -/* -pub fn calculate_fee_curve( - // "Centered" - base_fee: Uint128, - // How far off from where we want (abs(desired_price - cur_price)) - price_skew: Uint128, - // skew we should never reach (where fee maxes out) - asymptote: Uint128, -) -> Uint128 { - - /* aggressiveness is how sharply it turns up at the asymptote - * speed is the overall speed of increase - * how to include asymptote to push the threshold before acceleration? - * y = (x + speed) ^ (2 * aggressiveness) - */ -} -*/ - -pub fn calculate_portion(amount: Uint128, portion: Uint128) -> Uint128 { - /* amount: total amount sent to burn (uSSCRT/uSILK/uSHD) - * portion: percent * 10^18 e.g. 5_320_000_000_000_000_000 = 5.32% = .0532 - * - * return portion = amount * portion / 10^18 - */ - if portion == Uint128::zero() { - return Uint128::zero(); - } - - amount.multiply_ratio(portion, 10u128.pow(18)) -} - -fn oracle(deps: Deps, symbol: String) -> StdResult { - let config: Config = config_r(deps.storage).load()?; - let answer: ReferenceData = Price { symbol }.query(&deps.querier, &config.oracle)?; - - Ok(Uint128::from(answer.rate)) -} diff --git a/archived-contracts/mint/src/lib.rs b/archived-contracts/mint/src/lib.rs deleted file mode 100644 index a69b81f..0000000 --- a/archived-contracts/mint/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod contract; -pub mod handle; -pub mod query; -pub mod state; diff --git a/archived-contracts/mint/src/query.rs b/archived-contracts/mint/src/query.rs deleted file mode 100644 index 234284d..0000000 --- a/archived-contracts/mint/src/query.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::{ - handle::mint_amount, - state::{ - asset_list_r, - asset_peg_r, - assets_r, - config_r, - limit_r, - limit_refresh_r, - minted_r, - native_asset_r, - total_burned_r, - }, -}; -use shade_protocol::{ - c_std::{Addr, Deps, StdError, StdResult, Uint128}, - contract_interfaces::mint::mint::QueryAnswer, -}; - -pub fn native_asset(deps: Deps) -> StdResult { - Ok(QueryAnswer::NativeAsset { - asset: native_asset_r(deps.storage).load()?, - peg: asset_peg_r(deps.storage).load()?, - }) -} - -pub fn supported_assets(deps: Deps) -> StdResult { - Ok(QueryAnswer::SupportedAssets { - assets: asset_list_r(deps.storage).load()?, - }) -} - -pub fn asset(deps: Deps, contract: String) -> StdResult { - let assets = assets_r(deps.storage); - - match assets.may_load(contract.as_bytes())? { - Some(asset) => Ok(QueryAnswer::Asset { - asset, - burned: total_burned_r(deps.storage).load(contract.as_bytes())?, - }), - None => Err(StdError::not_found(contract)), - } -} - -pub fn config(deps: Deps) -> StdResult { - Ok(QueryAnswer::Config { - config: config_r(deps.storage).load()?, - }) -} - -pub fn limit(deps: Deps) -> StdResult { - Ok(QueryAnswer::Limit { - minted: minted_r(deps.storage).load()?, - limit: limit_r(deps.storage).load()?, - last_refresh: limit_refresh_r(deps.storage).load()?, - }) -} - -pub fn mint(deps: Deps, offer_asset: Addr, amount: Uint128) -> StdResult { - let native_asset = native_asset_r(deps.storage).load()?; - - match assets_r(deps.storage).may_load(offer_asset.to_string().as_bytes())? { - Some(asset) => { - //let fee = calculate_portion(amount, asset.fee); - //let amount = mint_amount(deps, amount.checked_sub(fee)?, &asset, &native_asset)?; - let amount = mint_amount(deps, amount, &asset, &native_asset)?; - Ok(QueryAnswer::Mint { - asset: native_asset.contract, - amount, - }) - } - None => Err(StdError::not_found(offer_asset.to_string())), - } -} diff --git a/archived-contracts/mint/src/state.rs b/archived-contracts/mint/src/state.rs deleted file mode 100644 index a739abc..0000000 --- a/archived-contracts/mint/src/state.rs +++ /dev/null @@ -1,107 +0,0 @@ -use shade_protocol::c_std::Uint128; -use shade_protocol::c_std::Storage; -use shade_protocol::storage::{ - bucket, - bucket_read, - singleton, - singleton_read, - Bucket, - ReadonlyBucket, - ReadonlySingleton, - Singleton, -}; -use shade_protocol::{ - contract_interfaces::{ - mint::mint::{Config, SupportedAsset}, - snip20::helpers::Snip20Asset, - }, - utils::asset::Contract, -}; - -pub static CONFIG: &[u8] = b"config"; -pub static LIMIT: &[u8] = b"mint_limit"; -pub static LIMIT_REFRESH: &[u8] = b"limit_refresh"; -pub static MINTED: &[u8] = b"minted"; -pub static NATIVE_ASSET: &[u8] = b"native_asset"; -pub static ASSET_PEG: &[u8] = b"asset_peg"; -pub static ASSET: &[u8] = b"assets"; -pub static ASSET_LIST: &[u8] = b"asset_list"; -pub static BURN_COUNT: &[u8] = b"burn_count"; - -pub fn config_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, CONFIG) -} - -pub fn config_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, CONFIG) -} - -/* Daily limit as (limit * total_supply) at the time of refresh - */ -pub fn limit_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, LIMIT) -} - -pub fn limit_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, LIMIT) -} - -/* RFC-3339 datetime str, last time limit was refreshed - */ -pub fn limit_refresh_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, LIMIT_REFRESH) -} - -pub fn limit_refresh_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, LIMIT_REFRESH) -} - -/* Amount minted this cycle (daily) - */ -pub fn minted_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, MINTED) -} - -pub fn minted_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, MINTED) -} - -pub fn native_asset_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, NATIVE_ASSET) -} - -pub fn native_asset_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, NATIVE_ASSET) -} - -pub fn asset_peg_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, ASSET_PEG) -} - -pub fn asset_peg_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, ASSET_PEG) -} - -pub fn asset_list_w(storage: &mut dyn Storage) -> Singleton> { - singleton(storage, ASSET_LIST) -} - -pub fn asset_list_r(storage: &dyn Storage) -> ReadonlySingleton> { - singleton_read(storage, ASSET_LIST) -} - -pub fn assets_r(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, ASSET) -} - -pub fn assets_w(storage: &mut dyn Storage) -> Bucket { - bucket(storage, ASSET) -} - -pub fn total_burned_r(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, BURN_COUNT) -} - -pub fn total_burned_w(storage: &mut dyn Storage) -> Bucket { - bucket(storage, BURN_COUNT) -} diff --git a/archived-contracts/mint/tests/integration.rs b/archived-contracts/mint/tests/integration.rs deleted file mode 100644 index ce398eb..0000000 --- a/archived-contracts/mint/tests/integration.rs +++ /dev/null @@ -1,260 +0,0 @@ -use shade_protocol::c_std::{ - coins, - from_binary, - to_binary, - Binary, - Env, - DepsMut, - Response, - Addr, - Response, - StdError, - StdResult, -}; - -use shade_protocol::c_std::Uint128; -use shade_protocol::{ - contract_interfaces::{ - snip20, - mint::mint::{ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg}, - oracles::band::{BandQuery, ReferenceData}, - }, - utils::{ - asset::Contract, - price::{normalize_price, translate_price}, - }, -}; - -use snip20_reference_impl; -use mock_band; -use oracle; - -use mint::{ - contract::{handle, instantiate, query}, - handle::{calculate_mint, calculate_portion, try_burn}, -}; - -use contract_harness::harness::{ - mint::Mint, - mock_band::MockBand, - oracle::Oracle, - snip20_reference_impl::Snip20ReferenceImpl as Snip20 -}; - -use shade_protocol::fadroma::{ - ensemble::{ContractEnsemble, ContractHarness, MockDeps, MockEnv}, -}; -use shade_protocol::fadroma::core::ContractLink; - -fn test_ensemble( - offer_price: Uint128, - offer_amount: Uint128, - mint_price: Uint128, - expected_amount: Uint128, -) { - let mut ensemble = ContractEnsemble::new(50); - - let reg_oracle = ensemble.register(Box::new(Oracle)); - let reg_mint = ensemble.register(Box::new(Mint)); - let reg_snip20 = ensemble.register(Box::new(Snip20)); - let reg_band = ensemble.register(Box::new(MockBand)); - - let sscrt = ensemble - .instantiate( - reg_snip20.id, - &snip20::InstantiateMsg { - name: "secretSCRT".into(), - admin: Some("admin".into()), - symbol: "SSCRT".into(), - decimals: 6, - initial_balances: None, - prng_seed: to_binary("").ok().unwrap(), - config: None, - }, - MockEnv::new("admin", ContractLink { - address: Addr::unchecked("sscrt".into()), - code_hash: reg_snip20.code_hash.clone(), - }), - ) - .unwrap().instance; - - let shade = ensemble - .instantiate( - reg_snip20.id, - &snip20::InstantiateMsg { - name: "Shade".into(), - admin: Some("admin".into()), - symbol: "SHD".into(), - decimals: 8, - initial_balances: None, - prng_seed: to_binary("").ok().unwrap(), - config: None, - }, - MockEnv::new("admin", ContractLink { - address: Addr::unchecked("shade".into()), - code_hash: reg_snip20.code_hash.clone(), - }), - ) - .unwrap().instance; - - let band = ensemble - .instantiate( - reg_band.id, - &shade_protocol::contract_interfaces::oracles::band::InstantiateMsg {}, - MockEnv::new("admin", ContractLink { - address: Addr::unchecked("band".into()), - code_hash: reg_band.code_hash.clone(), - }), - ) - .unwrap().instance; - - let oracle = ensemble - .instantiate( - reg_oracle.id, - &shade_protocol::contract_interfaces::oracles::oracle::InstantiateMsg { - admin: Some(Addr::unchecked("admin".into())), - band: Contract { - address: band.address.clone(), - code_hash: band.code_hash.clone(), - }, - sscrt: Contract { - address: sscrt.address.clone(), - code_hash: sscrt.code_hash.clone(), - }, - }, - MockEnv::new("admin", ContractLink { - address: Addr::unchecked("oracle".into()), - code_hash: reg_oracle.code_hash.clone(), - }), - ) - .unwrap().instance; - - let mint = ensemble - .instantiate( - reg_mint.id, - &shade_protocol::contract_interfaces::mint::mint::InstantiateMsg { - admin: Some(Addr::unchecked("admin".into())), - oracle: Contract { - address: oracle.address.clone(), - code_hash: oracle.code_hash.clone(), - }, - native_asset: Contract { - address: shade.address.clone(), - code_hash: shade.code_hash.clone(), - }, - peg: None, - treasury: Addr::unchecked("admin".into()), - secondary_burn: None, - limit: None, - }, - MockEnv::new("admin", ContractLink { - address: Addr::unchecked("mint".into()), - code_hash: reg_mint.code_hash, - }), - ) - .unwrap().instance; - - // Setup price feeds - ensemble - .execute( - &mock_band::contract::ExecuteMsg::MockPrice { - symbol: "SCRT".into(), - price: offer_price, - }, - MockEnv::new("admin", band.clone()), - ) - .unwrap(); - ensemble - .execute( - &mock_band::contract::ExecuteMsg::MockPrice { - symbol: "SHD".into(), - price: mint_price, - }, - MockEnv::new("admin", band.clone()), - ) - .unwrap(); - - // Register sSCRT burn - ensemble - .execute( - &shade_protocol::contract_interfaces::mint::mint::ExecuteMsg::RegisterAsset { - contract: Contract { - address: sscrt.address.clone(), - code_hash: sscrt.code_hash.clone(), - }, - capture: None, - fee: None, - unlimited: None, - }, - MockEnv::new("admin", mint.clone()), - ) - .unwrap(); - - // Check mint query - let (asset, amount) = match ensemble - .query( - mint.address.clone(), - &shade_protocol::contract_interfaces::mint::mint::QueryMsg::Mint { - offer_asset: sscrt.address.clone(), - amount: Uint128::new(offer_amount.u128()), - }, - ) - .unwrap() - { - shade_protocol::contract_interfaces::mint::mint::QueryAnswer::Mint { asset, amount } => { - (asset, amount) - } - _ => ( - Contract { - address: Addr::unchecked("".into()), - code_hash: "".into(), - }, - Uint128::new(0), - ), - }; - - assert_eq!(asset, Contract { - address: shade.address.clone(), - code_hash: shade.code_hash.clone(), - }); - - assert_eq!(amount, Uint128::new(expected_amount.u128())); -} - -macro_rules! mint_int_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let (offer_price, offer_amount, mint_price, expected_amount) = $value; - test_ensemble(offer_price, offer_amount, mint_price, expected_amount); - } - )* - } -} -mint_int_tests! { - mint_int_0: ( - Uint128::new(10u128.pow(18)), // $1 - Uint128::new(10u128.pow(6)), // 1sscrt - Uint128::new(10u128.pow(18)), // $1 - Uint128::new(10u128.pow(8)), // 1 SHD - ), - mint_int_1: ( - Uint128::new(2 * 10u128.pow(18)), // $2 - Uint128::new(10u128.pow(6)), // 1 sscrt - Uint128::new(10u128.pow(18)), // $1 - Uint128::new(2 * 10u128.pow(8)), // 2 SHD - ), - mint_int_2: ( - Uint128::new(1 * 10u128.pow(18)), // $1 - Uint128::new(4 * 10u128.pow(6)), // 4 sscrt - Uint128::new(10u128.pow(18)), // $1 - Uint128::new(4 * 10u128.pow(8)), // 4 SHD - ), - mint_int_3: ( - Uint128::new(10 * 10u128.pow(18)), // $10 - Uint128::new(30 * 10u128.pow(6)), // 30 sscrt - Uint128::new(5 * 10u128.pow(18)), // $5 - Uint128::new(60 * 10u128.pow(8)), // 60 SHD - ), -} diff --git a/archived-contracts/mint/tests/unit.rs b/archived-contracts/mint/tests/unit.rs deleted file mode 100644 index 44da68d..0000000 --- a/archived-contracts/mint/tests/unit.rs +++ /dev/null @@ -1,113 +0,0 @@ -use shade_protocol::c_std::Uint128; -use shade_protocol::c_std::{ - self, - coins, - from_binary, - to_binary, - Binary, - Env, - DepsMut, - Response, - Addr, - Response, - StdError, - StdResult, -}; - -use shade_protocol::{ - contract_interfaces::{ - mint::mint::{ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg}, - oracles::band::{BandQuery, ReferenceData}, - }, - utils::{ - asset::Contract, - price::{normalize_price, translate_price}, - }, -}; - -#[test] -fn capture_calc() { - let amount = Uint128::new(1_000_000_000_000_000_000u128); - //10% - let capture = Uint128::new(100_000_000_000_000_000u128); - let expected = Uint128::new(100_000_000_000_000_000u128); - let value = mint::handle::calculate_portion(amount, capture); - assert_eq!(value, expected); -} - -macro_rules! mint_algorithm_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let (in_price, in_amount, in_decimals, target_price, target_decimals, expected_value) = $value; - assert_eq!(mint::handle::calculate_mint(in_price, in_amount, in_decimals, target_price, target_decimals), expected_value); - } - )* - } -} - -mint_algorithm_tests! { - mint_simple_0: ( - // In this example the "sent" value is 1 with 6 decimal places - // The mint value will be 1 with 3 decimal places - Uint128::new(1_000_000_000_000_000_000), //Burn price - Uint128::new(1_000_000), //Burn amount - 6u8, //Burn decimals - Uint128::new(1_000_000_000_000_000_000), //Mint price - 3u8, //Mint decimals - Uint128::new(1_000), //Expected value - ), - mint_simple_1: ( - // In this example the "sent" value is 1 with 8 decimal places - // The mint value will be 1 with 3 decimal places - Uint128::new(1_000_000_000_000_000_000), //Burn price - Uint128::new(1_000_000), //Burn amount - 6u8, //Burn decimals - Uint128::new(1_000_000_000_000_000_000), //Mint price - 8u8, //Mint decimals - Uint128::new(100_000_000), //Expected value - ), - mint_complex_0: ( - // In this example the "sent" value is 1.8 with 6 decimal places - // The mint value will be 3.6 with 12 decimal places - Uint128::new(2_000_000_000_000_000_000), - Uint128::new(1_800_000), - 6u8, - Uint128::new(1_000_000_000_000_000_000), - 12u8, - Uint128::new(3_600_000_000_000), - ), - mint_complex_1: ( - // In amount is 50.000 valued at 20 - // target price is 100$ with 6 decimals - Uint128::new(20_000_000_000_000_000_000), - Uint128::new(50_000), - 3u8, - Uint128::new(100_000_000_000_000_000_000), - 6u8, - Uint128::new(10_000_000), - ), - mint_complex_2: ( - // In amount is 10,000,000 valued at 100 - // Target price is $10 with 6 decimals - Uint128::new(1_000_000_000_000_000_000_000), - Uint128::new(100_000_000_000_000), - 8u8, - Uint128::new(10_000_000_000_000_000_000), - 6u8, - Uint128::new(100_000_000_000_000), - ), - /* - mint_overflow_0: ( - // In amount is 1,000,000,000,000,000,000,000,000 valued at 1,000 - // Target price is $5 with 6 decimals - Uint128::new(1_000_000_000_000_000_000_000), - Uint128::new(100_000_000_000_000_000_000_000_000_000_000), - 8u8, - Uint128::new(5_000_000_000_000_000_000), - 6u8, - Uint128::new(500_000_000_000_000_000_000_000_000_000_000_000), - ), - */ -} diff --git a/archived-contracts/mint_router/.cargo/config b/archived-contracts/mint_router/.cargo/config deleted file mode 100644 index 882fe08..0000000 --- a/archived-contracts/mint_router/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[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/archived-contracts/mint_router/.circleci/config.yml b/archived-contracts/mint_router/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/archived-contracts/mint_router/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/mint_router/Cargo.toml b/archived-contracts/mint_router/Cargo.toml deleted file mode 100644 index 9557470..0000000 --- a/archived-contracts/mint_router/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "mint_router" -version = "0.1.0" -authors = ["Jackson Swenson "] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] -debug-print = ["shade-protocol/debug-print"] - -[dependencies] -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ - "mint_router", - "oracles", - "band", - "mint", - "dex", - "chrono", -] } -schemars = "0.7" diff --git a/archived-contracts/mint_router/Makefile b/archived-contracts/mint_router/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/archived-contracts/mint_router/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/archived-contracts/mint_router/README.md b/archived-contracts/mint_router/README.md deleted file mode 100644 index d21087c..0000000 --- a/archived-contracts/mint_router/README.md +++ /dev/null @@ -1,210 +0,0 @@ - -# Mint Router Contract -* [Introduction](#Introduction) -* [Sections](#Sections) - * [Init](#Init) - * [Admin](#Admin) - * Messages - * [UpdateConfig](#UpdateConfig) - * [UpdateMintLimit](#UpdateMintLimit) - * [RegisterAsset](#RegisterAsset) - * [RemoveAsset](#RemoveAsset) - * [User](#User) - * Messages - * [Receive](#Receive) - * Queries - * [NativeAsset](#GetNativeAsset) - * [Config](#GetConfig) - * [SupportedAssets](#GetSupportedAssets) - * [Asset](#GetAsset) -# Introduction -Contract responsible to mint a paired snip20 asset - -# Sections - -## Init -##### Request -|Name |Type |Description | optional | -|-----------------|------------|-------------------------------------------------------------------------------|----------| -|admin | string | New contract owner; SHOULD be a valid bech32 address | yes | -|oracle | Contract | Oracle contract | no | -|peg | String | Symbol to peg to when querying oracle (defaults to native_asset symbol) | yes | -|treasury | Contract | Treasury contract | yes | -|secondary_burn | Addrr | Where non-burnable assets will go | yes | -## Admin - -### Messages -#### UpdateConfig -Updates the given values -##### Request -|Name |Type |Description | optional | -|---------------|------------|-------------------------------------------------------|----------| -|admin | string | New contract admin; SHOULD be a valid bech32 address | yes | -|oracle | Contract | Oracle contract | yes | -|treasury | Contract | Treasury contract | yes | -|secondary_burn | Addrr | Where non-burnable assets will go | yes | -##### Response -```json -{ - "update_config": { - "status": "success" - } -} -``` - -#### UpdateMintLimit -Updates the mint limit and epoch time -##### Request -|Name |Type |Description | optional | -|-----------------|----------|--------------------------------------------------------------------------------|----------| -|start_epoch | String | The starting epoch | yes | -|epoch_frequency | String | The frequency in which the mint limit resets, if 0 then no limit is enforced | yes | -|epoch_mint_limit | String | The limit of uTokens to mint per epoch | yes | -##### Response -```json -{ - "update_mint_limit": { - "status": "success" - } -} -``` - -#### RegisterAsset -Registers a supported asset. The asset must be SNIP-20 compliant since [RegisterReceive](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md#RegisterReceive) is called. - -##### Request -|Name |Type |Description | optional | -|------------|--------|-------------------------------------|----------| -|contract | Contract | Type explained [here](#Contract) | no | -##### Response -```json -{ - "register_asset": { - "status": "success" - } -} -``` - -#### RemoveAsset -Remove a registered asset. -##### Request -|Name |Type |Description | optional | -|------------|--------|--------------------------------|----------| -|address | String | The asset to remove's address | no | -##### Response -```json -{ - "remove_asset": { - "status": "success" - } -} -``` - -##User - -### Messages - -#### Receive -To mint the user must use a supported asset's send function and send the amount over to the contract's address. The contract will take care of the rest. - -In the msg field of a snip20 send command you must send a base64 encoded json like this one -```json -{"minimum_expected_amount": "Uint128" } -``` - -### Queries - -#### GetNativeAsset -Gets the contract's minted asset -#### Response -```json -{ - "native_asset": { - "asset": "Snip20Asset Object", - "peg": "Pegged symbol" - } -} -``` - -#### GetConfig -Gets the contract's configuration variables -##### Response -```json -{ - "config": { - "config": { - "admin": "Owner address", - "oracle": { - "address": "Asset contract address", - "code_hash": "Asset callback code hash" - }, - "treasury": { - "address": "Asset contract address", - "code_hash": "Asset callback code hash" - }, - "secondary_burn": "Optional burn address", - "activated": "Boolean of contract's actviation status" - } - } -} -``` - -#### GetMintLimit -Gets the contract's configuration variables -##### Response -```json -{ - "limit": { - "mint_limit": { - "frequency": "Frequency per epoch reset", - "mint_capacity": "Mint capacity per epoch", - "total_minted": "Total minted in current epoch", - "next_epoch": "Timestamp for the next epoch" - } - } -} -``` - -#### GetSupportedAssets -Get all the contract's supported assets. -##### Response -```json -{ - "supported_assets": { - "assets": ["asset address"] - } -} -``` - -#### GetAsset -Get specific information on a supported asset. -##### Request -|Name |Type |Description | optional | -|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| -|contract | string | Snip20 contract address; SHOULD be a valid bech32 address, but contracts may use a different naming scheme as well | no | -##### Response -```json -{ - "asset": { - "asset": { - "Snip20Asset": { - "contract": "Asset contract", - "token_info": "Token info as per Snip20", - "token_config": "Optional information about the config if the Snip20 supports it" - }, - "burned": "Total burned on this contract" - } - } -} -``` - -## Contract -Type used in many of the admin commands -```json -{ - "config": { - "address": "Asset contract address", - "code_hash": "Asset callback code hash" - } -} -``` diff --git a/archived-contracts/mint_router/src/contract.rs b/archived-contracts/mint_router/src/contract.rs deleted file mode 100644 index 021d64a..0000000 --- a/archived-contracts/mint_router/src/contract.rs +++ /dev/null @@ -1,80 +0,0 @@ -use shade_protocol::c_std::{ - - to_binary, - Api, - Binary, - Env, - DepsMut, - Response, - Querier, - StdResult, - Storage, - Uint128, -}; -use shade_protocol::snip20::helpers::{register_receive, token_info, token_config}; - -use shade_protocol::contract_interfaces::{ - mint::mint_router::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}, - snip20::helpers::Snip20Asset, -}; - -use crate::{ - handle, - query, - state::{config_w, current_assets_w}, -}; - -pub fn init( - deps: DepsMut, - env: Env, - info: MessageInfo, -) -> StdResult { - let config = Config { - admin: match msg.admin { - None => info.sender.clone(), - Some(admin) => admin, - }, - path: msg.path, - }; - - config_w(deps.storage).save(&config)?; - //current_assets_w(deps.storage).save(&vec![])?; - - let mut messages = vec![]; - - if config.path.len() > 0 { - //messages.append(&mut handle::update_entry_assets(deps, env, info, config.path[0].clone())?); - messages.append(&mut handle::build_path(deps, env, info, config.path.clone())?); - } - - Ok(Response::new()) -} - -pub fn handle( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> StdResult { - match msg { - ExecuteMsg::UpdateConfig { config } => handle::try_update_config(deps, env, info, config), - ExecuteMsg::Receive { - sender, - from, - amount, - msg, - .. - } => handle::receive(deps, env, info, sender, from, amount, msg), - } -} - -pub fn query( - deps: Deps, - msg: QueryMsg, -) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query::config(deps)?), - QueryMsg::Assets {} => to_binary(&query::assets(deps)?), - QueryMsg::Route { asset, amount } => to_binary(&query::route(deps, asset, amount)?), - } -} diff --git a/archived-contracts/mint_router/src/handle.rs b/archived-contracts/mint_router/src/handle.rs deleted file mode 100644 index 851ca19..0000000 --- a/archived-contracts/mint_router/src/handle.rs +++ /dev/null @@ -1,230 +0,0 @@ -use shade_protocol::{ - c_std::{ - from_binary, - to_binary, - Addr, - Api, - Binary, - CosmosMsg, - DepsMut, - Env, - Querier, - Response, - StdError, - StdResult, - Storage, - Uint128, - }, - chrono::prelude::*, - contract_interfaces::{ - mint::{ - mint, - mint_router::{Config, HandleAnswer}, - }, - oracles::{band::ReferenceData, oracle::QueryMsg::Price}, - snip20::helpers::Snip20Asset, - }, - snip20::helpers::{ - burn_msg, - mint_msg, - register_receive, - send_msg, - token_config, - token_info_query, - TokenConfig, - }, - utils::{asset::Contract, generic_response::ResponseStatus, Query}, -}; -use std::{cmp::Ordering, convert::TryFrom}; - -use crate::state::{ - asset_path_r, - asset_path_w, - config_r, - config_w, - current_assets_w, - final_asset_r, - final_asset_w, - registered_asset_r, - registered_asset_w, -}; - -pub fn receive( - deps: DepsMut, - env: Env, - info: MessageInfo, - sender: Addr, - from: Addr, - amount: Uint128, - msg: Option, -) -> StdResult { - let mut messages = vec![]; - let asset_paths = asset_path_r(deps.storage); - - let mut input_asset = - registered_asset_r(deps.storage).load(&info.sender.to_string().as_bytes())?; - let mut input_amount = amount; - - let final_asset = final_asset_r(deps.storage).load()?; - - while input_asset.address != final_asset { - let mint = asset_paths.load(input_asset.address.to_string().as_bytes())?; - let (output_asset, output_amount) = match (mint::QueryMsg::Mint { - offer_asset: input_asset.address.clone(), - amount: input_amount, - } - .query(&deps.querier, mint.clone())?) - { - mint::QueryAnswer::Mint { asset, amount } => (asset, amount), - _ => { - return Err(StdError::generic_err("Failed to get mint asset/amount")); - } - }; - - if output_asset.address == final_asset { - // Send with the msg for slippage - messages.push(send_msg( - mint.address.to_string(), - input_amount.into(), - msg.clone(), - None, - None, - input_asset.clone * (), - )?); - } else { - // ignore slippage for intermediate steps - messages.push(send_msg( - mint.address.to_string(), - input_amount.into(), - None, - None, - None, - input_asset.clone(), - )?); - } - - input_asset = output_asset; - input_amount = output_amount; - } - - messages.push(send_msg( - from.to_string(), - input_amount.into(), - None, - None, - None, - input_asset.clone(), - )?); - - Ok(Response::new().set_data(to_binary(&HandleAnswer::Mint { - status: ResponseStatus::Success, - amount, - })?)) -} - -pub fn try_update_config( - deps: DepsMut, - env: Env, - info: MessageInfo, - config: Config, -) -> StdResult { - let cur_config = config_r(deps.storage).load()?; - - // Admin-only - if info.sender != cur_config.admin { - return Err(StdError::generic_err("unauthorized")); - } - - let mut messages = vec![]; - - if cur_config.path != config.path { - messages.append(&mut build_path(deps, env, info, config.path.clone())?); - } - - config_w(deps.storage).save(&config)?; - - Ok( - Response::new().set_data(to_binary(&HandleAnswer::UpdateConfig { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn build_path( - deps: DepsMut, - env: Env, - info: MessageInfo, - path: Vec, -) -> StdResult> { - let mut messages = vec![]; - let mut all_assets = vec![]; - - for mint in path.clone() { - let entry_assets = match (mint::QueryMsg::SupportedAssets {}.query( - &deps.querier, - mint.code_hash.clone(), - mint.address.clone(), - )?) { - mint::QueryAnswer::SupportedAssets { assets } => assets, - _ => { - return Err(StdError::generic_err("Failed to get entry assets")); - } - }; - - all_assets.append(&mut entry_assets.clone()); - - // Make sure all new assets are registered - for asset in entry_assets.clone() { - // Register receive if it hasn't been before - if (registered_asset_r(deps.storage).may_load(&asset.address.to_string().as_bytes())?) - .is_none() - { - messages.push(register_receive( - env.contract.code_hash.clone(), - None, - asset, - )?); - registered_asset_w(deps.storage) - .save(&asset.address.to_string().as_bytes(), &asset)?; - } - - // Set this assets node to the current mint contract - asset_path_w(deps.storage).save(&asset.address.to_string().as_bytes(), &mint)?; - } - } - - let exit = path.last().unwrap(); - let final_asset = match (mint::QueryMsg::NativeAsset {}.query( - &deps.querier, - exit.code_hash.clone(), - exit.address.clone(), - )?) { - mint::QueryAnswer::NativeAsset { asset, peg } => asset.contract, - _ => { - return Err(StdError::generic_err("Failed to get final asset")); - } - }; - - // Ensure final asset is registered - if (registered_asset_r(deps.storage).may_load(&final_asset.address.to_string().as_bytes())?) - .is_none() - { - messages.push(register_receive( - env.contract.code_hash.clone(), - None, - &final_asset, - )?); - registered_asset_w(deps.storage) - .save(&final_asset.address.to_string().as_bytes(), &final_asset)?; - } - - // remove final asset to prevent circles - if let Some(index) = all_assets.iter().position(|a| *a == final_asset) { - all_assets.remove(index); - } - - final_asset_w(deps.storage).save(&final_asset.address)?; - current_assets_w(deps.storage).save(&all_assets)?; - - Ok(messages) -} diff --git a/archived-contracts/mint_router/src/lib.rs b/archived-contracts/mint_router/src/lib.rs deleted file mode 100644 index e321ad6..0000000 --- a/archived-contracts/mint_router/src/lib.rs +++ /dev/null @@ -1,49 +0,0 @@ -pub mod contract; -pub mod handle; -pub mod query; -pub mod state; - -#[cfg(test)] -mod test; - -#[cfg(target_arch = "wasm32")] -mod wasm { - use super::contract; - use shade_protocol::c_std::{ - do_handle, - do_init, - do_query, - ExternalApi, - ExternalQuerier, - ExternalStorage, - }; - - #[no_mangle] - extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { - do_init( - &contract::init::, - env_ptr, - msg_ptr, - ) - } - - #[no_mangle] - extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { - do_handle( - &contract::handle::, - env_ptr, - msg_ptr, - ) - } - - #[no_mangle] - extern "C" fn query(msg_ptr: u32) -> u32 { - do_query( - &contract::query::, - msg_ptr, - ) - } - - // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available - // automatically because we `use cosmwasm_std`. -} diff --git a/archived-contracts/mint_router/src/query.rs b/archived-contracts/mint_router/src/query.rs deleted file mode 100644 index 99f4181..0000000 --- a/archived-contracts/mint_router/src/query.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::state::{asset_path_r, config_r, current_assets_r, final_asset_r, registered_asset_r}; -use shade_protocol::{ - c_std::{Addr, Deps, StdError, StdResult, Uint128}, - contract_interfaces::mint::{ - mint, - mint_router::{PathNode, QueryAnswer}, - }, - snip20::helpers::token_info_query, - utils::Query, -}; - -pub fn config(deps: Deps) -> StdResult { - Ok(QueryAnswer::Config { - config: config_r(deps.storage).load()?, - }) -} - -pub fn assets(deps: Deps) -> StdResult { - Ok(QueryAnswer::Assets { - assets: current_assets_r(deps.storage).load()?, - }) -} - -pub fn route(deps: Deps, asset: Addr, amount: Uint128) -> StdResult { - let mut path = vec![]; - let mut input_asset = registered_asset_r(deps.storage).load(&asset.to_string().as_bytes())?; - let mut input_amount = amount; - - let final_asset = final_asset_r(deps.storage).load()?; - - while input_asset.address != final_asset { - let mint = asset_path_r(deps.storage).load(&input_asset.address.to_string().as_bytes())?; - let (output_asset, output_amount) = match (mint::QueryMsg::Mint { - offer_asset: input_asset.address.clone(), - amount: input_amount, - } - .query(&deps.querier, mint.code_hash.clone(), mint.address.clone())?) - { - mint::QueryAnswer::Mint { asset, amount } => (asset, amount), - _ => { - return Err(StdError::generic_err("Failed to get native asset")); - } - }; - - path.push(PathNode { - input_asset: input_asset.address.clone(), - input_amount, - - mint: mint.address, - - output_asset: output_asset.address.clone(), - output_amount, - }); - - input_asset = output_asset; - input_amount = output_amount; - } - - Ok(QueryAnswer::Route { path }) -} diff --git a/archived-contracts/mint_router/src/state.rs b/archived-contracts/mint_router/src/state.rs deleted file mode 100644 index 8588e4f..0000000 --- a/archived-contracts/mint_router/src/state.rs +++ /dev/null @@ -1,76 +0,0 @@ -use shade_protocol::c_std::Uint128; -use shade_protocol::c_std::{Addr, Storage}; -use shade_protocol::storage::{ - bucket, - bucket_read, - singleton, - singleton_read, - Bucket, - ReadonlyBucket, - ReadonlySingleton, - Singleton, -}; -use shade_protocol::{ - contract_interfaces::{mint::mint_router::Config, snip20::helpers::Snip20Asset}, - utils::asset::Contract, -}; - -pub static CONFIG: &[u8] = b"config"; -pub static REGISTERED_ASSETS: &[u8] = b"registered_assets"; -pub static CURRENT_ASSETS: &[u8] = b"current_assets"; -pub static ASSET_PATH: &[u8] = b"asset_path"; -pub static FINAL_ASSET: &[u8] = b"final_asset"; -pub static USER: &[u8] = b"user"; - -pub fn config_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, CONFIG) -} - -pub fn config_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, CONFIG) -} - -pub fn registered_asset_w(storage: &mut dyn Storage) -> Bucket { - bucket(storage, REGISTERED_ASSETS) -} - -pub fn registered_asset_r(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, REGISTERED_ASSETS) -} - -/* Given a snip20 asset, gives the mint contract - * furthest down the path - */ -pub fn asset_path_w(storage: &mut dyn Storage) -> Bucket { - bucket(storage, ASSET_PATH) -} - -pub fn asset_path_r(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, ASSET_PATH) -} - -pub fn final_asset_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, FINAL_ASSET) -} - -pub fn final_asset_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, FINAL_ASSET) -} - -pub fn current_assets_w(storage: &mut dyn Storage) -> Singleton> { - singleton(storage, CURRENT_ASSETS) -} - -pub fn current_assets_r(storage: &dyn Storage) -> ReadonlySingleton> { - singleton_read(storage, CURRENT_ASSETS) -} - -/* Needs to track the originating user across receive calls - */ -pub fn user_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, USER) -} - -pub fn user_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, USER) -} diff --git a/archived-contracts/mint_router/src/test.rs b/archived-contracts/mint_router/src/test.rs deleted file mode 100644 index b41a801..0000000 --- a/archived-contracts/mint_router/src/test.rs +++ /dev/null @@ -1,414 +0,0 @@ -/* -#[cfg(test)] -pub mod tests { - use shade_protocol::c_std::{ - coins, from_binary, - testing::{mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage}, - DepsMut, StdError, Uint128, - }; - use shade_protocol::mint_router::{ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg}; - - use crate::{ - contract::{handle, init, query}, - handle::{calculate_capture, calculate_mint, try_burn}, - }; - - mod mock_secret_toolkit { - - use shade_protocol::c_std::{Addr, Querier, StdResult, Uint128}; - use shade_protocol::snip20::helpers::TokenInfo; - - pub fn mock_token_info_query( - _querier: &Q, - _block_size: usize, - _callback_code_hash: String, - _contract_addr: Addr, - ) -> StdResult { - Ok(TokenInfo { - name: "Token".to_string(), - symbol: "TKN".to_string(), - decimals: 6, - total_supply: Option::from(Uint128::new(150)), - }) - } - } - - #[double] - use mock_shade_protocol::secret_toolkit::token_info_query; - use shade_protocol::utils::asset::Contract; - - fn create_contract(address: &str, code_hash: &str) -> Contract { - let env = mock_env(address.to_string(), &[]); - return Contract { - address: info.sender, - code_hash: code_hash.to_string(), - }; - } - - fn dummy_init( - admin: String, - native_asset: Contract, - oracle: Contract, - peg: Option, - treasury: Option, - capture: Option, - ) -> Extern { - let mut deps = mock_dependencies(20, &[]); - let msg = InstantiateMsg { - admin: None, - native_asset, - oracle, - peg, - treasury, - secondary_burn: None, - limit: None, - /* - start_epoch: None, - epoch_frequency: None, - epoch_mint_limit: None, - */ - }; - let env = mock_env(admin, &coins(1000, "earth")); - let _res = init(&mut deps, env, info, msg).unwrap(); - - return deps; - } - - #[test] - /* - fn proper_initialization() { - let mut deps = mock_dependencies(20, &[]); - let msg = InstantiateMsg { - admin: None, - native_asset: create_contract("", ""), - oracle: create_contract("", ""), - peg: Option::from("TKN".to_string()), - treasury: Option::from(create_contract("", "")), - // 1% - capture: Option::from(Uint128::new(100)), - }; - let env = mock_env("creator", &coins(1000, "earth")); - - // we can just call .unwrap() to assert this was a success - let res = init(&mut deps, env, info, msg).unwrap(); - assert_eq!(0, res.messages.len()); - } - */ - - /* - #[test] - fn config_update() { - let native_asset = create_contract("snip20", "hash"); - let oracle = create_contract("oracle", "hash"); - let treasury = create_contract("treasury", "hash"); - let capture = Uint128::new(100); - - let admin_env = mock_env("admin", &coins(1000, "earth")); - let mut deps = dummy_init("admin".to_string(), - native_asset, - oracle, - None, - Option::from(treasury), - Option::from(capture)); - - // new config vars - let new_oracle = Option::from(create_contract("new_oracle", "hash")); - let new_treasury = Option::from(create_contract("new_treasury", "hash")); - let new_capture = Option::from(Uint128::new(200)); - - // Update config - let update_msg = ExecuteMsg::UpdateConfig { - owner: None, - oracle: new_oracle.clone(), - treasury: new_treasury.clone(), - // 2% - capture: new_capture.clone(), - }; - let update_res = handle(&mut deps, admin_env, update_msg); - - let config_res = query(&deps, QueryMsg::GetConfig {}).unwrap(); - let value: QueryAnswer = from_binary(&config_res).unwrap(); - match value { - QueryAnswer::Config { config } => { - assert_eq!(config.oracle, new_oracle.unwrap()); - assert_eq!(config.treasury, new_treasury); - assert_eq!(config.capture, new_capture); - } - _ => { panic!("Received wrong answer") } - } - } - */ - - /* - #[test] - fn user_register_asset() { - let mut deps = dummy_init("admin".to_string(), - create_contract("", ""), - create_contract("", ""), - None, None, None); - - // User should not be allowed to add an item - let user_env = mock_env("user", &coins(1000, "earth")); - let dummy_contract = create_contract("some_contract", "some_hash"); - let msg = ExecuteMsg::RegisterAsset { - contract: dummy_contract, - }; - let res = handle(&mut deps, user_env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("Must return unauthorized error"), - } - - // Response should be an empty array - let res = query(&deps, QueryMsg::GetSupportedAssets {}).unwrap(); - let value: QueryAnswer = from_binary(&res).unwrap(); - match value { - QueryAnswer::SupportedAssets { assets } => { assert_eq!(0, assets.len()) } - _ => { panic!("Received wrong answer") } - } - } - - #[test] - fn admin_register_asset() { - let mut deps = dummy_init("admin".to_string(), - create_contract("", ""), - create_contract("", ""), - None, - None, - None); - - // Admin should be allowed to add an item - let env = mock_env("admin", &coins(1000, "earth")); - let dummy_contract = create_contract("some_contract", "some_hash"); - let msg = ExecuteMsg::RegisterAsset { - contract: dummy_contract, - }; - let _res = handle(&mut deps, env, info, msg).unwrap(); - - // Response should be an array of size 1 - let res = query(&deps, QueryMsg::GetSupportedAssets {}).unwrap(); - let value: QueryAnswer = from_binary(&res).unwrap(); - match value { - QueryAnswer::SupportedAssets { assets } => { assert_eq!(1, assets.len()) } - _ => { panic!("Received wrong answer") } - } - } - - #[test] - fn duplicate_register_asset() { - let mut deps = dummy_init("admin".to_string(), - create_contract("", ""), - create_contract("", ""), - None, - None, - None); - - let env = mock_env("admin", &coins(1000, "earth")); - let dummy_contract = create_contract("some_contract", "some_hash"); - let msg = ExecuteMsg::RegisterAsset { - contract: dummy_contract, - }; - let _res = handle(&mut deps, env, info, msg).unwrap(); - - // Should not be allowed to add an existing asset - let env = mock_env("admin", &coins(1000, "earth")); - let dummy_contract = create_contract("some_contract", "other_hash"); - let msg = ExecuteMsg::RegisterAsset { - contract: dummy_contract, - }; - let res = handle(&mut deps, env, info, msg); - match res { - Err(StdError::GenericErr { .. }) => {} - _ => panic!("Must return not found error"), - }; - } - - /* - #[test] - fn user_update_asset() { - let mut deps = dummy_init("admin".to_string(), - create_contract("", ""), - create_contract("", "")); - - // Add a supported asset - let env = mock_env("admin", &coins(1000, "earth")); - let dummy_contract = create_contract("some_contract", "some_hash"); - let msg = ExecuteMsg::RegisterAsset { - contract: dummy_contract, - }; - let _res = handle(&mut deps, env, info, msg).unwrap(); - - // users should not be allowed to update assets - let user_env = mock_env("user", &coins(1000, "earth")); - let dummy_contract = create_contract("some_contract", "some_hash"); - let new_dummy_contract = create_contract("some_other_contract", "some_hash"); - let msg = ExecuteMsg::UpdateAsset { - asset: dummy_contract.address, - contract: new_dummy_contract, - }; - let res = handle(&mut deps, user_env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("Must return unauthorized error"), - }; - } - */ - - /* - #[test] - fn admin_update_asset() { - let mut deps = dummy_init("admin".to_string(), - create_contract("", ""), - create_contract("", "")); - - // Add a supported asset - let env = mock_env("admin", &coins(1000, "earth")); - let dummy_contract = create_contract("some_contract", "some_hash"); - let msg = ExecuteMsg::RegisterAsset { - contract: dummy_contract, - }; - let _res = handle(&mut deps, env, info, msg).unwrap(); - - // admins can update assets - let env = mock_env("admin", &coins(1000, "earth")); - let dummy_contract = create_contract("some_contract", "some_hash"); - let new_dummy_contract = create_contract("some_other_contract", "some_hash"); - let msg = ExecuteMsg::UpdateAsset { - asset: dummy_contract.address, - contract: new_dummy_contract, - }; - let _res = handle(&mut deps, env, info, msg).unwrap(); - - // Response should be new dummy contract - let res = query(&deps, QueryMsg::GetAsset { contract: "some_other_contract".to_string() }).unwrap(); - let value: QueryAnswer = from_binary(&res).unwrap(); - match value { - QueryAnswer::Asset { asset, burned } => { assert_eq!("some_other_contract".to_string(), asset.contract.address.to_string()) } - _ => { panic!("Received wrong answer") } - }; - } - */ - - #[test] - fn receiving_an_asset() { - let mut deps = dummy_init("admin".to_string(), - create_contract("", ""), - create_contract("", ""), - None, None, None); - - // Add a supported asset - let env = mock_env("admin", &coins(1000, "earth")); - let dummy_contract = create_contract("some_contract", "some_hash"); - let msg = ExecuteMsg::RegisterAsset { - contract: dummy_contract, - }; - let _res = handle(&mut deps, env, info, msg).unwrap(); - - // Contract tries to send funds - let env = mock_env("some_contract", &coins(1000, "earth")); - let dummy_contract = create_contract("some_owner", "some_hash"); - - let msg = ExecuteMsg::Receive { - sender: dummy_contract.address, - from: Default::default(), - amount: Uint128::new(100), - msg: None, - memo: None - }; - - let res = handle(&mut deps, env, info, msg); - match res { - Err(err) => { - match err { - StdError::NotFound { .. } => {panic!("Not found");} - StdError::Unauthorized { .. } => {panic!("Unauthorized");} - _ => {} - } - } - _ => {} - } - } - - #[test] - fn receiving_an_asset_from_non_supported_asset() { - let mut deps = dummy_init("admin".to_string(), - create_contract("", ""), - create_contract("", ""), - None, - None, - None, - ); - - // Add a supported asset - let env = mock_env("admin", &coins(1000, "earth")); - let dummy_contract = create_contract("some_contract", "some_hash"); - let msg = ExecuteMsg::RegisterAsset { - contract: dummy_contract, - }; - let _res = handle(&mut deps, env, info, msg).unwrap(); - - // Contract tries to send funds - let env = mock_env("some_other_contract", &coins(1000, "earth")); - let dummy_contract = create_contract("some_owner", "some_hash"); - let msg = ExecuteMsg::Receive { - sender: dummy_contract.address, - from: Default::default(), - amount: Uint128::new(100), - msg: None, - memo: None - }; - let res = handle(&mut deps, env, info, msg); - match res { - Err(StdError::NotFound { .. }) => {} - _ => {panic!("Must return not found error")}, - } - } - */ - #[test] - fn capture_calc() { - let amount = Uint128::new(1_000_000_000_000_000_000); - //10% - let capture = Uint128::new(100_000_000_000_000_000); - let expected = Uint128::new(100_000_000_000_000_000); - let value = calculate_capture(amount, capture); - assert_eq!(value, expected); - } - #[test] - fn mint_algorithm_simple() { - // In this example the "sent" value is 1 with 6 decimal places - // The mint value will be 1 with 3 decimal places - let price = Uint128::new(1_000_000_000_000_000_000); - let in_amount = Uint128::new(1_000_000); - let expected_value = Uint128::new(1_000); - let value = calculate_mint(price, in_amount, 6, price, 3); - - assert_eq!(value, expected_value); - } - - #[test] - fn mint_algorithm_complex_1() { - // In this example the "sent" value is 1.8 with 6 decimal places - // The mint value will be 3.6 with 12 decimal places - let in_price = Uint128::new(2_000_000_000_000_000_000); - let target_price = Uint128::new(1_000_000_000_000_000_000); - let in_amount = Uint128::new(1_800_000); - let expected_value = Uint128::new(3_600_000_000_000); - let value = calculate_mint(in_price, in_amount, 6, target_price, 12); - - assert_eq!(value, expected_value); - } - - #[test] - fn mint_algorithm_complex_2() { - // In amount is 50.000 valued at 20 - // target price is 100$ with 6 decimals - let in_price = Uint128::new(20_000_000_000_000_000_000); - let target_price = Uint128::new(100_000_000_000_000_000_000); - let in_amount = Uint128::new(50_000); - let expected_value = Uint128::new(10_000_000); - let value = calculate_mint(in_price, in_amount, 3, target_price, 6); - - assert_eq!(value, expected_value); - } -} -*/ diff --git a/archived-contracts/mock_band/.cargo/config b/archived-contracts/mock_band/.cargo/config deleted file mode 100644 index 882fe08..0000000 --- a/archived-contracts/mock_band/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[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/archived-contracts/mock_band/.circleci/config.yml b/archived-contracts/mock_band/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/archived-contracts/mock_band/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/mock_band/Cargo.toml b/archived-contracts/mock_band/Cargo.toml deleted file mode 100644 index 54a4287..0000000 --- a/archived-contracts/mock_band/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "mock_band" -version = "0.1.0" -authors = [ - "Jack Swenson ", - "Guy Garcia ", -] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] -debug-print = ["shade-protocol/debug-print"] - -[dependencies] -bincode = "1.3.1" -shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ - "band", -] } -schemars = "0.7" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/archived-contracts/mock_band/Makefile b/archived-contracts/mock_band/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/archived-contracts/mock_band/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/archived-contracts/mock_band/README.md b/archived-contracts/mock_band/README.md deleted file mode 100644 index 8fad675..0000000 --- a/archived-contracts/mock_band/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Mock Band Contract -* [Introduction](#Introduction) -* [Sections](#Sections) - * [User](#User) - * Queries - * [GetReferenceData](#GetReferenceData) -# Introduction -The Mocked Band contract is used to test locally when there is no official band contract available - -### Queries - -#### GetReferenceData -Get a hardcoded sample from an ETH query for testing locally -##### Response -```json -{ - "rate": "3119154999999000000000", - "last_updated_base": 1628548483, - "last_updated_quote": 3377610 -} -``` diff --git a/archived-contracts/mock_band/src/contract.rs b/archived-contracts/mock_band/src/contract.rs deleted file mode 100644 index d5d219b..0000000 --- a/archived-contracts/mock_band/src/contract.rs +++ /dev/null @@ -1,110 +0,0 @@ -use shade_protocol::c_std::{ - to_binary, - Api, - Binary, - Env, - DepsMut, - Response, - Querier, - StdError, - StdResult, - Storage, -}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use shade_protocol::contract_interfaces::oracles::band::{InstantiateMsg, ReferenceData}; -use shade_protocol::c_std::Uint128; - -use shade_protocol::storage::{bucket, bucket_read, Bucket, ReadonlyBucket}; - -pub static PRICE: &[u8] = b"prices"; - -pub fn price_r(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, PRICE) -} - -pub fn price_w(storage: &mut dyn Storage) -> Bucket { - bucket(storage, PRICE) -} - -pub fn init( - _deps: DepsMut, - _env: Env, - _msg: InstantiateMsg, -) -> StdResult { - Ok(Response::default()) -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - MockPrice { symbol: String, price: Uint128 }, -} - -pub fn handle( - deps: DepsMut, - _env: Env, - msg: ExecuteMsg, -) -> StdResult { - return match msg { - ExecuteMsg::MockPrice { symbol, price } => { - price_w(deps.storage).save(symbol.as_bytes(), &price)?; - Ok(Response::default()) - } - }; -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - GetReferenceData { - base_symbol: String, - quote_symbol: String, - }, - GetReferenceDataBulk { - base_symbols: Vec, - quote_symbols: Vec, - }, -} -pub fn query( - deps: Deps, - msg: QueryMsg, -) -> StdResult { - match msg { - QueryMsg::GetReferenceData { - base_symbol, - quote_symbol: _, - } => { - if let Some(price) = price_r(deps.storage).may_load(base_symbol.as_bytes())? { - return to_binary(&ReferenceData { - rate: price, - last_updated_base: 0, - last_updated_quote: 0, - }); - } - Err(StdError::generic_err("Missing Price Feed")) - } - QueryMsg::GetReferenceDataBulk { - base_symbols, - quote_symbols: _, - } => { - let mut results = Vec::new(); - - for sym in base_symbols { - if let Some(price) = price_r(deps.storage).may_load(sym.as_bytes())? { - results.push(ReferenceData { - rate: price, - last_updated_base: 0, - last_updated_quote: 0, - }); - } else { - return Err(StdError::GenericErr { - msg: "Missing Price Feed".to_string(), - backtrace: None, - }); - } - } - to_binary(&results) - } - } -} diff --git a/archived-contracts/mock_band/src/execute.rs b/archived-contracts/mock_band/src/execute.rs deleted file mode 100644 index 9949c6c..0000000 --- a/archived-contracts/mock_band/src/execute.rs +++ /dev/null @@ -1,424 +0,0 @@ -use shade_protocol::{ - admin::helpers::{validate_admin, AdminPermissions}, - c_std::{ - to_binary, - Addr, - Binary, - Coin, - CosmosMsg, - Deps, - DepsMut, - DistributionMsg, - Env, - MessageInfo, - Response, - StakingMsg, - StdError, - StdResult, - Uint128, - Validator, - }, -}; - -use shade_protocol::snip20::helpers::redeem_msg; - -use shade_protocol::{ - dao::{ - adapter, - scrt_staking::{Config, ExecuteAnswer, ValidatorBounds}, - }, - utils::{ - asset::{scrt_balance, Contract}, - generic_response::ResponseStatus, - wrap::{unwrap, wrap_and_send}, - }, -}; - -use crate::{ - query, - storage::{CONFIG, SELF_ADDRESS, UNBONDING}, -}; - -pub fn receive( - deps: DepsMut, - env: Env, - info: MessageInfo, - _sender: Addr, - _from: Addr, - amount: Uint128, - _msg: Option, -) -> StdResult { - //panic!("scrt staking Received {}", amount); - - let config = CONFIG.load(deps.storage)?; - - if info.sender != config.sscrt.address { - return Err(StdError::generic_err("Only accepts sSCRT")); - } - - let validator = choose_validator(deps, env.block.time.seconds())?; - - Ok(Response::new() - .add_messages(vec![ - redeem_msg(amount, None, None, &config.sscrt)?, - CosmosMsg::Staking(StakingMsg::Delegate { - validator: validator.address.clone(), - amount: Coin { - amount, - denom: "uscrt".to_string(), - }, - }), - ]) - .set_data(to_binary(&ExecuteAnswer::Receive { - status: ResponseStatus::Success, - validator, - })?)) -} - -pub fn try_update_config( - deps: DepsMut, - _env: Env, - info: MessageInfo, - config: Config, -) -> StdResult { - let cur_config = CONFIG.load(deps.storage)?; - - validate_admin( - &deps.querier, - AdminPermissions::ScrtStakingAdmin, - &info.sender, - &cur_config.admin_auth, - )?; - - // Save new info - CONFIG.save(deps.storage, &config)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { - status: ResponseStatus::Success, - })?), - ) -} - -/* Claim rewards and restake, hold enough for pending unbondings - * Send reserves unbonded funds to treasury - */ -pub fn update(deps: DepsMut, env: Env, _info: MessageInfo, asset: Addr) -> StdResult { - let mut messages = vec![]; - //let asset = deps.api.addr_validate(asset.as_str())?; - - let config = CONFIG.load(deps.storage)?; - - if asset != config.sscrt.address { - return Err(StdError::generic_err("Unrecognized Asset")); - } - - let scrt_balance = scrt_balance(deps, SELF_ADDRESS.load(deps.storage)?)?; - - // Claim Rewards - let rewards = query::rewards(deps.as_ref())?; - if !rewards.is_zero() { - messages.append(&mut withdraw_rewards(deps.as_ref())?); - } - - let mut stake_amount = rewards + scrt_balance; - let unbonding = UNBONDING.load(deps.storage)?; - - // Don't restake funds that unbonded - if unbonding < stake_amount { - stake_amount = stake_amount - unbonding; - } else { - stake_amount = Uint128::zero(); - } - - if stake_amount > Uint128::zero() { - let validator = choose_validator(deps, env.block.time.seconds())?; - messages.push(CosmosMsg::Staking(StakingMsg::Delegate { - validator: validator.address.clone(), - amount: Coin { - amount: stake_amount, - denom: "uscrt".to_string(), - }, - })); - } - - Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Update { - status: ResponseStatus::Success, - }, - )?)) -} - -pub fn unbond( - deps: DepsMut, - _env: Env, - info: MessageInfo, - asset: Addr, - amount: Uint128, -) -> StdResult { - /* Unbonding to the scrt staking contract - * Once scrt is on balance sheet, treasury can claim - * and this contract will take all scrt->sscrt and send - */ - - //let asset = deps.api.addr_validate(asset.as_str())?; - let config = CONFIG.load(deps.storage)?; - - if validate_admin( - &deps.querier, - AdminPermissions::ScrtStakingAdmin, - &info.sender, - &config.admin_auth, - ) - .is_err() - && config.owner != info.sender - { - return Err(StdError::generic_err("Unauthorized")); - } - - if asset != config.sscrt.address { - return Err(StdError::generic_err("Unrecognized Asset")); - } - - let self_address = SELF_ADDRESS.load(deps.storage)?; - let delegations = query::delegations(deps.as_ref())?; - - let delegated = Uint128::new( - delegations - .iter() - .map(|d| d.amount.amount.u128()) - .sum::(), - ); - let scrt_balance = scrt_balance(deps, self_address)?; - let rewards = query::rewards(deps.as_ref())?; - - let mut messages = vec![]; - - if !rewards.is_zero() { - messages.append(&mut withdraw_rewards(deps.as_ref())?); - } - - let mut undelegated = vec![]; - - let prev_unbonding = UNBONDING.load(deps.storage)?; - - let mut total = delegated + scrt_balance + rewards + delegated; - total -= prev_unbonding; - - if total - prev_unbonding < amount { - return Err(StdError::generic_err(format!( - "Total Unbond amount {} greater than delegated {}; rew {}, bal {}", - amount, delegated, rewards, scrt_balance - ))); - } - - let mut unbonding = amount; - let mut total_unbonding = amount + prev_unbonding; - let reserves = scrt_balance + rewards; - - if total_unbonding.is_zero() { - return Ok( - Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Unbond { - status: ResponseStatus::Success, - amount: unbonding, - })?), - ); - } - - // Send full unbonding - if total_unbonding <= reserves { - messages.append(&mut wrap_and_send( - unbonding, - config.owner, - config.sscrt, - None, - )?); - total_unbonding = Uint128::zero(); - } - // Send all reserves - else if !reserves.is_zero() { - messages.append(&mut wrap_and_send( - reserves, - config.owner, - config.sscrt, - None, - )?); - total_unbonding -= reserves; - } - - UNBONDING.save(deps.storage, &total_unbonding)?; - - while !total_unbonding.is_zero() { - // Unbond from largest validator first - let max_delegation = delegations.iter().max_by_key(|d| { - if undelegated.contains(&d.validator) { - Uint128::zero() - } else { - d.amount.amount - } - }); - - // No more delegated funds to unbond - match max_delegation { - None => { - break; - } - Some(delegation) => { - if undelegated.contains(&delegation.validator) - || delegation.amount.amount.clone() == Uint128::zero() - { - break; - } - - // This delegation isn't enough to fully unbond - if delegation.amount.amount.clone() < unbonding - && !delegation.amount.amount.clone().is_zero() - { - messages.push(CosmosMsg::Staking(StakingMsg::Undelegate { - validator: delegation.validator.clone(), - amount: delegation.amount.clone(), - })); - unbonding = unbonding - delegation.amount.amount.clone(); - undelegated.push(delegation.validator.clone()); - } else if !delegation.amount.amount.clone().is_zero() { - messages.push(CosmosMsg::Staking(StakingMsg::Undelegate { - validator: delegation.validator.clone(), - amount: Coin { - denom: delegation.amount.denom.clone(), - amount: unbonding, - }, - })); - unbonding = Uint128::zero(); - undelegated.push(delegation.validator.clone()); - } - } - } - } - - Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Unbond { - status: ResponseStatus::Success, - amount: unbonding, - }, - )?)) -} - -pub fn withdraw_rewards(deps: Deps) -> StdResult> { - let mut messages = vec![]; - let address = SELF_ADDRESS.load(deps.storage)?; - - for delegation in deps.querier.query_all_delegations(address.clone())? { - messages.push(CosmosMsg::Distribution( - DistributionMsg::WithdrawDelegatorReward { - validator: delegation.validator, - }, - )); - } - - Ok(messages) -} - -pub fn unwrap_and_stake( - _deps: DepsMut, - amount: Uint128, - validator: Validator, - token: Contract, -) -> StdResult> { - Ok(vec![ - // unwrap - unwrap(amount, token.clone())?, - // Stake - CosmosMsg::Staking(StakingMsg::Delegate { - validator: validator.address.clone(), - amount: Coin { - amount, - denom: "uscrt".to_string(), - }, - }), - ]) -} - -/* Claims completed unbondings, wraps them, - * and returns them to treasury - */ -pub fn claim(deps: DepsMut, _env: Env, _info: MessageInfo, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - //let asset = deps.api.addr_validate(asset.as_str())?; - if asset != config.sscrt.address { - return Err(StdError::generic_err("Unrecognized Asset")); - } - - let mut messages = vec![]; - - let unbond_amount = UNBONDING.load(deps.storage)?; - let claim_amount; - - let scrt_balance = scrt_balance(deps.as_ref(), SELF_ADDRESS.load(deps.storage)?)?; - - if scrt_balance >= unbond_amount { - claim_amount = unbond_amount; - } else { - // Claim Rewards - let rewards = query::rewards(deps.as_ref())?; - - if !rewards.is_zero() { - //assert!(false, "withdraw rewards"); - messages.append(&mut withdraw_rewards(deps.as_ref())?); - } - - if rewards + scrt_balance >= unbond_amount { - claim_amount = unbond_amount; - } else { - claim_amount = rewards + scrt_balance; - } - } - - if !claim_amount.is_zero() { - messages.append(&mut wrap_and_send( - claim_amount, - config.owner, - config.sscrt, - None, - )?); - - //assert!(false, "u - claim_amount: {} - {}", unbond_amount, claim_amount); - let u = UNBONDING.load(deps.storage)?; - UNBONDING.save(deps.storage, &(u - claim_amount))?; - } - - Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Claim { - status: ResponseStatus::Success, - amount: claim_amount, - }, - )?)) -} - -pub fn choose_validator(deps: DepsMut, seed: u64) -> StdResult { - let mut validators = deps.querier.query_all_validators()?; - - // filter down to viable candidates - if let Some(bounds) = (CONFIG.load(deps.storage)?).validator_bounds { - let mut candidates = vec![]; - - for validator in validators { - if is_validator_inbounds(&validator, &bounds) { - candidates.push(validator); - } - } - - validators = candidates; - } - - if validators.is_empty() { - return Err(StdError::generic_err("No validators within bounds")); - } - - // seed will likely be env.block.time.seconds() - Ok(validators[(seed % validators.len() as u64) as usize].clone()) -} - -pub fn is_validator_inbounds(validator: &Validator, bounds: &ValidatorBounds) -> bool { - validator.commission <= bounds.max_commission && validator.commission >= bounds.min_commission -} diff --git a/archived-contracts/mock_band/src/lib.rs b/archived-contracts/mock_band/src/lib.rs deleted file mode 100644 index 2943dbb..0000000 --- a/archived-contracts/mock_band/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod contract; diff --git a/archived-contracts/mock_secretswap_pair/.cargo/config b/archived-contracts/mock_secretswap_pair/.cargo/config deleted file mode 100644 index 882fe08..0000000 --- a/archived-contracts/mock_secretswap_pair/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[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/archived-contracts/mock_secretswap_pair/.circleci/config.yml b/archived-contracts/mock_secretswap_pair/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/archived-contracts/mock_secretswap_pair/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/mock_secretswap_pair/Cargo.toml b/archived-contracts/mock_secretswap_pair/Cargo.toml deleted file mode 100644 index 00380ef..0000000 --- a/archived-contracts/mock_secretswap_pair/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "mock_secretswap_pair" -version = "0.1.0" -authors = ["Jack Swenson "] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] -debug-print = ["shade-protocol/debug-print"] - -[dependencies] -bincode = "1.3.1" -shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ - "dex", -] } -schemars = "0.7" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/archived-contracts/mock_secretswap_pair/Makefile b/archived-contracts/mock_secretswap_pair/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/archived-contracts/mock_secretswap_pair/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/archived-contracts/mock_secretswap_pair/README.md b/archived-contracts/mock_secretswap_pair/README.md deleted file mode 100644 index 8fad675..0000000 --- a/archived-contracts/mock_secretswap_pair/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Mock Band Contract -* [Introduction](#Introduction) -* [Sections](#Sections) - * [User](#User) - * Queries - * [GetReferenceData](#GetReferenceData) -# Introduction -The Mocked Band contract is used to test locally when there is no official band contract available - -### Queries - -#### GetReferenceData -Get a hardcoded sample from an ETH query for testing locally -##### Response -```json -{ - "rate": "3119154999999000000000", - "last_updated_base": 1628548483, - "last_updated_quote": 3377610 -} -``` diff --git a/archived-contracts/mock_secretswap_pair/src/contract.rs b/archived-contracts/mock_secretswap_pair/src/contract.rs deleted file mode 100644 index c9127e9..0000000 --- a/archived-contracts/mock_secretswap_pair/src/contract.rs +++ /dev/null @@ -1,176 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use shade_protocol::{ - c_std::{ - to_binary, - Addr, - Api, - Binary, - Deps, - DepsMut, - Env, - Querier, - Response, - StdError, - StdResult, - Storage, - Uint128, - }, - contract_interfaces::{ - dex::{ - dex, - secretswap::{ - Asset, - AssetInfo, - PairQuery, - PairResponse, - PoolResponse, - SimulationResponse, - Token, - }, - }, - oracles::band::{InstantiateMsg, ReferenceData}, - }, - utils::asset::Contract, -}; - -use shade_protocol::storage::{singleton, singleton_read, ReadonlySingleton, Singleton}; - -pub static PAIR_INFO: &[u8] = b"pair_info"; -pub static POOL: &[u8] = b"pool"; - -pub fn pair_info_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, PAIR_INFO) -} - -pub fn pair_info_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, PAIR_INFO) -} - -pub fn pool_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, POOL) -} - -pub fn pool_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, POOL) -} - -pub fn init(_deps: DepsMut, _env: Env, _msg: InstantiateMsg) -> StdResult { - Ok(Response::default()) -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - MockPool { - token_a: Contract, - amount_a: Uint128, - token_b: Contract, - amount_b: Uint128, - }, -} - -pub fn handle(deps: DepsMut, _env: Env, msg: ExecuteMsg) -> StdResult { - return match msg { - ExecuteMsg::MockPool { - token_a, - amount_a, - token_b, - amount_b, - } => { - let asset_infos = vec![ - AssetInfo { - token: Token { - contract_addr: token_a.address, - token_code_hash: token_a.code_hash, - viewing_key: "SecretSwap".to_string(), - }, - }, - AssetInfo { - token: Token { - contract_addr: token_b.address, - token_code_hash: token_b.code_hash, - viewing_key: "SecretSwap".to_string(), - }, - }, - ]; - pool_w(deps.storage).save(&PoolResponse { - assets: vec![ - Asset { - amount: amount_a, - info: asset_infos[0].clone(), - }, - Asset { - amount: amount_b, - info: asset_infos[1].clone(), - }, - ], - total_share: Uint128::zero(), - })?; - - pair_info_w(deps.storage).save(&PairResponse { - asset_infos, - contract_addr: Addr::unchecked("".to_string()), - liquidity_token: Addr::unchecked("".to_string()), - token_code_hash: "".to_string(), - asset0_volume: Uint128::zero(), - asset1_volume: Uint128::zero(), - factory: Contract { - address: Addr::unchecked("".to_string()), - code_hash: "".to_string(), - }, - })?; - Ok(Response::default()) - } - }; -} - -pub fn query(deps: Deps, msg: PairQuery) -> StdResult { - match msg { - PairQuery::Pool {} => to_binary(&pool_r(deps.storage).load()?), - PairQuery::Pair {} => to_binary(&pair_info_r(deps.storage).load()?), - PairQuery::Simulation { offer_asset } => { - let pool = pool_r(deps.storage).load()?; - - if pool.assets[0].info == offer_asset.info { - /* - let take_amount = dex::pool_take_amount( - offer_asset.amount, - pool.assets[0].amount, - pool.assets[1].amount, - ); - - return Err(StdError::generic_err( - format!("INPUT 0 pools input: {}, give: {}, take: {}", - offer_asset.amount, - pool.assets[0].amount, - pool.assets[1].amount - ) - )); - */ - let resp = SimulationResponse { - return_amount: dex::pool_take_amount( - offer_asset.amount, - pool.assets[0].amount, - pool.assets[1].amount, - ), - spread_amount: Uint128::zero(), - commission_amount: Uint128::zero(), - }; - return to_binary(&resp); - } else if pool.assets[1].info == offer_asset.info { - return to_binary(&SimulationResponse { - return_amount: dex::pool_take_amount( - offer_asset.amount, - pool.assets[1].amount, - pool.assets[0].amount, - ), - spread_amount: Uint128::zero(), - commission_amount: Uint128::zero(), - }); - } - - return Err(StdError::generic_err("Not a token on this pair")); - } - } -} diff --git a/archived-contracts/mock_secretswap_pair/src/lib.rs b/archived-contracts/mock_secretswap_pair/src/lib.rs deleted file mode 100644 index d7d6c20..0000000 --- a/archived-contracts/mock_secretswap_pair/src/lib.rs +++ /dev/null @@ -1,43 +0,0 @@ -pub mod contract; - -#[cfg(target_arch = "wasm32")] -mod wasm { - use super::contract; - use shade_protocol::c_std::{ - do_handle, - do_init, - do_query, - ExternalApi, - ExternalQuerier, - ExternalStorage, - }; - - #[no_mangle] - extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { - do_init( - &contract::init::, - env_ptr, - msg_ptr, - ) - } - - #[no_mangle] - extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { - do_handle( - &contract::handle::, - env_ptr, - msg_ptr, - ) - } - - #[no_mangle] - extern "C" fn query(msg_ptr: u32) -> u32 { - do_query( - &contract::query::, - msg_ptr, - ) - } - - // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available - // automatically because we `use cosmwasm_std`. -} diff --git a/archived-contracts/mock_sienna_pair/Cargo.toml b/archived-contracts/mock_sienna_pair/Cargo.toml deleted file mode 100644 index 5508629..0000000 --- a/archived-contracts/mock_sienna_pair/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "mock_sienna_pair" -version = "0.1.0" -authors = ["Jack Swenson "] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] -debug-print = ["shade-protocol/debug-print"] - -[dependencies] -cosmwasm-schema = "1.1.5" -shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ - "dex", -] } diff --git a/archived-contracts/mock_sienna_pair/src/contract.rs b/archived-contracts/mock_sienna_pair/src/contract.rs deleted file mode 100644 index 6344be6..0000000 --- a/archived-contracts/mock_sienna_pair/src/contract.rs +++ /dev/null @@ -1,303 +0,0 @@ -use shade_protocol::{ - c_std::{ - shd_entry_point, from_binary, to_binary, - Addr, Binary, Decimal, Deps, DepsMut, - Env, MessageInfo, Response, StdError, - StdResult, QuerierWrapper, Uint128, - }, - contract_interfaces::{ - dex::{ - dex::pool_take_amount, - sienna::{ - self, - Pair, - TokenType, - }, - }, - snip20::helpers::{balance_query, send_msg, set_viewing_key_msg}, - }, - cosmwasm_schema::cw_serde, - utils::{ - asset::Contract, ExecuteCallback, InstantiateCallback, - storage::plus::{Item, ItemStorage}, - }, -}; -pub use shade_protocol::dex::sienna::{ - PairQuery as QueryMsg, - PairInfoResponse, - SimulationResponse, - ReceiverCallbackMsg, -}; - -#[cw_serde] -pub struct Config { - pub address: Addr, - pub viewing_key: String, - pub commission: Decimal, -} - -impl ItemStorage for Config { - const ITEM: Item<'static, Self> = Item::new("item-config"); -} - -#[cw_serde] -pub struct PairInfo { - pub token_0: Contract, - pub token_1: Contract, -} - -impl ItemStorage for PairInfo { - const ITEM: Item<'static, Self> = Item::new("item-pair"); -} - -#[cw_serde] -pub struct InstantiateMsg { - pub token_0: Contract, - pub token_1: Contract, - pub viewing_key: String, - pub commission: Decimal, -} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[shd_entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg -) -> StdResult { - let pair_info = PairInfo { - token_0: msg.token_0.clone(), - token_1: msg.token_1.clone(), - }; - pair_info.save(deps.storage)?; - - let config = Config { - address: env.contract.address, - viewing_key: msg.viewing_key.clone(), - commission: msg.commission, - }; - config.save(deps.storage)?; - - let messages = vec![ - set_viewing_key_msg( - msg.viewing_key.clone(), - None, - &msg.token_0, - )?, - set_viewing_key_msg( - msg.viewing_key, - None, - &msg.token_1, - )?, - ]; - Ok(Response::default() - .add_messages(messages)) -} - -#[cw_serde] -pub enum ExecuteMsg { - MockPool { - token_a: Contract, - token_b: Contract, - }, - // SNIP20 receiver interface - Receive { - sender: Addr, - from: Addr, - msg: Option, - amount: Uint128, - }, -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[shd_entry_point] -pub fn execute( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: ExecuteMsg -) -> StdResult { - match msg { - ExecuteMsg::MockPool { - token_a, - token_b, - } => { - let pair_info = PairInfo { - token_0: token_a, - token_1: token_b, - }; - - pair_info.save(deps.storage)?; - Ok(Response::default()) - } - - // Swap - ExecuteMsg::Receive { - from, - msg, - amount, - .. - } => { - let msg = msg.ok_or_else(|| { - StdError::generic_err("Receiver callback \"msg\" parameter cannot be empty.") - })?; - - match from_binary(&msg)? { - ReceiverCallbackMsg::Swap { expected_return, to } => { - let config = Config::load(deps.storage)?; - let pair = PairInfo::load(deps.storage)?; - - let (in_token, out_token) = if info.sender == pair.token_0.address { - (pair.token_0, pair.token_1) - } else if info.sender == pair.token_1.address { - (pair.token_1, pair.token_0) - } else { - return Err(StdError::generic_err("unauthorized")); - }; - - let (in_pool, out_pool) = query_pool_amounts( - &deps.querier, - &config, - in_token.clone(), - out_token.clone(), - )?; - - // Sienna takes commission before swap - let swap_amount = amount - (amount * config.commission); - let return_amount = pool_take_amount( - swap_amount, - in_pool - amount, // amount has already been added to this pool - out_pool, - ); - - if return_amount < expected_return.unwrap_or(Uint128::zero()) { - return Err(StdError::generic_err( - "Operation fell short of expected_return" - )); - } - - // send tokens - let return_addr = to.unwrap_or(from); - return Ok(Response::default() - .add_message(send_msg( - return_addr, - return_amount, - None, - None, - None, - &out_token, - )?)) - }, - } - } - } - -} - -#[shd_entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::PairInfo => { - let config = Config::load(deps.storage)?; - let pair_info = PairInfo::load(deps.storage)?; - let (amount_0, amount_1) = query_pool_amounts( - &deps.querier, - &config, - pair_info.token_0.clone(), - pair_info.token_1.clone(), - )?; - - to_binary(&PairInfoResponse { - pair_info: sienna::PairInfo { - liquidity_token: Contract { - address: Addr::unchecked("lp_token"), - code_hash: "hash".to_string(), - }, - factory: Contract { - address: Addr::unchecked("factory"), - code_hash: "hash".to_string(), - }, - pair: Pair { - token_0: TokenType::CustomToken { - contract_addr: pair_info.token_0.address, - token_code_hash: pair_info.token_0.code_hash, - }, - token_1: TokenType::CustomToken { - contract_addr: pair_info.token_1.address, - token_code_hash: pair_info.token_1.code_hash, - } - }, - amount_0, - amount_1, - total_liquidity: Uint128::zero(), - contract_version: 0, - }, - }) - }, - QueryMsg::SwapSimulation { offer } => { - let config = Config::load(deps.storage)?; - let pair = PairInfo::load(deps.storage)?; - let token_0 = pair.token_0; - let token_1 = pair.token_1; - - let (in_token, out_token) = match offer.token { - TokenType::CustomToken { contract_addr, .. } => { - if contract_addr == token_0.address { - (token_0, token_1) - } else if contract_addr == token_1.address { - (token_1, token_0) - } else { - return Err(StdError::generic_err(format!( - "The supplied token {}, is not managed by this contract", - contract_addr - ))) - } - }, - _ => { - return Err(StdError::generic_err("Only CustomToken supported")); - } - }; - - let (amount_0, amount_1) = query_pool_amounts( - &deps.querier, - &config, - in_token.clone(), - out_token.clone(), - )?; - - // Sienna takes commission before swap - let commission = offer.amount * config.commission; - let swap_amount = offer.amount - commission; - - return to_binary(&SimulationResponse { - return_amount: pool_take_amount( - swap_amount, - amount_0, - amount_1, - ), - spread_amount: Uint128::zero(), - commission_amount: commission, - }); - - } - } -} - -fn query_pool_amounts( - querier: &QuerierWrapper, - config: &Config, - token_0: Contract, - token_1: Contract, -) -> StdResult<(Uint128, Uint128)> { - Ok(( - balance_query(querier, config.address.clone(), config.viewing_key.clone(), &token_0)?, - balance_query(querier, config.address.clone(), config.viewing_key.clone(), &token_1)?, - )) -} diff --git a/archived-contracts/oracle/.cargo/config b/archived-contracts/oracle/.cargo/config deleted file mode 100644 index 882fe08..0000000 --- a/archived-contracts/oracle/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[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/archived-contracts/oracle/.circleci/config.yml b/archived-contracts/oracle/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/archived-contracts/oracle/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/oracle/Cargo.toml b/archived-contracts/oracle/Cargo.toml deleted file mode 100644 index 7a11ce9..0000000 --- a/archived-contracts/oracle/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "oracle" -version = "0.1.0" -authors = [ - "Guy Garcia ", - "Jackson Swenson ", -] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] -debug-print = ["shade-protocol/debug-print"] - -[dependencies] -bincode = "1.3.1" -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ - "oracles", - "dex", -] } -schemars = "0.7" - -[dev-dependencies] -fadroma = { branch = "v100", git = "https://github.com/a-stgeorge/fadroma-staking.git", features = ["ensemble", "scrt"] } -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ - "oracles", - "dex", - "ensemble" -] } diff --git a/archived-contracts/oracle/Makefile b/archived-contracts/oracle/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/archived-contracts/oracle/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/archived-contracts/oracle/README.md b/archived-contracts/oracle/README.md deleted file mode 100644 index c215ea3..0000000 --- a/archived-contracts/oracle/README.md +++ /dev/null @@ -1,142 +0,0 @@ -# Oracle Contract -* [Introduction](#Introduction) -* [Sections](#Sections) - * [Init](#Init) - * [User](#User) - * Queries - * [GetScrtPrice](#GetScrtPrice) -# Introduction -The oracle contract is used to query the price of different currencies - -# Sections - -## Init - -##### Request - -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|admin | string | New contract admin; SHOULD be a valid bech32 address, but contracts may use a different naming scheme as well | yes | -|sscrt | Contract | sSCRT snip20 token contract | no | -|band | Contract | Band protocol contract | no | - -## User - -### Messages - -#### UpdateConfig -Updates the given values - -##### Request - -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|owner | string | New contract owner; SHOULD be a valid bech32 address, but contracts may use a different naming scheme as well | yes | -|silk | Contract | Silk contract | no | -|oracle | Contract | Oracle contract | no | - -##### Response - -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|status | string | Always 'success' | no | - -###### Example - -```json -{ - "update_config": { - "status": "success" - } -} -``` - -#### RegisterSswapPair -Registers a Secret Swap pair that can then be queried - -##### Request - -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|pair | Contract | A Secret Swap Pair contract where one of the tokens must be sSCRT | no | - -##### Response - -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|status | string | Always 'success' | no | - -###### Example - -```json -{ - "register_sswap_pair": { - "status": "success" - } -} -``` - -### Queries - -#### Price -Get asset price according to band protocol or a registered SecretSwap pair - -##### Request - -|Name |Type |Description | optional | -|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| -|symbol | string | Asset abbreviation e.g. BTC/ETH/SCRT; | no | - -##### Response - -|Name |Type |Description | optional | -|-------------------|--------|----------------------------------------------------------------------------------------------------------------|----------| -|rate | u128 | The exchange rate of the asset against USD | no | -|last_updated_base | u64 | UNIX timestamp of when the base asset price was last updated (0 for SecretSwap pairs) | no | -|last_updated_quote | u64 | UNIX timestamp of when the quote asset price was last updated (0 for SecretSwap pairs) | no | - -###### Example - -```json -{ - "rate": 1470000000000000000, - "last_updated_base": 1628569146, - "last_updated_quote": 3377610 -} -``` - -#### Config -Get the current config - -#### Prices -Get prices of list of assets -##### Request - -|Name |Type |Description | optional | -|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| -|symbols | list | list of asset symbols e.g. BTC/ETH/SCRT; | no | -##### Response - -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|owner | string | Contract owner | no | -|band | Contract | Band contract | no | -|sscrt | Contract | sSCRT contract | no | - -###### Example - -Addresses are fictional. - -```json -{ - [ - { - "rate": "1470000000000000000", - "last_updated_base": 1628569146, - "last_updated_quote": 3377610 - }, - ... - ] -} -``` - diff --git a/archived-contracts/oracle/src/contract.rs b/archived-contracts/oracle/src/contract.rs deleted file mode 100644 index d82e9e8..0000000 --- a/archived-contracts/oracle/src/contract.rs +++ /dev/null @@ -1,72 +0,0 @@ -use crate::{handle, query, state::config_w}; -use shade_protocol::c_std::{ - - to_binary, - Api, - Binary, - Env, - DepsMut, - Response, - Querier, - StdResult, - Storage, -}; -use shade_protocol::contract_interfaces::oracles::oracle::{ - ExecuteMsg, - InstantiateMsg, - OracleConfig, - QueryMsg, -}; - -pub fn init( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - let state = OracleConfig { - admin: match msg.admin { - None => info.sender.clone(), - Some(admin) => admin, - }, - band: msg.band, - sscrt: msg.sscrt, - }; - - config_w(deps.storage).save(&state)?; - - deps.api.debug("Contract was initialized by {}", info.sender); - - Ok(Response::default()) -} - -pub fn handle( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> StdResult { - match msg { - ExecuteMsg::UpdateConfig { admin, band } => { - handle::try_update_config(deps, env, info, admin, band) - } - ExecuteMsg::RegisterPair { pair } => handle::register_pair(deps, env, info, pair), - ExecuteMsg::UnregisterPair { symbol, pair } => { - handle::unregister_pair(deps, env, info, symbol, pair) - } - ExecuteMsg::RegisterIndex { symbol, basket } => { - handle::register_index(deps, env, info, symbol, basket) - } - } -} - -pub fn query( - deps: Deps, - msg: QueryMsg, -) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query::config(deps)?), - QueryMsg::Price { symbol } => to_binary(&query::price(deps, symbol)?), - QueryMsg::Prices { symbols } => to_binary(&query::prices(deps, symbols)?), - } -} diff --git a/archived-contracts/oracle/src/handle.rs b/archived-contracts/oracle/src/handle.rs deleted file mode 100644 index 494f45f..0000000 --- a/archived-contracts/oracle/src/handle.rs +++ /dev/null @@ -1,313 +0,0 @@ -use crate::state::{config_r, config_w, dex_pairs_r, dex_pairs_w, index_r, index_w}; -use shade_protocol::c_std::{ - to_binary, - Api, - Env, - DepsMut, - Response, - Addr, - Querier, - StdError, - StdResult, - Storage, -}; -use shade_protocol::{ - snip20::helpers::{token_info_query, TokenInfo}, -}; -use shade_protocol::{ - contract_interfaces::{ - dex::{dex, secretswap, sienna}, - oracles::oracle::{HandleAnswer, IndexElement}, - snip20::helpers::Snip20Asset, - }, - utils::{asset::Contract, generic_response::ResponseStatus}, -}; - -pub fn register_pair( - deps: DepsMut, - env: Env, - info: MessageInfo, - pair: Contract, -) -> StdResult { - let config = config_r(deps.storage).load()?; - if info.sender != config.admin { - return Err(StdError::generic_err("unauthorized")); - } - - let mut trading_pair: Option = None; - let mut token_data: Option<(Contract, TokenInfo)> = None; - - if secretswap::is_pair(deps, pair.clone())? { - let td = fetch_token_paired_to_sscrt_on_sswap(deps, config.sscrt.address, &pair.clone())?; - token_data = Some(td.clone()); - - trading_pair = Some(dex::TradingPair { - contract: pair.clone(), - asset: Snip20Asset { - contract: td.clone().0, - token_info: td.clone().1, - token_config: None, - }, - dex: dex::Dex::SecretSwap, - }); - } else if sienna::is_pair(deps, pair.clone())? { - let td = fetch_token_paired_to_sscrt_on_sienna(deps, config.sscrt.address, &pair)?; - token_data = Some(td.clone()); - - trading_pair = Some(dex::TradingPair { - contract: pair.clone(), - asset: Snip20Asset { - contract: td.clone().0, - token_info: td.1, - token_config: None, - }, - dex: dex::Dex::SiennaSwap, - }); - } - - if let Some(tp) = trading_pair { - if let Some(td) = token_data { - // If symbol would override an index - if let Some(_) = index_r(deps.storage).may_load(td.1.symbol.as_bytes())? { - return Err(StdError::generic_err( - "Symbol already registered as an index", - )); - } - - if let Some(mut pairs) = dex_pairs_r(deps.storage).may_load(td.1.symbol.as_bytes())? { - //TODO: Check pair already registered - pairs.push(tp.clone()); - dex_pairs_w(deps.storage).save(td.1.symbol.as_bytes(), &pairs)?; - } else { - dex_pairs_w(deps.storage).save(td.1.symbol.as_bytes(), &vec![tp.clone()])?; - } - - return Ok(Response { - messages: vec![], - log: vec![], - data: Some(to_binary(&HandleAnswer::RegisterPair { - status: ResponseStatus::Success, - symbol: td.1.symbol, - pair: tp, - })?), - }); - } - return Err(StdError::generic_err("Failed to extract token data")); - } - - Err(StdError::generic_err("Failed to extract Trading Pair")) -} - -pub fn unregister_pair( - deps: DepsMut, - env: Env, - info: MessageInfo, - symbol: String, - pair: Contract, -) -> StdResult { - let config = config_r(deps.storage).load()?; - if info.sender != config.admin { - return Err(StdError::generic_err("unauthorized")); - } - - if let Some(mut pair_list) = dex_pairs_r(deps.storage).may_load(symbol.as_bytes())? { - if let Some(i) = pair_list - .iter() - .position(|p| p.contract.address == pair.address) - { - pair_list.remove(i); - - dex_pairs_w(deps.storage).save(symbol.as_bytes(), &pair_list)?; - - return Ok(Response { - messages: vec![], - log: vec![], - data: Some(to_binary(&HandleAnswer::UnregisterPair { - status: ResponseStatus::Success, - })?), - }); - } - } - - Err(StdError::generic_err("Pair not found")) -} - -/// -/// Will fetch token Contract along with TokenInfo for {symbol} in pair argument. -/// Pair argument must represent Secret Swap contract for {symbol}/sSCRT or sSCRT/{symbol}. -/// -fn fetch_token_paired_to_sscrt_on_sswap( - deps: DepsMut, - sscrt_addr: Addr, - pair: &Contract, -) -> StdResult<(Contract, TokenInfo)> { - // Query for snip20's in the pair - let response: secretswap::PairResponse = secretswap::PairQuery::Pair {}.query( - &deps.querier, - pair.code_hash.clone(), - pair.address.clone(), - )?; - - let mut token_contract = Contract { - address: response.asset_infos[0].token.contract_addr.clone(), - code_hash: response.asset_infos[0].token.token_code_hash.clone(), - }; - - // if thats sscrt, switch it - if token_contract.address == sscrt_addr { - token_contract = Contract { - address: response.asset_infos[1].token.contract_addr.clone(), - code_hash: response.asset_infos[1].token.token_code_hash.clone(), - } - } - // if neither is sscrt - else if response.asset_infos[1].token.contract_addr != sscrt_addr { - return Err(StdError::NotFound { - kind: "Not an sSCRT Pair".to_string(), - backtrace: None, - }); - } - - let token_info = token_info_query( - &deps.querier, - 1, - token_contract.code_hash.clone(), - token_contract.address.clone(), - )?; - - Ok((token_contract, token_info)) -} - -fn fetch_token_paired_to_sscrt_on_sienna( - deps: DepsMut, - sscrt_addr: Addr, - pair: &Contract, -) -> StdResult<(Contract, TokenInfo)> { - // Query for snip20's in the pair - let response: sienna::PairInfoResponse = (sienna::PairQuery::PairInfo).query( - &deps.querier, - pair.code_hash.clone(), - pair.address.clone(), - )?; - - let mut token_contract = match response.pair_info.pair.token_0 { - sienna::TokenType::CustomToken { - contract_addr, - token_code_hash, - } => Contract { - address: contract_addr, - code_hash: token_code_hash, - }, - sienna::TokenType::NativeToken { denom } => { - return Err(StdError::generic_err( - "Sienna Native Token pairs not supported", - )); - } - }; - - // if thats sscrt, switch it - if token_contract.address == sscrt_addr { - token_contract = match response.pair_info.pair.token_1 { - sienna::TokenType::CustomToken { - contract_addr, - token_code_hash, - } => Contract { - address: contract_addr, - code_hash: token_code_hash, - }, - sienna::TokenType::NativeToken { denom: _ } => { - return Err(StdError::generic_err( - "Sienna Native Token pairs not supported", - )); - } - }; - } - // if its not, make sure other is sscrt - else { - match response.pair_info.pair.token_1 { - sienna::TokenType::CustomToken { - contract_addr, - token_code_hash, - } => { - if contract_addr != sscrt_addr { - // if we get here, neither the first or second tokens were sscrt - return Err(StdError::NotFound { - kind: "Not an SSCRT Pair".to_string(), - backtrace: None, - }); - } - } - sienna::TokenType::NativeToken { denom: _ } => { - return Err(StdError::generic_err( - "Sienna Native Token pairs not supported", - )); - } - } - } - - let token_info = token_info_query( - &deps.querier, - 1, - token_contract.code_hash.clone(), - token_contract.address.clone(), - )?; - - Ok((token_contract, token_info)) -} - -pub fn register_index( - deps: DepsMut, - env: Env, - info: MessageInfo, - symbol: String, - basket: Vec, -) -> StdResult { - let config = config_r(deps.storage).load()?; - if info.sender != config.admin { - return Err(StdError::generic_err("unauthorized")); - } - - if let Some(pairs) = dex_pairs_r(deps.storage).may_load(symbol.as_bytes())? { - if pairs.len() > 0 { - return Err(StdError::generic_err( - "Symbol collides with an existing Dex pair", - )); - } - } - - index_w(deps.storage).save(symbol.as_bytes(), &basket)?; - - Ok(Response::new().set_data(to_binary(&HandleAnswer::RegisterIndex { - status: ResponseStatus::Success, - })?)) -} - -pub fn try_update_config( - deps: DepsMut, - env: Env, - info: MessageInfo, - admin: Option, - band: Option, -) -> StdResult { - let config = config_r(deps.storage).load()?; - if info.sender != config.admin { - return Err(StdError::generic_err("unauthorized")); - } - - // Save new info - let mut config = config_w(deps.storage); - config.update(|mut state| { - if let Some(admin) = admin { - state.admin = admin; - } - if let Some(band) = band { - state.band = band; - } - - Ok(state) - })?; - - Ok(Response::new().set_data(to_binary(&HandleAnswer::UpdateConfig { - status: ResponseStatus::Success, - })?)) -} diff --git a/archived-contracts/oracle/src/lib.rs b/archived-contracts/oracle/src/lib.rs deleted file mode 100644 index c5689f2..0000000 --- a/archived-contracts/oracle/src/lib.rs +++ /dev/null @@ -1,49 +0,0 @@ -pub mod contract; -pub mod handle; -pub mod query; -pub mod state; - -#[cfg(test)] -pub mod test; - -#[cfg(target_arch = "wasm32")] -mod wasm { - use super::contract; - use shade_protocol::c_std::{ - do_handle, - do_init, - do_query, - ExternalApi, - ExternalQuerier, - ExternalStorage, - }; - - #[no_mangle] - extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { - do_init( - &contract::init::, - env_ptr, - msg_ptr, - ) - } - - #[no_mangle] - extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { - do_handle( - &contract::handle::, - env_ptr, - msg_ptr, - ) - } - - #[no_mangle] - extern "C" fn query(msg_ptr: u32) -> u32 { - do_query( - &contract::query::, - msg_ptr, - ) - } - - // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available - // automatically because we `use cosmwasm_std`. -} diff --git a/archived-contracts/oracle/src/query.rs b/archived-contracts/oracle/src/query.rs deleted file mode 100644 index c3498ee..0000000 --- a/archived-contracts/oracle/src/query.rs +++ /dev/null @@ -1,177 +0,0 @@ -use crate::state::{config_r, dex_pairs_r, index_r}; -use shade_protocol::c_std::{Uint128, Uint512}; -use shade_protocol::c_std::{self, Api, DepsMut, Querier, StdError, StdResult, Storage}; -use shade_protocol::contract_interfaces::{ - dex::dex, - oracles::{ - band, - oracle::{IndexElement, QueryAnswer}, - }, -}; -use std::convert::TryFrom; - -pub fn config(deps: Deps) -> StdResult { - Ok(QueryAnswer::Config { - config: config_r(deps.storage).load()?, - }) -} - -pub fn price( - deps: Deps, - symbol: String, -) -> StdResult { - let config = config_r(deps.storage).load()?; - if symbol == "SSCRT" { - return band::reference_data(deps, "SCRT".to_string(), "USD".to_string(), config.band); - } - - if let Some(dex_pairs) = dex_pairs_r(deps.storage).may_load(symbol.as_bytes())? { - if dex_pairs.len() > 0 { - return Ok(band::ReferenceData { - rate: dex::aggregate_price(&deps, dex_pairs, config.sscrt, config.band)?, - last_updated_base: 0, - last_updated_quote: 0, - }); - } - } - - // Index - if let Some(index) = index_r(deps.storage).may_load(symbol.as_bytes())? { - return Ok(band::ReferenceData { - rate: eval_index(deps, index)?, - last_updated_base: 0, - last_updated_quote: 0, - }); - } - - // symbol/USD price from BAND - band::reference_data(deps, symbol, "USD".to_string(), config.band) -} - -pub fn prices( - deps: Deps, - symbols: Vec, -) -> StdResult> { - let mut band_symbols = vec![]; - let mut band_quotes = vec![]; - let mut results = vec![Uint128::zero(); symbols.len()]; - - let config = config_r(deps.storage).load()?; - - for (i, sym) in symbols.iter().enumerate() { - // Aggregate DEX pair prices - if let Some(dex_pairs) = dex_pairs_r(deps.storage).may_load(sym.as_bytes())? { - if dex_pairs.len() > 0 { - results[i] = dex::aggregate_price( - &deps, - dex_pairs, - config.sscrt.clone(), - config.band.clone(), - )?; - } - } - // Index - else if let Some(index) = index_r(deps.storage).may_load(sym.as_bytes())? { - results[i] = eval_index(deps, index)?; - } - // BAND - else { - band_symbols.push(sym.clone()); - band_quotes.push("USD".to_string()); - } - } - - // Query all the band prices - let ref_data = band::reference_data_bulk( - deps, - band_symbols.clone(), - band_quotes, - config_r(deps.storage).load()?.band, - )?; - - for (data, sym) in ref_data.iter().zip(band_symbols.iter()) { - let result_index = symbols - .iter() - .enumerate() - .find(|&s| *s.1 == *sym) - .unwrap() - .0; - results[result_index] = data.rate; - } - - Ok(results - .iter() - .map(|r| Uint128::new(r.u128())) - .collect()) -} - -pub fn eval_index( - deps: Deps, - index: Vec, -) -> StdResult { - let mut weight_sum = Uint512::zero(); - let mut price = Uint512::zero(); - - let mut band_bases = vec![]; - let mut band_quotes = vec![]; - let mut band_weights = vec![]; - let config = config_r(deps.storage).load()?; - - for element in index { - weight_sum += Uint512::from(element.weight.u128()); - - // Get dex prices - if let Some(dex_pairs) = dex_pairs_r(deps.storage).may_load(element.symbol.as_bytes())? { - return Err(StdError::generic_err(format!( - "EVAL INDEX DEX PAIRS {}", - element.symbol - ))); - - // NOTE: unreachable? - // price += - // dex::aggregate_price(deps, dex_pairs, config.sscrt.clone(), config.band.clone())? - // .multiply_ratio(element.weight, 10u128.pow(18)) - } - // Nested index - else if let Some(sub_index) = - index_r(deps.storage).may_load(element.symbol.as_bytes())? - { - // TODO: make sure no circular deps - return Err(StdError::generic_err(format!( - "EVAL NESTED INDEX {}", - element.symbol - ))); - // NOTE: unreachable? - // price += eval_index(&deps, sub_index)?.multiply_ratio(element.weight, 10u128.pow(18)) - } - // Setup to query for all at once from BAND - else { - band_weights.push(element.weight); - band_bases.push(element.symbol.clone()); - band_quotes.push("USD".to_string()); - } - } - - if band_bases.len() > 0 { - let ref_data = band::reference_data_bulk( - deps, - band_bases, - band_quotes, - config_r(deps.storage).load()?.band, - )?; - - for (reference, weight) in ref_data.iter().zip(band_weights.iter()) { - price += Uint512::from(reference.rate.u128()) * Uint512::from(weight.u128()) - / Uint512::from(10u128.pow(18)); - } - } - - Ok(Uint128::new( - Uint128::try_from( - price - .checked_mul(Uint512::from(10u128.pow(18)))? - .checked_div(weight_sum)?, - )? - .u128(), - )) -} diff --git a/archived-contracts/oracle/src/state.rs b/archived-contracts/oracle/src/state.rs deleted file mode 100644 index 8cc83ca..0000000 --- a/archived-contracts/oracle/src/state.rs +++ /dev/null @@ -1,45 +0,0 @@ -use shade_protocol::c_std::Storage; -use shade_protocol::storage::{ - bucket, - bucket_read, - singleton, - singleton_read, - Bucket, - ReadonlyBucket, - ReadonlySingleton, - Singleton, -}; -use shade_protocol::contract_interfaces::{ - dex::dex, - oracles::oracle::{IndexElement, OracleConfig}, -}; - -pub static CONFIG_KEY: &[u8] = b"config"; -pub static DEX_PAIRS: &[u8] = b"dex_pairs"; -pub static SSWAP_PAIRS: &[u8] = b"sswap_pairs"; -pub static SIENNA_PAIRS: &[u8] = b"sienna_pairs"; -pub static INDEX: &[u8] = b"index"; - -pub fn config_r(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, CONFIG_KEY) -} - -pub fn config_w(storage: &mut dyn Storage) -> Singleton { - singleton(storage, CONFIG_KEY) -} - -pub fn dex_pairs_r(storage: &dyn Storage) -> ReadonlyBucket> { - bucket_read(storage, DEX_PAIRS) -} - -pub fn dex_pairs_w(storage: &mut dyn Storage) -> Bucket> { - bucket(storage, DEX_PAIRS) -} - -pub fn index_r(storage: &dyn Storage) -> ReadonlyBucket> { - bucket_read(storage, INDEX) -} - -pub fn index_w(storage: &mut dyn Storage) -> Bucket> { - bucket(storage, INDEX) -} diff --git a/archived-contracts/oracle/src/test.rs b/archived-contracts/oracle/src/test.rs deleted file mode 100644 index 88790b3..0000000 --- a/archived-contracts/oracle/src/test.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::contract::{handle, init, query}; -use shade_protocol::c_std::{ - coins, - from_binary, - Binary, - Env, - DepsMut, - Response, - Addr, - Response, - StdError, - StdResult, -}; -use shade_protocol::fadroma::{ - ensemble::{ContractEnsemble, ContractHarness, MockDeps, MockEnv}, -}; - -pub struct Oracle; - -impl ContractHarness for Oracle { - // Use the method from the default implementation - fn init(&self, deps: &mut MockDeps, env: Env, msg: Binary) -> StdResult { - init( - deps, - env, - from_binary(&msg)?, - //mint::DefaultImpl, - ) - } - - fn handle(&self, deps: &mut MockDeps, env: Env, msg: Binary) -> StdResult { - handle( - deps, - env, - from_binary(&msg)?, - //mint::DefaultImpl, - ) - } - - // Override with some hardcoded value for the ease of testing - fn query(&self, deps: &MockDeps, msg: Binary) -> StdResult { - query( - deps, - from_binary(&msg)?, - //mint::DefaultImpl, - ) - } -} diff --git a/archived-contracts/oracle/tests/integration.rs b/archived-contracts/oracle/tests/integration.rs deleted file mode 100644 index 6c26b03..0000000 --- a/archived-contracts/oracle/tests/integration.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! This integration test tries to run and call the generated wasm. -//! It depends on a Wasm build being available, which you can create with `cargo wasm`. -//! Then running `cargo integration-test` will validate we can properly call into that generated Wasm. -//! -//! You can easily convert unit tests to integration tests. -//! 1. First copy them over verbatum, -//! 2. Then change -//! let mut deps = mock_dependencies(20, &[]); -//! to -//! let mut deps = mock_instance(WASM, &[]); -//! 3. If you access raw storage, where ever you see something like: -//! deps.storage.get(CONFIG_KEY).expect("no data stored"); -//! replace it with: -//! deps.with_storage(|store| { -//! let data = store.get(CONFIG_KEY).expect("no data stored"); -//! //... -//! }); -//! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) diff --git a/archived-contracts/peg_stability/Cargo.toml b/archived-contracts/peg_stability/Cargo.toml deleted file mode 100644 index a42b9e9..0000000 --- a/archived-contracts/peg_stability/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "peg_stability" -version = "0.1.0" -authors = [ - "jackb7", -] -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] - -[dependencies] -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["peg_stability", "admin"] } -shade-oracles = { git = "https://github.com/securesecrets/shade-oracle.git" } -cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" } -#shade-admin = { git = "https://github.com/securesecrets/shadeadmin", branch = "cosmwasm-v1-refactor", optional = true } -schemars = "0.7" - -[dev-dependencies] -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["multi-test"] } -shade-multi-test = { version = "0.1.0", path = "../../packages/multi_test", features = [ "snip20", "peg_stability", "admin"] } -#shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["ensemble"] } -#contract_harness = { version = "0.1.0", path = "../../packages/contract_harness" } diff --git a/archived-contracts/peg_stability/Makefile b/archived-contracts/peg_stability/Makefile deleted file mode 100644 index c49ed5d..0000000 --- a/archived-contracts/peg_stability/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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/archived-contracts/peg_stability/src/contract.rs b/archived-contracts/peg_stability/src/contract.rs deleted file mode 100644 index 3ab798d..0000000 --- a/archived-contracts/peg_stability/src/contract.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::{handle, query}; -use shade_protocol::{ - c_std::{ - shd_entry_point, - to_binary, - Binary, - Deps, - DepsMut, - Env, - MessageInfo, - Response, - StdResult, - }, - contract_interfaces::{ - peg_stability::{Config, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryMsg, ViewingKey}, - }, - snip20::helpers::set_viewing_key_msg, - utils::{ - generic_response::ResponseStatus, - storage::plus::{GenericItemStorage, ItemStorage}, - }, -}; - -#[shd_entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - let config = Config { - admin_auth: msg.admin_auth.clone(), - snip20: msg.snip20.clone(), - pairs: vec![], - oracle: msg.oracle.clone(), - treasury: msg.treasury.clone(), - symbols: vec![], - payback: msg.payback, - self_addr: env.contract.address.clone(), - dump_contract: msg.dump_contract, - }; - config.save(deps.storage)?; - ViewingKey::save(deps.storage, &msg.viewing_key.clone())?; - Ok(Response::new() - .add_message(set_viewing_key_msg( - msg.viewing_key.to_string(), - None, - &msg.snip20, - )?) - .set_data(to_binary(&ExecuteAnswer::Init { - status: ResponseStatus::Success, - })?)) -} - -#[shd_entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - ExecuteMsg::UpdateConfig { - admin_auth, - snip20, - oracle, - treasury, - payback, - dump_contract, - .. - } => handle::try_update_config( - deps, - env, - info, - admin_auth, - snip20, - oracle, - treasury, - payback, - dump_contract, - ), - ExecuteMsg::SetPairs { pairs, .. } => handle::try_set_pairs(deps, env, info, pairs), - ExecuteMsg::AppendPairs { pairs, .. } => handle::try_append_pairs(deps, env, info, pairs), - ExecuteMsg::RemovePair { pair_address, .. } => { - handle::try_remove_pair(deps, env, info, pair_address) - } - ExecuteMsg::Swap { .. } => handle::try_swap(deps, env, info), - } -} - -#[shd_entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::GetConfig {} => to_binary(&query::get_config(deps)?), - QueryMsg::Balance {} => to_binary(&query::get_balance(deps)?), - QueryMsg::GetPairs {} => to_binary(&query::get_pairs(deps)?), - QueryMsg::Profitable {} => to_binary(&query::profitable(deps)?), - } -} diff --git a/archived-contracts/peg_stability/src/handle.rs b/archived-contracts/peg_stability/src/handle.rs deleted file mode 100644 index bc395b4..0000000 --- a/archived-contracts/peg_stability/src/handle.rs +++ /dev/null @@ -1,233 +0,0 @@ -use crate::query::calculate_profit; -use shade_protocol::{ - admin::helpers::{validate_admin, AdminPermissions}, - c_std::{to_binary, Decimal, DepsMut, Env, MessageInfo, Response, StdError, StdResult}, - contract_interfaces::{ - peg_stability::{CalculateRes, Config, ExecuteAnswer, ViewingKey}, - sky::cycles::ArbPair, - }, - snip20::helpers::{send_msg, set_viewing_key_msg, token_info, TokenInfo}, - utils::{ - asset::Contract, - generic_response::ResponseStatus, - storage::plus::{GenericItemStorage, ItemStorage}, - }, -}; - -pub fn try_update_config( - deps: DepsMut, - _env: Env, - info: MessageInfo, - admin_auth: Option, - snip20: Option, - treasury: Option, - oracle: Option, - payback: Option, - dump_contract: Option, -) -> StdResult { - //Admin-only - let mut config = Config::load(deps.storage)?; - validate_admin( - &deps.querier, - AdminPermissions::StabilityAdmin, - info.sender.to_string(), - &config.admin_auth, - )?; - let mut messages = vec![]; - if let Some(admin_auth) = admin_auth { - config.admin_auth = admin_auth; - } - if let Some(snip20) = snip20 { - if !(config.pairs.len() == 0) { - return Err(StdError::generic_err( - "You must remove all pairs before chaning the snip20 asset", - )); - } - config.snip20 = snip20; - let viewing_key = ViewingKey::load(deps.storage)?; - messages.push(set_viewing_key_msg(viewing_key, None, &config.snip20)?) - } - if let Some(treasury) = treasury { - config.treasury = treasury; - } - if let Some(oracle) = oracle { - config.oracle = oracle; - } - if let Some(payback) = payback { - config.payback = payback; - } - if let Some(dump_contract) = dump_contract { - config.dump_contract = dump_contract; - } - Ok(Response::new() - .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::UpdateConfig { - config, - status: ResponseStatus::Success, - })?)) -} - -pub fn try_set_pairs( - deps: DepsMut, - _env: Env, - info: MessageInfo, - pairs: Vec, -) -> StdResult { - //Admin-only - let mut config = Config::load(deps.storage)?; - validate_admin( - &deps.querier, - AdminPermissions::StabilityAdmin, - info.sender.to_string(), - &config.admin_auth, - )?; - if pairs.is_empty() { - return Err(StdError::generic_err("Must pass at least one pair")); - } - let token0_info: TokenInfo = token_info(&deps.querier, &pairs[0].token0)?; - let token1_info: TokenInfo = token_info(&deps.querier, &pairs[0].token1)?; - let other_asset; - if config.snip20 == pairs[0].token0 { - config.symbols = vec![token0_info.symbol, token1_info.symbol]; - other_asset = pairs[0].token1.clone(); - } else { - config.symbols = vec![token1_info.symbol, token0_info.symbol]; - other_asset = pairs[0].token0.clone(); - } - for pair in pairs.clone() { - pair.validate_pair()?; - if !((pair.token0 == config.snip20 && pair.token1 == other_asset) - || (pair.token0 == other_asset && pair.token1 == config.snip20)) - { - return Err(StdError::generic_err( - "pairs must have the same assets as the rest of the pairs", - )); - } - } - config.pairs = pairs; - config.save(deps.storage)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::SetPairs { - pairs: config.pairs, - status: ResponseStatus::Success, - })?), - ) -} - -pub fn try_append_pairs( - deps: DepsMut, - env: Env, - info: MessageInfo, - pairs: Vec, -) -> StdResult { - let mut config = Config::load(deps.storage)?; - if config.pairs.is_empty() { - return Ok(try_set_pairs(deps, env, info, pairs)?); - } else if pairs.is_empty() { - return Err(StdError::generic_err("Must pass at least 1 pair")); - } - //Admin-only - validate_admin( - &deps.querier, - AdminPermissions::StabilityAdmin, - info.sender.to_string(), - &config.admin_auth, - )?; - let other_asset; - if config.snip20 == config.pairs[0].token0 { - other_asset = config.pairs[0].token1.clone(); - } else { - other_asset = config.pairs[0].token0.clone(); - } - for pair in pairs.clone() { - pair.validate_pair()?; - if !((pair.token0 == config.snip20 && pair.token1 == other_asset) - || (pair.token0 == other_asset && pair.token1 == config.snip20)) - { - return Err(StdError::generic_err( - "pairs must have the same assets as the rest of the pairs", - )); - } - } - config.pairs.append(&mut pairs.clone()); - config.save(deps.storage)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::AppendPairs { - pairs: config.pairs, - status: ResponseStatus::Success, - })?), - ) -} - -pub fn try_remove_pair( - deps: DepsMut, - _env: Env, - info: MessageInfo, - pair_address: String, -) -> StdResult { - //Admin-only - let mut config = Config::load(deps.storage)?; - validate_admin( - &deps.querier, - AdminPermissions::StabilityAdmin, - info.sender.to_string(), - &config.admin_auth, - )?; - if config.pairs.len() == 0 { - return Err(StdError::generic_err("No pairs to remove")); - } - for (i, pair) in config.pairs.iter().enumerate() { - match pair.pair_contract.clone() { - Some(contract) => { - if contract.address == pair_address { - config.pairs.remove(i); - config.save(deps.storage)?; - return Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::RemovePair { - pairs: config.pairs, - status: ResponseStatus::Success, - })?), - ); - } - } - None => continue, - } - } - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::RemovePair { - pairs: config.pairs, - status: ResponseStatus::Failure, - })?), - ) -} - -pub fn try_swap(deps: DepsMut, _env: Env, info: MessageInfo) -> StdResult { - let res: CalculateRes = calculate_profit(deps.as_ref())?; - let other_asset; - if res.config.snip20 == res.config.pairs[0].token0 { - other_asset = res.config.pairs[0].token1.clone(); - } else { - other_asset = res.config.pairs[0].token0.clone(); - } - let messages = vec![ - res.config.pairs[res.index].to_cosmos_msg(res.offer, res.min_expected)?, - send_msg( - res.config.dump_contract.address, - res.min_expected - res.payback, - None, - None, - None, - &other_asset, - )?, - send_msg(info.sender, res.payback, None, None, None, &other_asset)?, - ]; - Ok(Response::new() - .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::Swap { - profit: res.profit, - payback: res.payback, - status: ResponseStatus::Success, - })?)) -} diff --git a/archived-contracts/peg_stability/src/lib.rs b/archived-contracts/peg_stability/src/lib.rs deleted file mode 100644 index 9bb1910..0000000 --- a/archived-contracts/peg_stability/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod contract; -pub mod handle; -pub mod query; - -#[cfg(test)] -pub mod tests; diff --git a/archived-contracts/peg_stability/src/query.rs b/archived-contracts/peg_stability/src/query.rs deleted file mode 100644 index dee9575..0000000 --- a/archived-contracts/peg_stability/src/query.rs +++ /dev/null @@ -1,199 +0,0 @@ - -use shade_protocol::{ - c_std::{Deps, Isqrt, StdError, StdResult, Uint128, Uint256}, - contract_interfaces::{ - peg_stability::{CalculateRes, Config, QueryAnswer, ViewingKey}, - sky::cycles::Offer, - snip20, - }, - snip20::helpers::balance_query, - utils::{ - callback::Query, - storage::plus::{GenericItemStorage, ItemStorage}, - }, -}; -use std::convert::TryFrom; - -pub fn get_config(deps: Deps) -> StdResult { - Ok(QueryAnswer::Config { - config: Config::load(deps.storage)?, - }) -} - -pub fn get_balance(deps: Deps) -> StdResult { - let viewing_key = ViewingKey::load(deps.storage)?; - let config = Config::load(deps.storage)?; - - let res = snip20::QueryMsg::Balance { - address: config.self_addr.clone().to_string(), - key: viewing_key, - } - .query(&deps.querier, &config.snip20)?; - - match res { - snip20::QueryAnswer::Balance { amount } => Ok(QueryAnswer::Balance { snip20_bal: amount }), - _ => Err(StdError::generic_err("snip20 bal query failed")), - } -} - -pub fn get_pairs(deps: Deps) -> StdResult { - Ok(QueryAnswer::GetPairs { - pairs: Config::load(deps.storage)?.pairs, - }) -} - -pub fn profitable(deps: Deps) -> StdResult { - let res: CalculateRes = calculate_profit(deps)?; - Ok(QueryAnswer::Profitable { - profit: res.profit, - payback: res.payback, - }) -} - -pub fn calculate_profit(deps: Deps) -> StdResult { - let config = Config::load(deps.storage)?; - if config.pairs.len() < 1 { - return Err(StdError::generic_err("Must have pairs saved")); - } - /*let res: Vec = router::QueryMsg::GetPrices { - keys: config.symbols, - } - .query(&deps.querier, &config.oracle)? - .prices; - let prices = vec![ - Uint128::new(res[0].data.rate.u128()), - Uint128::new(res[1].data.rate.u128()), - ];*/ - let prices = vec![Uint128::zero(); 2]; - let mut max_swap_amount = Uint128::zero(); - let mut index = 0usize; - let snip20_dec; - let other_dec; - if config.pairs[0].token0 == config.snip20 { - snip20_dec = config.pairs[0].token0_decimals.u128() as u32; - other_dec = config.pairs[0].token1_decimals.u128() as u32; - } else { - snip20_dec = config.pairs[0].token1_decimals.u128() as u32; - other_dec = config.pairs[0].token0_decimals.u128() as u32; - } - for (i, pair) in config.pairs.iter().enumerate() { - let (t0_amount, t1_amount) = pair.clone().pool_amounts(deps)?; - let mut temp; - if config.snip20 == pair.token0 { - temp = calculate_swap_amount( - t0_amount.checked_mul(Uint128::new(10).pow(18 - snip20_dec.clone()))?, - t1_amount.checked_mul(Uint128::new(10).pow(18 - other_dec.clone()))?, - prices[0], - prices[1], - ); - } else { - temp = calculate_swap_amount( - t1_amount.checked_mul(Uint128::new(10).pow(18 - snip20_dec.clone()))?, - t0_amount.checked_mul(Uint128::new(10).pow(18 - other_dec.clone()))?, - prices[0], - prices[1], - ); - } - temp = temp / Uint128::new(10).pow(18 - snip20_dec); - if temp > max_swap_amount { - max_swap_amount = temp; - index = i; - } - } - let balance = balance_query( - &deps.querier, - config.self_addr.clone(), - ViewingKey::load(deps.storage)?, - &config.snip20, - )?; - if max_swap_amount > balance { - max_swap_amount = balance; - } - let initial_value = Uint256::from(max_swap_amount) - / Uint256::from(Uint128::new(10).pow(snip20_dec)) - * Uint256::from(prices[0]); - let offer = Offer { - asset: config.snip20.clone(), - amount: max_swap_amount.clone(), - }; - let swap_res = config.pairs[index] - .clone() - .simulate_swap(deps, offer.clone())?; - let after_swap = Uint256::from(swap_res) / Uint256::from(Uint128::new(10).pow(other_dec)) - * Uint256::from(prices[1]); - if after_swap > initial_value { - let profit = Uint128::try_from(after_swap - initial_value)?; - let payback = profit / prices[1] * config.payback; - return Ok(CalculateRes { - profit, - payback, - index, - config, - offer, - min_expected: swap_res, - }); - } - Ok(CalculateRes { - profit: Uint128::zero(), - payback: Uint128::zero(), - index: 0usize, - config, - offer, - min_expected: Uint128::zero(), - }) -} - -fn calculate_swap_amount( - poolsell: Uint128, - poolbuy: Uint128, - pricesell: Uint128, - pricebuy: Uint128, -) -> Uint128 { - let nom = Uint256::from(pricebuy.isqrt()) - * Uint256::from(poolbuy.isqrt()) - * Uint256::from(poolsell.isqrt()); - let denominator = Uint256::from(pricesell.isqrt()); - let right = nom / denominator; - let res = right.checked_sub(Uint256::from(poolsell)); - match res { - Ok(amount) => match Uint128::try_from(amount) { - Ok(amount) => amount, - Err(_error) => Uint128::MAX, - }, - Err(_error) => Uint128::zero(), - } -} - -#[cfg(test)] -mod test { - use crate::query::calculate_swap_amount; - use shade_protocol::{ - c_std::{Uint128}, - }; - - #[test] - fn test_swapamount1() { - assert_eq!( - calculate_swap_amount( - Uint128::new(10_000_000_000_000_000_000_000), - Uint128::new(100_000_000_000_000_000_000_000), - Uint128::new(10_000_000_000_000_000_000), - Uint128::new(1_000_000_000_000_000_000), - ) / Uint128::new(10).pow(12), - Uint128::zero() - ) - } - - #[test] - fn test_swapamount2() { - assert_eq!( - calculate_swap_amount( - Uint128::new(10_000_000_000_000_000_000_000), - Uint128::new(100_000_000_000_000_000_000_000), - Uint128::new(10_000_000_000_000_000_000), - Uint128::new(1_100_000_000_000_000_000), - ) / Uint128::new(10).pow(13), - Uint128::new(48_808_848) - ) - } -} diff --git a/archived-contracts/peg_stability/src/tests/handle.rs b/archived-contracts/peg_stability/src/tests/handle.rs deleted file mode 100644 index ab1ef55..0000000 --- a/archived-contracts/peg_stability/src/tests/handle.rs +++ /dev/null @@ -1,59 +0,0 @@ -/*#[test] -pub fn update_config_err_1() { - assert!(true); -} - -#[test] -pub fn update_config_err_2() { - assert!(true); -} - -#[test] -pub fn update_config_success() { - assert!(true); -} - -#[test] -pub fn set_pairs_err_1() { - assert!(true); -} - -#[test] -pub fn set_pairs_err_2() { - assert!(true); -} - -#[test] -pub fn set_pairs_success() { - assert!(true); -} - -#[test] -pub fn append_pairs_err() { - assert!(true); -} - -#[test] -pub fn append_pairs_success() { - assert!(true); -} - -#[test] -pub fn remove_pair_err_1() { - assert!(true); -} - -#[test] -pub fn remove_pair_err_2() { - assert!(true); -} - -#[test] -pub fn remove_pair_success() { - assert!(true); -} - -#[test] -pub fn swap_success() { - assert!(true); -}*/ diff --git a/archived-contracts/peg_stability/src/tests/mod.rs b/archived-contracts/peg_stability/src/tests/mod.rs deleted file mode 100644 index e45a6b4..0000000 --- a/archived-contracts/peg_stability/src/tests/mod.rs +++ /dev/null @@ -1,113 +0,0 @@ -pub mod handle; -pub mod query; - -use shade_multi_test::multi::{admin::Admin, peg_stability::PegStability, snip20::Snip20}; -use shade_protocol::{ - admin, - c_std::{Addr, Binary, ContractInfo, Decimal, Uint128}, - contract_interfaces::peg_stability, - multi_test::{App, Executor}, - snip20, - utils::{asset::Contract, MultiTestable}, -}; - -pub fn init_chain() -> (App, ContractInfo) { - let mut chain = App::default(); - - let stored_code = chain.store_code(Admin::default().contract()); - let admin = chain - .instantiate_contract( - stored_code, - Addr::unchecked("admin"), - &admin::InstantiateMsg { super_admin: None }, - &[], - "admin", - None, - ) - .unwrap(); - - //Instantiate band - //Instantiate oracle - (chain, admin) -} - -pub fn ps_no_oracle( - chain: App, - shd_admin: ContractInfo, - snip20: ContractInfo, -) -> (App, ContractInfo) { - let mut chain = chain; - - let stored_code = chain.store_code(PegStability::default().contract()); - let init_msg = peg_stability::InstantiateMsg { - admin_auth: Contract { - address: shd_admin.address, - code_hash: shd_admin.code_hash, - }, - snip20: Contract { - address: snip20.address, - code_hash: snip20.code_hash, - }, - oracle: Contract::default(), - treasury: Contract { - address: Addr::unchecked("admin"), - code_hash: "".into(), - }, - payback: Decimal::percent(15), - viewing_key: "SecureSoftware".into(), - dump_contract: Contract::default(), - }; - let pstable = chain - .instantiate_contract( - stored_code, - Addr::unchecked("admin"), - &init_msg, - &[], - "admin", - None, - ) - .unwrap(); - - (chain, pstable) -} - -pub fn init_snip20( - chain: App, - name: String, - symbol: String, - decimals: u128, -) -> (App, ContractInfo) { - let mut chain = chain; - - let stored_code = chain.store_code(Snip20::default().contract()); - let init_msg = snip20::InstantiateMsg { - name, - admin: Some("admin".into()), - query_auth: None, - symbol, - decimals: decimals as u8, - initial_balances: Some(vec![snip20::InitialBalance { - address: "admin".into(), - amount: Uint128::new(1_000_000_000_000_000 * 10 ^ decimals), - }]), - prng_seed: Binary::default(), - config: None, - }; - let snip20 = chain - .instantiate_contract( - stored_code, - Addr::unchecked("admin"), - &init_msg, - &[], - "admin", - None, - ) - .unwrap(); - - (chain, snip20) -} - -/*#[test] -pub fn test_test() { - assert!(true); -}*/ diff --git a/archived-contracts/peg_stability/src/tests/query.rs b/archived-contracts/peg_stability/src/tests/query.rs deleted file mode 100644 index 7aa053c..0000000 --- a/archived-contracts/peg_stability/src/tests/query.rs +++ /dev/null @@ -1,29 +0,0 @@ -/*#[test] -pub fn get_config() { - assert!(true); -} - -#[test] -pub fn get_balance() { - assert!(true); -} - -#[test] -pub fn get_pairs() { - assert!(true); -} - -#[test] -pub fn profitable_err_1() { - assert!(true); -} - -#[test] -pub fn profitable_err_2() { - assert!(true); -} - -#[test] -pub fn profitable() { - assert!(true); -}*/ diff --git a/archived-contracts/sky/.cargo/config b/archived-contracts/sky/.cargo/config deleted file mode 100644 index c1e7c50..0000000 --- a/archived-contracts/sky/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" -schema = "run --example schema" \ No newline at end of file diff --git a/archived-contracts/sky/.circleci/config.yml b/archived-contracts/sky/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/archived-contracts/sky/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/sky/Cargo.toml b/archived-contracts/sky/Cargo.toml deleted file mode 100644 index aa47a17..0000000 --- a/archived-contracts/sky/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "sky" -version = "0.1.0" -authors = [ - "jackb7", -] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] - -[dependencies] -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["sky", "sky-utils", "adapter", "math", "admin"] } -#shade-admin = { git = "https://github.com/securesecrets/shadeadmin", branch = "cosmwasm-v1-refactor", optional = true } -schemars = "0.7" - -[dev-dependencies] -#shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["ensemble"] } -#contract_harness = { version = "0.1.0", path = "../../packages/contract_harness" } diff --git a/archived-contracts/sky/Makefile b/archived-contracts/sky/Makefile deleted file mode 100644 index c49ed5d..0000000 --- a/archived-contracts/sky/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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/archived-contracts/sky/src/contract.rs b/archived-contracts/sky/src/contract.rs deleted file mode 100644 index 6973237..0000000 --- a/archived-contracts/sky/src/contract.rs +++ /dev/null @@ -1,161 +0,0 @@ -use crate::{execute, query}; -use shade_protocol::{ - c_std::{ - shd_entry_point, - to_binary, - Binary, - Decimal, - Deps, - DepsMut, - Env, - MessageInfo, - Response, - StdError, - StdResult, - SubMsg, - }, - contract_interfaces::{ - dao::adapter, - sky::{Config, Cycles, ExecuteMsg, InstantiateMsg, QueryMsg, SelfAddr, ViewingKeys}, - }, - snip20::helpers::set_viewing_key_msg, - utils::storage::plus::ItemStorage, -}; - -#[shd_entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - let state = Config { - shade_admin: msg.shade_admin, - shd_token: msg.shd_token.clone(), - silk_token: msg.silk_token.clone(), - sscrt_token: msg.sscrt_token.clone(), - treasury: msg.treasury, - payback_rate: msg.payback_rate, - }; - - if msg.payback_rate == Decimal::zero() { - return Err(StdError::generic_err("payback rate cannot be zero")); - } - - state.save(deps.storage)?; - SelfAddr(env.contract.address).save(deps.storage)?; - Cycles(vec![]).save(deps.storage)?; - - deps.api - .debug(&format!("Contract was initialized by {}", info.sender)); - - let messages = vec![ - SubMsg::new(set_viewing_key_msg( - msg.viewing_key.clone().to_string(), - None, - &msg.shd_token.clone(), - )?), - SubMsg::new(set_viewing_key_msg( - msg.viewing_key.clone().to_string(), - None, - &msg.silk_token.clone(), - )?), - SubMsg::new(set_viewing_key_msg( - msg.viewing_key.clone().to_string(), - None, - &msg.sscrt_token.clone(), - )?), - ]; - - ViewingKeys(msg.viewing_key).save(deps.storage)?; - - Ok(Response::new().add_submessages(messages)) -} - -#[shd_entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - ExecuteMsg::UpdateConfig { - shade_admin, - shd_token, - silk_token, - sscrt_token, - treasury, - payback_rate, - .. - } => execute::try_update_config( - deps, - env, - info, - shade_admin, - shd_token, - silk_token, - sscrt_token, - treasury, - payback_rate, - ), - ExecuteMsg::SetCycles { cycles, .. } => execute::try_set_cycles(deps, env, info, cycles), - ExecuteMsg::AppendCycles { cycle, .. } => execute::try_append_cycle(deps, env, info, cycle), - ExecuteMsg::UpdateCycle { cycle, index, .. } => { - execute::try_update_cycle(deps, env, info, cycle, index) - } - ExecuteMsg::RemoveCycle { index, .. } => execute::try_remove_cycle(deps, env, info, index), - ExecuteMsg::ArbCycle { amount, index, .. } => { - execute::try_arb_cycle(deps, env, info, amount, index) - } - ExecuteMsg::ArbAllCycles { amount, .. } => { - execute::try_arb_all_cycles(deps, env, info, amount) - } - ExecuteMsg::Adapter(adapter) => match adapter { - adapter::SubExecuteMsg::Unbond { asset, amount } => { - let asset = deps.api.addr_validate(&asset)?; - execute::try_adapter_unbond(deps, env, info, asset, amount) - } - adapter::SubExecuteMsg::Claim { asset } => { - let asset = deps.api.addr_validate(&asset)?; - execute::try_adapter_claim(deps, env, asset) - } - adapter::SubExecuteMsg::Update { asset } => { - let asset = deps.api.addr_validate(&asset)?; - execute::try_adapter_update(deps, env, asset) - } - }, - } -} - -#[shd_entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::GetConfig {} => to_binary(&query::config(deps)?), - QueryMsg::Balance {} => to_binary(&query::get_balances(deps)?), - QueryMsg::GetCycles {} => to_binary(&query::get_cycles(deps)?), - QueryMsg::IsCycleProfitable { amount, index } => { - to_binary(&query::cycle_profitability(deps, amount, index)?) - } - QueryMsg::IsAnyCycleProfitable { amount } => { - to_binary(&query::any_cycles_profitable(deps, amount)?) - } - QueryMsg::Adapter(adapter) => match adapter { - adapter::SubQueryMsg::Balance { asset } => to_binary(&query::adapter_balance( - deps, - deps.api.addr_validate(&asset)?, - )?), - adapter::SubQueryMsg::Claimable { asset } => to_binary(&query::adapter_claimable( - deps, - deps.api.addr_validate(&asset)?, - )?), - adapter::SubQueryMsg::Unbonding { asset } => to_binary(&query::adapter_unbonding( - deps, - deps.api.addr_validate(&asset)?, - )?), - adapter::SubQueryMsg::Unbondable { asset } => to_binary(&query::adapter_unbondable( - deps, - deps.api.addr_validate(&asset)?, - )?), - adapter::SubQueryMsg::Reserves { asset } => to_binary(&query::adapter_reserves( - deps, - deps.api.addr_validate(&asset)?, - )?), - }, - } -} diff --git a/archived-contracts/sky/src/execute.rs b/archived-contracts/sky/src/execute.rs deleted file mode 100644 index afea57a..0000000 --- a/archived-contracts/sky/src/execute.rs +++ /dev/null @@ -1,433 +0,0 @@ -use crate::query::{any_cycles_profitable, cycle_profitability}; -use shade_protocol::{ - admin::helpers::{validate_admin, AdminPermissions}, - c_std::{ - to_binary, - Addr, - Decimal, - DepsMut, - Env, - MessageInfo, - Response, - StdError, - StdResult, - SubMsg, - Uint128, - }, - contract_interfaces::{ - dao::adapter, - sky::{ - self, - cycles::{Cycle, Offer}, - Config, - Cycles, - ExecuteAnswer, - ViewingKeys, - }, - }, - snip20::helpers::{send_msg, set_viewing_key_msg}, - utils::{ - asset::Contract, - generic_response::ResponseStatus, - storage::plus::ItemStorage, - ExecuteCallback, - }, -}; - -pub fn try_update_config( - deps: DepsMut, - _env: Env, - info: MessageInfo, - shade_admin: Option, - shd_token: Option, - silk_token: Option, - sscrt_token: Option, - treasury: Option, - payback_rate: Option, -) -> StdResult { - //Admin-only - let mut config = Config::load(deps.storage)?; - validate_admin( - &deps.querier, - AdminPermissions::SkyAdmin, - info.sender.to_string(), - &config.shade_admin, - )?; - - let mut messages = vec![]; - - if let Some(shade_admin) = shade_admin { - config.shade_admin = shade_admin; - } - if let Some(shd_token) = shd_token { - config.shd_token = shd_token; - messages.push(SubMsg::new(set_viewing_key_msg( - ViewingKeys::load(deps.storage)?.0, - None, - &config.shd_token.clone(), - )?)); - } - if let Some(silk_token) = silk_token { - config.silk_token = silk_token; - messages.push(SubMsg::new(set_viewing_key_msg( - ViewingKeys::load(deps.storage)?.0, - None, - &config.silk_token.clone(), - )?)); - } - if let Some(sscrt_token) = sscrt_token { - config.sscrt_token = sscrt_token; - messages.push(SubMsg::new(set_viewing_key_msg( - ViewingKeys::load(deps.storage)?.0, - None, - &config.sscrt_token.clone(), - )?)); - } - if let Some(treasury) = treasury { - config.treasury = treasury; - } - if let Some(payback_rate) = payback_rate { - if payback_rate == Decimal::zero() { - return Err(StdError::generic_err("payback_rate cannot be zero")); - } - config.payback_rate = payback_rate; - } - config.save(deps.storage)?; - Ok(Response::new() - .set_data(to_binary(&ExecuteAnswer::UpdateConfig { status: true })?) - .add_submessages(messages)) -} - -pub fn try_set_cycles( - deps: DepsMut, - _env: Env, - info: MessageInfo, - cycles_to_set: Vec, -) -> StdResult { - //Admin-only - let shade_admin = Config::load(deps.storage)?.shade_admin; - validate_admin( - &deps.querier, - AdminPermissions::SkyAdmin, - info.sender.to_string(), - &shade_admin, - )?; - - if cycles_to_set.clone().len() > 40 { - return Err(StdError::generic_err("Too many cycles")); - } - - // validate cycles - for cycle in cycles_to_set.clone() { - cycle.validate_cycle()?; - } - - let new_cycles = Cycles(cycles_to_set); - new_cycles.save(deps.storage)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetCycles { status: true })?)) -} - -pub fn try_append_cycle( - deps: DepsMut, - _env: Env, - info: MessageInfo, - cycles_to_add: Vec, -) -> StdResult { - //Admin-only - let shade_admin = Config::load(deps.storage)?.shade_admin; - validate_admin( - &deps.querier, - AdminPermissions::SkyAdmin, - info.sender.to_string(), - &shade_admin, - )?; - - for cycle in cycles_to_add.clone() { - cycle.validate_cycle()?; - } - - let mut cycles = Cycles::load(deps.storage)?; - - if cycles.0.clone().len() + cycles_to_add.clone().len() > 40 { - return Err(StdError::generic_err("Too many cycles")); - } - - cycles.0.append(&mut cycles_to_add.clone()); - - cycles.save(deps.storage)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::AppendCycles { status: true })?)) -} - -pub fn try_update_cycle( - deps: DepsMut, - _env: Env, - info: MessageInfo, - cycle: Cycle, - index: Uint128, -) -> StdResult { - let i = index.u128() as usize; - //Admin-only - let shade_admin = Config::load(deps.storage)?.shade_admin; - validate_admin( - &deps.querier, - AdminPermissions::SkyAdmin, - info.sender.to_string(), - &shade_admin, - )?; - - cycle.validate_cycle()?; - let mut cycles = Cycles::load(deps.storage)?; - if i > cycles.0.clone().len() - 1 { - return Err(StdError::generic_err("index out of bounds")); - } - cycles.0[i] = cycle; - cycles.save(deps.storage)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::UpdateCycle { status: true })?)) -} - -pub fn try_remove_cycle( - deps: DepsMut, - _env: Env, - info: MessageInfo, - index: Uint128, -) -> StdResult { - let i = index.u128() as usize; - //Admin-only - let shade_admin = Config::load(deps.storage)?.shade_admin; - validate_admin( - &deps.querier, - AdminPermissions::SkyAdmin, - info.sender.to_string(), - &shade_admin, - )?; - - // I'm pissed I couldn't do this in one line - let mut cycles = Cycles::load(deps.storage)?.0; - - if i > cycles.clone().len() - 1 { - return Err(StdError::generic_err("index out of bounds")); - } - - cycles.remove(i); - Cycles(cycles).save(deps.storage)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RemoveCycle { status: true })?)) -} - -pub fn try_arb_cycle( - deps: DepsMut, - _env: Env, - info: MessageInfo, - amount: Uint128, - index: Uint128, -) -> StdResult { - let mut messages = vec![]; - let mut return_swap_amounts = vec![]; - let mut payback_amount = Uint128::zero(); - let i = index.u128() as usize; - // cur_asset will keep track of the asset that we currently "have" - let mut cur_asset = Contract { - address: info.sender.clone(), - code_hash: "".to_string(), - }; - - // don't need to check for an index out of bounds since that check will happen in - // cycle_profitability - let res = cycle_profitability(deps.as_ref(), amount, index)?; // get profitability data from query - match res { - sky::QueryAnswer::IsCycleProfitable { - is_profitable, - direction, - swap_amounts, - profit, - } => { - return_swap_amounts = swap_amounts.clone(); - if direction.pair_addrs[0] // test to see which of the token attributes are the proposed starting addr - .token0 - == direction.start_addr.clone() - { - cur_asset = direction.pair_addrs[0].token0.clone(); - } else { - cur_asset = direction.pair_addrs[0].token1.clone(); - } - // if tx is unprofitable, err out - if !is_profitable { - return Err(StdError::generic_err("Unprofitable")); - } - //loop through the pairs in the cycle - for (i, arb_pair) in direction.pair_addrs.clone().iter().enumerate() { - // if it's the last pair, set our minimum expected amount, otherwise, this field - // should be zero - if direction.pair_addrs.len() - 1 == i { - messages.push(SubMsg::new(arb_pair.to_cosmos_msg( - Offer { - asset: cur_asset.clone(), - amount: swap_amounts[i], - }, - amount, - )?)); - } else { - messages.push(SubMsg::new(arb_pair.to_cosmos_msg( - Offer { - asset: cur_asset.clone(), - amount: swap_amounts[i], - }, - Uint128::zero(), - )?)); - } - // reset cur asset to the other asset held in the struct - if cur_asset == arb_pair.token0.clone() { - cur_asset = arb_pair.token1.clone(); - } else { - cur_asset = arb_pair.token0.clone(); - } - } - // calculate payback amount - payback_amount = profit * Config::load(deps.storage)?.payback_rate; - - // add the payback msg - messages.push(SubMsg::new(send_msg( - info.sender, - Uint128::new(payback_amount.u128()), - None, - None, - None, - &cur_asset.clone(), - )?)); - } - _ => {} - } - - // the final cur_asset should be the same as the start_addr - if !(cur_asset.clone() == Cycles::load(deps.storage)?.0[i].start_addr) { - return Err(StdError::generic_err( - "final asset not equal to start asset", - )); - } - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::ExecuteArbCycle { - status: true, - swap_amounts: return_swap_amounts, - payback_amount, - })?), - ) -} - -pub fn try_arb_all_cycles( - deps: DepsMut, - env: Env, - _info: MessageInfo, - amount: Uint128, -) -> StdResult { - let mut total_profit = Uint128::zero(); - let mut messages = vec![]; - let res = any_cycles_profitable(deps.as_ref(), amount)?; // get profitability data from query - match res { - sky::QueryAnswer::IsAnyCycleProfitable { - is_profitable, - profit, - .. - } => { - // loop through the data returned for each cycle - for (i, profit_bool) in is_profitable.iter().enumerate() { - // if a cycle is profitable call the try_arb_cycle fn and keep track of the - // total_profit - if profit_bool.clone() { - messages.push(SubMsg::new( - sky::ExecuteMsg::ArbCycle { - amount, - index: Uint128::from(i as u128), - padding: None, - } - .to_cosmos_msg( - &Contract { - address: env.contract.address.clone(), - code_hash: env.contract.code_hash.clone(), - }, - vec![], - )?, - )); - total_profit = total_profit.clone().checked_add(profit[i])?; - } - } - } - _ => {} - } - // calculate payback_amount - let payback_amount = total_profit * Config::load(deps.storage)?.payback_rate; - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::ArbAllCycles { - status: true, - payback_amount, - })?), - ) -} - -pub fn try_adapter_unbond( - deps: DepsMut, - _env: Env, - info: MessageInfo, - asset: Addr, - amount: Uint128, -) -> StdResult { - let config = Config::load(deps.storage)?; - // Error out if anyone other than the treasury is asking for money - if !(info.sender == config.treasury.address) { - return Err(StdError::generic_err("Unauthorized")); - } - // Error out if the treasury is asking for an asset sky doesn't account for - if !(config.shd_token.address == asset - || config.silk_token.address == asset - || config.sscrt_token.address == asset) - { - return Err(StdError::generic_err("Unrecognized asset")); - } - // initialize this var to whichever token the treasury is asking for - let contract; - if config.shd_token.address == asset { - contract = config.shd_token; - } else if config.silk_token.address == asset { - contract = config.silk_token; - } else { - contract = config.sscrt_token; - } - // send the msg - let messages = vec![send_msg( - config.treasury.address, - Uint128::new(amount.u128()), - None, - None, - None, - &contract, - )?]; - - Ok(Response::new() - .set_data(to_binary(&adapter::ExecuteAnswer::Unbond { - status: ResponseStatus::Success, - amount: Uint128::new(amount.u128()), - })?) - .add_messages(messages)) -} - -// Unessesary for sky -pub fn try_adapter_claim(_deps: DepsMut, _env: Env, _asset: Addr) -> StdResult { - Ok( - Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Claim { - status: ResponseStatus::Success, - amount: Uint128::zero(), - })?), - ) -} - -// Unessesary for sky -pub fn try_adapter_update(_deps: DepsMut, _env: Env, _asset: Addr) -> StdResult { - Ok( - Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Update { - status: ResponseStatus::Success, - })?), - ) -} diff --git a/archived-contracts/sky/src/lib.rs b/archived-contracts/sky/src/lib.rs deleted file mode 100644 index a4f4984..0000000 --- a/archived-contracts/sky/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod contract; -pub mod execute; -pub mod query; diff --git a/archived-contracts/sky/src/query.rs b/archived-contracts/sky/src/query.rs deleted file mode 100644 index bb43ec6..0000000 --- a/archived-contracts/sky/src/query.rs +++ /dev/null @@ -1,334 +0,0 @@ -use shade_protocol::{ - c_std::{Addr, Deps, StdError, StdResult, Uint128}, - contract_interfaces::{ - dao::adapter, - sky::{ - cycles::{Offer}, - Config, - Cycles, - QueryAnswer, - SelfAddr, - ViewingKeys, - }, - snip20, - }, - utils::{storage::plus::ItemStorage, Query}, -}; - -pub fn config(deps: Deps) -> StdResult { - Ok(QueryAnswer::Config { - config: Config::load(deps.storage)?, - }) -} - -pub fn get_balances(deps: Deps) -> StdResult { - let viewing_key = ViewingKeys::load(deps.storage)?.0; - let self_addr = SelfAddr::load(deps.storage)?.0; - let config = Config::load(deps.storage)?; - - // Query shd balance - let mut res = snip20::QueryMsg::Balance { - address: self_addr.clone().to_string(), - key: viewing_key.clone(), - } - .query(&deps.querier, &config.shd_token.clone())?; - - let shd_bal = match res { - snip20::QueryAnswer::Balance { amount } => amount, - _ => Uint128::zero(), - }; - - // Query silk balance - res = snip20::QueryMsg::Balance { - address: self_addr.clone().to_string(), - key: viewing_key.clone(), - } - .query(&deps.querier, &config.silk_token.clone())?; - - let silk_bal = match res { - snip20::QueryAnswer::Balance { amount } => amount, - _ => Uint128::zero(), - }; - - // Query sscrt balance - res = snip20::QueryMsg::Balance { - address: self_addr.clone().to_string(), - key: viewing_key.clone(), - } - .query(&deps.querier, &config.sscrt_token.clone())?; - - let sscrt_bal = match res { - snip20::QueryAnswer::Balance { amount } => amount, - _ => Uint128::zero(), - }; - - Ok(QueryAnswer::Balance { - shd_bal, - silk_bal, - sscrt_bal, - }) -} - -pub fn get_cycles(deps: Deps) -> StdResult { - //Need to make private eventually - Ok(QueryAnswer::GetCycles { - cycles: Cycles::load(deps.storage)?.0, - }) -} - -pub fn cycle_profitability(deps: Deps, amount: Uint128, index: Uint128) -> StdResult { - let mut cycles = Cycles::load(deps.storage)?.0; - let mut swap_amounts = vec![amount]; - let i = index.u128() as usize; - - if (i) >= cycles.len() { - return Err(StdError::generic_err("Index passed is out of bounds")); - } - - // set up inital offer - let mut current_offer = Offer { - asset: cycles[i].start_addr.clone(), - amount, - }; - - //loop through the pairs in the cycle - for arb_pair in cycles[i].pair_addrs.clone() { - // simulate swap will run a query with respect to which dex or minting that the pair says - // it is - let estimated_return = arb_pair - .clone() - .simulate_swap(deps, current_offer.clone())?; - swap_amounts.push(estimated_return.clone()); - // set up the next offer with the other token contract in the pair and the expected return - // from the last query - if current_offer.asset.code_hash.clone() == arb_pair.token0.code_hash.clone() { - current_offer = Offer { - asset: arb_pair.token1.clone(), - amount: estimated_return, - }; - } else { - current_offer = Offer { - asset: arb_pair.token0.clone(), - amount: estimated_return, - }; - } - } - - if swap_amounts.len() > cycles[i].pair_addrs.clone().len() { - return Err(StdError::generic_err("More swap amounts than arb pairs")); - } - - // if the last calculated swap is greater than the initial amount, return true - if current_offer.amount.u128() > amount.u128() { - return Ok(QueryAnswer::IsCycleProfitable { - is_profitable: true, - direction: cycles[i].clone(), - swap_amounts, - profit: current_offer.amount.checked_sub(amount)?, - }); - } - - // reset these variables in order to check the other way - swap_amounts = vec![amount]; - current_offer = Offer { - asset: cycles[i].start_addr.clone(), - amount, - }; - - // this is a fancy way of iterating through a vec in reverse - for arb_pair in cycles[i].pair_addrs.clone().iter().rev() { - // get the estimated return from the simulate swap function - let estimated_return = arb_pair - .clone() - .simulate_swap(deps, current_offer.clone())?; - swap_amounts.push(estimated_return.clone()); - // set the current offer to the other asset we are swapping into - if current_offer.asset.code_hash.clone() == arb_pair.token0.code_hash.clone() { - current_offer = Offer { - asset: arb_pair.token1.clone(), - amount: estimated_return, - }; - } else { - current_offer = Offer { - asset: arb_pair.token0.clone(), - amount: estimated_return, - }; - } - } - - // check to see if this direction was profitable - if current_offer.amount > amount { - // do an inplace reversal of the pair_addrs so that we know which way the opportunity goes - cycles[i].pair_addrs.reverse(); - return Ok(QueryAnswer::IsCycleProfitable { - is_profitable: true, - direction: cycles[i].clone(), - swap_amounts, - profit: current_offer.amount.checked_sub(amount)?, - }); - } - - // If both possible directions are unprofitable, return false - Ok(QueryAnswer::IsCycleProfitable { - is_profitable: false, - direction: cycles[0].clone(), - swap_amounts: vec![], - profit: Uint128::zero(), - }) -} - -pub fn any_cycles_profitable(deps: Deps, amount: Uint128) -> StdResult { - let cycles = Cycles::load(deps.storage)?.0; - let mut return_is_profitable = vec![]; - let mut return_directions = vec![]; - let mut return_swap_amounts = vec![]; - let mut return_profit = vec![]; - - // loop through the cycles with an index - for index in 0..cycles.len() { - // for each cycle, check its profitability - let res = cycle_profitability(deps, amount, Uint128::from(index as u128)).unwrap(); - match res { - QueryAnswer::IsCycleProfitable { - is_profitable, - direction, - swap_amounts, - profit, - } => { - if is_profitable { - // push the results to a vec - return_is_profitable.push(is_profitable); - return_directions.push(direction); - return_swap_amounts.push(swap_amounts); - return_profit.push(profit); - } - } - _ => { - return Err(StdError::generic_err("Unexpected result")); - } - } - } - - Ok(QueryAnswer::IsAnyCycleProfitable { - is_profitable: return_is_profitable, - direction: return_directions, - swap_amounts: return_swap_amounts, - profit: return_profit, - }) -} - -pub fn adapter_balance(deps: Deps, asset: Addr) -> StdResult { - let config = Config::load(deps.storage)?; - let viewing_key = ViewingKeys::load(deps.storage)?.0; - let self_addr = SelfAddr::load(deps.storage)?.0; - - let contract; - if config.shd_token.address == asset { - contract = config.shd_token.clone(); - } else if config.silk_token.address == asset { - contract = config.silk_token.clone(); - } else if config.sscrt_token.address == asset { - contract = config.sscrt_token.clone(); - } else { - return Ok(adapter::QueryAnswer::Unbondable { - amount: Uint128::zero(), - }); - } - - let res = snip20::QueryMsg::Balance { - address: self_addr.clone().to_string(), - key: viewing_key.clone(), - } - .query(&deps.querier, &contract.clone())?; - - let amount = match res { - snip20::QueryAnswer::Balance { amount } => amount, - _ => Uint128::zero(), - }; - - Ok(adapter::QueryAnswer::Unbondable { - amount: Uint128::new(amount.u128()), - }) -} - -pub fn adapter_claimable(_deps: Deps, _asset: Addr) -> StdResult { - Ok(adapter::QueryAnswer::Claimable { - amount: Uint128::zero(), - }) -} - -// Same as adapter_balance -pub fn adapter_unbondable(deps: Deps, asset: Addr) -> StdResult { - let config = Config::load(deps.storage)?; - let viewing_key = ViewingKeys::load(deps.storage)?.0; - let self_addr = SelfAddr::load(deps.storage)?.0; - - let contract; - if config.shd_token.address == asset { - contract = config.shd_token.clone(); - } else if config.silk_token.address == asset { - contract = config.silk_token.clone(); - } else if config.sscrt_token.address == asset { - contract = config.sscrt_token.clone(); - } else { - return Ok(adapter::QueryAnswer::Unbondable { - amount: Uint128::zero(), - }); - } - - let res = snip20::QueryMsg::Balance { - address: self_addr.clone().to_string(), - key: viewing_key.clone(), - } - .query(&deps.querier, &contract.clone())?; - - let amount = match res { - snip20::QueryAnswer::Balance { amount } => amount, - _ => Uint128::zero(), - }; - - Ok(adapter::QueryAnswer::Unbondable { - amount: Uint128::new(amount.u128()), - }) -} - -pub fn adapter_unbonding(_deps: Deps, _asset: Addr) -> StdResult { - Ok(adapter::QueryAnswer::Unbonding { - amount: Uint128::zero(), - }) -} - -// Same as adapter_balance -pub fn adapter_reserves(deps: Deps, asset: Addr) -> StdResult { - let config = Config::load(deps.storage)?; - let viewing_key = ViewingKeys::load(deps.storage)?.0; - let self_addr = SelfAddr::load(deps.storage)?.0; - - let contract; - if config.shd_token.address == asset { - contract = config.shd_token.clone(); - } else if config.silk_token.address == asset { - contract = config.silk_token.clone(); - } else if config.sscrt_token.address == asset { - contract = config.sscrt_token.clone(); - } else { - return Ok(adapter::QueryAnswer::Unbondable { - amount: Uint128::zero(), - }); - } - - let res = snip20::QueryMsg::Balance { - address: self_addr.clone().to_string(), - key: viewing_key.clone(), - } - .query(&deps.querier, &contract.clone())?; - - let amount = match res { - snip20::QueryAnswer::Balance { amount } => amount, - _ => Uint128::zero(), - }; - - Ok(adapter::QueryAnswer::Unbondable { - amount: Uint128::new(amount.u128()), - }) -} diff --git a/archived-contracts/sky/tests/integration.rs b/archived-contracts/sky/tests/integration.rs deleted file mode 100644 index ab2798c..0000000 --- a/archived-contracts/sky/tests/integration.rs +++ /dev/null @@ -1,318 +0,0 @@ -//! This integration test tries to run and call the generated wasm. -//! It depends on a Wasm build being available, which you can create with `cargo wasm`. -//! Then running `cargo integration-test` will validate we can properly call into that generated Wasm. -//! -//! You can easily convert unit tests to integration tests. -//! 1. First copy them over verbatum, -//! 2. Then change -//! let mut deps = mock_dependencies(20, &[]); -//! to -//! let mut deps = mock_instance(WASM, &[]); -//! 3. If you access raw storage, where ever you see something like: -//! deps.storage.get(CONFIG_KEY).expect("no data stored"); -//! replace it with: -//! deps.with_storage(|store| { -//! let data = store.get(CONFIG_KEY).expect("no data stored"); -//! //... -//! }); -//! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) - -/*use contract_harness::harness::snip20::Snip20; -use cosmwasm_math_compat as compat; -use cosmwasm_math_compat::Uint128; -use cosmwasm_std::{ - self, - coins, - from_binary, - to_binary, - Binary, - Env, - Extern, - HandleResponse, - Addr, - InitResponse, - StdError, - StdResult, -}; -use fadroma::{ - ensemble::{ContractEnsemble, MockEnv}, - prelude::{Callback, ContractInstantiationInfo, ContractLink}, -}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use shade_protocol::contract_interfaces::{ - dex::{self, shadeswap}, - snip20::{self}, -}; - -fn test_ensemble_sky(swap_amount: Uint128) { - let mut ensemble = ContractEnsemble::new(50); - - let reg_snip20 = ensemble.register(Box::new(Snip20)); - - //let reg_mock_shdswp = ensemble.register(Box::new(MockShdSwp)); - //let reg_shadeswap_exchange = ensemble.register(Box::new(ShadeswapExchange)); - //let reg_shadeswap_factory = ensemble.register(Box::new(ShadeswapFactory)); - //let reg_sienna_lp_token = ensemble.register(Box::new(SiennaLpToken)); - - println!("Deploying sscrt contract"); - - let sscrt = ensemble - .instantiate( - reg_snip20.id, - &snip20_reference_impl::msg::InitMsg { - name: "secretSCRT".into(), - admin: Some(Addr("admin".into())), - symbol: "SSCRT".into(), - decimals: 6, - initial_balances: Some(vec![snip20_reference_impl::msg::InitialBalance { - address: Addr("admin".into()), - amount: cosmwasm_std::Uint128(100000000000), // 100,000 SSCRT - }]), - prng_seed: to_binary("").ok().unwrap(), - config: None, - }, - MockEnv::new("admin", ContractLink { - address: Addr("sscrt".into()), - code_hash: reg_snip20.code_hash.clone(), - }), - ) - .unwrap(); - - println!("Sscrt contract addr: {}", sscrt.instance.address); - println!("Deploying shd contract"); - - let shd = ensemble - .instantiate( - reg_snip20.id, - &snip20_reference_impl::msg::InitMsg { - name: "Shade".into(), - admin: Some(Addr("admin".into())), - symbol: "SHD".into(), - decimals: 8, - initial_balances: Some(vec![snip20_reference_impl::msg::InitialBalance { - address: Addr("admin".into()), - amount: cosmwasm_std::Uint128(10000000000000), // 100,000 SHD - }]), - prng_seed: to_binary("").ok().unwrap(), - config: None, - }, - MockEnv::new("admin", ContractLink { - address: Addr("secret1k0jntykt7e4g3y88ltc60czgjuqdy4c9e8fzek".into()), - code_hash: reg_snip20.code_hash.clone(), - }), - ) - .unwrap(); - - println!("Shd contract addr: {}", shd.instance.address); - println!("Deploying silk contract"); - - let silk = ensemble - .instantiate( - reg_snip20.id, - &snip20_reference_impl::msg::InitMsg { - name: "Silk".into(), - admin: Some(Addr("admin".into())), - symbol: "SILK".into(), - decimals: 6, - initial_balances: Some(vec![snip20_reference_impl::msg::InitialBalance { - address: Addr("admin".into()), - amount: cosmwasm_std::Uint128(100000000000), // 100,000 SILK - }]), - prng_seed: to_binary("").ok().unwrap(), - config: None, - }, - MockEnv::new("admin", ContractLink { - address: Addr("secret14m2ffr7fyjhzv8cdknn2yp8sneht3luvsh9495".into()), - code_hash: reg_snip20.code_hash.clone(), - }), - ) - .unwrap(); - - println!("Silk contract addr: {}", silk.instance.address); - - let key = String::from("key"); - - ensemble - .execute( - &snip20_reference_impl::msg::HandleMsg::SetViewingKey { - key: key.clone(), - padding: None, - }, - MockEnv::new("admin", sscrt.instance.clone()), - ) - .unwrap(); - - ensemble - .execute( - &snip20_reference_impl::msg::HandleMsg::SetViewingKey { - key: key.clone(), - padding: None, - }, - MockEnv::new("admin", shd.instance.clone()), - ) - .unwrap(); - - ensemble - .execute( - &snip20_reference_impl::msg::HandleMsg::SetViewingKey { - key: key.clone(), - padding: None, - }, - MockEnv::new("admin", silk.instance.clone()), - ) - .unwrap(); - - let mut query_res = ensemble - .query(sscrt.instance.address.clone(), &snip20::QueryMsg::Balance { - address: "admin".into(), - key: key.clone(), - }) - .unwrap(); - - match query_res { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, Uint128::new(100000000000)) - } - _ => { - assert!(false) - } - } - - query_res = ensemble - .query(shd.instance.address.clone(), &snip20::QueryMsg::Balance { - address: "admin".into(), - key: key.clone(), - }) - .unwrap(); - - match query_res { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, Uint128::new(10000000000000)) - } - _ => { - assert!(false) - } - } - - query_res = ensemble - .query(silk.instance.address.clone(), &snip20::QueryMsg::Balance { - address: "admin".into(), - key: key.clone(), - }) - .unwrap(); - - match query_res { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, Uint128::new(100000000000)) - } - _ => { - assert!(false) - } - } - - /*println!("{}", reg_sienna_factory.code_hash); - println!("{}", reg_sienna_lp_token.code_hash); - - let sienna_factory = ensemble.instantiate( - reg_sienna_factory.id, - &factory::InitMsg { - lp_token_contract: reg_sienna_lp_token.clone(), - pair_contract: reg_sienna_exchange.clone(), - exchange_settings: factory::ExchangeSettings{ - swap_fee: factory::Fee { nom: 0, denom: 0 }, - sienna_fee: factory::Fee { nom: 0, denom: 0 }, - sienna_burner: None, - }, - admin: Some(Addr("admin".into())), - prng_seed: to_binary("").ok().unwrap(), - }, - MockEnv::new("admin", ContractLink { - address: Addr("reg_sienna_factory".into()), - code_hash: reg_sienna_factory.code_hash.clone(), - }), - ).unwrap(); - - println!("{}", silk.address); - println!("{}", shd.address); - - let mut res = ensemble.execute( - &factory::HandleMsg::CreateExchange { - pair: sienna::Pair { - token_0: sienna::TokenType::CustomToken { - contract_addr: shd.address, - token_code_hash: shd.code_hash, - }, - token_1: sienna::TokenType::CustomToken { - contract_addr: silk.address, - token_code_hash: silk.code_hash, - } - }, - entropy: to_binary("").ok().unwrap(), - }, - MockEnv::new("admin", sienna_factory.clone()), - ).unwrap();*/ - - println!("here"); - - /* let sienna_pair = ensemble.instantiate( - reg_sienna_exchange.id, - &amm_pair::InitMsg{ - pair: sienna::Pair { - token_0: sienna::TokenType::CustomToken{ - contract_addr: shd.address.clone(), - token_code_hash: shd.code_hash.clone(), - }, - token_1: sienna::TokenType::CustomToken{ - contract_addr: silk.address.clone(), - token_code_hash: silk.code_hash.clone(), - }, - }, - lp_token_contract: reg_sienna_lp_token.clone(), - factory_info: sienna_factory.clone(), - callback: Callback { - msg: to_binary("").ok().unwrap(), - contract: sienna_factory.clone(), - }, - prng_seed: to_binary("").ok().unwrap(), - entropy: to_binary("").ok().unwrap(), - }, - MockEnv::new("admin", ContractLink { - address: Addr("reg_sienna_exchange".into()), - code_hash: reg_sienna_exchange.code_hash.clone(), - }), - ).unwrap();*/ -} - -macro_rules! sky_int_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let (swap_amount,) = $value; - test_ensemble_sky(swap_amount); - } - )* - } -} - -sky_int_tests! { - sky_int_0: ( - Uint128::zero(), - ), -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct InitMsg { - /// The tokens that will be managed by the exchange - pub pair: dex::sienna::Pair, - /// LP token instantiation info - pub lp_token_contract: ContractInstantiationInfo, - /// Used by the exchange contract to - /// send back its address to the factory on init - pub factory_info: ContractLink, - pub callback: Callback, - pub prng_seed: Binary, - pub entropy: Binary, -}*/ diff --git a/archived-contracts/snip20_staking/.cargo/config b/archived-contracts/snip20_staking/.cargo/config deleted file mode 100644 index 9519d41..0000000 --- a/archived-contracts/snip20_staking/.cargo/config +++ /dev/null @@ -1,4 +0,0 @@ -[alias] -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" -schema = "run --example schema" diff --git a/archived-contracts/snip20_staking/.circleci/config.yml b/archived-contracts/snip20_staking/.circleci/config.yml deleted file mode 100644 index a6f10d6..0000000 --- a/archived-contracts/snip20_staking/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.46 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/archived-contracts/snip20_staking/Cargo.toml b/archived-contracts/snip20_staking/Cargo.toml deleted file mode 100644 index 0c5b67f..0000000 --- a/archived-contracts/snip20_staking/Cargo.toml +++ /dev/null @@ -1,49 +0,0 @@ -[package] -name = "spip_stkd_0" -version = "0.1.0" -authors = ["Guy "] -edition = "2018" -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] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] -debug-print = ["shade-protocol/debug-print"] - -# debug-print = ["cosmwasm-std/debug-print"] -[dependencies] -schemars = "0.8.9" -serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] } -thiserror = "1.0" - -bincode2 = "2.0.1" -subtle = { version = "2.2.3", default-features = false } -base64 = "0.12.3" -rand_chacha = { version = "0.2.2", default-features = false } -rand_core = { version = "0.5.1", default-features = false } -sha2 = { version = "0.9.1", default-features = false } - -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["snip20_staking", "snip20", "storage"] } - -[dev-dependencies] -rand = "0.8.4" diff --git a/archived-contracts/snip20_staking/README.md b/archived-contracts/snip20_staking/README.md deleted file mode 100644 index 7c50fc2..0000000 --- a/archived-contracts/snip20_staking/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# SNIP-20 Reference Implementation - -This is an implementation of a [SNIP-20](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md), [SNIP-21](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-21.md), [SNIP-22](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-22.md), [SNIP-23](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-23.md) and [SNIP-24](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-24.md) compliant token contract. -At the time of token creation you may configure: -* Public Total Supply: If you enable this, the token's total supply will be displayed whenever a TokenInfo query is performed. DEFAULT: false -* Enable Deposit: If you enable this, you will be able to convert from SCRT to the token.* DEFAULT: false -* Enable Redeem: If you enable this, you will be able to redeem your token for SCRT.* It should be noted that if you have redeem enabled, but deposit disabled, all redeem attempts will fail unless someone has sent SCRT to the token contract. DEFAULT: false -* Enable Mint: If you enable this, any address in the list of minters will be able to mint new tokens. The admin address is the default minter, but can use the set/add/remove_minters functions to change the list of approved minting addresses. DEFAULT: false -* Enable Burn: If you enable this, addresses will be able to burn tokens. DEFAULT: false - - -\*:The conversion rate will be 1 uscrt for 1 minimum denomination of the token. This means that if your token has 6 decimal places, it will convert 1:1 with SCRT. If your token has 10 decimal places, it will have an exchange rate of 10000 SCRT for 1 token. If your token has 3 decimal places, it will have an exchange rate of 1000 tokens for 1 SCRT. You can use the exchange_rate query to view the exchange rate for the token. The query response will display either how many tokens are worth 1 SCRT, or how many SCRT are worth 1 token. That is, the response lists the symbol of the coin that has less value (either SCRT or the token), and the number of those coins that are worth 1 of the other. - -## Usage examples: - -To create a new token: - -```secretcli tx compute instantiate '{"name":"","symbol":"","admin":"","decimals":,"initial_balances":[{"address":"","amount":""}],"prng_seed":"","config":{"public_total_supply":,"enable_deposit":,"enable_redeem":,"enable_mint":,"enable_burn":}}' --label --from ``` - -The `admin` field is optional and will default to the "--from" address if you do not specify it. The `initial_balances` field is optional, and you can specify as many addresses/balances as you like. The `config` field as well as every field in the `config` is optional. Any `config` fields not specified will default to `false`. - -To deposit: ***(This is public)*** - -```secretcli tx compute execute '{"deposit": {}}' --amount 1000000uscrt --from ``` - -To send SSCRT: - -```secretcli tx compute execute '{"transfer": {"recipient": "", "amount": ""}}' --from ``` - -To set your viewing key: - -```secretcli tx compute execute '{"create_viewing_key": {"entropy": ""}}' --from ``` - -To check your balance: - -```secretcli q compute query '{"balance": {"address":"", "key":"your_viewing_key"}}'``` - -To view your transaction history: - -```secretcli q compute query '{"transfer_history": {"address": "", "key": "", "page": , "page_size": }}'``` - -To withdraw: ***(This is public)*** - -```secretcli tx compute execute '{"redeem": {"amount": ""}}' --from ``` - -To view the token contract's configuration: - -```secretcli q compute query '{"token_config": {}}'``` - -To view the deposit/redeem exchange rate: - -```secretcli q compute query '{"exchange_rate": {}}'``` - - -## Troubleshooting - -All transactions are encrypted, so if you want to see the error returned by a failed transaction, you need to use the command - -`secretcli q compute tx ` diff --git a/archived-contracts/snip20_staking/src/batch.rs b/archived-contracts/snip20_staking/src/batch.rs deleted file mode 100644 index 070595b..0000000 --- a/archived-contracts/snip20_staking/src/batch.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Types used in batch operations - - -use shade_protocol::cosmwasm_schema::cw_serde; - -use shade_protocol::c_std::Uint128; -use shade_protocol::c_std::{Binary, Addr}; - -#[cw_serde] -pub struct TransferAction { - pub recipient: Addr, - pub amount: Uint128, - pub memo: Option, -} - -#[cw_serde] -pub struct SendAction { - pub recipient: Addr, - pub recipient_code_hash: Option, - pub amount: Uint128, - pub msg: Option, - pub memo: Option, -} - -#[cw_serde] -pub struct TransferFromAction { - pub owner: Addr, - pub recipient: Addr, - pub amount: Uint128, - pub memo: Option, -} - -#[cw_serde] -pub struct SendFromAction { - pub owner: Addr, - pub recipient: Addr, - pub recipient_code_hash: Option, - pub amount: Uint128, - pub msg: Option, - pub memo: Option, -} - -#[cw_serde] -pub struct MintAction { - pub recipient: Addr, - pub amount: Uint128, - pub memo: Option, -} - -#[cw_serde] -pub struct BurnFromAction { - pub owner: Addr, - pub amount: Uint128, - pub memo: Option, -} diff --git a/archived-contracts/snip20_staking/src/contract.rs b/archived-contracts/snip20_staking/src/contract.rs deleted file mode 100644 index ef6647b..0000000 --- a/archived-contracts/snip20_staking/src/contract.rs +++ /dev/null @@ -1,4738 +0,0 @@ -use crate::{ - batch, - distributors, - distributors::{ - get_distributor, - try_add_distributors, - try_set_distributors, - try_set_distributors_status, - }, - expose_balance::{try_expose_balance, try_expose_balance_with_cooldown}, - msg::{ - space_pad, - status_level_to_u8, - ContractStatusLevel, - HandleAnswer, - ExecuteMsg, - InstantiateMsg, - QueryAnswer, - QueryMsg, - QueryWithPermit, - ResponseStatus::Success, - }, - rand::sha_256, - receiver::Snip20ReceiveMsg, - stake::{ - claim_rewards, - remove_from_cooldown, - shares_per_token, - try_claim_rewards, - try_claim_unbond, - try_receive, - try_stake_rewards, - try_unbond, - try_update_stake_config, - }, - stake_queries, - state::{ - get_receiver_hash, - read_allowance, - read_viewing_key, - set_receiver_hash, - write_allowance, - write_viewing_key, - Balances, - Config, - Constants, - ReadonlyBalances, - ReadonlyConfig, - }, - state_staking::{ - DailyUnbondingQueue, - Distributors, - DistributorsEnabled, - TotalShares, - TotalTokens, - TotalUnbonding, - UnsentStakedTokens, - UserCooldown, - UserShares, - }, - transaction_history::{get_transfers, get_txs, store_claim_reward, store_mint, store_transfer}, - viewing_key::{ViewingKey, VIEWING_KEY_SIZE}, -}; -use shade_protocol::c_std::{Deps, MessageInfo, Uint128, Uint256}; -/// This contract implements SNIP-20 standard: -/// https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md -use shade_protocol::c_std::{ - from_binary, - to_binary, - Api, - Binary, - CanonicalAddr, - CosmosMsg, - Env, - DepsMut, - Addr, - Response, - Querier, - StdError, - StdResult, - Storage, -}; -use shade_protocol::{ - // permit::{validate, Permission, Permit, RevokedPermits}, - contract_interfaces::snip20::helpers::{register_receive, send_msg, token_info}, -}; -use shade_protocol::{Contract, contract_interfaces::staking::snip20_staking::{ - stake::{Cooldown, StakeConfig, VecQueue}, - ReceiveType, -}, utils::storage::default::{BucketStorage, SingletonStorage}}; -use shade_protocol::query_authentication::permit::Permit; -use shade_protocol::snip20::Permission; - -/// We make sure that responses from `execute` are padded to a multiple of this size. -pub const RESPONSE_BLOCK_SIZE: usize = 256; -pub const PREFIX_REVOKED_PERMITS: &str = "revoked_permits"; - -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - // Check name, symbol, decimals - if !is_valid_name(&msg.name) { - return Err(StdError::generic_err( - "Name is not in the expected format (3-30 UTF-8 bytes)", - )); - } - if !is_valid_symbol(&msg.symbol) { - return Err(StdError::generic_err( - "Ticker symbol is not in expected format [A-Z]{3,6}", - )); - } - - let init_config = msg.config(); - let admin = msg.admin.unwrap_or(info.sender); - - let total_supply: u128 = 0; - - let prng_seed_hashed = sha_256(&msg.prng_seed.0); - - // Set stake config - let staked_token_decimals: u8; - if let Some(decimals) = msg.decimals { - staked_token_decimals = decimals; - } else { - staked_token_decimals = token_info_query( - &deps.querier, - 256, - msg.staked_token.code_hash.clone(), - msg.staked_token.address.clone(), - )? - .decimals; - } - - let mut config = Config::from_storage(deps.storage); - config.set_constants(&Constants { - name: msg.name, - symbol: "STKD-".to_string() + &msg.symbol, - decimals: staked_token_decimals, - admin, - prng_seed: prng_seed_hashed.to_vec(), - total_supply_is_public: init_config.public_total_supply(), - contract_address: env.contract.address, - })?; - config.set_total_supply(total_supply); - config.set_contract_status(ContractStatusLevel::NormalRun); - - // Set distributors - Distributors(msg.distributors.unwrap_or_default()).save(deps.storage)?; - DistributorsEnabled(msg.limit_transfer).save(deps.storage)?; - - if staked_token_decimals * 2 > msg.share_decimals { - return Err(StdError::generic_err( - "Share decimals must be two times greater than the token decimals", - )); - } - - StakeConfig { - unbond_time: msg.unbond_time, - staked_token: msg.staked_token.clone(), - decimal_difference: msg.share_decimals - staked_token_decimals, - treasury: msg.treasury.clone(), - } - .save(deps.storage)?; - - // Set shares state to 0 - TotalShares(Uint256::zero()).save(deps.storage)?; - - // Initialize unbonding queue - DailyUnbondingQueue(VecQueue::new(vec![])).save(deps.storage)?; - - // Set tokens - TotalTokens(Uint128::zero()).save(deps.storage)?; - - TotalUnbonding(Uint128::zero()).save(deps.storage)?; - - UnsentStakedTokens(Uint128::zero()).save(deps.storage)?; - - // Register receive if necessary - let mut messages = vec![]; - if let Some(addr) = msg.treasury { - if let Some(code_hash) = msg.treasury_code_hash { - messages.push(register_receive( - env.contract.code_hash.clone(), - None, - &Contract { - address: addr, - code_hash - } - )?); - } - } - - messages.push(register_receive( - env.contract.code_hash, - None, - msg.staked_token - )?); - - Ok(Response::new()) -} - -fn pad_response(response: StdResult) -> StdResult { - response.map(|mut response| { - response.data = response.data.map(|mut data| { - space_pad(RESPONSE_BLOCK_SIZE, &mut data.0); - data - }); - response - }) -} - -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> StdResult { - let contract_status = ReadonlyConfig::from_storage(deps.storage).contract_status(); - - match contract_status { - ContractStatusLevel::NormalRun => {} // If it's a normal run just continue - _ => { - let mut not_authorized = false; - let status_code = status_level_to_u8(contract_status); - - match msg.clone() { - // This is always allowed - ExecuteMsg::SetContractStatus { .. } => {} - ExecuteMsg::UpdateStakeConfig { .. } => {} - - // If receive check that msg is not bonding or reward - ExecuteMsg::Receive { msg, .. } => { - let receive_type: ReceiveType; - if let Some(msg) = msg { - receive_type = from_binary(&msg)?; - } else { - return Err(StdError::generic_err("No receive type supplied in message")); - } - - match receive_type { - ReceiveType::Bond { .. } | ReceiveType::Reward => not_authorized = true, - _ => {} - } - } - // Relates to bonding - ExecuteMsg::StakeRewards { .. } => { - if status_code > 0 { - not_authorized = true; - } - } - - ExecuteMsg::ClaimRewards { .. } => { - if status_code > 1 { - not_authorized = true; - } - } - // If unbonding check that msg is not stop all - ExecuteMsg::Unbond { .. } => { - if status_code > 2 { - not_authorized = true; - } - } - ExecuteMsg::ClaimUnbond { .. } => { - if status_code > 2 { - not_authorized = true; - } - } - // All other msgs can only work if status is 1 or below - _ => { - if status_code > 1 { - not_authorized = true; - } - } - } - - if not_authorized { - return pad_response(Err(StdError::generic_err( - "This contract is stopped and this action is not allowed", - ))); - } - } - }; - - let response = match msg { - // Staking - ExecuteMsg::UpdateStakeConfig { - unbond_time, - disable_treasury, - treasury, - .. - } => try_update_stake_config(deps, env, info, unbond_time, disable_treasury, treasury), - ExecuteMsg::Receive { - sender, - from, - amount, - msg, - memo, - .. - } => try_receive(deps, env, info, sender, from, amount, msg, memo), - ExecuteMsg::Unbond { amount, .. } => try_unbond(deps, env, info, amount), - ExecuteMsg::ClaimUnbond { .. } => try_claim_unbond(deps, env, info), - ExecuteMsg::ClaimRewards { .. } => try_claim_rewards(deps, env, info), - ExecuteMsg::StakeRewards { .. } => try_stake_rewards(deps, env, info), - - // Balance - ExecuteMsg::ExposeBalance { - recipient, - code_hash, - msg, - memo, - .. - } => try_expose_balance(deps, env, info, recipient, code_hash, msg, memo), - ExecuteMsg::ExposeBalanceWithCooldown { - recipient, - code_hash, - msg, - memo, - .. - } => try_expose_balance_with_cooldown(deps, env, info, recipient, code_hash, msg, memo), - - // Distributors - ExecuteMsg::SetDistributorsStatus { enabled, .. } => { - try_set_distributors_status(deps, env, info, enabled) - } - ExecuteMsg::AddDistributors { distributors, .. } => { - try_add_distributors(deps, env, info, distributors) - } - ExecuteMsg::SetDistributors { distributors, .. } => { - try_set_distributors(deps, env, info, distributors) - } - - // Base - ExecuteMsg::Transfer { - recipient, - amount, - memo, - .. - } => try_transfer(deps, env, info, recipient, amount, memo), - ExecuteMsg::Send { - recipient, - recipient_code_hash, - amount, - msg, - memo, - .. - } => try_send(deps, env, info, recipient, recipient_code_hash, amount, memo, msg), - ExecuteMsg::BatchTransfer { actions, .. } => try_batch_transfer(deps, env, info, actions), - ExecuteMsg::BatchSend { actions, .. } => try_batch_send(deps, env, info, actions), - ExecuteMsg::RegisterReceive { code_hash, .. } => try_register_receive(deps, env, info, code_hash), - ExecuteMsg::CreateViewingKey { entropy, .. } => try_create_key(deps, env, info, entropy), - ExecuteMsg::SetViewingKey { key, .. } => try_set_key(deps, env, info, key), - - // Allowance - ExecuteMsg::IncreaseAllowance { - spender, - amount, - expiration, - .. - } => try_increase_allowance(deps, env, info, spender, amount, expiration), - ExecuteMsg::DecreaseAllowance { - spender, - amount, - expiration, - .. - } => try_decrease_allowance(deps, env, info, spender, amount, expiration), - ExecuteMsg::TransferFrom { - owner, - recipient, - amount, - memo, - .. - } => try_transfer_from(deps, &env, &owner, &recipient, amount, memo), - ExecuteMsg::SendFrom { - owner, - recipient, - recipient_code_hash, - amount, - msg, - memo, - .. - } => try_send_from( - deps, - env, - info, - owner, - recipient, - recipient_code_hash, - amount, - memo, - msg, - ), - ExecuteMsg::BatchTransferFrom { actions, .. } => { - try_batch_transfer_from(deps, &env, actions) - } - ExecuteMsg::BatchSendFrom { actions, .. } => try_batch_send_from(deps, env, info, actions), - - // Other - ExecuteMsg::ChangeAdmin { address, .. } => change_admin(deps, env, info, address), - ExecuteMsg::SetContractStatus { level, .. } => set_contract_status(deps, env, info, level), - ExecuteMsg::RevokePermit { permit_name, .. } => revoke_permit(deps, env, info, permit_name), - }; - - pad_response(response) -} - -pub fn query(deps: Deps, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::StakeConfig {} => stake_queries::stake_config(deps), - QueryMsg::TotalStaked {} => stake_queries::total_staked(deps), - QueryMsg::StakeRate {} => stake_queries::stake_rate(deps), - QueryMsg::Unbonding {} => stake_queries::unbonding(deps), - QueryMsg::Unfunded { start, total } => stake_queries::unfunded(deps, start, total), - QueryMsg::Distributors {} => distributors::distributors(deps), - QueryMsg::TokenInfo {} => query_token_info(deps.storage), - QueryMsg::TokenConfig {} => query_token_config(deps.storage), - QueryMsg::ContractStatus {} => query_contract_status(deps.storage), - QueryMsg::WithPermit { permit, query } => permit_queries(deps, permit, query), - _ => viewing_keys_queries(deps, msg), - } -} - -fn permit_queries( - deps: Deps, - permit: Permit, - query: QueryWithPermit, -) -> Result { - // Validate permit content - let token_address = ReadonlyConfig::from_storage(deps.storage) - .constants()? - .contract_address; - - let account = validate(deps, PREFIX_REVOKED_PERMITS, &permit, token_address)?; - - // Permit validated! We can now execute the query. - match query { - QueryWithPermit::Staked { time } => { - if !permit.check_permission(&Permission::Balance) { - return Err(StdError::generic_err(format!( - "No permission to query balance / stake, got permissions {:?}", - permit.params.permissions - ))); - } - - stake_queries::staked(deps, account, time) - } - QueryWithPermit::Balance {} => { - if !permit.check_permission(&Permission::Balance) { - return Err(StdError::generic_err(format!( - "No permission to query balance, got permissions {:?}", - permit.params.permissions - ))); - } - - query_balance(deps, &account) - } - QueryWithPermit::TransferHistory { page, page_size } => { - if !permit.check_permission(&Permission::History) { - return Err(StdError::generic_err(format!( - "No permission to query history, got permissions {:?}", - permit.params.permissions - ))); - } - - query_transfers(deps, &account, page.unwrap_or(0), page_size) - } - QueryWithPermit::TransactionHistory { page, page_size } => { - if !permit.check_permission(&Permission::History) { - return Err(StdError::generic_err(format!( - "No permission to query history, got permissions {:?}", - permit.params.permissions - ))); - } - - query_transactions(deps, &account, page.unwrap_or(0), page_size) - } - QueryWithPermit::Allowance { owner, spender } => { - if !permit.check_permission(&Permission::Allowance) { - return Err(StdError::generic_err(format!( - "No permission to query allowance, got permissions {:?}", - permit.params.permissions - ))); - } - - if account != owner && account != spender { - return Err(StdError::generic_err(format!( - "Cannot query allowance. Requires permit for either owner {:?} or spender {:?}, got permit for {:?}", - owner.as_str(), - spender.as_str(), - account.as_str() - ))); - } - - query_allowance(deps, owner, spender) - } - } -} - -pub fn viewing_keys_queries( - deps: Deps, - msg: QueryMsg, -) -> StdResult { - let (addresses, key) = msg.get_validation_params(); - - for address in addresses { - let canonical_addr = deps.api.canonical_address(address)?; - - let expected_key = read_viewing_key(deps.storage, &canonical_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::Staked { address, time, .. } => { - stake_queries::staked(deps, address, time) - } - QueryMsg::Balance { address, .. } => query_balance(deps, &address), - QueryMsg::TransferHistory { - address, - page, - page_size, - .. - } => query_transfers(deps, &address, page.unwrap_or(0), page_size), - QueryMsg::TransactionHistory { - address, - page, - page_size, - .. - } => query_transactions(deps, &address, page.unwrap_or(0), page_size), - QueryMsg::Allowance { owner, spender, .. } => query_allowance(deps, owner, spender), - _ => panic!("This query type does not require authentication"), - }; - } - } - - to_binary(&QueryAnswer::ViewingKeyError { - msg: "Wrong viewing key for this address or viewing key not set".to_string(), - }) -} - -fn query_token_info(storage: &dyn Storage) -> StdResult { - let config = ReadonlyConfig::from_storage(storage); - let constants = config.constants()?; - - let total_supply = if constants.total_supply_is_public { - Some(Uint128::new(config.total_supply())) - } else { - None - }; - - to_binary(&QueryAnswer::TokenInfo { - name: constants.name, - symbol: constants.symbol, - decimals: constants.decimals, - total_supply, - }) -} - -fn query_token_config(storage: &dyn Storage) -> StdResult { - let config = ReadonlyConfig::from_storage(storage); - let constants = config.constants()?; - - to_binary(&QueryAnswer::TokenConfig { - public_total_supply: constants.total_supply_is_public, - }) -} - -fn query_contract_status(storage: &dyn Storage) -> StdResult { - let config = ReadonlyConfig::from_storage(storage); - - to_binary(&QueryAnswer::ContractStatus { - status: config.contract_status(), - }) -} - -pub fn query_transfers( - deps: Deps, - account: &Addr, - page: u32, - page_size: u32, -) -> StdResult { - let address = deps.api.canonical_address(account)?; - let (txs, total) = get_transfers(&deps.api, deps.storage, &address, page, page_size)?; - - let result = QueryAnswer::TransferHistory { - txs, - total: Some(total), - }; - to_binary(&result) -} - -pub fn query_transactions( - deps: Deps, - account: &Addr, - page: u32, - page_size: u32, -) -> StdResult { - let address = deps.api.canonical_address(account)?; - let (txs, total) = get_txs(&deps.api, deps.storage, &address, page, page_size)?; - - let result = QueryAnswer::TransactionHistory { - txs, - total: Some(total), - }; - to_binary(&result) -} - -pub fn query_balance( - deps: Deps, - account: &Addr, -) -> StdResult { - let address = deps.api.canonical_address(account)?; - - let amount = - Uint128::new(ReadonlyBalances::from_storage(deps.storage).account_amount(&address)); - let response = QueryAnswer::Balance { amount }; - to_binary(&response) -} - -fn change_admin( - deps: DepsMut, - env: Env, - info: MessageInfo, - address: Addr, -) -> StdResult { - let mut config = Config::from_storage(deps.storage); - - check_if_admin(&config, &info.sender)?; - - let mut consts = config.constants()?; - consts.admin = address; - config.set_constants(&consts)?; - - Ok(Response::new().set_data(to_binary(&HandleAnswer::ChangeAdmin { status: Success })?)) -} - -pub fn try_mint_impl( - storage: &mut dyn Storage, - minter: &CanonicalAddr, - recipient: &CanonicalAddr, - amount: Uint128, - denom: String, - memo: Option, - block: &shade_protocol::c_std::BlockInfo, -) -> StdResult<()> { - let raw_amount = amount.u128(); - - let mut balances = Balances::from_storage(storage); - - let mut account_balance = balances.balance(recipient); - - if let Some(new_balance) = account_balance.checked_add(raw_amount) { - account_balance = new_balance; - } else { - // This error literally can not happen, since the account's funds are a subset - // of the total supply, both are stored as u128, and we check for overflow of - // the total supply just a couple lines before. - // Still, writing this to cover all overflows. - return Err(StdError::generic_err( - "This mint attempt would increase the account's balance above the supported maximum", - )); - } - - balances.set_account_balance(recipient, account_balance); - - store_mint(storage, minter, recipient, amount, denom, memo, block)?; - - Ok(()) -} - -pub fn try_set_key( - deps: DepsMut, - env: Env, - info: MessageInfo, - key: String, -) -> StdResult { - let vk = ViewingKey(key); - - let message_sender = deps.api.canonical_address(&info.sender)?; - write_viewing_key(deps.storage, &message_sender, &vk); - - Ok(Response::new().set_data(to_binary(&HandleAnswer::SetViewingKey { status: Success })?)) -} - -pub fn try_create_key( - deps: DepsMut, - env: Env, - info: MessageInfo, - entropy: String, -) -> StdResult { - let constants = ReadonlyConfig::from_storage(deps.storage).constants()?; - let prng_seed = constants.prng_seed; - - let key = ViewingKey::new(&env, &prng_seed, (&entropy).as_ref()); - - let message_sender = deps.api.canonical_address(&info.sender)?; - write_viewing_key(deps.storage, &message_sender, &key); - - Ok(Response::new().set_data(to_binary(&HandleAnswer::CreateViewingKey { key })?)) -} - -fn set_contract_status( - deps: DepsMut, - env: Env, - info: MessageInfo, - status_level: ContractStatusLevel, -) -> StdResult { - let mut config = Config::from_storage(deps.storage); - - check_if_admin(&config, &info.sender)?; - - config.set_contract_status(status_level); - - Ok(Response::new().set_data(to_binary(&HandleAnswer::SetContractStatus { - status: Success, - })?)) -} - -pub fn query_allowance( - deps: Deps, - owner: Addr, - spender: Addr, -) -> StdResult { - let owner_address = deps.api.canonical_address(&owner)?; - let spender_address = deps.api.canonical_address(&spender)?; - - let allowance = read_allowance(deps.storage, &owner_address, &spender_address)?; - - let response = QueryAnswer::Allowance { - owner, - spender, - allowance: Uint128::new(allowance.amount), - expiration: allowance.expiration, - }; - to_binary(&response) -} - -#[allow(clippy::too_many_arguments)] -fn try_transfer_impl( - deps: DepsMut, - messages: &mut Vec, - sender: &Addr, - sender_canon: &CanonicalAddr, - recipient: &Addr, - recipient_canon: &CanonicalAddr, - amount: Uint128, - memo: Option, - block: &shade_protocol::c_std::BlockInfo, - - distributors: &Option>, - time: u64, -) -> StdResult<()> { - // Verify that this transfer is allowed - if let Some(distributors) = distributors { - if !distributors.contains(sender) && !distributors.contains(recipient) { - return Err(StdError::generic_err("unauthorized")); - } - } - - let symbol = Config::from_storage(deps.storage).constants()?.symbol; - - let stake_config = StakeConfig::load(deps.storage)?; - let claim = claim_rewards(deps.storage, &stake_config, sender, sender_canon)?; - if !claim.is_zero() { - messages.push(send_msg( - sender.clone(), - claim.into(), - None, - None, - None, - 256, - stake_config.staked_token.code_hash, - stake_config.staked_token.address, - )?); - - store_claim_reward( - deps.storage, - sender_canon, - claim, - symbol.clone(), - None, - block, - )?; - } - - perform_transfer( - deps.storage, - sender, - sender_canon, - recipient, - recipient_canon, - amount, - time, - )?; - - store_transfer( - deps.storage, - sender_canon, - sender_canon, - recipient_canon, - amount, - symbol, - memo, - block, - )?; - - Ok(()) -} - -fn try_transfer( - deps: DepsMut, - env: Env, - info: MessageInfo, - recipient: Addr, - amount: Uint128, - memo: Option, -) -> StdResult { - let sender = info.sender; - let sender_canon = deps.api.canonical_address(&sender)?; - let recipient_canon = deps.api.canonical_address(&recipient)?; - - let distributor = get_distributor(deps)?; - - let mut messages = vec![]; - - try_transfer_impl( - deps, - &mut messages, - &sender, - &sender_canon, - &recipient, - &recipient_canon, - amount, - memo, - &env.block, - &distributor, - env.block.time.seconds(), - )?; - - let res = Response { - messages, - log: vec![], - data: Some(to_binary(&HandleAnswer::Transfer { status: Success })?), - }; - Ok(res) -} - -fn try_batch_transfer( - deps: DepsMut, - env: Env, - info: MessageInfo, - actions: Vec, -) -> StdResult { - let sender = info.sender; - let sender_canon = deps.api.canonical_address(&sender)?; - - let distributor = get_distributor(deps)?; - - let mut messages = vec![]; - - for action in actions { - let recipient = action.recipient; - let recipient_canon = deps.api.canonical_address(&recipient)?; - try_transfer_impl( - deps, - &mut messages, - &sender, - &sender_canon, - &recipient, - &recipient_canon, - action.amount, - action.memo, - &env.block, - &distributor, - env.block.time.seconds(), - )?; - } - - let res = Response { - messages, - log: vec![], - data: Some(to_binary(&HandleAnswer::BatchTransfer { status: Success })?), - }; - Ok(res) -} - -#[allow(clippy::too_many_arguments)] -fn try_add_receiver_api_callback( - storage: &dyn Storage, - messages: &mut Vec, - recipient: Addr, - recipient_code_hash: Option, - msg: Option, - sender: Addr, - from: Addr, - amount: Uint128, - memo: Option, -) -> StdResult<()> { - if let Some(receiver_hash) = recipient_code_hash { - let receiver_msg = Snip20ReceiveMsg::new(sender, from, amount, memo, msg); - let callback_msg = receiver_msg.into_cosmos_msg(receiver_hash, recipient)?; - - messages.push(callback_msg); - return Ok(()); - } - - let receiver_hash = get_receiver_hash(storage, &recipient); - if let Some(receiver_hash) = receiver_hash { - let receiver_hash = receiver_hash?; - let receiver_msg = Snip20ReceiveMsg::new(sender, from, amount, memo, msg); - let callback_msg = receiver_msg.into_cosmos_msg(receiver_hash, recipient)?; - - messages.push(callback_msg); - } - Ok(()) -} - -#[allow(clippy::too_many_arguments)] -fn try_send_impl( - deps: DepsMut, - messages: &mut Vec, - sender: Addr, - sender_canon: &CanonicalAddr, // redundant but more efficient - recipient: Addr, - recipient_code_hash: Option, - amount: Uint128, - memo: Option, - msg: Option, - block: &shade_protocol::c_std::BlockInfo, - - distributors: &Option>, - time: u64, -) -> StdResult<()> { - let recipient_canon = deps.api.canonical_address(&recipient)?; - try_transfer_impl( - deps, - messages, - &sender, - sender_canon, - &recipient, - &recipient_canon, - amount, - memo.clone(), - block, - distributors, - time, - )?; - - try_add_receiver_api_callback( - deps.storage, - messages, - recipient, - recipient_code_hash, - msg, - sender.clone(), - sender, - amount, - memo, - )?; - - Ok(()) -} - -fn try_send( - deps: DepsMut, - env: Env, - info: MessageInfo, - recipient: Addr, - recipient_code_hash: Option, - amount: Uint128, - memo: Option, - msg: Option, -) -> StdResult { - let mut messages = vec![]; - let sender = info.sender; - let sender_canon = deps.api.canonical_address(&sender)?; - - let distributor = get_distributor(deps)?; - - try_send_impl( - deps, - &mut messages, - sender, - &sender_canon, - recipient, - recipient_code_hash, - amount, - memo, - msg, - &env.block, - &distributor, - env.block.time.seconds(), - )?; - - let res = Response { - messages, - log: vec![], - data: Some(to_binary(&HandleAnswer::Send { status: Success })?), - }; - Ok(res) -} - -fn try_batch_send( - deps: DepsMut, - env: Env, - info: MessageInfo, - actions: Vec, -) -> StdResult { - let mut messages = vec![]; - let sender = info.sender; - let sender_canon = deps.api.canonical_address(&sender)?; - - let distributor = get_distributor(deps)?; - - for action in actions { - try_send_impl( - deps, - &mut messages, - sender.clone(), - &sender_canon, - action.recipient, - action.recipient_code_hash, - action.amount, - action.memo, - action.msg, - &env.block, - &distributor, - env.block.time.seconds(), - )?; - } - - let res = Response { - messages, - log: vec![], - data: Some(to_binary(&HandleAnswer::BatchSend { status: Success })?), - }; - Ok(res) -} - -fn try_register_receive( - deps: DepsMut, - env: Env, - info: MessageInfo, - code_hash: String, -) -> StdResult { - set_receiver_hash(deps.storage, &info.sender, code_hash); - let res = Response { - messages: vec![], - log: vec![log("register_status", "success")], - data: Some(to_binary(&HandleAnswer::RegisterReceive { - status: Success, - })?), - }; - Ok(res) -} - -fn insufficient_allowance(allowance: u128, required: u128) -> StdError { - StdError::generic_err(format!( - "insufficient allowance: allowance={}, required={}", - allowance, required - )) -} - -fn use_allowance( - storage: &mut dyn Storage, - env: &Env, - owner: &CanonicalAddr, - spender: &CanonicalAddr, - amount: u128, -) -> StdResult<()> { - let mut allowance = read_allowance(storage, owner, spender)?; - - if allowance.is_expired_at(&env.block) { - return Err(insufficient_allowance(0, amount)); - } - if let Some(new_allowance) = allowance.amount.checked_sub(amount) { - allowance.amount = new_allowance; - } else { - return Err(insufficient_allowance(allowance.amount, amount)); - } - - write_allowance(storage, owner, spender, allowance)?; - - Ok(()) -} - -#[allow(clippy::too_many_arguments)] -fn try_transfer_from_impl( - deps: DepsMut, - env: &Env, - spender: &Addr, - spender_canon: &CanonicalAddr, - owner: &Addr, - owner_canon: &CanonicalAddr, - recipient: &Addr, - recipient_canon: &CanonicalAddr, - amount: Uint128, - memo: Option, - - distributors: &Option>, - time: u64, -) -> StdResult<()> { - // Verify that this transfer is allowed - if let Some(distributors) = distributors { - if !distributors.contains(spender) - && !distributors.contains(owner) - && !distributors.contains(recipient) - { - return Err(StdError::generic_err("unauthorized")); - } - } - - let raw_amount = amount.u128(); - - use_allowance( - deps.storage, - env, - owner_canon, - spender_canon, - raw_amount, - )?; - - perform_transfer( - deps.storage, - owner, - owner_canon, - recipient, - recipient_canon, - amount, - time, - )?; - - let symbol = Config::from_storage(deps.storage).constants()?.symbol; - - store_transfer( - deps.storage, - owner_canon, - spender_canon, - recipient_canon, - amount, - symbol, - memo, - &env.block, - )?; - - Ok(()) -} - -fn try_transfer_from( - deps: DepsMut, - env: &Env, - owner: &Addr, - recipient: &Addr, - amount: Uint128, - memo: Option, -) -> StdResult { - let spender = &info.sender; - let spender_canon = deps.api.canonical_address(spender)?; - let owner_canon = deps.api.canonical_address(owner)?; - let recipient_canon = deps.api.canonical_address(recipient)?; - try_transfer_from_impl( - deps, - env, - spender, - &spender_canon, - owner, - &owner_canon, - recipient, - &recipient_canon, - amount, - memo, - &get_distributor(deps)?, - env.block.time.seconds(), - )?; - - let res = Response { - messages: vec![], - log: vec![], - data: Some(to_binary(&HandleAnswer::TransferFrom { status: Success })?), - }; - Ok(res) -} - -fn try_batch_transfer_from( - deps: DepsMut, - env: &Env, - actions: Vec, -) -> StdResult { - let spender = &info.sender; - let spender_canon = deps.api.canonical_address(spender)?; - - let distributor = get_distributor(deps)?; - - for action in actions { - let owner_canon = deps.api.canonical_address(&action.owner)?; - let recipient_canon = deps.api.canonical_address(&action.recipient)?; - try_transfer_from_impl( - deps, - env, - spender, - &spender_canon, - &action.owner, - &owner_canon, - &action.recipient, - &recipient_canon, - action.amount, - action.memo, - &distributor, - env.block.time.seconds(), - )?; - } - - let res = Response { - messages: vec![], - log: vec![], - data: Some(to_binary(&HandleAnswer::BatchTransferFrom { - status: Success, - })?), - }; - Ok(res) -} - -#[allow(clippy::too_many_arguments)] -fn try_send_from_impl( - deps: DepsMut, - env: Env, - info: MessageInfo, - messages: &mut Vec, - spender: &Addr, - spender_canon: &CanonicalAddr, // redundant but more efficient - owner: Addr, - recipient: Addr, - recipient_code_hash: Option, - amount: Uint128, - memo: Option, - msg: Option, - - distributors: &Option>, -) -> StdResult<()> { - let owner_canon = deps.api.canonical_address(&owner)?; - let recipient_canon = deps.api.canonical_address(&recipient)?; - try_transfer_from_impl( - deps, - &env, - spender, - spender_canon, - &owner, - &owner_canon, - &recipient, - &recipient_canon, - amount, - memo.clone(), - distributors, - env.block.time.seconds(), - )?; - - try_add_receiver_api_callback( - deps.storage, - messages, - recipient, - recipient_code_hash, - msg, - info.sender, - owner, - amount, - memo, - )?; - - Ok(()) -} - -#[allow(clippy::too_many_arguments)] -fn try_send_from( - deps: DepsMut, - env: Env, - info: MessageInfo, - owner: Addr, - recipient: Addr, - recipient_code_hash: Option, - amount: Uint128, - memo: Option, - msg: Option, -) -> StdResult { - let spender = &info.sender.clone(); - let spender_canon = deps.api.canonical_address(spender)?; - - let mut messages = vec![]; - try_send_from_impl( - deps, - env, - &mut messages, - spender, - &spender_canon, - owner, - recipient, - recipient_code_hash, - amount, - memo, - msg, - &get_distributor(deps)?, - )?; - - let res = Response { - messages, - log: vec![], - data: Some(to_binary(&HandleAnswer::SendFrom { status: Success })?), - }; - Ok(res) -} - -fn try_batch_send_from( - deps: DepsMut, - env: Env, - info: MessageInfo, - actions: Vec, -) -> StdResult { - let spender = &info.sender; - let spender_canon = deps.api.canonical_address(spender)?; - let mut messages = vec![]; - - let distributor = get_distributor(deps)?; - - for action in actions { - try_send_from_impl( - deps, - env.clone(), - &mut messages, - spender, - &spender_canon, - action.owner, - action.recipient, - action.recipient_code_hash, - action.amount, - action.memo, - action.msg, - &distributor, - )?; - } - - let res = Response { - messages, - log: vec![], - data: Some(to_binary(&HandleAnswer::BatchSendFrom { status: Success })?), - }; - Ok(res) -} - -fn try_increase_allowance( - deps: DepsMut, - env: Env, - info: MessageInfo, - spender: Addr, - amount: Uint128, - expiration: Option, -) -> StdResult { - let owner_address = deps.api.canonical_address(&info.sender)?; - let spender_address = deps.api.canonical_address(&spender)?; - - let mut allowance = read_allowance(deps.storage, &owner_address, &spender_address)?; - - // If the previous allowance has expired, reset the allowance. - // Without this users can take advantage of an expired allowance given to - // them long ago. - if allowance.is_expired_at(&env.block) { - allowance.amount = amount.u128(); - allowance.expiration = None; - } else { - allowance.amount = allowance.amount.saturating_add(amount.u128()); - } - - if expiration.is_some() { - allowance.expiration = expiration; - } - let new_amount = allowance.amount; - write_allowance( - deps.storage, - &owner_address, - &spender_address, - allowance, - )?; - - let res = Response { - messages: vec![], - log: vec![], - data: Some(to_binary(&HandleAnswer::IncreaseAllowance { - owner: info.sender, - spender, - allowance: Uint128::new(new_amount), - })?), - }; - Ok(res) -} - -fn try_decrease_allowance( - deps: DepsMut, - env: Env, - info: MessageInfo, - spender: Addr, - amount: Uint128, - expiration: Option, -) -> StdResult { - let owner_address = deps.api.canonical_address(&info.sender)?; - let spender_address = deps.api.canonical_address(&spender)?; - - let mut allowance = read_allowance(deps.storage, &owner_address, &spender_address)?; - - // If the previous allowance has expired, reset the allowance. - // Without this users can take advantage of an expired allowance given to - // them long ago. - if allowance.is_expired_at(&env.block) { - allowance.amount = 0; - allowance.expiration = None; - } else { - allowance.amount = allowance.amount.saturating_sub(amount.u128()); - } - - if expiration.is_some() { - allowance.expiration = expiration; - } - let new_amount = allowance.amount; - write_allowance( - deps.storage, - &owner_address, - &spender_address, - allowance, - )?; - - let res = Response { - messages: vec![], - log: vec![], - data: Some(to_binary(&HandleAnswer::DecreaseAllowance { - owner: info.sender, - spender, - allowance: Uint128::new(new_amount), - })?), - }; - Ok(res) -} - -fn perform_transfer( - store: &mut T, - from: &Addr, - from_canon: &CanonicalAddr, - to: &Addr, - to_canon: &CanonicalAddr, - amount: Uint128, - time: u64, -) -> StdResult<()> { - let mut balances = Balances::from_storage(store); - - let mut from_balance = balances.balance(from_canon); - let from_tokens = from_balance; - - if let Some(new_from_balance) = from_balance.checked_sub(amount.u128()) { - from_balance = new_from_balance; - } else { - return Err(StdError::generic_err(format!( - "insufficient funds: balance={}, required={}", - from_balance, amount - ))); - } - balances.set_account_balance(from_canon, from_balance); - - let mut to_balance = balances.balance(to_canon); - - to_balance = to_balance.checked_add(amount.u128()).ok_or_else(|| { - StdError::generic_err("This tx will literally make them too rich. Try transferring less") - })?; - balances.set_account_balance(to_canon, to_balance); - - // Transfer shares - let total_tokens = TotalTokens::load(store)?; - let total_shares = TotalShares::load(store)?; - - let config = StakeConfig::load(store)?; - - // calculate shares per token - let transfer_shares = shares_per_token(&config, &amount, &total_tokens.0, &total_shares.0)?; - - // move shares from one user to another - let mut from_shares = UserShares::load(store, from.as_str().as_bytes())?; - - from_shares.0 = from_shares.0.checked_sub(transfer_shares)?; - from_shares.save(store, from.as_str().as_bytes())?; - - let mut to_shares = - UserShares::may_load(store, to.as_str().as_bytes())?.unwrap_or(UserShares(Uint256::zero())); - to_shares.0 += transfer_shares; - to_shares.save(store, to.as_str().as_bytes())?; - - // check for what should be removed from the queue - let wrapped_amount = amount; - - // Update from cooldown - remove_from_cooldown(store, from, Uint128::new(from_tokens), wrapped_amount, time)?; - - // Update to cooldown - { - let mut to_cooldown = - UserCooldown::may_load(store, to.as_str().as_bytes())?.unwrap_or(UserCooldown { - total: Uint128::zero(), - queue: VecQueue(vec![]), - }); - // try to remove items that have already passed - to_cooldown.update(time); - // add the new cooldown - to_cooldown.add_cooldown(Cooldown { - amount: wrapped_amount, - release: time + StakeConfig::load(store)?.unbond_time, - }); - to_cooldown.save(store, to.as_str().as_bytes())?; - } - - Ok(()) -} - -fn revoke_permit( - deps: DepsMut, - env: Env, - info: MessageInfo, - permit_name: String, -) -> StdResult { - RevokedPermits::revoke_permit( - deps.storage, - PREFIX_REVOKED_PERMITS, - &info.sender, - &permit_name, - ); - - Ok(Response::new().set_data(to_binary(&HandleAnswer::RevokePermit { status: Success })?)) -} - -fn is_admin(config: &Config, account: &Addr) -> StdResult { - let consts = config.constants()?; - if &consts.admin != account { - return Ok(false); - } - - Ok(true) -} - -pub fn check_if_admin(config: &Config, account: &Addr) -> StdResult<()> { - if !is_admin(config, account)? { - return Err(StdError::generic_err( - "This is an admin command. Admin commands can only be run from admin address", - )); - } - - Ok(()) -} - -fn is_valid_name(name: &str) -> bool { - let len = name.len(); - (3..=30).contains(&len) -} - -fn is_valid_symbol(symbol: &str) -> bool { - let len = symbol.len(); - let len_is_valid = (3..=6).contains(&len); - - len_is_valid && symbol.bytes().all(|byte| (b'A'..=b'Z').contains(&byte)) -} - -// pub fn migrate( -// _deps: DepsMut, -// _env: Env, -// _msg: MigrateMsg, -// ) -> StdResult { -// Ok(MigrateResponse::default()) -// } - -#[cfg(test)] -mod staking_tests { - use super::*; - use crate::msg::{InitConfig, ResponseStatus}; - use shade_protocol::c_std::Uint256; - use shade_protocol::c_std::{ - from_binary, - testing::*, - BlockInfo, - ContractInfo, - MessageInfo, - Binary, - WasmMsg, - }; - use shade_protocol::{ - contract_interfaces::staking::snip20_staking::ReceiveType, - utils::asset::Contract, - }; - use std::any::Any; - - fn init_helper_staking() -> ( - StdResult, - Extern, - ) { - let mut deps = mock_dependencies(20, &[]); - let env = mock_env("instantiator", &[]); - let init_msg = InstantiateMsg { - name: "sec-sec".to_string(), - admin: Some(Addr::unchecked("admin".to_string())), - symbol: "SECSEC".to_string(), - decimals: Some(8), - share_decimals: 18, - prng_seed: Binary::from("lolz fun yay".as_bytes()), - config: None, - unbond_time: 10, - staked_token: Contract { - address: Addr::unchecked("token".to_string()), - code_hash: "hash".to_string(), - }, - treasury: Some(Addr::unchecked("treasury".to_string())), - treasury_code_hash: None, - limit_transfer: true, - distributors: Some(vec![Addr::unchecked("distributor".to_string())]), - }; - - (instantiate(&mut deps, env, info, init_msg), deps) - } - - // Handle tests - #[test] - fn test_handle_update_stake_config() { - let (init_result, mut deps) = init_helper_staking(); - - let handle_msg = ExecuteMsg::UpdateStakeConfig { - unbond_time: Some(100), - disable_treasury: true, - treasury: None, - padding: None, - }; - // Check that only admins can interact - let handle_result = execute(&mut deps, mock_env("not_admin", &[]), handle_msg.clone()); - assert!(handle_result.is_err()); - let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg); - assert!(handle_result.is_ok()); - - let query_balance_msg = QueryMsg::StakeConfig {}; - - let query_response = query(&deps, query_balance_msg).unwrap(); - let config = match from_binary(&query_response).unwrap() { - QueryAnswer::StakedConfig { config } => config, - _ => panic!("Unexpected result from query"), - }; - - assert_eq!(config.treasury, None); - assert_eq!(config.unbond_time, 100); - assert_eq!(config.decimal_difference, 10); - } - - fn new_staked_account( - deps: &mut Extern, - acc: &str, - pwd: &str, - stake: Uint128, - ) { - let handle_msg = ExecuteMsg::Receive { - sender: Addr(acc.to_string()), - from: Default::default(), - amount: stake, - msg: Some(to_binary(&ReceiveType::Bond { use_from: None }).unwrap()), - memo: None, - padding: None, - }; - // Bond tokens - let handle_result = execute(deps, mock_env("token", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - let handle_msg = ExecuteMsg::SetViewingKey { - key: pwd.to_string(), - padding: None, - }; - let handle_result = execute(deps, mock_env(acc, &[]), handle_msg.clone()); - } - - fn check_staked_state( - deps: &Extern, - expected_tokens: Uint128, - expected_shares: Uint256, - ) { - let query_balance_msg = QueryMsg::TotalStaked {}; - - let query_response = query(&deps, query_balance_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::TotalStaked { shares, tokens } => { - assert_eq!(tokens, expected_tokens); - assert_eq!(shares, expected_shares) - } - _ => panic!("Unexpected result from query"), - }; - } - - #[test] - fn test_handle_receive_bonding() { - let (init_result, mut deps) = init_helper_staking(); - - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked("foo".to_string()), - from: Default::default(), - amount: Uint128::new(100 * 10u128.pow(8)), - msg: Some(to_binary(&ReceiveType::Bond { use_from: None }).unwrap()), - memo: None, - padding: None, - }; - // Bond tokens with unsupported token - let handle_result = execute(&mut deps, mock_env("not_token", &[]), handle_msg.clone()); - assert!(handle_result.is_err()); - // Bond tokens - let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - let handle_msg = ExecuteMsg::SetViewingKey { - key: "key".to_string(), - padding: None, - }; - // Bond tokens with unsupported token - let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); - - check_staked_state( - &deps, - Uint128::new(100 * 10u128.pow(8)), - Uint256::from(100 * 10u128.pow(18)), - ); - - new_staked_account(&mut deps, "bar", "key", Uint128::new(100 * 10u128.pow(8))); - // Query user stake - let query_balance_msg = QueryMsg::Staked { - address: Addr::unchecked("bar".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_balance_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - .. - } => { - assert_eq!(tokens, Uint128::new(100 * 10u128.pow(8))); - assert_eq!(shares, Uint256::from(100 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::zero()); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, None); - } - _ => panic!("Unexpected result from query"), - }; - check_staked_state( - &deps, - Uint128::new(200 * 10u128.pow(8)), - Uint256::from(200 * 10u128.pow(18)), - ); - } - - #[test] - fn test_handle_unbond() { - let (init_result, mut deps) = init_helper_staking(); - - new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); - - // Query unbonding queue - let query_msg = QueryMsg::Unbonding {}; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Unbonding { total } => { - assert_eq!(total, Uint128::zero()); - } - _ => panic!("Unexpected result from query"), - }; - - // Unbond more than allowed - let handle_msg = ExecuteMsg::Unbond { - amount: Uint128::new(1000 * 10u128.pow(8)), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); - assert!(handle_result.is_err()); - - // Unbond - let handle_msg = ExecuteMsg::Unbond { - amount: Uint128::new(50 * 10u128.pow(8)), - padding: None, - }; - // Set time for ease of prediction - let mut env = mock_env("foo", &[]); - env.block.time.seconds() = 10; - let handle_result = execute(&mut deps, env, info, handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Query unbonding queue - let query_msg = QueryMsg::Unbonding {}; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Unbonding { total } => { - assert_eq!(total, Uint128::new(50 * 10u128.pow(8))); - } - _ => panic!("Unexpected result from query"), - }; - - // Query unbonding queue - let query_msg = QueryMsg::Unfunded { start: 0, total: 1 }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Unfunded { total } => { - assert_eq!(total, Uint128::new(50 * 10u128.pow(8))); - } - _ => panic!("Unexpected result from query"), - }; - - // Query user stake - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("foo".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - .. - } => { - assert_eq!(tokens, Uint128::new(50 * 10u128.pow(8))); - assert_eq!(shares, Uint256::from(50 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::zero()); - assert_eq!(unbonding, Uint128::new(50 * 10u128.pow(8))); - assert_eq!(unbonded, None); - } - _ => panic!("Unexpected result from query"), - }; - check_staked_state( - &deps, - Uint128::new(50 * 10u128.pow(8)), - Uint256::from(50 * 10u128.pow(18)), - ); - } - - #[test] - fn test_handle_fund_unbond() { - let (init_result, mut deps) = init_helper_staking(); - - new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); - - // Bond some amount - // Unbond - let handle_msg = ExecuteMsg::Unbond { - amount: Uint128::new(50 * 10u128.pow(8)), - padding: None, - }; - // Set time for ease of prediction - let mut env = mock_env("foo", &[]); - env.block.time.seconds() = 10; - let handle_result = execute(&mut deps, env, info, handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Query unbonding queue - let query_msg = QueryMsg::Unfunded { start: 0, total: 1 }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Unfunded { total } => { - assert_eq!(total, Uint128::new(50 * 10u128.pow(8))); - } - _ => panic!("Unexpected result from query"), - }; - - // Fund half the unbond - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked("treasury".to_string()), - from: Default::default(), - amount: Uint128::new(25 * 10u128.pow(8)), - msg: Some(to_binary(&ReceiveType::Unbond).unwrap()), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Query unbonding queue - let query_msg = QueryMsg::Unfunded { start: 0, total: 1 }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Unfunded { total } => { - assert_eq!(total, Uint128::new(25 * 10u128.pow(8))); - } - _ => panic!("Unexpected result from query"), - }; - - // Unbond in the middle of funding - let handle_msg = ExecuteMsg::Unbond { - amount: Uint128::new(25 * 10u128.pow(8)), - padding: None, - }; - // Set time for ease of prediction - let mut env = mock_env("foo", &[]); - env.block.time.seconds() = 10; - let handle_result = execute(&mut deps, env, info, handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Query unbonding queue - let query_msg = QueryMsg::Unfunded { start: 0, total: 1 }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Unfunded { total } => { - assert_eq!(total, Uint128::new(50 * 10u128.pow(8))); - } - _ => panic!("Unexpected result from query"), - }; - - // Overflow unbond - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked("treasury".to_string()), - from: Default::default(), - amount: Uint128::new(500 * 10u128.pow(8)), - msg: Some(to_binary(&ReceiveType::Unbond).unwrap()), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Query unbonding queue - let query_msg = QueryMsg::Unfunded { start: 0, total: 1 }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Unfunded { total } => { - assert_eq!(total, Uint128::zero()); - } - _ => panic!("Unexpected result from query"), - }; - } - - #[test] - fn test_handle_claim_unbond() { - let (init_result, mut deps) = init_helper_staking(); - - new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); - - // Bond some amount - // Unbond - let handle_msg = ExecuteMsg::Unbond { - amount: Uint128::new(25 * 10u128.pow(8)), - padding: None, - }; - // Set time for ease of prediction - let mut env = mock_env("foo", &[]); - env.block.time.seconds() = 0; - let handle_result = execute(&mut deps, env, info, handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Fund the unbond - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked("treasury".to_string()), - from: Default::default(), - amount: Uint128::new(25 * 10u128.pow(8)), - msg: Some(to_binary(&ReceiveType::Unbond).unwrap()), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Query user stake - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("foo".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - .. - } => { - assert_eq!(tokens, Uint128::new(75 * 10u128.pow(8))); - assert_eq!(shares, Uint256::from(75 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::zero()); - assert_eq!(unbonding, Uint128::new(25 * 10u128.pow(8))); - assert_eq!(unbonded, None); - } - _ => panic!("Unexpected result from query"), - }; - - // Try to claim when its funded but the date hasn't been reached - let handle_msg = ExecuteMsg::ClaimUnbond { padding: None }; - let mut env = mock_env("foo", &[]); - env.block.time.seconds() = 0; - let handle_result = execute(&mut deps, env, info, handle_msg.clone()); - assert!(handle_result.is_err()); - - // Query user stake - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("foo".to_string()), - key: "key".to_string(), - time: Some(10), - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - .. - } => { - assert_eq!(tokens, Uint128::new(75 * 10u128.pow(8))); - assert_eq!(shares, Uint256::from(75 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::zero()); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, Some(Uint128::new(25 * 10u128.pow(8)))); - } - _ => panic!("Unexpected result from query"), - }; - - // Claim - let handle_msg = ExecuteMsg::ClaimUnbond { padding: None }; - let mut env = mock_env("foo", &[]); - env.block.time.seconds() = 11; - let handle_result = execute(&mut deps, env, info, handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Query user stake - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("foo".to_string()), - key: "key".to_string(), - time: Some(10), - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - .. - } => { - assert_eq!(tokens, Uint128::new(75 * 10u128.pow(8))); - assert_eq!(shares, Uint256::from(75 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::zero()); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, Some(Uint128::zero())); - } - _ => panic!("Unexpected result from query"), - }; - - // Try to claim when its not funded and the date has been reached - let handle_msg = ExecuteMsg::Unbond { - amount: Uint128::new(25 * 10u128.pow(8)), - padding: None, - }; - // Set time for ease of prediction - let mut env = mock_env("foo", &[]); - env.block.time.seconds() = 0; - let handle_result = execute(&mut deps, env, info, handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Claim - let handle_msg = ExecuteMsg::ClaimUnbond { padding: None }; - let mut env = mock_env("foo", &[]); - env.block.time.seconds() = 11; - let handle_result = execute(&mut deps, env, info, handle_msg.clone()); - assert!(handle_result.is_err()); - } - - #[test] - fn test_handle_fund_and_claim_rewards() { - let (init_result, mut deps) = init_helper_staking(); - - // Foo should get 2x more rewards than bar - new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); - new_staked_account(&mut deps, "bar", "key", Uint128::new(50 * 10u128.pow(8))); - - // Claim rewards - let handle_msg = ExecuteMsg::ClaimRewards { padding: None }; - - let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); - assert!(handle_result.is_err()); - - // Add rewards; foo should get 50 tkn and bar 25 - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked("treasury".to_string()), - from: Default::default(), - amount: Uint128::new(75 * 10u128.pow(8)), - msg: Some(to_binary(&ReceiveType::Reward).unwrap()), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Query user stake - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("foo".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - .. - } => { - assert_eq!(tokens, Uint128::new(100 * 10u128.pow(8))); - assert_eq!(shares, Uint256::from(100 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::new(50 * 10u128.pow(8))); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, None); - } - _ => panic!("Unexpected result from query"), - }; - - // Query user stake - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("bar".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - .. - } => { - assert_eq!(tokens, Uint128::new(50 * 10u128.pow(8))); - assert_eq!(shares, Uint256::from(50 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::new(25 * 10u128.pow(8))); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, None); - } - _ => panic!("Unexpected result from query"), - }; - - // Total tokens should be total staked plus the rewards - check_staked_state( - &deps, - Uint128::new(225 * 10u128.pow(8)), - Uint256::from(150 * 10u128.pow(18)), - ); - - // Claim rewards - let handle_msg = ExecuteMsg::ClaimRewards { padding: None }; - - let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("foo".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - .. - } => { - assert_eq!(tokens, Uint128::new(100 * 10u128.pow(8))); - assert!(shares < Uint256::from(100 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::zero()); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, None); - } - _ => panic!("Unexpected result from query"), - }; - } - - #[test] - fn test_handle_stake_rewards() { - let (init_result, mut deps) = init_helper_staking(); - - new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); - - // Add rewards - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked("treasury".to_string()), - from: Default::default(), - amount: Uint128::new(50 * 10u128.pow(8)), - msg: Some(to_binary(&ReceiveType::Reward).unwrap()), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Check account to confirm it works - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("foo".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - .. - } => { - assert_eq!(tokens, Uint128::new(100 * 10u128.pow(8))); - assert_eq!(shares, Uint256::from(100 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::new(50 * 10u128.pow(8))); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, None); - } - _ => panic!("Unexpected result from query"), - }; - - let handle_msg = ExecuteMsg::StakeRewards { padding: None }; - let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("foo".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - .. - } => { - assert_eq!(tokens, Uint128::new(150 * 10u128.pow(8))); - assert_eq!(shares, Uint256::from(100 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::zero()); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, None); - } - _ => panic!("Unexpected result from query"), - }; - } - - #[test] - fn test_handle_unbond_with_rewards() { - let (init_result, mut deps) = init_helper_staking(); - - // Foo should get 2x more rewards than bar - new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); - new_staked_account(&mut deps, "bar", "key", Uint128::new(50 * 10u128.pow(8))); - - // Add rewards; foo should get 50 tkn and bar 25 - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked("treasury".to_string()), - from: Default::default(), - amount: Uint128::new(75 * 10u128.pow(8)), - msg: Some(to_binary(&ReceiveType::Reward).unwrap()), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Query user stake - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("foo".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - .. - } => { - assert_eq!(tokens, Uint128::new(100 * 10u128.pow(8))); - assert_eq!(shares, Uint256::from(100 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::new(50 * 10u128.pow(8))); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, None); - } - _ => panic!("Unexpected result from query"), - }; - - // Query user stake - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("bar".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - .. - } => { - assert_eq!(tokens, Uint128::new(50 * 10u128.pow(8))); - assert_eq!(shares, Uint256::from(50 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::new(25 * 10u128.pow(8))); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, None); - } - _ => panic!("Unexpected result from query"), - }; - - // Total tokens should be total staked plus the rewards - check_staked_state( - &deps, - Uint128::new(225 * 10u128.pow(8)), - Uint256::from(150 * 10u128.pow(18)), - ); - - // Unbond more than allowed - let handle_msg = ExecuteMsg::Unbond { - amount: Uint128::new(50 * 10u128.pow(8)), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("foo".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - .. - } => { - assert_eq!(tokens, Uint128::new(50 * 10u128.pow(8))); - assert!(shares < Uint256::from(50 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::zero()); - assert_eq!(unbonding, Uint128::new(50 * 10u128.pow(8))); - assert_eq!(unbonded, None); - } - _ => panic!("Unexpected result from query"), - }; - } - - #[test] - fn test_handle_set_distributors_status() { - let (init_result, mut deps) = init_helper_staking(); - new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); - - let handle_msg = ExecuteMsg::SetDistributorsStatus { - enabled: false, - padding: None, - }; - - let handle_result = execute(&mut deps, mock_env("other", &[]), handle_msg.clone()); - assert!(handle_result.is_err()); - - let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - } - - #[test] - fn test_handle_add_distributors() { - let (init_result, mut deps) = init_helper_staking(); - - let query_msg = QueryMsg::Distributors {}; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Distributors { distributors } => { - assert_eq!(distributors.unwrap().len(), 1); - } - _ => panic!("Unexpected result from query"), - }; - - let handle_msg = ExecuteMsg::AddDistributors { - distributors: vec![Addr::unchecked("new_distrib".to_string())], - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("not_admin", &[]), handle_msg.clone()); - assert!(handle_result.is_err()); - - let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - let query_msg = QueryMsg::Distributors {}; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Distributors { distributors } => { - let distrib = distributors.unwrap(); - assert_eq!(distrib.len(), 2); - assert_eq!(distrib[1], Addr::unchecked("new_distrib".to_string())); - } - _ => panic!("Unexpected result from query"), - }; - } - - #[test] - fn test_handle_set_distributors() { - let (init_result, mut deps) = init_helper_staking(); - - let handle_msg = ExecuteMsg::SetDistributors { - distributors: vec![Addr::unchecked("new_distrib".to_string())], - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("not_admin", &[]), handle_msg.clone()); - assert!(handle_result.is_err()); - - let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - let query_msg = QueryMsg::Distributors {}; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Distributors { distributors } => { - let distrib = distributors.unwrap(); - assert_eq!(distrib.len(), 1); - assert_eq!(distrib[0], Addr::unchecked("new_distrib".to_string())); - } - _ => panic!("Unexpected result from query"), - }; - } - - #[test] - fn test_send_with_distributors() { - let (init_result, mut deps) = init_helper_staking(); - new_staked_account( - &mut deps, - "sender", - "key", - Uint128::new(100 * 10u128.pow(8)), - ); - new_staked_account( - &mut deps, - "distrib", - "key", - Uint128::new(100 * 10u128.pow(8)), - ); - new_staked_account( - &mut deps, - "not_distrib", - "key", - Uint128::new(100 * 10u128.pow(8)), - ); - - let handle_msg = ExecuteMsg::SetDistributors { - distributors: vec![Addr::unchecked("distrib".to_string())], - padding: None, - }; - - let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Distrib is sender - let handle_msg = ExecuteMsg::Send { - recipient: Addr::unchecked("someone".to_string()), - recipient_code_hash: None, - amount: Uint128::new(10 * 10u128.pow(8)), - msg: None, - memo: None, - padding: None, - }; - - let handle_result = execute(&mut deps, mock_env("not_distrib", &[]), handle_msg.clone()); - assert!(handle_result.is_err()); - - let handle_result = execute(&mut deps, mock_env("distrib", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Send to distrib - let handle_msg = ExecuteMsg::Send { - recipient: Addr::unchecked("distrib".to_string()), - recipient_code_hash: None, - amount: Uint128::new(10 * 10u128.pow(8)), - msg: None, - memo: None, - padding: None, - }; - - let handle_result = execute(&mut deps, mock_env("sender", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - let handle_msg = ExecuteMsg::Send { - recipient: Addr::unchecked("not_distrib".to_string()), - recipient_code_hash: None, - amount: Uint128::new(10 * 10u128.pow(8)), - msg: None, - memo: None, - padding: None, - }; - - let handle_result = execute(&mut deps, mock_env("sender", &[]), handle_msg.clone()); - assert!(handle_result.is_err()); - } - - #[test] - fn test_handle_send_with_rewards() { - let (init_result, mut deps) = init_helper_staking(); - new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); - - let handle_msg = ExecuteMsg::SetDistributorsStatus { - enabled: false, - padding: None, - }; - - let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Add rewards - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked("treasury".to_string()), - from: Default::default(), - amount: Uint128::new(50 * 10u128.pow(8)), - msg: Some(to_binary(&ReceiveType::Reward).unwrap()), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Check account to confirm it works - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("foo".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - .. - } => { - assert_eq!(tokens, Uint128::new(100 * 10u128.pow(8))); - assert_eq!(shares, Uint256::from(100 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::new(50 * 10u128.pow(8))); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, None); - } - _ => panic!("Unexpected result from query"), - }; - - // Send msg - let handle_msg = ExecuteMsg::Send { - recipient: Addr::unchecked("other".to_string()), - recipient_code_hash: None, - amount: Uint128::new(10 * 10u128.pow(8)), - msg: None, - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Check that it was autoclaimed - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("foo".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - .. - } => { - assert_eq!(tokens, Uint128::new(90 * 10u128.pow(8))); - assert!(shares < Uint256::from(90 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::zero()); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, None); - } - _ => panic!("Unexpected result from query"), - }; - } - - #[test] - fn test_handle_send_cooldown() { - let (init_result, mut deps) = init_helper_staking(); - new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); - new_staked_account(&mut deps, "bar", "key", Uint128::new(100 * 10u128.pow(8))); - - let handle_msg = ExecuteMsg::SetDistributorsStatus { - enabled: false, - padding: None, - }; - - let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Send msg - let handle_msg = ExecuteMsg::Send { - recipient: Addr::unchecked("bar".to_string()), - recipient_code_hash: None, - amount: Uint128::new(10 * 10u128.pow(8)), - msg: None, - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Check that it was autoclaimed - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("bar".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - cooldown, - .. - } => { - assert_eq!(tokens, Uint128::new(110 * 10u128.pow(8))); - assert_eq!(shares, Uint256::from(110 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::zero()); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, None); - assert_eq!(cooldown.0.len(), 1); - assert_eq!(cooldown.0[0].amount, Uint128::new(10 * 10u128.pow(8))); - } - _ => panic!("Unexpected result from query"), - }; - - // Send msg - let handle_msg = ExecuteMsg::Send { - recipient: Addr::unchecked("foo".to_string()), - recipient_code_hash: None, - amount: Uint128::new(100 * 10u128.pow(8)), - msg: None, - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bar", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Check that it was autoclaimed - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("bar".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - cooldown, - .. - } => { - assert_eq!(tokens, Uint128::new(10 * 10u128.pow(8))); - assert_eq!(shares, Uint256::from(10 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::zero()); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, None); - assert_eq!(cooldown.0.len(), 1); - assert_eq!(cooldown.0[0].amount, Uint128::new(10 * 10u128.pow(8))); - } - _ => panic!("Unexpected result from query"), - }; - - // Send msg - let handle_msg = ExecuteMsg::Send { - recipient: Addr::unchecked("foo".to_string()), - recipient_code_hash: None, - amount: Uint128::new(10 * 10u128.pow(8)), - msg: None, - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bar", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Check that it was autoclaimed - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("bar".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - cooldown, - .. - } => { - assert_eq!(tokens, Uint128::zero()); - assert_eq!(shares, Uint256::zero()); - assert_eq!(pending_rewards, Uint128::zero()); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, None); - assert_eq!(cooldown.0.len(), 0); - } - _ => panic!("Unexpected result from query"), - }; - } - - #[test] - fn test_handle_unbond_cooldown() { - let (init_result, mut deps) = init_helper_staking(); - new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); - new_staked_account(&mut deps, "bar", "key", Uint128::new(100 * 10u128.pow(8))); - - let handle_msg = ExecuteMsg::SetDistributorsStatus { - enabled: false, - padding: None, - }; - - let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Send msg - let handle_msg = ExecuteMsg::Send { - recipient: Addr::unchecked("bar".to_string()), - recipient_code_hash: None, - amount: Uint128::new(10 * 10u128.pow(8)), - msg: None, - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Check that it was autoclaimed - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("bar".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - cooldown, - .. - } => { - assert_eq!(tokens, Uint128::new(110 * 10u128.pow(8))); - assert_eq!(shares, Uint256::from(110 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::zero()); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, None); - assert_eq!(cooldown.0.len(), 1); - assert_eq!(cooldown.0[0].amount, Uint128::new(10 * 10u128.pow(8))); - } - _ => panic!("Unexpected result from query"), - }; - - // Unbond - let handle_msg = ExecuteMsg::Unbond { - amount: Uint128::new(100 * 10u128.pow(8)), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bar", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Check that it was autoclaimed - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("bar".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - cooldown, - .. - } => { - assert_eq!(tokens, Uint128::new(10 * 10u128.pow(8))); - assert_eq!(shares, Uint256::from(10 * 10u128.pow(18))); - assert_eq!(pending_rewards, Uint128::zero()); - assert_eq!(unbonding, Uint128::new(100 * 10u128.pow(8))); - assert_eq!(unbonded, None); - assert_eq!(cooldown.0.len(), 1); - assert_eq!(cooldown.0[0].amount, Uint128::new(10 * 10u128.pow(8))); - } - _ => panic!("Unexpected result from query"), - }; - - // Unbond - let handle_msg = ExecuteMsg::Unbond { - amount: Uint128::new(10 * 10u128.pow(8)), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bar", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - // Check that it was autoclaimed - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("bar".to_string()), - key: "key".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - cooldown, - .. - } => { - assert_eq!(tokens, Uint128::zero()); - assert_eq!(shares, Uint256::zero()); - assert_eq!(pending_rewards, Uint128::zero()); - assert_eq!(unbonding, Uint128::new(110 * 10u128.pow(8))); - assert_eq!(unbonded, None); - assert_eq!(cooldown.0.len(), 0); - } - _ => panic!("Unexpected result from query"), - }; - } - - #[test] - fn test_handle_stop_bonding() { - let (init_result, mut deps) = init_helper_staking(); - new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); - - let handle_msg = ExecuteMsg::SetDistributorsStatus { - enabled: false, - padding: None, - }; - - let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - let pause_msg = ExecuteMsg::SetContractStatus { - level: ContractStatusLevel::StopBonding, - padding: None, - }; - - let handle_result = execute(&mut deps, mock_env("admin", &[]), pause_msg); - assert!(handle_result.is_ok()); - - let send_msg = ExecuteMsg::Transfer { - recipient: Addr::unchecked("account".to_string()), - amount: Uint128::new(123), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("foo", &[]), send_msg); - assert!(handle_result.is_ok()); - - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked("foo".to_string()), - from: Default::default(), - amount: Uint128::new(100 * 10u128.pow(8)), - msg: Some(to_binary(&ReceiveType::Bond { use_from: None }).unwrap()), - memo: None, - padding: None, - }; - // Bond tokens - let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); - assert!(handle_result.is_err()); - - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked("foo".to_string()), - from: Default::default(), - amount: Uint128::new(100 * 10u128.pow(8)), - msg: Some(to_binary(&ReceiveType::Reward).unwrap()), - memo: None, - padding: None, - }; - // Bond tokens - let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); - assert!(handle_result.is_err()); - - let handle_msg = ExecuteMsg::Unbond { - amount: Uint128::new(10 * 10u128.pow(8)), - padding: None, - }; - // Bond tokens - let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - } - - #[test] - fn test_handle_stop_all_but_unbond() { - let (init_result, mut deps) = init_helper_staking(); - new_staked_account(&mut deps, "foo", "key", Uint128::new(100 * 10u128.pow(8))); - - let handle_msg = ExecuteMsg::SetDistributorsStatus { - enabled: false, - padding: None, - }; - - let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - - let pause_msg = ExecuteMsg::SetContractStatus { - level: ContractStatusLevel::StopAllButUnbond, - padding: None, - }; - - let handle_result = execute(&mut deps, mock_env("admin", &[]), pause_msg); - assert!(handle_result.is_ok()); - - let send_msg = ExecuteMsg::Transfer { - recipient: Addr::unchecked("account".to_string()), - amount: Uint128::new(123), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("foo", &[]), send_msg); - assert!(handle_result.is_err()); - - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked("foo".to_string()), - from: Default::default(), - amount: Uint128::new(100 * 10u128.pow(8)), - msg: Some(to_binary(&ReceiveType::Bond { use_from: None }).unwrap()), - memo: None, - padding: None, - }; - // Bond tokens - let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); - assert!(handle_result.is_err()); - - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked("foo".to_string()), - from: Default::default(), - amount: Uint128::new(100 * 10u128.pow(8)), - msg: Some(to_binary(&ReceiveType::Reward).unwrap()), - memo: None, - padding: None, - }; - // Bond tokens - let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); - assert!(handle_result.is_err()); - - let handle_msg = ExecuteMsg::Unbond { - amount: Uint128::new(10 * 10u128.pow(8)), - padding: None, - }; - // Bond tokens - let handle_result = execute(&mut deps, mock_env("foo", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - } -} - -#[cfg(test)] -mod snip20_tests { - use super::*; - use crate::msg::{InitConfig, ResponseStatus}; - use shade_protocol::c_std::{ - from_binary, - testing::*, - BlockInfo, - Coin, - ContractInfo, - MessageInfo, - Binary, - WasmMsg, - }; - use shade_protocol::{ - contract_interfaces::staking::snip20_staking::ReceiveType, - utils::asset::Contract, - }; - use std::any::Any; - - // Helper functions - #[derive(Clone)] - struct InitBalance { - pub acc: &'static str, - pub pwd: &'static str, - pub stake: Uint128, - } - - fn new_staked_account( - deps: &mut Extern, - acc: &str, - pwd: &str, - stake: Uint128, - ) { - let handle_msg = ExecuteMsg::Receive { - sender: Addr(acc.to_string()), - from: Default::default(), - amount: stake, - msg: Some(to_binary(&ReceiveType::Bond { use_from: None }).unwrap()), - memo: None, - padding: None, - }; - // Bond tokens - let handle_result = execute(deps, mock_env("token", &[]), handle_msg.clone()); - assert!(handle_result.is_ok()); - let handle_msg = ExecuteMsg::SetViewingKey { - key: pwd.to_string(), - padding: None, - }; - let handle_result = execute(deps, mock_env(acc, &[]), handle_msg.clone()); - assert!(handle_result.is_ok()) - } - - fn init_helper( - initial_balances: Vec, - ) -> ( - StdResult, - Extern, - ) { - let mut deps = mock_dependencies(20, &[]); - let env = mock_env("instantiator", &[]); - - let init_msg = InstantiateMsg { - name: "sec-sec".to_string(), - admin: Some(Addr::unchecked("admin".to_string())), - symbol: "SECSEC".to_string(), - decimals: Some(8), - share_decimals: 18, - prng_seed: Binary::from("lolz fun yay".as_bytes()), - config: None, - unbond_time: 10, - staked_token: Contract { - address: Addr::unchecked("token".to_string()), - code_hash: "hash".to_string(), - }, - treasury: Some(Addr::unchecked("treasury".to_string())), - treasury_code_hash: None, - limit_transfer: false, - distributors: None, - }; - - let instantiate = instantiate(&mut deps, env, info, init_msg); - - for account in initial_balances.iter() { - new_staked_account(&mut deps, account.acc, account.pwd, account.stake); - } - - (instantiate, deps) - } - - fn init_helper_with_config( - initial_balances: Vec, - enable_deposit: bool, - enable_redeem: bool, - enable_mint: bool, - enable_burn: bool, - contract_bal: u128, - ) -> ( - StdResult, - Extern, - ) { - let mut deps = mock_dependencies(20, &[Coin { - denom: "uscrt".to_string(), - amount: Uint128::new(contract_bal).into(), - }]); - - let env = mock_env("instantiator", &[]); - let init_config: InitConfig = from_binary(&Binary::from( - format!( - "{{\"public_total_supply\":false, - \"enable_deposit\":{}, - \"enable_redeem\":{}, - \"enable_mint\":{}, - \"enable_burn\":{}}}", - enable_deposit, enable_redeem, enable_mint, enable_burn - ) - .as_bytes(), - )) - .unwrap(); - let init_msg = InstantiateMsg { - name: "sec-sec".to_string(), - admin: Some(Addr::unchecked("admin".to_string())), - symbol: "SECSEC".to_string(), - decimals: Some(8), - share_decimals: 18, - prng_seed: Binary::from("lolz fun yay".as_bytes()), - config: Some(init_config), - unbond_time: 10, - staked_token: Contract { - address: Addr::unchecked("token".to_string()), - code_hash: "hash".to_string(), - }, - treasury: Some(Addr::unchecked("treasury".to_string())), - treasury_code_hash: None, - limit_transfer: false, - distributors: None, - }; - - let instantiate = instantiate(&mut deps, env, info, init_msg); - - for account in initial_balances.iter() { - new_staked_account(&mut deps, account.acc, account.pwd, account.stake); - } - - (instantiate, deps) - } - - /// Will return a ViewingKey only for the first account in `initial_balances` - fn _auth_query_helper( - initial_balances: Vec, - ) -> (ViewingKey, Extern) { - let (init_result, mut deps) = init_helper(initial_balances.clone()); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let account = initial_balances[0].acc; - let create_vk_msg = ExecuteMsg::CreateViewingKey { - entropy: "42".to_string(), - padding: None, - }; - let handle_response = execute(&mut deps, mock_env(account, &[]), create_vk_msg).unwrap(); - let vk = match from_binary(&handle_response.data.unwrap()).unwrap() { - HandleAnswer::CreateViewingKey { key } => key, - _ => panic!("Unexpected result from execute"), - }; - - (vk, deps) - } - - fn extract_error_msg(error: StdResult) -> String { - match error { - Ok(response) => { - let bin_err = (&response as &dyn Any) - .downcast_ref::() - .expect("An error was expected, but no error could be extracted"); - match from_binary(bin_err).unwrap() { - QueryAnswer::ViewingKeyError { msg } => msg, - _ => panic!("Unexpected query answer"), - } - } - Err(err) => match err { - StdError::GenericErr { msg, .. } => msg, - _ => panic!("Unexpected result from instantiate"), - }, - } - } - - fn ensure_success(handle_result: Response) -> bool { - let handle_result: HandleAnswer = from_binary(&handle_result.data.unwrap()).unwrap(); - - match handle_result { - HandleAnswer::UpdateStakeConfig { status } - | HandleAnswer::Receive { status } - | HandleAnswer::Unbond { status } - | HandleAnswer::ClaimUnbond { status } - | HandleAnswer::ClaimRewards { status } - | HandleAnswer::StakeRewards { status } - | HandleAnswer::ExposeBalance { status } - | HandleAnswer::AddDistributors { status } - | HandleAnswer::SetDistributors { status } - | HandleAnswer::Transfer { status } - | HandleAnswer::Send { status } - | HandleAnswer::RegisterReceive { status } - | HandleAnswer::SetViewingKey { status } - | HandleAnswer::TransferFrom { status } - | HandleAnswer::SendFrom { status } - | HandleAnswer::ChangeAdmin { status } - | HandleAnswer::SetContractStatus { status } => { - matches!(status, ResponseStatus::Success { .. }) - } - _ => panic!( - "HandleAnswer not supported for success extraction: {:?}", - handle_result - ), - } - } - - // Init tests - - #[test] - fn test_init_sanity() { - let (init_result, deps) = init_helper(vec![InitBalance { - acc: "lebron", - pwd: "pwd", - stake: Uint128::new(5000), - }]); - - let config = ReadonlyConfig::from_storage(deps.storage); - let constants = config.constants().unwrap(); - assert_eq!(config.total_supply(), 5000); - assert_eq!(config.contract_status(), ContractStatusLevel::NormalRun); - assert_eq!(constants.name, "sec-sec".to_string()); - assert_eq!(constants.admin, Addr::unchecked("admin".to_string())); - assert_eq!(constants.symbol, "STKD-SECSEC".to_string()); - assert_eq!(constants.decimals, 8); - assert_eq!( - constants.prng_seed, - sha_256("lolz fun yay".to_owned().as_bytes()) - ); - assert_eq!(constants.total_supply_is_public, false); - } - - #[test] - fn test_init_with_config_sanity() { - let (init_result, deps) = init_helper_with_config( - vec![InitBalance { - acc: "lebron", - pwd: "pwd", - stake: Uint128::new(5000), - }], - true, - true, - true, - true, - 0, - ); - - let config = ReadonlyConfig::from_storage(deps.storage); - let constants = config.constants().unwrap(); - assert_eq!(config.total_supply(), 5000); - assert_eq!(config.contract_status(), ContractStatusLevel::NormalRun); - assert_eq!(constants.name, "sec-sec".to_string()); - assert_eq!(constants.admin, Addr::unchecked("admin".to_string())); - assert_eq!(constants.symbol, "STKD-SECSEC".to_string()); - assert_eq!(constants.decimals, 8); - assert_eq!( - constants.prng_seed, - sha_256("lolz fun yay".to_owned().as_bytes()) - ); - assert_eq!(constants.total_supply_is_public, false); - } - - #[test] - fn test_total_supply_overflow() { - let (init_result, mut deps) = init_helper(vec![InitBalance { - acc: "lebron", - pwd: "pwd", - stake: Uint128::new(u128::MAX), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let (init_result, _deps) = init_helper(vec![InitBalance { - acc: "lebron", - pwd: "pwd", - stake: Uint128::new(u128::MAX), - }]); - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked("giannis".to_string()), - from: Default::default(), - amount: Uint128::new(1), - msg: Some(to_binary(&ReceiveType::Bond { use_from: None }).unwrap()), - memo: None, - padding: None, - }; - // Bond tokens - let handle_result = execute(&mut deps, mock_env("token", &[]), handle_msg.clone()); - assert!(handle_result.is_err()); - } - - #[test] - fn test_handle_transfer() { - let (init_result, mut deps) = init_helper(vec![InitBalance { - acc: "bob", - pwd: "pwd", - stake: Uint128::new(5000), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("bob".to_string()), - key: "pwd".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - .. - } => { - assert_eq!(tokens, Uint128::new(5000)); - assert_eq!(shares, Uint256::from(50000000000000u128)); - assert_eq!(pending_rewards, Uint128::zero()); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, None); - } - _ => panic!("Unexpected result from query"), - }; - - let query_balance_msg = QueryMsg::TotalStaked {}; - - let query_response = query(&deps, query_balance_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::TotalStaked { shares, tokens } => { - assert_eq!(tokens, Uint128::new(5000)); - assert_eq!(shares, Uint256::from(50000000000000u128)) - } - _ => panic!("Unexpected result from query"), - }; - - let handle_msg = ExecuteMsg::Transfer { - recipient: Addr::unchecked("alice".to_string()), - amount: Uint128::new(1000), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - let bob_canonical = deps - .api - .canonical_address(&Addr::unchecked("bob".to_string())) - .unwrap(); - let alice_canonical = deps - .api - .canonical_address(&Addr::unchecked("alice".to_string())) - .unwrap(); - let balances = ReadonlyBalances::from_storage(deps.storage); - assert_eq!(5000 - 1000, balances.account_amount(&bob_canonical)); - assert_eq!(1000, balances.account_amount(&alice_canonical)); - - let handle_msg = ExecuteMsg::Transfer { - recipient: Addr::unchecked("alice".to_string()), - amount: Uint128::new(10000), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - let error = extract_error_msg(handle_result); - assert!(error.contains("insufficient funds")); - - let query_msg = QueryMsg::Staked { - address: Addr::unchecked("bob".to_string()), - key: "pwd".to_string(), - time: None, - }; - - let query_response = query(&deps, query_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::Staked { - tokens, - shares, - pending_rewards, - unbonding, - unbonded, - .. - } => { - assert_eq!(tokens, Uint128::new(4000)); - assert_eq!(shares, Uint256::from(40000000000000u128)); - assert_eq!(pending_rewards, Uint128::zero()); - assert_eq!(unbonding, Uint128::zero()); - assert_eq!(unbonded, None); - } - _ => panic!("Unexpected result from query"), - }; - - let query_balance_msg = QueryMsg::TotalStaked {}; - - let query_response = query(&deps, query_balance_msg).unwrap(); - match from_binary(&query_response).unwrap() { - QueryAnswer::TotalStaked { shares, tokens } => { - assert_eq!(tokens, Uint128::new(5000)); - assert_eq!(shares, Uint256::from(50000000000000u128)) - } - _ => panic!("Unexpected result from query"), - }; - } - - #[test] - fn test_handle_send() { - let (init_result, mut deps) = init_helper(vec![InitBalance { - acc: "bob", - pwd: "pwd", - stake: Uint128::new(5000), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::RegisterReceive { - code_hash: "this_is_a_hash_of_a_code".to_string(), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("contract", &[]), handle_msg); - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - - let handle_msg = ExecuteMsg::Send { - recipient: Addr::unchecked("contract".to_string()), - recipient_code_hash: None, - amount: Uint128::new(100), - memo: Some("my memo".to_string()), - padding: None, - msg: Some(to_binary("hey hey you you").unwrap()), - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - let result = handle_result.unwrap(); - assert!(ensure_success(result.clone())); - assert!( - result.messages.contains(&CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: Addr::unchecked("contract".to_string()), - callback_code_hash: "this_is_a_hash_of_a_code".to_string(), - msg: Snip20ReceiveMsg::new( - Addr::unchecked("bob".to_string()), - Addr::unchecked("bob".to_string()), - Uint128::new(100), - Some("my memo".to_string()), - Some(to_binary("hey hey you you").unwrap()) - ) - .into_binary() - .unwrap(), - send: vec![] - })) - ); - } - - #[test] - fn test_handle_register_receive() { - let (init_result, mut deps) = init_helper(vec![InitBalance { - acc: "bob", - pwd: "pwd", - stake: Uint128::new(5000), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::RegisterReceive { - code_hash: "this_is_a_hash_of_a_code".to_string(), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("contract", &[]), handle_msg); - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - - let hash = get_receiver_hash(deps.storage, &Addr::unchecked("contract".to_string())) - .unwrap() - .unwrap(); - assert_eq!(hash, "this_is_a_hash_of_a_code".to_string()); - } - - #[test] - fn test_handle_create_viewing_key() { - let (init_result, mut deps) = init_helper(vec![InitBalance { - acc: "bob", - pwd: "pwd", - stake: Uint128::new(5000), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::CreateViewingKey { - entropy: "".to_string(), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - assert!( - handle_result.is_ok(), - "execute() failed: {}", - handle_result.err().unwrap() - ); - let answer: HandleAnswer = from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); - - let key = match answer { - HandleAnswer::CreateViewingKey { key } => key, - _ => panic!("NOPE"), - }; - let bob_canonical = deps - .api - .canonical_address(&Addr::unchecked("bob".to_string())) - .unwrap(); - let saved_vk = read_viewing_key(deps.storage, &bob_canonical).unwrap(); - assert!(key.check_viewing_key(saved_vk.as_slice())); - } - - #[test] - fn test_handle_set_viewing_key() { - let (init_result, mut deps) = init_helper(vec![InitBalance { - acc: "bob", - pwd: "pwd", - stake: Uint128::new(5000), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - // Set VK - let handle_msg = ExecuteMsg::SetViewingKey { - key: "hi lol".to_string(), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - let unwrapped_result: HandleAnswer = - from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); - assert_eq!( - to_binary(&unwrapped_result).unwrap(), - to_binary(&HandleAnswer::SetViewingKey { - status: ResponseStatus::Success - }) - .unwrap(), - ); - - // Set valid VK - let actual_vk = ViewingKey("x".to_string().repeat(VIEWING_KEY_SIZE)); - let handle_msg = ExecuteMsg::SetViewingKey { - key: actual_vk.0.clone(), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - let unwrapped_result: HandleAnswer = - from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); - assert_eq!( - to_binary(&unwrapped_result).unwrap(), - to_binary(&HandleAnswer::SetViewingKey { status: Success }).unwrap(), - ); - let bob_canonical = deps - .api - .canonical_address(&Addr::unchecked("bob".to_string())) - .unwrap(); - let saved_vk = read_viewing_key(deps.storage, &bob_canonical).unwrap(); - assert!(actual_vk.check_viewing_key(&saved_vk)); - } - - #[test] - fn test_handle_transfer_from() { - let (init_result, mut deps) = init_helper(vec![InitBalance { - acc: "bob", - pwd: "pwd", - stake: Uint128::new(5000), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - // Transfer before allowance - let handle_msg = ExecuteMsg::TransferFrom { - owner: Addr::unchecked("bob".to_string()), - recipient: Addr::unchecked("alice".to_string()), - amount: Uint128::new(2500), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("alice", &[]), handle_msg); - let error = extract_error_msg(handle_result); - assert!(error.contains("insufficient allowance")); - - // Transfer more than allowance - let handle_msg = ExecuteMsg::IncreaseAllowance { - spender: Addr::unchecked("alice".to_string()), - amount: Uint128::new(2000), - padding: None, - expiration: Some(1_571_797_420), - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - assert!( - handle_result.is_ok(), - "execute() failed: {}", - handle_result.err().unwrap() - ); - let handle_msg = ExecuteMsg::TransferFrom { - owner: Addr::unchecked("bob".to_string()), - recipient: Addr::unchecked("alice".to_string()), - amount: Uint128::new(2500), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("alice", &[]), handle_msg); - let error = extract_error_msg(handle_result); - assert!(error.contains("insufficient allowance")); - - // Transfer after allowance expired - let handle_msg = ExecuteMsg::TransferFrom { - owner: Addr::unchecked("bob".to_string()), - recipient: Addr::unchecked("alice".to_string()), - amount: Uint128::new(2000), - memo: None, - padding: None, - }; - let handle_result = execute( - &mut deps, - Env { - block: BlockInfo { - height: 12_345, - time: 1_571_797_420, - chain_id: "cosmos-testnet-14002".to_string(), - }, - message: MessageInfo { - sender: Addr::unchecked("bob".to_string()), - sent_funds: vec![], - }, - contract: ContractInfo { - address: Addr::unchecked(MOCK_CONTRACT_ADDR), - }, - contract_key: Some("".to_string()), - contract_code_hash: "".to_string(), - }, - handle_msg, - ); - let error = extract_error_msg(handle_result); - assert!(error.contains("insufficient allowance")); - - // Sanity check - let handle_msg = ExecuteMsg::TransferFrom { - owner: Addr::unchecked("bob".to_string()), - recipient: Addr::unchecked("alice".to_string()), - amount: Uint128::new(2000), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("alice", &[]), handle_msg); - assert!( - handle_result.is_ok(), - "execute() failed: {}", - handle_result.err().unwrap() - ); - let bob_canonical = deps - .api - .canonical_address(&Addr::unchecked("bob".to_string())) - .unwrap(); - let alice_canonical = deps - .api - .canonical_address(&Addr::unchecked("alice".to_string())) - .unwrap(); - let bob_balance = crate::state::ReadonlyBalances::from_storage(deps.storage) - .account_amount(&bob_canonical); - let alice_balance = crate::state::ReadonlyBalances::from_storage(deps.storage) - .account_amount(&alice_canonical); - assert_eq!(bob_balance, 5000 - 2000); - assert_eq!(alice_balance, 2000); - let total_supply = ReadonlyConfig::from_storage(deps.storage).total_supply(); - assert_eq!(total_supply, 5000); - - // Second send more than allowance - let handle_msg = ExecuteMsg::TransferFrom { - owner: Addr::unchecked("bob".to_string()), - recipient: Addr::unchecked("alice".to_string()), - amount: Uint128::new(1), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("alice", &[]), handle_msg); - let error = extract_error_msg(handle_result); - assert!(error.contains("insufficient allowance")); - } - - #[test] - fn test_handle_send_from() { - let (init_result, mut deps) = init_helper(vec![InitBalance { - acc: "bob", - pwd: "pwd", - stake: Uint128::new(5000), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - // Send before allowance - let handle_msg = ExecuteMsg::SendFrom { - owner: Addr::unchecked("bob".to_string()), - recipient: Addr::unchecked("alice".to_string()), - recipient_code_hash: None, - amount: Uint128::new(2500), - memo: None, - msg: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("alice", &[]), handle_msg); - let error = extract_error_msg(handle_result); - assert!(error.contains("insufficient allowance")); - - // Send more than allowance - let handle_msg = ExecuteMsg::IncreaseAllowance { - spender: Addr::unchecked("alice".to_string()), - amount: Uint128::new(2000), - padding: None, - expiration: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - assert!( - handle_result.is_ok(), - "execute() failed: {}", - handle_result.err().unwrap() - ); - let handle_msg = ExecuteMsg::SendFrom { - owner: Addr::unchecked("bob".to_string()), - recipient: Addr::unchecked("alice".to_string()), - recipient_code_hash: None, - amount: Uint128::new(2500), - memo: None, - msg: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("alice", &[]), handle_msg); - let error = extract_error_msg(handle_result); - assert!(error.contains("insufficient allowance")); - - // Sanity check - let handle_msg = ExecuteMsg::RegisterReceive { - code_hash: "lolz".to_string(), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("contract", &[]), handle_msg); - assert!( - handle_result.is_ok(), - "execute() failed: {}", - handle_result.err().unwrap() - ); - let send_msg = Binary::from(r#"{ "some_msg": { "some_key": "some_val" } }"#.as_bytes()); - let snip20_msg = Snip20ReceiveMsg::new( - Addr::unchecked("alice".to_string()), - Addr::unchecked("bob".to_string()), - Uint128::new(2000), - Some("my memo".to_string()), - Some(send_msg.clone()), - ); - let handle_msg = ExecuteMsg::SendFrom { - owner: Addr::unchecked("bob".to_string()), - recipient: Addr::unchecked("contract".to_string()), - recipient_code_hash: None, - amount: Uint128::new(2000), - memo: Some("my memo".to_string()), - msg: Some(send_msg), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("alice", &[]), handle_msg); - assert!( - handle_result.is_ok(), - "execute() failed: {}", - handle_result.err().unwrap() - ); - assert!( - handle_result.unwrap().messages.contains( - &snip20_msg - .into_cosmos_msg("lolz".to_string(), Addr::unchecked("contract".to_string())) - .unwrap() - ) - ); - let bob_canonical = deps - .api - .canonical_address(&Addr::unchecked("bob".to_string())) - .unwrap(); - let contract_canonical = deps - .api - .canonical_address(&Addr::unchecked("contract".to_string())) - .unwrap(); - let bob_balance = crate::state::ReadonlyBalances::from_storage(deps.storage) - .account_amount(&bob_canonical); - let contract_balance = crate::state::ReadonlyBalances::from_storage(deps.storage) - .account_amount(&contract_canonical); - assert_eq!(bob_balance, 5000 - 2000); - assert_eq!(contract_balance, 2000); - let total_supply = ReadonlyConfig::from_storage(deps.storage).total_supply(); - assert_eq!(total_supply, 5000); - - // Second send more than allowance - let handle_msg = ExecuteMsg::SendFrom { - owner: Addr::unchecked("bob".to_string()), - recipient: Addr::unchecked("alice".to_string()), - recipient_code_hash: None, - amount: Uint128::new(1), - memo: None, - msg: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("alice", &[]), handle_msg); - let error = extract_error_msg(handle_result); - assert!(error.contains("insufficient allowance")); - } - - #[test] - fn test_handle_decrease_allowance() { - let (init_result, mut deps) = init_helper(vec![InitBalance { - acc: "bob", - pwd: "pwd", - stake: Uint128::new(5000), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::DecreaseAllowance { - spender: Addr::unchecked("alice".to_string()), - amount: Uint128::new(2000), - padding: None, - expiration: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - assert!( - handle_result.is_ok(), - "execute() failed: {}", - handle_result.err().unwrap() - ); - - let bob_canonical = deps - .api - .canonical_address(&Addr::unchecked("bob".to_string())) - .unwrap(); - let alice_canonical = deps - .api - .canonical_address(&Addr::unchecked("alice".to_string())) - .unwrap(); - - let allowance = read_allowance(deps.storage, &bob_canonical, &alice_canonical).unwrap(); - assert_eq!(allowance, crate::state::Allowance { - amount: 0, - expiration: None - }); - - let handle_msg = ExecuteMsg::IncreaseAllowance { - spender: Addr::unchecked("alice".to_string()), - amount: Uint128::new(2000), - padding: None, - expiration: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - assert!( - handle_result.is_ok(), - "execute() failed: {}", - handle_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::DecreaseAllowance { - spender: Addr::unchecked("alice".to_string()), - amount: Uint128::new(50), - padding: None, - expiration: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - assert!( - handle_result.is_ok(), - "execute() failed: {}", - handle_result.err().unwrap() - ); - - let allowance = read_allowance(deps.storage, &bob_canonical, &alice_canonical).unwrap(); - assert_eq!(allowance, crate::state::Allowance { - amount: 1950, - expiration: None - }); - } - - #[test] - fn test_handle_increase_allowance() { - let (init_result, mut deps) = init_helper(vec![InitBalance { - acc: "bob", - pwd: "pwd", - stake: Uint128::new(5000), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::IncreaseAllowance { - spender: Addr::unchecked("alice".to_string()), - amount: Uint128::new(2000), - padding: None, - expiration: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - assert!( - handle_result.is_ok(), - "execute() failed: {}", - handle_result.err().unwrap() - ); - - let bob_canonical = deps - .api - .canonical_address(&Addr::unchecked("bob".to_string())) - .unwrap(); - let alice_canonical = deps - .api - .canonical_address(&Addr::unchecked("alice".to_string())) - .unwrap(); - - let allowance = read_allowance(deps.storage, &bob_canonical, &alice_canonical).unwrap(); - assert_eq!(allowance, crate::state::Allowance { - amount: 2000, - expiration: None - }); - - let handle_msg = ExecuteMsg::IncreaseAllowance { - spender: Addr::unchecked("alice".to_string()), - amount: Uint128::new(2000), - padding: None, - expiration: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - assert!( - handle_result.is_ok(), - "execute() failed: {}", - handle_result.err().unwrap() - ); - - let allowance = read_allowance(deps.storage, &bob_canonical, &alice_canonical).unwrap(); - assert_eq!(allowance, crate::state::Allowance { - amount: 4000, - expiration: None - }); - } - - #[test] - fn test_handle_change_admin() { - let (init_result, mut deps) = init_helper(vec![InitBalance { - acc: "bob", - pwd: "pwd", - stake: Uint128::new(5000), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::ChangeAdmin { - address: Addr::unchecked("bob".to_string()), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg); - assert!( - handle_result.is_ok(), - "execute() failed: {}", - handle_result.err().unwrap() - ); - - let admin = ReadonlyConfig::from_storage(deps.storage) - .constants() - .unwrap() - .admin; - assert_eq!(admin, Addr::unchecked("bob".to_string())); - } - - #[test] - fn test_handle_set_contract_status() { - let (init_result, mut deps) = init_helper(vec![InitBalance { - acc: "admin", - pwd: "pwd", - stake: Uint128::new(5000), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::SetContractStatus { - level: ContractStatusLevel::StopAll, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("admin", &[]), handle_msg); - assert!( - handle_result.is_ok(), - "execute() failed: {}", - handle_result.err().unwrap() - ); - - let contract_status = ReadonlyConfig::from_storage(deps.storage).contract_status(); - assert!(matches!( - contract_status, - ContractStatusLevel::StopAll { .. } - )); - } - - #[test] - fn test_handle_admin_commands() { - let admin_err = "Admin commands can only be run from admin address".to_string(); - let (init_result, mut deps) = init_helper_with_config( - vec![InitBalance { - acc: "lebron", - pwd: "pwd", - stake: Uint128::new(5000), - }], - false, - false, - true, - false, - 0, - ); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let pause_msg = ExecuteMsg::SetContractStatus { - level: ContractStatusLevel::StopAll, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("not_admin", &[]), pause_msg); - let error = extract_error_msg(handle_result); - assert!(error.contains(&admin_err.clone())); - - let change_admin_msg = ExecuteMsg::ChangeAdmin { - address: Addr::unchecked("not_admin".to_string()), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("not_admin", &[]), change_admin_msg); - let error = extract_error_msg(handle_result); - assert!(error.contains(&admin_err.clone())); - } - - #[test] - fn test_handle_pause_all() { - let (init_result, mut deps) = init_helper(vec![InitBalance { - acc: "lebron", - pwd: "pwd", - stake: Uint128::new(5000), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let pause_msg = ExecuteMsg::SetContractStatus { - level: ContractStatusLevel::StopAll, - padding: None, - }; - - let handle_result = execute(&mut deps, mock_env("admin", &[]), pause_msg); - assert!( - handle_result.is_ok(), - "Pause execute failed: {}", - handle_result.err().unwrap() - ); - - let send_msg = ExecuteMsg::Transfer { - recipient: Addr::unchecked("account".to_string()), - amount: Uint128::new(123), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("admin", &[]), send_msg); - let error = extract_error_msg(handle_result); - assert_eq!( - error, - "This contract is stopped and this action is not allowed".to_string() - ); - } - - // Query tests - - #[test] - fn test_authenticated_queries() { - let (init_result, mut deps) = init_helper(vec![InitBalance { - acc: "giannis", - pwd: "pwd", - stake: Uint128::new(5000), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let no_vk_yet_query_msg = QueryMsg::Balance { - address: Addr::unchecked("giannis".to_string()), - key: "no_vk_yet".to_string(), - }; - let query_result = query(&deps, no_vk_yet_query_msg); - let error = extract_error_msg(query_result); - assert_eq!( - error, - "Wrong viewing key for this address or viewing key not set".to_string() - ); - - let create_vk_msg = ExecuteMsg::CreateViewingKey { - entropy: "34".to_string(), - padding: None, - }; - let handle_response = execute(&mut deps, mock_env("giannis", &[]), create_vk_msg).unwrap(); - let vk = match from_binary(&handle_response.data.unwrap()).unwrap() { - HandleAnswer::CreateViewingKey { key } => key, - _ => panic!("Unexpected result from execute"), - }; - - let query_balance_msg = QueryMsg::Balance { - address: Addr::unchecked("giannis".to_string()), - key: vk.0, - }; - - let query_response = query(&deps, query_balance_msg).unwrap(); - let balance = match from_binary(&query_response).unwrap() { - QueryAnswer::Balance { amount } => amount, - _ => panic!("Unexpected result from query"), - }; - assert_eq!(balance, Uint128::new(5000)); - - let wrong_vk_query_msg = QueryMsg::Balance { - address: Addr::unchecked("giannis".to_string()), - key: "wrong_vk".to_string(), - }; - let query_result = query(&deps, wrong_vk_query_msg); - let error = extract_error_msg(query_result); - assert_eq!( - error, - "Wrong viewing key for this address or viewing key not set".to_string() - ); - } - - #[test] - fn test_query_token_info() { - let init_name = "sec-sec".to_string(); - let init_admin = Addr::unchecked("admin".to_string()); - let init_symbol = "SECSEC".to_string(); - let init_decimals = 8; - let init_config: InitConfig = from_binary(&Binary::from( - r#"{ "public_total_supply": true }"#.as_bytes(), - )) - .unwrap(); - let init_supply = Uint128::new(5000); - - let mut deps = mock_dependencies(20, &[]); - let env = mock_env("instantiator", &[]); - let init_msg = InstantiateMsg { - name: init_name.clone(), - admin: Some(init_admin.clone()), - symbol: init_symbol.clone(), - decimals: Some(init_decimals.clone()), - share_decimals: 18, - prng_seed: Binary::from("lolz fun yay".as_bytes()), - config: Some(init_config), - unbond_time: 10, - staked_token: Contract { - address: Addr::unchecked("token".to_string()), - code_hash: "hash".to_string(), - }, - treasury: Some(Addr::unchecked("treasury".to_string())), - treasury_code_hash: None, - limit_transfer: true, - distributors: None, - }; - let init_result = instantiate(&mut deps, env, info, init_msg); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - new_staked_account(&mut deps, "giannis", "pwd", init_supply); - - let query_msg = QueryMsg::TokenInfo {}; - let query_result = query(&deps, query_msg); - assert!( - query_result.is_ok(), - "Init failed: {}", - query_result.err().unwrap() - ); - let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap(); - match query_answer { - QueryAnswer::TokenInfo { - name, - symbol, - decimals, - total_supply, - } => { - assert_eq!(name, init_name); - assert_eq!(symbol, "STKD-".to_string() + &init_symbol); - assert_eq!(decimals, init_decimals); - assert_eq!(total_supply, Some(Uint128::new(5000))); - } - _ => panic!("unexpected"), - } - } - - #[test] - fn test_query_token_config() { - let init_name = "sec-sec".to_string(); - let init_admin = Addr::unchecked("admin".to_string()); - let init_symbol = "SECSEC".to_string(); - let init_decimals = 8; - let init_config: InitConfig = from_binary(&Binary::from( - format!( - "{{\"public_total_supply\":{}, - \"enable_mint\":{}, - \"enable_burn\":{}}}", - true, true, false - ) - .as_bytes(), - )) - .unwrap(); - - let init_supply = Uint128::new(5000); - - let mut deps = mock_dependencies(20, &[]); - let env = mock_env("instantiator", &[]); - let init_msg = InstantiateMsg { - name: init_name.clone(), - admin: Some(init_admin.clone()), - symbol: init_symbol.clone(), - decimals: Some(init_decimals.clone()), - share_decimals: 18, - prng_seed: Binary::from("lolz fun yay".as_bytes()), - config: Some(init_config), - unbond_time: 10, - staked_token: Contract { - address: Addr::unchecked("token".to_string()), - code_hash: "hash".to_string(), - }, - treasury: Some(Addr::unchecked("treasury".to_string())), - treasury_code_hash: None, - limit_transfer: true, - distributors: None, - }; - let init_result = instantiate(&mut deps, env, info, init_msg); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - new_staked_account(&mut deps, "giannis", "pwd", init_supply); - - let query_msg = QueryMsg::TokenConfig {}; - let query_result = query(&deps, query_msg); - assert!( - query_result.is_ok(), - "Init failed: {}", - query_result.err().unwrap() - ); - let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap(); - match query_answer { - QueryAnswer::TokenConfig { - public_total_supply, - } => { - assert_eq!(public_total_supply, true); - } - _ => panic!("unexpected"), - } - } - - #[test] - fn test_query_allowance() { - let (init_result, mut deps) = init_helper(vec![InitBalance { - acc: "giannis", - pwd: "pwd", - stake: Uint128::new(5000), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::IncreaseAllowance { - spender: Addr::unchecked("lebron".to_string()), - amount: Uint128::new(2000), - padding: None, - expiration: None, - }; - let handle_result = execute(&mut deps, mock_env("giannis", &[]), handle_msg); - assert!( - handle_result.is_ok(), - "execute() failed: {}", - handle_result.err().unwrap() - ); - - let vk1 = ViewingKey("key1".to_string()); - let vk2 = ViewingKey("key2".to_string()); - - let query_msg = QueryMsg::Allowance { - owner: Addr::unchecked("giannis".to_string()), - spender: Addr::unchecked("lebron".to_string()), - key: vk1.0.clone(), - }; - let query_result = query(&deps, query_msg); - assert!( - query_result.is_ok(), - "Query failed: {}", - query_result.err().unwrap() - ); - let error = extract_error_msg(query_result); - assert!(error.contains("Wrong viewing key")); - - let handle_msg = ExecuteMsg::SetViewingKey { - key: vk1.0.clone(), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("lebron", &[]), handle_msg); - let unwrapped_result: HandleAnswer = - from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); - assert_eq!( - to_binary(&unwrapped_result).unwrap(), - to_binary(&HandleAnswer::SetViewingKey { - status: ResponseStatus::Success - }) - .unwrap(), - ); - - let handle_msg = ExecuteMsg::SetViewingKey { - key: vk2.0.clone(), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("giannis", &[]), handle_msg); - let unwrapped_result: HandleAnswer = - from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); - assert_eq!( - to_binary(&unwrapped_result).unwrap(), - to_binary(&HandleAnswer::SetViewingKey { - status: ResponseStatus::Success - }) - .unwrap(), - ); - - let query_msg = QueryMsg::Allowance { - owner: Addr::unchecked("giannis".to_string()), - spender: Addr::unchecked("lebron".to_string()), - key: vk1.0.clone(), - }; - let query_result = query(&deps, query_msg); - let allowance = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::Allowance { allowance, .. } => allowance, - _ => panic!("Unexpected"), - }; - assert_eq!(allowance, Uint128::new(2000)); - - let query_msg = QueryMsg::Allowance { - owner: Addr::unchecked("giannis".to_string()), - spender: Addr::unchecked("lebron".to_string()), - key: vk2.0.clone(), - }; - let query_result = query(&deps, query_msg); - let allowance = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::Allowance { allowance, .. } => allowance, - _ => panic!("Unexpected"), - }; - assert_eq!(allowance, Uint128::new(2000)); - - let query_msg = QueryMsg::Allowance { - owner: Addr::unchecked("lebron".to_string()), - spender: Addr::unchecked("giannis".to_string()), - key: vk2.0.clone(), - }; - let query_result = query(&deps, query_msg); - let allowance = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::Allowance { allowance, .. } => allowance, - _ => panic!("Unexpected"), - }; - assert_eq!(allowance, Uint128::new(0)); - } - - #[test] - fn test_query_balance() { - let (init_result, mut deps) = init_helper(vec![InitBalance { - acc: "bob", - pwd: "pwd", - stake: Uint128::new(5000), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::SetViewingKey { - key: "key".to_string(), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - let unwrapped_result: HandleAnswer = - from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); - assert_eq!( - to_binary(&unwrapped_result).unwrap(), - to_binary(&HandleAnswer::SetViewingKey { - status: ResponseStatus::Success - }) - .unwrap(), - ); - - let query_msg = QueryMsg::Balance { - address: Addr::unchecked("bob".to_string()), - key: "wrong_key".to_string(), - }; - let query_result = query(&deps, query_msg); - let error = extract_error_msg(query_result); - assert!(error.contains("Wrong viewing key")); - - let query_msg = QueryMsg::Balance { - address: Addr::unchecked("bob".to_string()), - key: "key".to_string(), - }; - let query_result = query(&deps, query_msg); - let balance = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::Balance { amount } => amount, - _ => panic!("Unexpected"), - }; - assert_eq!(balance, Uint128::new(5000)); - } - - #[test] - fn test_query_transfer_history() { - let (init_result, mut deps) = init_helper(vec![InitBalance { - acc: "bob", - pwd: "pwd", - stake: Uint128::new(5000), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::SetViewingKey { - key: "key".to_string(), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - assert!(ensure_success(handle_result.unwrap())); - - let handle_msg = ExecuteMsg::Transfer { - recipient: Addr::unchecked("alice".to_string()), - amount: Uint128::new(1000), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - let handle_msg = ExecuteMsg::Transfer { - recipient: Addr::unchecked("banana".to_string()), - amount: Uint128::new(500), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - let handle_msg = ExecuteMsg::Transfer { - recipient: Addr::unchecked("mango".to_string()), - amount: Uint128::new(2500), - memo: None, - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - - let query_msg = QueryMsg::TransferHistory { - address: Addr::unchecked("bob".to_string()), - key: "key".to_string(), - page: None, - page_size: 0, - }; - let query_result = query(&deps, query_msg); - // let a: QueryAnswer = from_binary(&query_result.unwrap()).unwrap(); - // println!("{:?}", a); - let transfers = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransferHistory { txs, .. } => txs, - _ => panic!("Unexpected"), - }; - assert!(transfers.is_empty()); - - let query_msg = QueryMsg::TransferHistory { - address: Addr::unchecked("bob".to_string()), - key: "key".to_string(), - page: None, - page_size: 10, - }; - let query_result = query(&deps, query_msg); - let transfers = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransferHistory { txs, .. } => txs, - _ => panic!("Unexpected"), - }; - assert_eq!(transfers.len(), 3); - - let query_msg = QueryMsg::TransferHistory { - address: Addr::unchecked("bob".to_string()), - key: "key".to_string(), - page: None, - page_size: 2, - }; - let query_result = query(&deps, query_msg); - let transfers = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransferHistory { txs, .. } => txs, - _ => panic!("Unexpected"), - }; - assert_eq!(transfers.len(), 2); - - let query_msg = QueryMsg::TransferHistory { - address: Addr::unchecked("bob".to_string()), - key: "key".to_string(), - page: Some(1), - page_size: 2, - }; - let query_result = query(&deps, query_msg); - let transfers = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransferHistory { txs, .. } => txs, - _ => panic!("Unexpected"), - }; - assert_eq!(transfers.len(), 1); - } - - #[test] - fn test_query_transaction_history() { - let (init_result, mut deps) = init_helper_with_config( - vec![InitBalance { - acc: "bob", - pwd: "pwd", - stake: Uint128::new(10000), - }], - true, - true, - false, - false, - 0, - ); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::SetViewingKey { - key: "key".to_string(), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - assert!(ensure_success(handle_result.unwrap())); - - let handle_msg = ExecuteMsg::Transfer { - recipient: Addr::unchecked("alice".to_string()), - amount: Uint128::new(1000), - memo: Some("my transfer message #1".to_string()), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - - let handle_msg = ExecuteMsg::Transfer { - recipient: Addr::unchecked("banana".to_string()), - amount: Uint128::new(500), - memo: Some("my transfer message #2".to_string()), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - - let handle_msg = ExecuteMsg::Transfer { - recipient: Addr::unchecked("mango".to_string()), - amount: Uint128::new(2500), - memo: Some("my transfer message #3".to_string()), - padding: None, - }; - let handle_result = execute(&mut deps, mock_env("bob", &[]), handle_msg); - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - - let query_msg = QueryMsg::TransferHistory { - address: Addr::unchecked("bob".to_string()), - key: "key".to_string(), - page: None, - page_size: 10, - }; - let query_result = query(&deps, query_msg); - let transfers = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransferHistory { txs, .. } => txs, - _ => panic!("Unexpected"), - }; - assert_eq!(transfers.len(), 3); - - let query_msg = QueryMsg::TransactionHistory { - address: Addr::unchecked("bob".to_string()), - key: "key".to_string(), - page: None, - page_size: 10, - }; - let query_result = query(&deps, query_msg); - let transfers = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransactionHistory { txs, .. } => txs, - other => panic!("Unexpected: {:?}", other), - }; - - use crate::transaction_history::{RichTx, TxAction}; - let expected_transfers = [ - RichTx { - id: 4, - action: TxAction::Transfer { - from: Addr::unchecked("bob".to_string()), - sender: Addr::unchecked("bob".to_string()), - recipient: Addr::unchecked("mango".to_string()), - }, - coins: Coin { - denom: "STKD-SECSEC".to_string(), - amount: Uint128::new(2500).into(), - }, - memo: Some("my transfer message #3".to_string()), - block_time: 1571797419, - block_height: 12345, - }, - RichTx { - id: 3, - action: TxAction::Transfer { - from: Addr::unchecked("bob".to_string()), - sender: Addr::unchecked("bob".to_string()), - recipient: Addr::unchecked("banana".to_string()), - }, - coins: Coin { - denom: "STKD-SECSEC".to_string(), - amount: Uint128::new(500).into(), - }, - memo: Some("my transfer message #2".to_string()), - block_time: 1571797419, - block_height: 12345, - }, - RichTx { - id: 2, - action: TxAction::Transfer { - from: Addr::unchecked("bob".to_string()), - sender: Addr::unchecked("bob".to_string()), - recipient: Addr::unchecked("alice".to_string()), - }, - coins: Coin { - denom: "STKD-SECSEC".to_string(), - amount: Uint128::new(1000).into(), - }, - memo: Some("my transfer message #1".to_string()), - block_time: 1571797419, - block_height: 12345, - }, - RichTx { - id: 1, - action: TxAction::Stake { - staker: Addr::unchecked("bob".to_string()), - }, - coins: Coin { - denom: "STKD-SECSEC".to_string(), - amount: Uint128::new(10000).into(), - }, - memo: None, - block_time: 1571797419, - block_height: 12345, - }, - ]; - - assert_eq!(transfers, expected_transfers); - } -} diff --git a/archived-contracts/snip20_staking/src/distributors.rs b/archived-contracts/snip20_staking/src/distributors.rs deleted file mode 100644 index 98859fc..0000000 --- a/archived-contracts/snip20_staking/src/distributors.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::{ - contract::check_if_admin, - msg::{HandleAnswer, QueryAnswer, ResponseStatus::Success}, - state::Config, - state_staking::{Distributors, DistributorsEnabled}, -}; -use cosmwasm_std::{Deps, MessageInfo}; -use shade_protocol::c_std::{ - to_binary, - Api, - Binary, - Env, - DepsMut, - Response, - Addr, - Querier, - StdResult, - Storage, -}; -use shade_protocol::utils::storage::default::SingletonStorage; - -pub fn get_distributor( - deps: Deps, -) -> StdResult>> { - Ok(match DistributorsEnabled::load(deps.storage)?.0 { - true => Some(Distributors::load(deps.storage)?.0), - false => None, - }) -} - -pub fn try_set_distributors_status( - deps: DepsMut, - env: Env, - info: MessageInfo, - enabled: bool, -) -> StdResult { - let config = Config::from_storage(deps.storage); - - check_if_admin(&config, &info.sender)?; - - DistributorsEnabled(enabled).save(deps.storage)?; - - Ok(Response::new().set_data(to_binary(&HandleAnswer::SetDistributorsStatus { - status: Success, - })?)) -} - -pub fn try_add_distributors( - deps: DepsMut, - env: Env, - info: MessageInfo, - new_distributors: Vec, -) -> StdResult { - let config = Config::from_storage(deps.storage); - - check_if_admin(&config, &info.sender)?; - - let mut distributors = Distributors::load(deps.storage)?; - distributors.0.extend(new_distributors); - distributors.save(deps.storage)?; - - Ok(Response::new().set_data(to_binary(&HandleAnswer::AddDistributors { - status: Success, - })?)) -} - -pub fn try_set_distributors( - deps: DepsMut, - env: Env, - info: MessageInfo, - distributors: Vec, -) -> StdResult { - let config = Config::from_storage(deps.storage); - - check_if_admin(&config, &info.sender)?; - - Distributors(distributors).save(deps.storage)?; - - Ok(Response::new().set_data(to_binary(&HandleAnswer::SetDistributors { - status: Success, - })?)) -} - -pub fn distributors(deps: Deps) -> StdResult { - to_binary(&QueryAnswer::Distributors { - distributors: match DistributorsEnabled::load(deps.storage)?.0 { - true => Some(Distributors::load(deps.storage)?.0), - false => None, - }, - }) -} diff --git a/archived-contracts/snip20_staking/src/expose_balance.rs b/archived-contracts/snip20_staking/src/expose_balance.rs deleted file mode 100644 index 79c1821..0000000 --- a/archived-contracts/snip20_staking/src/expose_balance.rs +++ /dev/null @@ -1,150 +0,0 @@ - -use shade_protocol::cosmwasm_schema::cw_serde; - -use crate::{ - msg::{HandleAnswer, ResponseStatus::Success}, - state::{get_receiver_hash, Balances}, - state_staking::UserCooldown, -}; -use shade_protocol::c_std::Uint128; -use shade_protocol::c_std::{ - to_binary, - Api, - Binary, - CosmosMsg, - Env, - DepsMut, - Response, - Addr, - Querier, - StdError, - StdResult, - Storage, -}; -use shade_protocol::{ - contract_interfaces::staking::snip20_staking::stake::VecQueue, - utils::storage::default::BucketStorage, -}; - -pub fn try_expose_balance( - deps: DepsMut, - env: Env, - info: MessageInfo, - recipient: Addr, - code_hash: Option, - msg: Option, - memo: Option, -) -> StdResult { - // Get balance to expose - let balance = Balances::from_storage(deps.storage) - .balance(&deps.api.canonical_address(&info.sender)?); - - let receiver_hash: String; - if let Some(code_hash) = code_hash { - receiver_hash = code_hash; - } else if let Some(code_hash) = get_receiver_hash(deps.storage, &recipient) { - receiver_hash = code_hash?; - } else { - return Err(StdError::generic_err("No code hash received")); - } - - let messages = vec![ - Snip20BalanceReceiverMsg::new(info.sender, Uint128::new(balance), memo, msg) - .to_cosmos_msg(receiver_hash, recipient)?, - ]; - - Ok(Response::new().set_data(to_binary(&HandleAnswer::ExposeBalance { status: Success })?)) -} - -pub fn try_expose_balance_with_cooldown( - deps: DepsMut, - env: Env, - info: MessageInfo, - recipient: Addr, - code_hash: Option, - msg: Option, - memo: Option, -) -> StdResult { - // Get balance to expose - let balance = Balances::from_storage(deps.storage) - .balance(&deps.api.canonical_address(&info.sender)?); - - let receiver_hash: String; - if let Some(code_hash) = code_hash { - receiver_hash = code_hash; - } else if let Some(code_hash) = get_receiver_hash(deps.storage, &recipient) { - receiver_hash = code_hash?; - } else { - return Err(StdError::generic_err("No code hash received")); - } - - let mut cooldown = - UserCooldown::may_load(deps.storage, info.sender.to_string().as_bytes())? - .unwrap_or(UserCooldown { - total: Uint128::zero(), - queue: VecQueue(vec![]), - }); - cooldown.update(env.block.time.seconds()); - cooldown.save(deps.storage, info.sender.to_string().as_bytes())?; - - let messages = vec![ - Snip20BalanceReceiverMsg::new( - info.sender, - Uint128::new(balance).checked_sub(cooldown.total)?, - memo, - msg, - ) - .to_cosmos_msg_cooldown(receiver_hash, recipient)?, - ]; - - Ok(Response::new().set_data(to_binary(&HandleAnswer::ExposeBalance { status: Success })?)) -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct Snip20BalanceReceiverMsg { - pub sender: Addr, - pub balance: Uint128, - pub memo: Option, - pub msg: Option, -} - -impl Snip20BalanceReceiverMsg { - pub fn new( - sender: Addr, - balance: Uint128, - memo: Option, - msg: Option, - ) -> Self { - Self { - sender, - balance, - memo, - msg, - } - } - - pub fn to_cosmos_msg(self, code_hash: String, address: Addr) -> StdResult { - BalanceReceiverHandleMsg::ReceiveBalance(self).to_cosmos_msg(code_hash, address, None) - } - - pub fn to_cosmos_msg_cooldown( - self, - code_hash: String, - address: Addr, - ) -> StdResult { - BalanceReceiverHandleMsg::ReceiveBalanceWithCooldown(self) - .to_cosmos_msg(code_hash, address, None) - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum BalanceReceiverHandleMsg { - ReceiveBalance(Snip20BalanceReceiverMsg), - ReceiveBalanceWithCooldown(Snip20BalanceReceiverMsg), -} - -impl ExecuteCallback for BalanceReceiverHandleMsg { - const BLOCK_SIZE: usize = 256; -} diff --git a/archived-contracts/snip20_staking/src/lib.rs b/archived-contracts/snip20_staking/src/lib.rs deleted file mode 100644 index d788e34..0000000 --- a/archived-contracts/snip20_staking/src/lib.rs +++ /dev/null @@ -1,56 +0,0 @@ -mod batch; -pub mod contract; -mod distributors; -mod expose_balance; -pub mod msg; -mod rand; -pub mod receiver; -mod stake; -mod stake_queries; -pub mod state; -mod state_staking; -mod transaction_history; -mod utils; -mod viewing_key; - -#[cfg(target_arch = "wasm32")] -mod wasm { - use super::contract; - use shade_protocol::c_std::{ - do_handle, - do_init, - do_query, - ExternalApi, - ExternalQuerier, - ExternalStorage, - }; - - #[no_mangle] - extern "C" fn instantiate(env_ptr: u32, msg_ptr: u32) -> u32 { - do_init( - &contract::instantiate::, - env_ptr, - msg_ptr, - ) - } - - #[no_mangle] - extern "C" fn execute(env_ptr: u32, msg_ptr: u32) -> u32 { - do_handle( - &contract::execute::, - env_ptr, - msg_ptr, - ) - } - - #[no_mangle] - extern "C" fn query(msg_ptr: u32) -> u32 { - do_query( - &contract::query::, - msg_ptr, - ) - } - - // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available - // automatically because we `use cosmwasm_std`. -} diff --git a/archived-contracts/snip20_staking/src/msg.rs b/archived-contracts/snip20_staking/src/msg.rs deleted file mode 100644 index 3b759ff..0000000 --- a/archived-contracts/snip20_staking/src/msg.rs +++ /dev/null @@ -1,556 +0,0 @@ -#![allow(clippy::field_reassign_with_default)] // This is triggered in `#[derive(JsonSchema)]` - - -use shade_protocol::cosmwasm_schema::cw_serde; - -use crate::{ - batch, - transaction_history::{RichTx, Tx}, - viewing_key::ViewingKey, -}; -use shade_protocol::c_std::{Uint128, Uint256}; -use shade_protocol::c_std::{Binary, Addr, StdError, StdResult}; -use shade_protocol::secret_toolkit::permit::Permit; -use shade_protocol::{ - contract_interfaces::staking::snip20_staking::stake::{QueueItem, StakeConfig, VecQueue}, - utils::asset::Contract, -}; - -#[derive(Serialize, Deserialize)] -pub struct InstantiateMsg { - pub name: String, - pub admin: Option, - pub symbol: String, - // Will default to staked token decimals if not set - pub decimals: Option, - pub share_decimals: u8, - pub prng_seed: Binary, - pub config: Option, - - // Stake - pub unbond_time: u64, - pub staked_token: Contract, - pub treasury: Option, - pub treasury_code_hash: Option, - - // Distributors - pub limit_transfer: bool, - pub distributors: Option>, -} - -impl InstantiateMsg { - pub fn config(&self) -> InitConfig { - self.config.clone().unwrap_or_default() - } -} - -/// This type represents optional configuration values which can be overridden. -/// All values are optional and have defaults which are more private by default, -/// but can be overridden if necessary -#[derive(Serialize, Deserialize, Clone, Default, Debug)] -#[serde(rename_all = "snake_case")] -pub struct InitConfig { - /// Indicates whether the total supply is public or should be kept secret. - /// default: False - pub public_total_supply: Option, -} - -impl InitConfig { - pub fn public_total_supply(&self) -> bool { - self.public_total_supply.unwrap_or(false) - } -} - -#[cw_serde] -pub enum ExecuteMsg { - // Staking - UpdateStakeConfig { - unbond_time: Option, - disable_treasury: bool, - treasury: Option, - padding: Option, - }, - Receive { - sender: Addr, - from: Addr, - amount: Uint128, - msg: Option, - memo: Option, - padding: Option, - }, - Unbond { - amount: Uint128, - padding: Option, - }, - ClaimUnbond { - padding: Option, - }, - ClaimRewards { - padding: Option, - }, - StakeRewards { - padding: Option, - }, - - // Balance - ExposeBalance { - recipient: Addr, - code_hash: Option, - msg: Option, - memo: Option, - padding: Option, - }, - ExposeBalanceWithCooldown { - recipient: Addr, - code_hash: Option, - msg: Option, - memo: Option, - padding: Option, - }, - - // Distributors - SetDistributorsStatus { - enabled: bool, - padding: Option, - }, - AddDistributors { - distributors: Vec, - padding: Option, - }, - SetDistributors { - distributors: Vec, - padding: Option, - }, - - // Base ERC-20 stuff - Transfer { - recipient: Addr, - amount: Uint128, - memo: Option, - padding: Option, - }, - Send { - recipient: Addr, - recipient_code_hash: Option, - amount: Uint128, - msg: Option, - memo: Option, - padding: Option, - }, - BatchTransfer { - actions: Vec, - padding: Option, - }, - BatchSend { - actions: Vec, - padding: Option, - }, - RegisterReceive { - code_hash: String, - padding: Option, - }, - CreateViewingKey { - entropy: String, - padding: Option, - }, - SetViewingKey { - key: String, - padding: Option, - }, - - // Allowance - IncreaseAllowance { - spender: Addr, - amount: Uint128, - expiration: Option, - padding: Option, - }, - DecreaseAllowance { - spender: Addr, - amount: Uint128, - expiration: Option, - padding: Option, - }, - TransferFrom { - owner: Addr, - recipient: Addr, - amount: Uint128, - memo: Option, - padding: Option, - }, - SendFrom { - owner: Addr, - recipient: Addr, - recipient_code_hash: Option, - amount: Uint128, - msg: Option, - memo: Option, - padding: Option, - }, - BatchTransferFrom { - actions: Vec, - padding: Option, - }, - BatchSendFrom { - actions: Vec, - padding: Option, - }, - - // Admin - ChangeAdmin { - address: Addr, - padding: Option, - }, - SetContractStatus { - level: ContractStatusLevel, - padding: Option, - }, - - // Permit - RevokePermit { - permit_name: String, - padding: Option, - }, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "snake_case")] -pub enum HandleAnswer { - UpdateStakeConfig { - status: ResponseStatus, - }, - Receive { - status: ResponseStatus, - }, - Unbond { - status: ResponseStatus, - }, - ClaimUnbond { - status: ResponseStatus, - }, - ClaimRewards { - status: ResponseStatus, - }, - StakeRewards { - status: ResponseStatus, - }, - ExposeBalance { - status: ResponseStatus, - }, - SetDistributorsStatus { - status: ResponseStatus, - }, - AddDistributors { - status: ResponseStatus, - }, - SetDistributors { - status: ResponseStatus, - }, - - // Base - Transfer { - status: ResponseStatus, - }, - Send { - status: ResponseStatus, - }, - BatchTransfer { - status: ResponseStatus, - }, - BatchSend { - status: ResponseStatus, - }, - RegisterReceive { - status: ResponseStatus, - }, - CreateViewingKey { - key: ViewingKey, - }, - SetViewingKey { - status: ResponseStatus, - }, - - // Allowance - IncreaseAllowance { - spender: Addr, - owner: Addr, - allowance: Uint128, - }, - DecreaseAllowance { - spender: Addr, - owner: Addr, - allowance: Uint128, - }, - TransferFrom { - status: ResponseStatus, - }, - SendFrom { - status: ResponseStatus, - }, - BatchTransferFrom { - status: ResponseStatus, - }, - BatchSendFrom { - status: ResponseStatus, - }, - - // Other - ChangeAdmin { - status: ResponseStatus, - }, - SetContractStatus { - status: ResponseStatus, - }, - - // Permit - RevokePermit { - status: ResponseStatus, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - // Staking - StakeConfig {}, - TotalStaked {}, - // Total token shares per token - StakeRate {}, - Unbonding {}, - Unfunded { - start: u64, - total: u64, - }, - Staked { - address: Addr, - key: String, - time: Option, - }, - - // Distributors - Distributors {}, - - // Snip20 stuff - TokenInfo {}, - TokenConfig {}, - ContractStatus {}, - Allowance { - owner: Addr, - spender: Addr, - key: String, - }, - Balance { - address: Addr, - key: String, - }, - TransferHistory { - address: Addr, - key: String, - page: Option, - page_size: u32, - }, - TransactionHistory { - address: Addr, - key: String, - page: Option, - page_size: u32, - }, - WithPermit { - permit: Permit, - query: QueryWithPermit, - }, -} - -impl QueryMsg { - pub fn get_validation_params(&self) -> (Vec<&Addr>, ViewingKey) { - match self { - Self::Staked { address, key, .. } => (vec![address], ViewingKey(key.clone())), - Self::Balance { address, key } => (vec![address], ViewingKey(key.clone())), - Self::TransferHistory { address, key, .. } => (vec![address], ViewingKey(key.clone())), - Self::TransactionHistory { address, key, .. } => { - (vec![address], ViewingKey(key.clone())) - } - Self::Allowance { - owner, - spender, - key, - .. - } => (vec![owner, spender], ViewingKey(key.clone())), - _ => panic!("This query type does not require authentication"), - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum QueryWithPermit { - Staked { - time: Option, - }, - - // Snip20 stuff - Allowance { - owner: Addr, - spender: Addr, - }, - Balance {}, - TransferHistory { - page: Option, - page_size: u32, - }, - TransactionHistory { - page: Option, - page_size: u32, - }, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "snake_case")] -pub enum QueryAnswer { - // Stake - StakedConfig { - config: StakeConfig, - }, - TotalStaked { - tokens: Uint128, - shares: Uint256, - }, - // Shares per token - StakeRate { - shares: Uint256, - }, - Staked { - tokens: Uint128, - shares: Uint256, - pending_rewards: Uint128, - unbonding: Uint128, - unbonded: Option, - cooldown: VecQueue, - }, - Unbonding { - total: Uint128, - }, - Unfunded { - total: Uint128, - }, - - // Distributors - Distributors { - distributors: Option>, - }, - - // Snip20 stuff - TokenInfo { - name: String, - symbol: String, - decimals: u8, - total_supply: Option, - }, - TokenConfig { - public_total_supply: bool, - }, - ContractStatus { - status: ContractStatusLevel, - }, - ExchangeRate { - rate: Uint128, - denom: String, - }, - Allowance { - spender: Addr, - owner: Addr, - allowance: Uint128, - expiration: Option, - }, - Balance { - amount: Uint128, - }, - TransferHistory { - txs: Vec, - total: Option, - }, - TransactionHistory { - txs: Vec, - total: Option, - }, - ViewingKeyError { - msg: String, - }, -} - -#[derive(Serialize, Deserialize, Clone, PartialEq)] -pub struct CreateViewingKeyResponse { - pub key: String, -} - -#[cw_serde] -pub enum ResponseStatus { - Success, - Failure, -} - -#[cw_serde] -pub enum ContractStatusLevel { - NormalRun, - StopBonding, - StopAllButUnbond, //Can set time to 0 for instant unbond - StopAll, -} - -pub fn status_level_to_u8(status_level: ContractStatusLevel) -> u8 { - match status_level { - ContractStatusLevel::NormalRun => 0, - ContractStatusLevel::StopBonding => 1, - ContractStatusLevel::StopAllButUnbond => 2, - ContractStatusLevel::StopAll => 3, - } -} - -pub fn u8_to_status_level(status_level: u8) -> StdResult { - match status_level { - 0 => Ok(ContractStatusLevel::NormalRun), - 1 => Ok(ContractStatusLevel::StopBonding), - 2 => Ok(ContractStatusLevel::StopAllButUnbond), - 3 => Ok(ContractStatusLevel::StopAll), - _ => Err(StdError::generic_err("Invalid state level")), - } -} - -// 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 -} - -#[cfg(test)] -mod tests { - use super::*; - use shade_protocol::c_std::{from_slice, StdResult}; - - #[derive(Serialize, Deserialize, Debug, PartialEq)] - #[serde(rename_all = "snake_case")] - pub enum Something { - Var { padding: Option }, - } - - #[test] - fn test_deserialization_of_missing_option_fields() -> StdResult<()> { - let input = b"{ \"var\": {} }"; - let obj: Something = from_slice(input)?; - assert_eq!( - obj, - Something::Var { padding: None }, - "unexpected value: {:?}", - obj - ); - Ok(()) - } -} diff --git a/archived-contracts/snip20_staking/src/rand.rs b/archived-contracts/snip20_staking/src/rand.rs deleted file mode 100644 index 41c3944..0000000 --- a/archived-contracts/snip20_staking/src/rand.rs +++ /dev/null @@ -1,75 +0,0 @@ -use rand_chacha::ChaChaRng; -use rand_core::{RngCore, SeedableRng}; - -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/archived-contracts/snip20_staking/src/receiver.rs b/archived-contracts/snip20_staking/src/receiver.rs deleted file mode 100644 index 80378a8..0000000 --- a/archived-contracts/snip20_staking/src/receiver.rs +++ /dev/null @@ -1,68 +0,0 @@ -#![allow(clippy::field_reassign_with_default)] // This is triggered in `#[derive(JsonSchema)]` - - -use shade_protocol::cosmwasm_schema::cw_serde; - -use shade_protocol::c_std::Uint128; -use shade_protocol::c_std::{to_binary, Binary, CosmosMsg, Addr, StdResult, WasmMsg}; - -use crate::{contract::RESPONSE_BLOCK_SIZE, msg::space_pad}; - -/// Snip20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg -#[cw_serde] -pub struct Snip20ReceiveMsg { - pub sender: Addr, - pub from: Addr, - pub amount: Uint128, - #[serde(skip_serializing_if = "Option::is_none")] - pub memo: Option, - pub msg: Option, -} - -impl Snip20ReceiveMsg { - pub fn new( - sender: Addr, - from: Addr, - amount: Uint128, - memo: Option, - msg: Option, - ) -> Self { - Self { - sender, - from, - amount, - memo, - msg, - } - } - - /// serializes the message, and pads it to 256 bytes - pub fn into_binary(self) -> StdResult { - let msg = ReceiverHandleMsg::Receive(self); - let mut data = to_binary(&msg)?; - space_pad(RESPONSE_BLOCK_SIZE, &mut data.0); - Ok(data) - } - - /// creates a cosmos_msg sending this struct to the named contract - pub fn into_cosmos_msg( - self, - callback_code_hash: String, - contract_addr: Addr, - ) -> StdResult { - let msg = self.into_binary()?; - let execute = WasmMsg::Execute { - msg, - code_hash: callback_code_hash, - contract_addr, - funds: vec![], - }; - Ok(execute.into()) - } -} - -// This is just a helper to properly serialize the above message -#[cw_serde] -enum ReceiverHandleMsg { - Receive(Snip20ReceiveMsg), -} diff --git a/archived-contracts/snip20_staking/src/stake.rs b/archived-contracts/snip20_staking/src/stake.rs deleted file mode 100644 index 3deee5f..0000000 --- a/archived-contracts/snip20_staking/src/stake.rs +++ /dev/null @@ -1,1068 +0,0 @@ -use crate::{ - contract::check_if_admin, - msg::{HandleAnswer, ResponseStatus::Success}, - state::{Balances, Config, ReadonlyConfig}, - state_staking::{ - DailyUnbondingQueue, - TotalShares, - TotalTokens, - TotalUnbonding, - UnbondingQueue, - UnsentStakedTokens, - UserCooldown, - UserShares, - }, - transaction_history::{ - store_add_reward, - store_claim_reward, - store_claim_unbond, - store_fund_unbond, - store_stake, - store_unbond, - }, -}; -use shade_protocol::c_std::{Uint128, Uint256}; -use shade_protocol::c_std::{ - from_binary, - to_binary, - Api, - Binary, - CanonicalAddr, - Env, - DepsMut, - Response, - Addr, - Querier, - StdError, - StdResult, - Storage, -}; -use shade_protocol::snip20::helpers::send_msg; -use shade_protocol::{ - contract_interfaces::staking::snip20_staking::{ - stake::{DailyUnbonding, StakeConfig, Unbonding, VecQueue}, - ReceiveType, - }, - utils::storage::default::{BucketStorage, SingletonStorage}, -}; -use std::convert::TryInto; - -//TODO: set errors - -pub fn try_update_stake_config( - deps: DepsMut, - env: Env, - info: MessageInfo, - unbond_time: Option, - disable_treasury: bool, - treasury: Option, -) -> StdResult { - let config = Config::from_storage(deps.storage); - - check_if_admin(&config, &info.sender)?; - - let mut stake_config = StakeConfig::load(deps.storage)?; - - if let Some(unbond_time) = unbond_time { - stake_config.unbond_time = unbond_time; - } - - let mut messages = vec![]; - - if disable_treasury { - stake_config.treasury = None; - } else if let Some(treasury) = treasury { - stake_config.treasury = Some(treasury.clone()); - - let unsent_tokens = UnsentStakedTokens::load(deps.storage)?.0; - if unsent_tokens != Uint128::zero() { - messages.push(send_msg( - treasury, - unsent_tokens.into(), - None, - None, - None, - 258, - stake_config.staked_token.code_hash.clone(), - stake_config.staked_token.address.clone(), - )?); - UnsentStakedTokens(Uint128::zero()).save(deps.storage)?; - } - } - - stake_config.save(deps.storage)?; - - Ok(Response::new().set_data(to_binary(&HandleAnswer::UpdateStakeConfig { - status: Success, - })?)) -} - -const DAY: u64 = 86400; //60 * 60 * 24 - -/// -/// Rounds down a date to the nearest day -/// -fn round_date(date: u64) -> u64 { - date - (date % DAY) -} - -/// -/// Updates total states to reflect balance changes -/// -fn add_balance( - storage: &mut dyn Storage, - stake_config: &StakeConfig, - sender: &Addr, - sender_canon: &CanonicalAddr, - amount: Uint128, -) -> StdResult<()> { - // Check if user account exists - let mut user_shares = UserShares::may_load(storage, sender.as_str().as_bytes())? - .unwrap_or(UserShares(Uint256::zero())); - - // Update user staked tokens - let mut balances = Balances::from_storage(storage); - let mut account_balance = balances.balance(sender_canon); - if let Some(new_balance) = account_balance.checked_add(amount.u128()) { - account_balance = new_balance; - } else { - return Err(StdError::generic_err( - "This mint attempt would increase the account's balance above the supported maximum", - )); - } - balances.set_account_balance(sender_canon, account_balance); - - // Get total supplied tokens - let mut total_shares = TotalShares::load(storage)?; - let total_tokens = TotalTokens::load(storage)?; - - // Update total staked - // We do this before reaching shares to get overflows out of the way - match total_tokens.0.checked_add(amount) { - Ok(total_staked) => TotalTokens(total_staked).save(storage)?, - Err(_) => return Err(StdError::generic_err("Total staked tokens overflow")), - }; - - let supply = ReadonlyConfig::from_storage(storage).total_supply(); - Config::from_storage(storage).set_total_supply(supply + amount.u128()); - - // Calculate shares per token supplied - let shares = shares_per_token(stake_config, &amount, &total_tokens.0, &total_shares.0)?; - - // Update total shares - match total_shares.0.checked_add(shares) { - Ok(total_added_shares) => total_shares = TotalShares(total_added_shares), - Err(_) => return Err(StdError::generic_err("Shares overflow")), - }; - - total_shares.save(storage)?; - - // Update user's shares - this will not break as total_shares >= user_shares - user_shares.0 += shares; - user_shares.save(storage, sender.as_str().as_bytes())?; - - Ok(()) -} - -/// -/// Removed items from internal supply -/// -fn subtract_internal_supply( - storage: &mut dyn Storage, - total_shares: &mut TotalShares, - shares: Uint256, - total_tokens: &mut TotalTokens, - tokens: Uint128, - remove_supply: bool, -) -> StdResult<()> { - // Update total shares - match total_shares.0.checked_sub(shares) { - Ok(total) => TotalShares(total).save(storage)?, - Err(_) => return Err(StdError::generic_err("Insufficient shares")), - }; - - // Update total staked - match total_tokens.0.checked_sub(tokens) { - Ok(total) => TotalTokens(total).save(storage)?, - Err(_) => return Err(StdError::generic_err("Insufficient tokens")), - }; - - if remove_supply { - let supply = ReadonlyConfig::from_storage(storage).total_supply(); - if let Some(total) = supply.checked_sub(tokens.u128()) { - Config::from_storage(storage).set_total_supply(total); - } else { - return Err(StdError::generic_err("Insufficient shares")); - } - } - - Ok(()) -} - -/// -/// Updates total states to reflect balance changes -/// -fn remove_balance( - storage: &mut dyn Storage, - stake_config: &StakeConfig, - account: &Addr, - account_cannon: &CanonicalAddr, - amount: Uint128, - time: u64, -) -> StdResult<()> { - // Return insufficient funds - let user_shares = - UserShares::may_load(storage, account.as_str().as_bytes())?.expect("No funds"); - - // Get total supplied tokens - let mut total_shares = TotalShares::load(storage)?; - let mut total_tokens = TotalTokens::load(storage)?; - - // Calculate shares per token supplied - let shares = shares_per_token(stake_config, &amount, &total_tokens.0, &total_shares.0)?; - - // Update user's shares - match user_shares.0.checked_sub(shares) { - Ok(user_shares) => UserShares(user_shares).save(storage, account.as_str().as_bytes())?, - Err(_) => return Err(StdError::generic_err("Insufficient shares")), - } - - subtract_internal_supply( - storage, - &mut total_shares, - shares, - &mut total_tokens, - amount, - true, - )?; - - // Load balance - let mut balances = Balances::from_storage(storage); - let mut account_balance = balances.balance(account_cannon); - let account_tokens = account_balance; - - if let Some(new_balance) = account_balance.checked_sub(amount.u128()) { - account_balance = new_balance; - } else { - return Err(StdError::generic_err( - "This burn attempt would decrease the account's balance to a negative", - )); - } - balances.set_account_balance(account_cannon, account_balance); - remove_from_cooldown(storage, account, Uint128::new(account_tokens), amount, time)?; - Ok(()) -} - -pub fn claim_rewards( - storage: &mut dyn Storage, - stake_config: &StakeConfig, - sender: &Addr, - sender_canon: &CanonicalAddr, -) -> StdResult { - let user_shares = UserShares::may_load(storage, sender.as_str().as_bytes())?.expect("No funds"); - - let user_balance = Balances::from_storage(storage).balance(sender_canon); - - // Get total supplied tokens - let mut total_shares = TotalShares::load(storage)?; - let mut total_tokens = TotalTokens::load(storage)?; - - let (reward_token, reward_shares) = calculate_rewards( - stake_config, - Uint128::new(user_balance), - user_shares.0, - total_tokens.0, - total_shares.0, - )?; - - // Do nothing if no rewards are gonna be claimed - if reward_token.is_zero() { - return Ok(reward_token); - } - - match user_shares.0.checked_sub(reward_shares) { - Ok(user_shares) => UserShares(user_shares).save(storage, sender.as_str().as_bytes())?, - Err(_) => return Err(StdError::generic_err("Insufficient shares")), - }; - - subtract_internal_supply( - storage, - &mut total_shares, - reward_shares, - &mut total_tokens, - reward_token, - false, - )?; - - Ok(reward_token) -} - -pub fn shares_per_token( - config: &StakeConfig, - token_amount: &Uint128, - total_tokens: &Uint128, - total_shares: &Uint256, -) -> StdResult { - let t_tokens = Uint256::from(*total_tokens); - let t_shares = *total_shares; - let tokens = Uint256::from(*token_amount); - - if total_tokens.is_zero() && total_shares.is_zero() { - // Used to normalize the staked token to the stake token - let token_multiplier = - Uint256::from(10u128).checked_pow(config.decimal_difference.into())?; - - return match tokens.checked_mul(token_multiplier) { - Ok(shares) => Ok(shares), - Err(_) => Err(StdError::generic_err("Share calculation overflow")), - }; - } - - return match tokens.checked_mul(t_shares) { - Ok(shares) => Ok(shares.checked_div(t_tokens)?), - Err(_) => Err(StdError::generic_err("Share calculation overflow")), - }; -} - -pub fn tokens_per_share( - config: &StakeConfig, - shares_amount: &Uint256, - total_tokens: &Uint128, - total_shares: &Uint256, -) -> StdResult { - let t_tokens = Uint256::from(*total_tokens); - let t_shares = *total_shares; - let shares = *shares_amount; - - if total_tokens.is_zero() && total_shares.is_zero() { - // Used to normalize the staked token to the stake tokes - let token_multiplier = - Uint256::from(10u128).checked_pow(config.decimal_difference.try_into().unwrap())?; - - return match shares.checked_div(token_multiplier) { - Ok(tokens) => Ok(tokens.try_into()?), - Err(_) => Err(StdError::generic_err("Token calculation overflow")), - }; - } - - return match shares.checked_mul(t_tokens) { - Ok(tokens) => Ok(tokens.checked_div(t_shares)?.try_into()?), - Err(_) => Err(StdError::generic_err("Token calculation overflow")), - }; -} - -/// -/// Returns rewards in tokens, and shares -/// -pub fn calculate_rewards( - config: &StakeConfig, - tokens: Uint128, - shares: Uint256, - total_tokens: Uint128, - total_shares: Uint256, -) -> StdResult<(Uint128, Uint256)> { - let token_reward = tokens_per_share(config, &shares, &total_tokens, &total_shares)? - .checked_sub(tokens.into())?; - Ok(( - token_reward, - shares_per_token(config, &token_reward, &total_tokens, &total_shares)?, - )) -} - -pub fn try_receive( - deps: DepsMut, - env: Env, - info: MessageInfo, - sender: Addr, - from: Addr, - amount: Uint128, - msg: Option, - memo: Option, -) -> StdResult { - let sender_canon = deps.api.canonical_address(&sender)?; - - let stake_config = StakeConfig::load(deps.storage)?; - - if info.sender != stake_config.staked_token.address { - return Err(StdError::generic_err("Not the stake token")); - } - - let receive_type: ReceiveType; - if let Some(msg) = msg { - receive_type = from_binary(&msg)?; - } else { - return Err(StdError::generic_err("No receive type supplied in message")); - } - - let symbol = ReadonlyConfig::from_storage(deps.storage) - .constants()? - .symbol; - let mut messages = vec![]; - match receive_type { - ReceiveType::Bond { use_from } => { - let mut target = sender; - let mut target_canon = sender_canon; - if let Some(use_from) = use_from { - if use_from { - target_canon = deps.api.canonical_address(&from)?; - target = from; - } - } - - // Update user stake - add_balance( - deps.storage, - &stake_config, - &target, - &target_canon, - amount, - )?; - - // Store data - store_stake( - deps.storage, - &target_canon, - amount, - symbol, - memo, - &env.block, - )?; - - // Send tokens - if let Some(treasury) = stake_config.treasury { - messages.push(send_msg( - treasury, - amount.into(), - None, - None, - None, - 256, - stake_config.staked_token.code_hash, - stake_config.staked_token.address, - )?); - } else { - let mut stored_tokens = UnsentStakedTokens::load(deps.storage)?; - stored_tokens.0 += amount; - stored_tokens.save(deps.storage)?; - } - } - - ReceiveType::Reward => { - let mut total_tokens = TotalTokens::load(deps.storage)?; - total_tokens.0 += amount; - total_tokens.save(deps.storage)?; - - // Store data - store_add_reward( - deps.storage, - &sender_canon, - amount, - symbol, - memo, - &env.block, - )?; - } - - ReceiveType::Unbond => { - let mut remaining_amount = amount; - - let mut daily_unbond_queue = DailyUnbondingQueue::load(deps.storage)?; - - while !daily_unbond_queue.0.0.is_empty() { - remaining_amount = daily_unbond_queue.0.0[0].fund(remaining_amount); - if daily_unbond_queue.0.0[0].is_funded() { - daily_unbond_queue.0.0.pop(); - } - if remaining_amount == Uint128::zero() { - break; - } - } - - daily_unbond_queue.save(deps.storage)?; - - // Send back if overfunded - if remaining_amount > Uint128::zero() { - messages.push(send_msg( - sender, - remaining_amount.into(), - None, - None, - None, - 256, - stake_config.staked_token.code_hash, - stake_config.staked_token.address, - )?); - } - - store_fund_unbond( - deps.storage, - &sender_canon, - amount.checked_sub(remaining_amount)?, - symbol, - None, - &env.block, - )?; - } - }; - - Ok(Response::new().set_data(to_binary(&HandleAnswer::Receive { status: Success })?)) -} - -pub fn remove_from_cooldown( - store: &mut dyn Storage, - user: &Addr, - user_tokens: Uint128, - remove_amount: Uint128, - time: u64, -) -> StdResult<()> { - let mut cooldown = - UserCooldown::may_load(store, user.as_str().as_bytes())?.unwrap_or(UserCooldown { - total: Uint128::zero(), - queue: VecQueue(vec![]), - }); - - cooldown.update(time); - - let unlocked_tokens = user_tokens.checked_sub(cooldown.total)?; - if remove_amount > unlocked_tokens { - cooldown.remove_cooldown(remove_amount.checked_sub(unlocked_tokens)?); - } - cooldown.save(store, user.as_str().as_bytes())?; - - Ok(()) -} - -pub fn try_unbond( - deps: DepsMut, - env: Env, - info: MessageInfo, - amount: Uint128, -) -> StdResult { - let sender = info.sender; - let sender_canon = deps.api.canonical_address(&sender)?; - - let stake_config = StakeConfig::load(deps.storage)?; - - // Try to claim before unbonding - let claim = claim_rewards(deps.storage, &stake_config, &sender, &sender_canon)?; - - // Subtract tokens from user balance - remove_balance( - deps.storage, - &stake_config, - &sender, - &sender_canon, - amount, - env.block.time.seconds(), - )?; - - let mut total_unbonding = TotalUnbonding::load(deps.storage)?; - total_unbonding.0 += amount; - total_unbonding.save(deps.storage)?; - - // Round to that day's public unbonding queue, initialize one if empty - let mut daily_unbond_queue = DailyUnbondingQueue::load(deps.storage)?; - // Will add or merge a new unbonding date - daily_unbond_queue.0.push(&DailyUnbonding { - unbonding: amount, - funded: Default::default(), - release: round_date(env.block.time.seconds() + stake_config.unbond_time), - }); - - daily_unbond_queue.save(deps.storage)?; - - // Check if user has an existing queue, if not, instantiate one - let mut unbond_queue = UnbondingQueue::may_load(deps.storage, sender.as_str().as_bytes())? - .unwrap_or(UnbondingQueue(VecQueue::new(vec![]))); - - // Add unbonding to user queue - unbond_queue.0.push(&Unbonding { - amount, - release: env.block.time.seconds() + stake_config.unbond_time, - }); - - unbond_queue.save(deps.storage, sender.as_str().as_bytes())?; - - // Store the tx - let symbol = ReadonlyConfig::from_storage(deps.storage) - .constants()? - .symbol; - let mut messages = vec![]; - if !claim.is_zero() { - messages.push(send_msg( - sender.clone(), - claim.into(), - None, - None, - None, - 256, - stake_config.staked_token.code_hash, - stake_config.staked_token.address, - )?); - - store_claim_reward( - deps.storage, - &sender_canon, - claim, - symbol.clone(), - None, - &env.block, - )?; - } - store_unbond( - deps.storage, - &deps.api.canonical_address(&sender)?, - amount, - symbol, - None, - &env.block, - )?; - - Ok(Response::new().set_data(to_binary(&HandleAnswer::Unbond { status: Success })?)) -} - -pub fn try_claim_unbond( - deps: DepsMut, - env: Env, - info: MessageInfo, -) -> StdResult { - let sender = &info.sender; - let sender_canon = &deps.api.canonical_address(sender)?; - - let stake_config = StakeConfig::load(deps.storage)?; - - let mut total_unbonding = TotalUnbonding::load(deps.storage)?; - - // Instead of iterating over it we just look at its smallest value (first in queue) - let daily_unbond_queue = DailyUnbondingQueue::load(deps.storage)?.0; - - // Check if user has an existing queue, if not, instantiate one - let mut unbond_queue = UnbondingQueue::may_load(deps.storage, sender.as_str().as_bytes())? - .expect("No unbonding queue found"); - - let mut total = Uint128::zero(); - // Iterate over the sorted queue - while !unbond_queue.0.0.is_empty() { - // Since the queue is sorted, the moment we find a date above the current then we assume - // that no other item in the queue is eligible - if unbond_queue.0.0[0].release <= env.block.time.seconds() { - // Daily unbond queue is also sorted, therefore as long as its next item is greater - // than the unbond then we assume its funded - if daily_unbond_queue.0.is_empty() - || round_date(unbond_queue.0.0[0].release) < daily_unbond_queue.0[0].release - { - total += unbond_queue.0.0[0].amount; - unbond_queue.0.pop(); - } else { - break; - } - } else { - break; - } - } - - if total == Uint128::zero() { - return Err(StdError::generic_err("Nothing to claim")); - } - - unbond_queue.save(deps.storage, sender.as_str().as_bytes())?; - total_unbonding.0 = total_unbonding.0.checked_sub(total)?; - total_unbonding.save(deps.storage)?; - - let symbol = ReadonlyConfig::from_storage(deps.storage) - .constants()? - .symbol; - store_claim_unbond( - deps.storage, - sender_canon, - total, - symbol, - None, - &env.block, - )?; - - let messages = vec![send_msg( - sender.clone(), - total.into(), - None, - None, - None, - 256, - stake_config.staked_token.code_hash, - stake_config.staked_token.address, - )?]; - - Ok(Response::new().set_data(to_binary(&HandleAnswer::ClaimUnbond { status: Success })?)) -} - -pub fn try_claim_rewards( - deps: DepsMut, - env: Env, - info: MessageInfo, -) -> StdResult { - let stake_config = StakeConfig::load(deps.storage)?; - - let sender = &info.sender; - let sender_canon = &deps.api.canonical_address(sender)?; - - let claim = claim_rewards(deps.storage, &stake_config, sender, sender_canon)?; - - if claim.is_zero() { - return Err(StdError::generic_err("Nothing to claim")); - } - - let messages = vec![send_msg( - sender.clone(), - claim.into(), - None, - None, - None, - 256, - stake_config.staked_token.code_hash, - stake_config.staked_token.address, - )?]; - - let symbol = ReadonlyConfig::from_storage(deps.storage) - .constants()? - .symbol; - store_claim_reward( - deps.storage, - sender_canon, - claim, - symbol, - None, - &env.block, - )?; - - Ok(Response::new().set_data(to_binary(&HandleAnswer::ClaimRewards { status: Success })?)) -} - -pub fn try_stake_rewards( - deps: DepsMut, - env: Env, - info: MessageInfo, -) -> StdResult { - // Clam rewards - let symbol = ReadonlyConfig::from_storage(deps.storage) - .constants()? - .symbol; - let stake_config = StakeConfig::load(deps.storage)?; - - let sender = &info.sender; - let sender_canon = &deps.api.canonical_address(sender)?; - - let claim = claim_rewards(deps.storage, &stake_config, sender, sender_canon)?; - - store_claim_reward( - deps.storage, - sender_canon, - claim, - symbol.clone(), - None, - &env.block, - )?; - - // Stake rewards - // Update user stake - add_balance( - deps.storage, - &stake_config, - sender, - sender_canon, - claim, - )?; - - // Store data - // Store data - store_stake( - deps.storage, - sender_canon, - claim, - symbol, - None, - &env.block, - )?; - - let mut messages = vec![]; - - // Send tokens - if let Some(treasury) = stake_config.treasury { - messages.push(send_msg( - treasury, - claim.into(), - None, - None, - None, - 256, - stake_config.staked_token.code_hash, - stake_config.staked_token.address, - )?); - } else { - let mut stored_tokens = UnsentStakedTokens::load(deps.storage)?; - stored_tokens.0 += claim; - stored_tokens.save(deps.storage)?; - } - - Ok(Response::new().set_data(to_binary(&HandleAnswer::StakeRewards { status: Success })?)) -} - -#[cfg(test)] -mod tests { - use crate::stake::{calculate_rewards, round_date, shares_per_token, tokens_per_share}; - use shade_protocol::{ - contract_interfaces::staking::snip20_staking::stake::StakeConfig, - utils::asset::Contract, - }; - - fn init_config(token_decimals: u8, shares_decimals: u8) -> StakeConfig { - StakeConfig { - unbond_time: 0, - staked_token: Contract { - address: Default::default(), - code_hash: "".to_string(), - }, - decimal_difference: shares_decimals - token_decimals, - treasury: None, - } - } - - #[test] - fn tokens_per_share_test() { - let token_decimals = 8; - let shares_decimals = 18; - let config = init_config(token_decimals, shares_decimals); - - let token_1 = Uint128::new(10000000 * 10u128.pow(token_decimals.into())); - let share_1 = Uint256::from(10000000 * 10u128.pow(shares_decimals.into())); - - // Check for proper instantiate - assert_eq!( - tokens_per_share(&config, &share_1, &Uint128::zero(), &Uint256::zero()).unwrap(), - token_1 - ); - - // Check for stability - assert_eq!( - tokens_per_share(&config, &share_1, &token_1, &share_1).unwrap(), - token_1 - ); - assert_eq!( - tokens_per_share( - &config, - &share_1, - &(token_1 * Uint128::new(2)), - &(share_1 * Uint256::from(2u32)) - ) - .unwrap(), - token_1 - ); - - // check that shares increase when tokens decrease - assert!( - tokens_per_share(&config, &share_1, &(token_1 * Uint128::new(2)), &share_1).unwrap() - > token_1 - ); - - // check that shares decrease when tokens increase - assert!( - tokens_per_share( - &config, - &share_1, - &token_1, - &(share_1 * Uint256::from(2u32)) - ) - .unwrap() - < token_1 - ); - } - - #[test] - fn shares_per_token_test() { - let token_decimals = 8; - let shares_decimals = 18; - let config = init_config(token_decimals, shares_decimals); - - let token_1 = Uint128::new(100 * 10u128.pow(token_decimals.into())); - let share_1 = Uint256::from(100 * 10u128.pow(shares_decimals.into())); - - // Check for proper instantiate - assert_eq!( - shares_per_token(&config, &token_1, &Uint128::zero(), &Uint256::zero()).unwrap(), - share_1 - ); - - // Check for stability - assert_eq!( - shares_per_token(&config, &token_1, &token_1, &share_1).unwrap(), - share_1 - ); - assert_eq!( - shares_per_token( - &config, - &token_1, - &(token_1 * Uint128::new(2)), - &(share_1 * Uint256::from(2u32)) - ) - .unwrap(), - share_1 - ); - - // check that shares increase when tokens decrease - assert!( - shares_per_token(&config, &token_1, &(token_1 * Uint128::new(2)), &share_1).unwrap() - < share_1 - ); - - // check that shares decrease when tokens increase - assert!( - shares_per_token( - &config, - &token_1, - &token_1, - &(share_1 * Uint256::from(2u32)) - ) - .unwrap() - > share_1 - ); - } - - #[test] - fn round_date_test() { - assert_eq!(round_date(1645740448), 1645660800) - } - - #[test] - fn calculate_rewards_test() { - let token_decimals = 8; - let shares_decimals = 18; - let config = init_config(token_decimals, shares_decimals); - - // Tester has 100 tokens - // Other user has 50 - - let u_t = Uint128::new(100 * 10u128.pow(token_decimals.into())); - let mut u_s = Uint256::from(100 * 10u128.pow(shares_decimals.into())); - let mut t_t = Uint128::new(150 * 10u128.pow(token_decimals.into())); - let mut t_s = Uint256::from(150 * 10u128.pow(shares_decimals.into())); - - // No rewards - let (tokens, shares) = calculate_rewards(&config, u_t, u_s, t_t, t_s).unwrap(); - - assert_eq!(tokens, Uint128::zero()); - assert_eq!(shares, Uint256::zero()); - - // Some rewards - // We add 300 tokens, tester should get 200 tokens - let reward = 300 * 10u128.pow(token_decimals.into()); - t_t += Uint128::new(reward); - let (tokens, shares) = calculate_rewards(&config, u_t, u_s, t_t, t_s).unwrap(); - - assert_eq!(tokens.u128(), reward * 2 / 3); - t_t = t_t - tokens; - // We should receive 2/3 of current shares - assert_eq!(shares, u_s * Uint256::from(2u32) / Uint256::from(3u32)); - u_s = u_s - shares; - t_s = t_s - shares; - - // After claiming - let (tokens, shares) = calculate_rewards(&config, u_t, u_s, t_t, t_s).unwrap(); - - assert_eq!(tokens, Uint128::zero()); - assert_eq!(shares, Uint256::zero()); - } - - #[test] - fn simulate_claim_rewards() { - let token_decimals = 8; - let shares_decimals = 18; - let config = init_config(token_decimals, shares_decimals); - let mut user_shares = Uint256::from(50000000000000u128); - - let user_balance = Uint128::new(5000); - - // Get total supplied tokens - let mut total_shares = Uint256::from(50000000000000u128); - let mut total_tokens = Uint128::new(5000); - - let (reward_token, reward_shares) = calculate_rewards( - &config, - user_balance, - user_shares, - total_tokens, - total_shares, - ) - .unwrap(); - - assert_eq!(reward_token, Uint128::zero()); - } - - use shade_protocol::c_std::{Uint128, Uint256}; - use rand::Rng; - - #[test] - fn staking_simulation() { - let token_decimals = 8; - let shares_decimals = 18; - let config = init_config(token_decimals, shares_decimals); - - let mut t_t = Uint128::zero(); - let mut t_s = Uint256::zero(); - let mut rand = rand::thread_rng(); - - let mut stakers = vec![]; - - for _ in 0..10 { - // Generate stakers in this round - for _ in 0..rand.gen_range(1..=4) { - let tokens = - Uint128::new(rand.gen_range(1..100 * 10u128.pow(token_decimals.into()))); - - let shares = shares_per_token(&config, &tokens, &t_t, &t_s).unwrap(); - - stakers.push((tokens, shares)); - - t_t += tokens; - t_s += shares; - } - - // Add random rewards - t_t += Uint128::new(rand.gen_range(1u128..t_t.u128() / 2u128)); - - // Claim and unstake - for _ in 0..rand.gen_range(0..=stakers.len() / 2) { - let (mut tokens, mut shares) = stakers.remove(rand.gen_range(0..stakers.len())); - let (r_tokens, r_shares) = - calculate_rewards(&config, tokens, shares, t_t, t_s).unwrap(); - - t_t -= r_tokens; - t_s -= r_shares; - shares -= r_shares; - - let (r_tokens, r_shares) = - calculate_rewards(&config, tokens, shares, t_t, t_s).unwrap(); - assert_eq!(r_tokens, Uint128::zero()); - assert_eq!(r_shares, Uint256::zero()); - - // Unstake - t_t -= tokens; - t_s -= shares; - } - - // Claim the rest - while !stakers.is_empty() { - let (mut tokens, mut shares) = stakers.pop().unwrap(); - let (r_tokens, r_shares) = - calculate_rewards(&config, tokens, shares, t_t, t_s).unwrap(); - - t_t -= r_tokens; - t_s -= r_shares; - shares -= r_shares; - - let (r_tokens, r_shares) = - calculate_rewards(&config, tokens, shares, t_t, t_s).unwrap(); - assert_eq!(r_tokens, Uint128::zero()); - assert_eq!(r_shares, Uint256::zero()); - } - } - } -} diff --git a/archived-contracts/snip20_staking/src/stake_queries.rs b/archived-contracts/snip20_staking/src/stake_queries.rs deleted file mode 100644 index 5d71bcd..0000000 --- a/archived-contracts/snip20_staking/src/stake_queries.rs +++ /dev/null @@ -1,126 +0,0 @@ -use crate::{ - msg::QueryAnswer, - stake::{calculate_rewards, shares_per_token}, - state::ReadonlyBalances, - state_staking::{ - DailyUnbondingQueue, - TotalShares, - TotalTokens, - TotalUnbonding, - UnbondingQueue, - UserCooldown, - UserShares, - }, -}; -use shade_protocol::c_std::Uint128; -use shade_protocol::c_std::{to_binary, Api, Binary, DepsMut, Addr, Querier, StdResult, Storage}; -use shade_protocol::{ - contract_interfaces::staking::snip20_staking::stake::{StakeConfig, VecQueue}, - utils::storage::default::{BucketStorage, SingletonStorage}, -}; - -pub fn stake_config(deps: Deps) -> StdResult { - to_binary(&QueryAnswer::StakedConfig { - config: StakeConfig::load(deps.storage)?, - }) -} - -pub fn total_staked(deps: Deps) -> StdResult { - to_binary(&QueryAnswer::TotalStaked { - tokens: TotalTokens::load(deps.storage)?.0, - shares: TotalShares::load(deps.storage)?.0, - }) -} - -pub fn stake_rate(deps: Deps) -> StdResult { - to_binary(&QueryAnswer::StakeRate { - shares: shares_per_token( - &StakeConfig::load(deps.storage)?, - &Uint128::new(1), - &TotalTokens::load(deps.storage)?.0, - &TotalShares::load(deps.storage)?.0, - )?, - }) -} - -pub fn unfunded( - deps: Deps, - start: u64, - total: u64, -) -> StdResult { - let mut total_bonded = Uint128::zero(); - - let queue = DailyUnbondingQueue::load(deps.storage)?.0; - - let mut count = 0; - for item in queue.0.iter() { - if item.release >= start { - if count >= total { - break; - } - total_bonded += item.unbonding.checked_sub(item.funded)?; - count += 1; - } - } - - to_binary(&QueryAnswer::Unfunded { - total: total_bonded, - }) -} - -pub fn unbonding(deps: Deps) -> StdResult { - to_binary(&QueryAnswer::Unbonding { - total: TotalUnbonding::load(deps.storage)?.0, - }) -} - -pub fn staked( - deps: Deps, - account: Addr, - time: Option, -) -> StdResult { - let tokens = ReadonlyBalances::from_storage(deps.storage) - .account_amount(&deps.api.canonical_address(&account)?); - - let shares = UserShares::load(deps.storage, account.as_str().as_bytes())?.0; - - let (rewards, _) = calculate_rewards( - &StakeConfig::load(deps.storage)?, - Uint128::new(tokens), - shares, - TotalTokens::load(deps.storage)?.0, - TotalShares::load(deps.storage)?.0, - )?; - - let queue = UnbondingQueue::may_load(deps.storage, account.as_str().as_bytes())? - .unwrap_or_else(|| UnbondingQueue(VecQueue::new(vec![]))); - - let mut unbonding = Uint128::zero(); - let mut unbonded = Uint128::zero(); - - for item in queue.0.0.iter() { - if let Some(time) = time { - if item.release <= time { - unbonded += item.amount; - } else { - unbonding += item.amount; - } - } else { - unbonding += item.amount; - } - } - - to_binary(&QueryAnswer::Staked { - tokens: Uint128::new(tokens), - shares, - pending_rewards: rewards, - unbonding, - unbonded: time.map(|_| unbonded), - cooldown: UserCooldown::may_load(deps.storage, account.as_str().as_bytes())? - .unwrap_or(UserCooldown { - total: Default::default(), - queue: VecQueue(vec![]), - }) - .queue, - }) -} diff --git a/archived-contracts/snip20_staking/src/state.rs b/archived-contracts/snip20_staking/src/state.rs deleted file mode 100644 index 168a2c3..0000000 --- a/archived-contracts/snip20_staking/src/state.rs +++ /dev/null @@ -1,389 +0,0 @@ -use std::{any::type_name, convert::TryFrom}; - -use shade_protocol::c_std::{CanonicalAddr, Addr, ReadonlyStorage, StdError, StdResult, Storage}; -use shade_protocol::storage::{PrefixedStorage, ReadonlyPrefixedStorage}; - -use shade_protocol::secret_toolkit::storage::{TypedStore, TypedStoreMut}; - - -use shade_protocol::cosmwasm_schema::cw_serde; - -use crate::{ - msg::{status_level_to_u8, u8_to_status_level, ContractStatusLevel}, - viewing_key::ViewingKey, -}; -use shade_protocol::serde::de::DeserializeOwned; - -// Snip20 -pub static CONFIG_KEY: &[u8] = b"config"; -pub const PREFIX_TXS: &[u8] = b"transfers"; - -pub const KEY_CONSTANTS: &[u8] = b"constants"; -pub const KEY_TOTAL_SUPPLY: &[u8] = b"total_supply"; -pub const KEY_CONTRACT_STATUS: &[u8] = b"contract_status"; -pub const KEY_MINTERS: &[u8] = b"minters"; -pub const KEY_TX_COUNT: &[u8] = b"tx-count"; - -pub const PREFIX_CONFIG: &[u8] = b"config"; -pub const PREFIX_BALANCES: &[u8] = b"balances"; -pub const PREFIX_ALLOWANCES: &[u8] = b"allowances"; -pub const PREFIX_VIEW_KEY: &[u8] = b"viewingkey"; -pub const PREFIX_RECEIVERS: &[u8] = b"receivers"; - -// Config - -#[derive(Serialize, Debug, Deserialize, Clone, PartialEq)] -pub struct Constants { - pub name: String, - pub admin: Addr, - pub symbol: String, - pub decimals: u8, - pub prng_seed: Vec, - // privacy configuration - pub total_supply_is_public: bool, - // the address of this contract, used to validate query permits - pub contract_address: Addr, -} - -pub struct ReadonlyConfig<'a> { - storage: ReadonlyPrefixedStorage<'a>, -} - -impl<'a, S: Storage> ReadonlyConfig<'a> { - pub fn from_storage(storage: &'a mut dyn Storage) -> Self { - Self { - storage: ReadonlyPrefixedStorage::new(PREFIX_CONFIG, storage), - } - } - - fn as_readonly(&self) -> ReadonlyConfigImpl { - ReadonlyConfigImpl(self.storage) - } - - pub fn constants(&self) -> StdResult { - self.as_readonly().constants() - } - - pub fn total_supply(&self) -> u128 { - self.as_readonly().total_supply() - } - - pub fn contract_status(&self) -> ContractStatusLevel { - self.as_readonly().contract_status() - } - - pub fn minters(&self) -> Vec { - self.as_readonly().minters() - } - - pub fn tx_count(&self) -> u64 { - self.as_readonly().tx_count() - } -} - -fn ser_bin_data(obj: &T) -> StdResult> { - bincode2::serialize(&obj).map_err(|e| StdError::serialize_err(type_name::(), e)) -} - -fn deser_bin_data(data: &[u8]) -> StdResult { - bincode2::deserialize::(data).map_err(|e| StdError::serialize_err(type_name::(), e)) -} - -fn set_bin_data(storage: &mut dyn Storage, key: &[u8], data: &T) -> StdResult<()> { - let bin_data = ser_bin_data(data)?; - - storage.set(key, &bin_data); - Ok(()) -} - -fn get_bin_data(storage: &dyn Storage, key: &[u8]) -> StdResult { - let bin_data = storage.get(key); - - match bin_data { - None => Err(StdError::not_found("Key not found in storage")), - Some(bin_data) => Ok(deser_bin_data(&bin_data)?), - } -} - -pub struct Config<'a> { - storage: PrefixedStorage<'a>, -} - -impl<'a> Config<'a> { - pub fn from_storage(storage: &mut dyn Storage) -> Self { - Self { - storage: PrefixedStorage::new(PREFIX_CONFIG, storage), - } - } - - fn as_readonly(&self) -> ReadonlyConfigImpl> { - ReadonlyConfigImpl(&self.storage) - } - - pub fn constants(&self) -> StdResult { - self.as_readonly().constants() - } - - pub fn set_constants(&mut self, constants: &Constants) -> StdResult<()> { - set_bin_data(&mut self.storage, KEY_CONSTANTS, constants) - } - - pub fn total_supply(&self) -> u128 { - self.as_readonly().total_supply() - } - - pub fn set_total_supply(&mut self, supply: u128) { - self.storage.set(KEY_TOTAL_SUPPLY, &supply.to_be_bytes()); - } - - pub fn contract_status(&self) -> ContractStatusLevel { - self.as_readonly().contract_status() - } - - pub fn set_contract_status(&mut self, status: ContractStatusLevel) { - let status_u8 = status_level_to_u8(status); - self.storage - .set(KEY_CONTRACT_STATUS, &status_u8.to_be_bytes()); - } - - pub fn set_minters(&mut self, minters_to_set: Vec) -> StdResult<()> { - set_bin_data(&mut self.storage, KEY_MINTERS, &minters_to_set) - } - - pub fn add_minters(&mut self, minters_to_add: Vec) -> StdResult<()> { - let mut minters = self.minters(); - minters.extend(minters_to_add); - - self.set_minters(minters) - } - - pub fn remove_minters(&mut self, minters_to_remove: Vec) -> StdResult<()> { - let mut minters = self.minters(); - - for minter in minters_to_remove { - minters.retain(|x| x != &minter); - } - - self.set_minters(minters) - } - - pub fn minters(&mut self) -> Vec { - self.as_readonly().minters() - } - - pub fn tx_count(&self) -> u64 { - self.as_readonly().tx_count() - } - - pub fn set_tx_count(&mut self, count: u64) -> StdResult<()> { - set_bin_data(&mut self.storage, KEY_TX_COUNT, &count) - } -} - -/// This struct refactors out the readonly methods that we need for `Config` and `ReadonlyConfig` -/// in a way that is generic over their mutability. -/// -/// This was the only way to prevent code duplication of these methods because of the way -/// that `ReadonlyPrefixedStorage` and `PrefixedStorage` are implemented in `cosmwasm-std` -struct ReadonlyConfigImpl<'a, S: ReadonlyStorage>(&'a mut dyn Storage); - -impl<'a, S: ReadonlyStorage> ReadonlyConfigImpl<'a> { - fn constants(&self) -> StdResult { - let consts_bytes = self - .0 - .get(KEY_CONSTANTS) - .ok_or_else(|| StdError::generic_err("no constants stored in configuration"))?; - bincode2::deserialize::(&consts_bytes) - .map_err(|e| StdError::serialize_err(type_name::(), e)) - } - - fn total_supply(&self) -> u128 { - let supply_bytes = self - .0 - .get(KEY_TOTAL_SUPPLY) - .expect("no total supply stored in config"); - // This unwrap is ok because we know we stored things correctly - slice_to_u128(&supply_bytes).unwrap() - } - - fn contract_status(&self) -> ContractStatusLevel { - let supply_bytes = self - .0 - .get(KEY_CONTRACT_STATUS) - .expect("no contract status stored in config"); - - // These unwraps are ok because we know we stored things correctly - let status = slice_to_u8(&supply_bytes).unwrap(); - u8_to_status_level(status).unwrap() - } - - fn minters(&self) -> Vec { - get_bin_data(self.0, KEY_MINTERS).unwrap() - } - - pub fn tx_count(&self) -> u64 { - get_bin_data(self.0, KEY_TX_COUNT).unwrap_or_default() - } -} - -// Balances - -pub struct ReadonlyBalances<'a, S: ReadonlyStorage> { - storage: ReadonlyPrefixedStorage<'a>, -} - -impl<'a, S: ReadonlyStorage> ReadonlyBalances<'a> { - pub fn from_storage(storage: &'a mut dyn Storage) -> Self { - Self { - storage: ReadonlyPrefixedStorage::new(PREFIX_BALANCES, storage), - } - } - - fn as_readonly(&self) -> ReadonlyBalancesImpl> { - ReadonlyBalancesImpl(&self.storage) - } - - pub fn account_amount(&self, account: &CanonicalAddr) -> u128 { - self.as_readonly().account_amount(account) - } -} - -pub struct Balances<'a> { - storage: PrefixedStorage<'a>, -} - -impl<'a> Balances<'a> { - pub fn from_storage(storage: &mut dyn Storage) -> Self { - Self { - storage: PrefixedStorage::new(PREFIX_BALANCES, storage), - } - } - - fn as_readonly(&self) -> ReadonlyBalancesImpl> { - ReadonlyBalancesImpl(&self.storage) - } - - pub fn balance(&self, account: &CanonicalAddr) -> u128 { - self.as_readonly().account_amount(account) - } - - pub fn set_account_balance(&mut self, account: &CanonicalAddr, amount: u128) { - self.storage.set(account.as_slice(), &amount.to_be_bytes()) - } -} - -/// This struct refactors out the readonly methods that we need for `Balances` and `ReadonlyBalances` -/// in a way that is generic over their mutability. -/// -/// This was the only way to prevent code duplication of these methods because of the way -/// that `ReadonlyPrefixedStorage` and `PrefixedStorage` are implemented in `cosmwasm-std` -struct ReadonlyBalancesImpl<'a, S: ReadonlyStorage>(&'a mut dyn Storage); - -impl<'a, S: ReadonlyStorage> ReadonlyBalancesImpl<'a> { - pub fn account_amount(&self, account: &CanonicalAddr) -> u128 { - let account_bytes = account.as_slice(); - let result = self.0.get(account_bytes); - match result { - // This unwrap is ok because we know we stored things correctly - Some(balance_bytes) => slice_to_u128(&balance_bytes).unwrap(), - None => 0, - } - } -} - -// Allowances - -#[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Default)] -pub struct Allowance { - pub amount: u128, - pub expiration: Option, -} - -impl Allowance { - pub fn is_expired_at(&self, block: &shade_protocol::c_std::BlockInfo) -> bool { - match self.expiration { - Some(time) => block.time >= time, - None => false, // allowance has no expiration - } - } -} - -pub fn read_allowance( - store: &dyn Storage, - owner: &CanonicalAddr, - spender: &CanonicalAddr, -) -> StdResult { - let owner_store = - ReadonlyPrefixedStorage::multilevel(&[PREFIX_ALLOWANCES, owner.as_slice()], store); - let owner_store = TypedStore::attach(&owner_store); - let allowance = owner_store.may_load(spender.as_slice()); - allowance.map(Option::unwrap_or_default) -} - -pub fn write_allowance( - store: &mut dyn Storage, - owner: &CanonicalAddr, - spender: &CanonicalAddr, - allowance: Allowance, -) -> StdResult<()> { - let mut owner_store = - PrefixedStorage::multilevel(&[PREFIX_ALLOWANCES, owner.as_slice()], store); - let mut owner_store = TypedStoreMut::attach(&mut owner_store); - - owner_store.store(spender.as_slice(), &allowance) -} - -// Viewing Keys - -pub fn write_viewing_key(store: &mut dyn Storage, owner: &CanonicalAddr, key: &ViewingKey) { - let mut balance_store = PrefixedStorage::new(PREFIX_VIEW_KEY, store); - balance_store.set(owner.as_slice(), &key.to_hashed()); -} - -pub fn read_viewing_key(store: &dyn Storage, owner: &CanonicalAddr) -> Option> { - let balance_store = ReadonlyPrefixedStorage::new(PREFIX_VIEW_KEY, store); - balance_store.get(owner.as_slice()) -} - -// Receiver Interface - -pub fn get_receiver_hash( - store: &dyn Storage, - account: &Addr, -) -> Option> { - let store = ReadonlyPrefixedStorage::new(PREFIX_RECEIVERS, store); - store.get(account.as_str().as_bytes()).map(|data| { - String::from_utf8(data) - .map_err(|_err| StdError::invalid_utf8("stored code hash was not a valid String")) - }) -} - -pub fn set_receiver_hash(store: &mut dyn Storage, account: &Addr, code_hash: String) { - let mut store = PrefixedStorage::new(PREFIX_RECEIVERS, store); - store.set(account.as_str().as_bytes(), code_hash.as_bytes()); -} - -// Helpers - -/// Converts 16 bytes value into u128 -/// Errors if data found that is not 16 bytes -fn slice_to_u128(data: &[u8]) -> StdResult { - match <[u8; 16]>::try_from(data) { - Ok(bytes) => Ok(u128::from_be_bytes(bytes)), - Err(_) => Err(StdError::generic_err( - "Corrupted data found. 16 byte expected.", - )), - } -} - -/// Converts 1 byte value into u8 -/// Errors if data found that is not 1 byte -fn slice_to_u8(data: &[u8]) -> StdResult { - if data.len() == 1 { - Ok(data[0]) - } else { - Err(StdError::generic_err( - "Corrupted data found. 1 byte expected.", - )) - } -} diff --git a/archived-contracts/snip20_staking/src/state_staking.rs b/archived-contracts/snip20_staking/src/state_staking.rs deleted file mode 100644 index 409a6f6..0000000 --- a/archived-contracts/snip20_staking/src/state_staking.rs +++ /dev/null @@ -1,135 +0,0 @@ -use shade_protocol::c_std::{Uint128, Uint256}; -use shade_protocol::c_std::Addr; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use shade_protocol::{ - contract_interfaces::staking::snip20_staking::stake::{ - Cooldown, - DailyUnbonding, - Unbonding, - VecQueue, - }, - utils::storage::default::{BucketStorage, SingletonStorage}, -}; - -// used to determine what each token is worth to calculate rewards -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct TotalShares(pub Uint256); - -impl SingletonStorage for TotalShares { - const NAMESPACE: &'static [u8] = b"total_shares"; -} - -// used to separate tokens minted from total tokens (includes rewards) -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct TotalTokens(pub Uint128); - -impl SingletonStorage for TotalTokens { - const NAMESPACE: &'static [u8] = b"total_tokens"; -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct UserShares(pub Uint256); - -impl BucketStorage for UserShares { - const NAMESPACE: &'static [u8] = b"user_shares"; -} - -// stores received token info if no treasury is set -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct UnsentStakedTokens(pub Uint128); - -impl SingletonStorage for UnsentStakedTokens { - const NAMESPACE: &'static [u8] = b"unsent_staked_tokens"; -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct TotalUnbonding(pub Uint128); - -impl SingletonStorage for TotalUnbonding { - const NAMESPACE: &'static [u8] = b"total_unbonding"; -} - -// Distributors wrappers - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct Distributors(pub Vec); - -impl SingletonStorage for Distributors { - const NAMESPACE: &'static [u8] = b"distributors"; -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct DistributorsEnabled(pub bool); - -impl SingletonStorage for DistributorsEnabled { - const NAMESPACE: &'static [u8] = b"distributors_transfer"; -} - -// Unbonding Queues - -#[cw_serde] -pub struct UnbondingQueue(pub VecQueue); - -impl BucketStorage for UnbondingQueue { - const NAMESPACE: &'static [u8] = b"unbonding_queue"; -} - -#[cw_serde] -pub struct DailyUnbondingQueue(pub VecQueue); - -impl SingletonStorage for DailyUnbondingQueue { - const NAMESPACE: &'static [u8] = b"daily_unbonding_queue"; -} - -// Used for vote cooldown after send -#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct UserCooldown { - pub total: Uint128, - pub queue: VecQueue, -} - -impl BucketStorage for UserCooldown { - const NAMESPACE: &'static [u8] = b"user_cooldown"; -} - -impl UserCooldown { - pub fn add_cooldown(&mut self, cooldown: Cooldown) { - self.total += cooldown.amount; - self.queue.push(&cooldown); - } - - pub fn remove_cooldown(&mut self, amount: Uint128) { - let mut remaining = amount; - while remaining != Uint128::zero() { - let index = self.queue.0.len() - 1; - if self.queue.0[index].amount <= remaining { - let item = self.queue.0.remove(index); - remaining = remaining.checked_sub(item.amount).unwrap(); - } else { - self.queue.0[index].amount = - self.queue.0[index].amount.checked_sub(remaining).unwrap(); - break; - } - } - } - - pub fn update(&mut self, time: u64) { - while !self.queue.0.is_empty() { - if self.queue.0[0].release <= time { - let i = self.queue.pop().unwrap(); - self.total = self.total.checked_sub(i.amount).unwrap(); - } else { - break; - } - } - } -} diff --git a/archived-contracts/snip20_staking/src/transaction_history.rs b/archived-contracts/snip20_staking/src/transaction_history.rs deleted file mode 100644 index 4c54b03..0000000 --- a/archived-contracts/snip20_staking/src/transaction_history.rs +++ /dev/null @@ -1,723 +0,0 @@ - -use shade_protocol::cosmwasm_schema::cw_serde; - -use shade_protocol::c_std::{ - Api, - CanonicalAddr, - Coin, - Addr, - ReadonlyStorage, - StdError, - StdResult, - Storage, -}; -use shade_protocol::storage::{PrefixedStorage, ReadonlyPrefixedStorage}; - -use shade_protocol::c_std::Uint128; -use shade_protocol::secret_toolkit::storage::{AppendStore, AppendStoreMut}; - -use crate::state::Config; - -const PREFIX_TXS: &[u8] = b"transactions"; -const PREFIX_TRANSFERS: &[u8] = b"transfers"; - -// Note that id is a globally incrementing counter. -// Since it's 64 bits long, even at 50 tx/s it would take -// over 11 billion years for it to rollback. I'm pretty sure -// we'll have bigger issues by then. -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct Tx { - pub id: u64, - pub from: Addr, - pub sender: Addr, - pub receiver: Addr, - pub coins: Coin, - #[serde(skip_serializing_if = "Option::is_none")] - pub memo: Option, - // The block time and block height are optional so that the JSON schema - // reflects that some SNIP-20 contracts may not include this info. - pub block_time: Option, - pub block_height: Option, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum TxAction { - Transfer { - from: Addr, - sender: Addr, - recipient: Addr, - }, - Mint { - minter: Addr, - recipient: Addr, - }, - Burn { - burner: Addr, - owner: Addr, - }, - Deposit {}, - Redeem {}, - Stake { - staker: Addr, - }, - AddReward { - funder: Addr, - }, - FundUnbond { - funder: Addr, - }, - Unbond { - staker: Addr, - }, - ClaimUnbond { - staker: Addr, - }, - ClaimReward { - staker: Addr, - }, -} - -// Note that id is a globally incrementing counter. -// Since it's 64 bits long, even at 50 tx/s it would take -// over 11 billion years for it to rollback. I'm pretty sure -// we'll have bigger issues by then. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct RichTx { - pub id: u64, - pub action: TxAction, - pub coins: Coin, - #[serde(skip_serializing_if = "Option::is_none")] - pub memo: Option, - pub block_time: u64, - pub block_height: u64, -} - -// Stored types: - -/// This type is the stored version of the legacy transfers -#[cw_serde] -struct StoredLegacyTransfer { - id: u64, - from: CanonicalAddr, - sender: CanonicalAddr, - receiver: CanonicalAddr, - coins: Coin, - memo: Option, - block_time: u64, - block_height: u64, -} - -impl StoredLegacyTransfer { - pub fn into_humanized(self, api: &dyn Api) -> StdResult { - let tx = Tx { - id: self.id, - from: api.human_address(&self.from)?, - sender: api.human_address(&self.sender)?, - receiver: api.human_address(&self.receiver)?, - coins: self.coins, - memo: self.memo, - block_time: Some(self.block_time), - block_height: Some(self.block_height), - }; - Ok(tx) - } -} - -#[derive(Clone, Copy, Debug)] -#[repr(u8)] -enum TxCode { - Transfer = 0, - Mint = 1, - Burn = 2, - Deposit = 3, - Redeem = 4, - Stake = 5, - AddReward = 6, - FundUnbond = 7, - Unbond = 8, - ClaimUnbond = 9, - ClaimReward = 10, -} - -impl TxCode { - fn to_u8(self) -> u8 { - self as u8 - } - - fn from_u8(n: u8) -> StdResult { - use TxCode::*; - match n { - 0 => Ok(Transfer), - 1 => Ok(Mint), - 2 => Ok(Burn), - 3 => Ok(Deposit), - 4 => Ok(Redeem), - 5 => Ok(Stake), - 6 => Ok(AddReward), - 7 => Ok(FundUnbond), - 8 => Ok(Unbond), - 9 => Ok(ClaimUnbond), - 10 => Ok(ClaimReward), - other => Err(StdError::generic_err(format!( - "Unexpected Tx code in transaction history: {} Storage is corrupted.", - other - ))), - } - } -} - -#[cw_serde] -struct StoredTxAction { - tx_type: u8, - address1: Option, - address2: Option, - address3: Option, -} - -impl StoredTxAction { - fn transfer(from: CanonicalAddr, sender: CanonicalAddr, recipient: CanonicalAddr) -> Self { - Self { - tx_type: TxCode::Transfer.to_u8(), - address1: Some(from), - address2: Some(sender), - address3: Some(recipient), - } - } - - fn mint(minter: CanonicalAddr, recipient: CanonicalAddr) -> Self { - Self { - tx_type: TxCode::Mint.to_u8(), - address1: Some(minter), - address2: Some(recipient), - address3: None, - } - } - - fn burn(owner: CanonicalAddr, burner: CanonicalAddr) -> Self { - Self { - tx_type: TxCode::Burn.to_u8(), - address1: Some(burner), - address2: Some(owner), - address3: None, - } - } - - fn deposit() -> Self { - Self { - tx_type: TxCode::Deposit.to_u8(), - address1: None, - address2: None, - address3: None, - } - } - - fn stake(staker: CanonicalAddr) -> Self { - Self { - tx_type: TxCode::Stake.to_u8(), - address1: Some(staker), - address2: None, - address3: None, - } - } - - fn add_reward(funder: CanonicalAddr) -> Self { - Self { - tx_type: TxCode::AddReward.to_u8(), - address1: Some(funder), - address2: None, - address3: None, - } - } - - fn fund_unbond(funder: CanonicalAddr) -> Self { - Self { - tx_type: TxCode::FundUnbond.to_u8(), - address1: Some(funder), - address2: None, - address3: None, - } - } - - fn unbond(staker: CanonicalAddr) -> Self { - Self { - tx_type: TxCode::Unbond.to_u8(), - address1: Some(staker), - address2: None, - address3: None, - } - } - - fn claim_unbond(staker: CanonicalAddr) -> Self { - Self { - tx_type: TxCode::ClaimUnbond.to_u8(), - address1: Some(staker), - address2: None, - address3: None, - } - } - - fn claim_reward(staker: CanonicalAddr) -> Self { - Self { - tx_type: TxCode::ClaimReward.to_u8(), - address1: Some(staker), - address2: None, - address3: None, - } - } - - fn into_humanized(self, api: &dyn Api) -> StdResult { - let transfer_addr_err = || { - StdError::generic_err( - "Missing address in stored Transfer transaction. Storage is corrupt", - ) - }; - let mint_addr_err = || { - StdError::generic_err("Missing address in stored Mint transaction. Storage is corrupt") - }; - let burn_addr_err = || { - StdError::generic_err("Missing address in stored Burn transaction. Storage is corrupt") - }; - let staker_addr_err = || { - StdError::generic_err("Missing address in stored Stake transaction. Storage is corrupt") - }; - - // In all of these, we ignore fields that we don't expect to find populated - let action = match TxCode::from_u8(self.tx_type)? { - TxCode::Transfer => { - let from = self.address1.ok_or_else(transfer_addr_err)?; - let sender = self.address2.ok_or_else(transfer_addr_err)?; - let recipient = self.address3.ok_or_else(transfer_addr_err)?; - let from = api.human_address(&from)?; - let sender = api.human_address(&sender)?; - let recipient = api.human_address(&recipient)?; - TxAction::Transfer { - from, - sender, - recipient, - } - } - TxCode::Mint => { - let minter = self.address1.ok_or_else(mint_addr_err)?; - let recipient = self.address2.ok_or_else(mint_addr_err)?; - let minter = api.human_address(&minter)?; - let recipient = api.human_address(&recipient)?; - TxAction::Mint { minter, recipient } - } - TxCode::Burn => { - let burner = self.address1.ok_or_else(burn_addr_err)?; - let owner = self.address2.ok_or_else(burn_addr_err)?; - let burner = api.human_address(&burner)?; - let owner = api.human_address(&owner)?; - TxAction::Burn { burner, owner } - } - TxCode::Deposit => TxAction::Deposit {}, - TxCode::Redeem => TxAction::Redeem {}, - TxCode::Stake => { - let staker = self.address1.ok_or_else(staker_addr_err)?; - let staker = api.human_address(&staker)?; - TxAction::Stake { staker } - } - TxCode::AddReward => { - let funder = self.address1.ok_or_else(staker_addr_err)?; - let funder = api.human_address(&funder)?; - TxAction::AddReward { funder } - } - TxCode::FundUnbond => { - let funder = self.address1.ok_or_else(staker_addr_err)?; - let funder = api.human_address(&funder)?; - TxAction::FundUnbond { funder } - } - TxCode::Unbond => { - let staker = self.address1.ok_or_else(staker_addr_err)?; - let staker = api.human_address(&staker)?; - TxAction::Unbond { staker } - } - TxCode::ClaimUnbond => { - let staker = self.address1.ok_or_else(staker_addr_err)?; - let staker = api.human_address(&staker)?; - TxAction::ClaimUnbond { staker } - } - TxCode::ClaimReward => { - let staker = self.address1.ok_or_else(staker_addr_err)?; - let staker = api.human_address(&staker)?; - TxAction::ClaimReward { staker } - } - }; - - Ok(action) - } -} - -#[cw_serde] -struct StoredRichTx { - id: u64, - action: StoredTxAction, - coins: Coin, - memo: Option, - block_time: u64, - block_height: u64, -} - -impl StoredRichTx { - fn new( - id: u64, - action: StoredTxAction, - coins: Coin, - memo: Option, - block: &shade_protocol::c_std::BlockInfo, - ) -> Self { - Self { - id, - action, - coins, - memo, - block_time: block.time, - block_height: block.height, - } - } - - fn into_humanized(self, api: &dyn Api) -> StdResult { - Ok(RichTx { - id: self.id, - action: self.action.into_humanized(api)?, - coins: self.coins, - memo: self.memo, - block_time: self.block_time, - block_height: self.block_height, - }) - } - - fn from_stored_legacy_transfer(transfer: StoredLegacyTransfer) -> Self { - let action = StoredTxAction::transfer(transfer.from, transfer.sender, transfer.receiver); - Self { - id: transfer.id, - action, - coins: transfer.coins, - memo: transfer.memo, - block_time: transfer.block_time, - block_height: transfer.block_height, - } - } -} - -// Storage functions: - -fn increment_tx_count(store: &mut dyn Storage) -> StdResult { - let mut config = Config::from_storage(store); - let id = config.tx_count() + 1; - config.set_tx_count(id)?; - Ok(id) -} - -#[allow(clippy::too_many_arguments)] // We just need them -pub fn store_transfer( - store: &mut dyn Storage, - owner: &CanonicalAddr, - sender: &CanonicalAddr, - receiver: &CanonicalAddr, - amount: Uint128, - denom: String, - memo: Option, - block: &shade_protocol::c_std::BlockInfo, -) -> StdResult<()> { - let id = increment_tx_count(store)?; - let coins = Coin { - denom, - amount: amount.into(), - }; - let transfer = StoredLegacyTransfer { - id, - from: owner.clone(), - sender: sender.clone(), - receiver: receiver.clone(), - coins, - memo, - block_time: block.time, - block_height: block.height, - }; - let tx = StoredRichTx::from_stored_legacy_transfer(transfer.clone()); - - // Write to the owners history if it's different from the other two addresses - if owner != sender && owner != receiver { - // shade_protocol::c_std::debug_print("saving transaction history for owner"); - append_tx(store, &tx, owner)?; - append_transfer(store, &transfer, owner)?; - } - // Write to the sender's history if it's different from the receiver - if sender != receiver { - // shade_protocol::c_std::debug_print("saving transaction history for sender"); - append_tx(store, &tx, sender)?; - append_transfer(store, &transfer, sender)?; - } - // Always write to the recipient's history - // shade_protocol::c_std::debug_print("saving transaction history for receiver"); - append_tx(store, &tx, receiver)?; - append_transfer(store, &transfer, receiver)?; - - Ok(()) -} - -pub fn store_mint( - store: &mut dyn Storage, - minter: &CanonicalAddr, - recipient: &CanonicalAddr, - amount: Uint128, - denom: String, - memo: Option, - block: &shade_protocol::c_std::BlockInfo, -) -> StdResult<()> { - let id = increment_tx_count(store)?; - let coins = Coin { - denom, - amount: amount.into(), - }; - let action = StoredTxAction::mint(minter.clone(), recipient.clone()); - let tx = StoredRichTx::new(id, action, coins, memo, block); - - if minter != recipient { - append_tx(store, &tx, recipient)?; - } - append_tx(store, &tx, minter)?; - - Ok(()) -} - -pub fn store_burn( - store: &mut dyn Storage, - owner: &CanonicalAddr, - burner: &CanonicalAddr, - amount: Uint128, - denom: String, - memo: Option, - block: &shade_protocol::c_std::BlockInfo, -) -> StdResult<()> { - let id = increment_tx_count(store)?; - let coins = Coin { - denom, - amount: amount.into(), - }; - let action = StoredTxAction::burn(owner.clone(), burner.clone()); - let tx = StoredRichTx::new(id, action, coins, memo, block); - - if burner != owner { - append_tx(store, &tx, owner)?; - } - append_tx(store, &tx, burner)?; - - Ok(()) -} - -pub fn store_stake( - store: &mut dyn Storage, - staker: &CanonicalAddr, - amount: Uint128, - denom: String, - memo: Option, - block: &shade_protocol::c_std::BlockInfo, -) -> StdResult<()> { - let id = increment_tx_count(store)?; - let coins = Coin { - denom, - amount: amount.into(), - }; - let action = StoredTxAction::stake(staker.clone()); - let tx = StoredRichTx::new(id, action, coins, memo, block); - - append_tx(store, &tx, staker)?; - - Ok(()) -} - -pub fn store_add_reward( - store: &mut dyn Storage, - staker: &CanonicalAddr, - amount: Uint128, - denom: String, - memo: Option, - block: &shade_protocol::c_std::BlockInfo, -) -> StdResult<()> { - let id = increment_tx_count(store)?; - let coins = Coin { - denom, - amount: amount.into(), - }; - let action = StoredTxAction::add_reward(staker.clone()); - let tx = StoredRichTx::new(id, action, coins, memo, block); - - append_tx(store, &tx, staker)?; - - Ok(()) -} - -pub fn store_fund_unbond( - store: &mut dyn Storage, - staker: &CanonicalAddr, - amount: Uint128, - denom: String, - memo: Option, - block: &shade_protocol::c_std::BlockInfo, -) -> StdResult<()> { - let id = increment_tx_count(store)?; - let coins = Coin { - denom, - amount: amount.into(), - }; - let action = StoredTxAction::fund_unbond(staker.clone()); - let tx = StoredRichTx::new(id, action, coins, memo, block); - - append_tx(store, &tx, staker)?; - - Ok(()) -} - -pub fn store_unbond( - store: &mut dyn Storage, - staker: &CanonicalAddr, - amount: Uint128, - denom: String, - memo: Option, - block: &shade_protocol::c_std::BlockInfo, -) -> StdResult<()> { - let id = increment_tx_count(store)?; - let coins = Coin { - denom, - amount: amount.into(), - }; - let action = StoredTxAction::unbond(staker.clone()); - let tx = StoredRichTx::new(id, action, coins, memo, block); - - append_tx(store, &tx, staker)?; - - Ok(()) -} - -pub fn store_claim_unbond( - store: &mut dyn Storage, - staker: &CanonicalAddr, - amount: Uint128, - denom: String, - memo: Option, - block: &shade_protocol::c_std::BlockInfo, -) -> StdResult<()> { - let id = increment_tx_count(store)?; - let coins = Coin { - denom, - amount: amount.into(), - }; - let action = StoredTxAction::claim_unbond(staker.clone()); - let tx = StoredRichTx::new(id, action, coins, memo, block); - - append_tx(store, &tx, staker)?; - - Ok(()) -} - -pub fn store_claim_reward( - store: &mut dyn Storage, - staker: &CanonicalAddr, - amount: Uint128, - denom: String, - memo: Option, - block: &shade_protocol::c_std::BlockInfo, -) -> StdResult<()> { - let id = increment_tx_count(store)?; - let coins = Coin { - denom, - amount: amount.into(), - }; - let action = StoredTxAction::claim_reward(staker.clone()); - let tx = StoredRichTx::new(id, action, coins, memo, block); - - append_tx(store, &tx, staker)?; - - Ok(()) -} - -fn append_tx( - store: &mut dyn Storage, - tx: &StoredRichTx, - for_address: &CanonicalAddr, -) -> StdResult<()> { - let mut store = PrefixedStorage::multilevel(&[PREFIX_TXS, for_address.as_slice()], store); - let mut store = AppendStoreMut::attach_or_create(&mut store)?; - store.push(tx) -} - -fn append_transfer( - store: &mut dyn Storage, - tx: &StoredLegacyTransfer, - for_address: &CanonicalAddr, -) -> StdResult<()> { - let mut store = PrefixedStorage::multilevel(&[PREFIX_TRANSFERS, for_address.as_slice()], store); - let mut store = AppendStoreMut::attach_or_create(&mut store)?; - store.push(tx) -} - -pub fn get_txs( - api: &dyn Api, - storage: &dyn Storage, - for_address: &CanonicalAddr, - page: u32, - page_size: u32, -) -> StdResult<(Vec, u64)> { - let store = ReadonlyPrefixedStorage::multilevel(&[PREFIX_TXS, for_address.as_slice()], storage); - - // Try to access the storage of txs for the account. - // If it doesn't exist yet, return an empty list of transfers. - let store = AppendStore::::attach(&store); - let store = if let Some(result) = store { - result? - } else { - return Ok((vec![], 0)); - }; - - // Take `page_size` txs starting from the latest tx, potentially skipping `page * page_size` - // txs from the start. - let tx_iter = store - .iter() - .rev() - .skip((page * page_size) as _) - .take(page_size as _); - - // The `and_then` here flattens the `StdResult>` to an `StdResult` - let txs: StdResult> = tx_iter - .map(|tx| tx.map(|tx| tx.into_humanized(api)).and_then(|x| x)) - .collect(); - txs.map(|txs| (txs, store.len() as u64)) -} - -pub fn get_transfers( - api: &dyn Api, - storage: &dyn Storage, - for_address: &CanonicalAddr, - page: u32, - page_size: u32, -) -> StdResult<(Vec, u64)> { - let store = - ReadonlyPrefixedStorage::multilevel(&[PREFIX_TRANSFERS, for_address.as_slice()], storage); - - // Try to access the storage of transfers for the account. - // If it doesn't exist yet, return an empty list of transfers. - let store = AppendStore::::attach(&store); - let store = if let Some(result) = store { - result? - } else { - return Ok((vec![], 0)); - }; - - // Take `page_size` txs starting from the latest tx, potentially skipping `page * page_size` - // txs from the start. - let transfer_iter = store - .iter() - .rev() - .skip((page * page_size) as _) - .take(page_size as _); - - // The `and_then` here flattens the `StdResult>` to an `StdResult` - let transfers: StdResult> = transfer_iter - .map(|tx| tx.map(|tx| tx.into_humanized(api)).and_then(|x| x)) - .collect(); - transfers.map(|txs| (txs, store.len() as u64)) -} diff --git a/archived-contracts/snip20_staking/src/utils.rs b/archived-contracts/snip20_staking/src/utils.rs deleted file mode 100644 index ec153e6..0000000 --- a/archived-contracts/snip20_staking/src/utils.rs +++ /dev/null @@ -1,15 +0,0 @@ -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/archived-contracts/snip20_staking/src/viewing_key.rs b/archived-contracts/snip20_staking/src/viewing_key.rs deleted file mode 100644 index e0dff0b..0000000 --- a/archived-contracts/snip20_staking/src/viewing_key.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::fmt; - - -use shade_protocol::cosmwasm_schema::cw_serde; - -use shade_protocol::c_std::Env; - -use crate::{ - rand::{sha_256, 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, Clone, Debug)] -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, seed: &[u8], entropy: &[u8]) -> Self { - // 16 here represents the lengths in bytes of the block height and time. - let entropy_len = 16 + info.sender.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.0.as_bytes()); - rng_entropy.extend_from_slice(entropy); - - let mut rng = Prng::new(seed, &rng_entropy); - - let rand_slice = rng.rand_bytes(); - - let key = sha_256(&rand_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/archived_packages/network_integration/src/testnet_airdrop.rs b/archived_packages/network_integration/src/testnet_airdrop.rs deleted file mode 100644 index e69de29..0000000 diff --git a/contractlib/oraclelib.py b/contractlib/oraclelib.py deleted file mode 100644 index 819ec81..0000000 --- a/contractlib/oraclelib.py +++ /dev/null @@ -1,53 +0,0 @@ -import copy - -from .contractlib import Contract -from .secretlib import secretlib -import json - - -class Oracle(Contract): - def __init__(self, label, band_contract, sscrt, contract='oracle.wasm.gz', admin='a', uploader='a', backend='test', - instantiated_contract=None, code_id=None): - - init_msg = json.dumps({ - 'band': { - 'address': band_contract.address, - 'code_hash': band_contract.code_hash, - }, - 'sscrt': { - 'address': sscrt.address, - 'code_hash': sscrt.code_hash, - } - }) - - super().__init__(contract, init_msg, label, admin, uploader, backend, - instantiated_contract=instantiated_contract, code_id=code_id) - - def price(self, symbol): - """ - Get current coin price - :param symbol: Coin ticker - :return: - """ - msg = json.dumps({'price': {'symbol': symbol}}) - - return self.query(msg) - - def register_sswap_pair(self, pair): - msg = json.dumps({'pair': { - 'address': pair.address, - 'code_hash': pair.code_hash, - }}) - - return self.execute(msg) - - def register_index(self, symbol, basket: list): - msg = json.dumps({ - 'register_index': { - 'symbol': symbol, - 'basket': basket, - } - }) - print(msg) - - return self.execute(msg) diff --git a/contractlib/treasurylib.py b/contractlib/treasurylib.py deleted file mode 100644 index 2b8475d..0000000 --- a/contractlib/treasurylib.py +++ /dev/null @@ -1,72 +0,0 @@ -import copy - -from .contractlib import Contract -from .secretlib import secretlib -import json - - -class Treasury(Contract): - def __init__(self, label, viewing_key='password', contract='treasury.wasm.gz', admin='drpresident', uploader='drpresident', - backend='test', instantiated_contract=None, code_id=None): - init_msg = json.dumps({ - 'viewing_key': viewing_key, - }) - - super().__init__(contract, init_msg, label, admin, uploader, backend, - instantiated_contract=instantiated_contract, code_id=code_id) - - def get_balance(self, contract): - return self.query(json.dumps({ - 'get_balance': { - 'contract': contract.address, - } - }))['balance']['amount'] - - - def update_config(self, owner=None, native_asset=None, oracle=None): - """ - Updates the minting contract's config - :param owner: New admin - :param native_asset: Snip20 to Mint - :param oracle: Oracle contract - :return: Result - """ - raw_msg = {"update_config": {}} - if owner is not None: - raw_msg["update_config"]["owner"] = owner - if native_asset is not None: - contract = { - "address": native_asset.address, - "code_hash": native_asset.code_hash - } - raw_msg["update_config"]["native_asset"] = contract - if oracle is not None: - contract = { - "address": oracle.address, - "code_hash": oracle.code_hash - } - raw_msg["update_config"]["oracle"] = contract - - msg = json.dumps(raw_msg) - return self.execute(msg) - - def register_asset(self, snip20): - """ - Registers a SNIP20 asset - :param snip20: SNIP20 object to add - :return: Result - """ - msg = json.dumps( - {"register_asset": {"contract": {"address": snip20.address, "code_hash": snip20.code_hash}}}) - - return self.execute(msg) - - def get_config(self): - """ - Get the contracts config information - :return: Contract config info - """ - msg = json.dumps( - {"get_config": {}}) - - return self.query(msg) diff --git a/contracts/Staking Deep Dive b/contracts/Staking Deep Dive deleted file mode 100644 index fc4c9c1..0000000 --- a/contracts/Staking Deep Dive +++ /dev/null @@ -1 +0,0 @@ -UzV2zq1wL0osyPDNT0nNUTV2VTV2LsrPL4GwciucU3NyVI0MMlNUjV1UjYwMgFjVyA2HrCFY1qAgsSg1rwSLBiADYTaQg2Y1AA== \ No newline at end of file diff --git a/contracts/Staking Overview b/contracts/Staking Overview deleted file mode 100644 index 45f9123..0000000 --- a/contracts/Staking Overview +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contracts/admin/.cargo/config b/contracts/admin/.cargo/config deleted file mode 100644 index c1e7c50..0000000 --- a/contracts/admin/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" -schema = "run --example schema" \ No newline at end of file diff --git a/contracts/admin/.circleci/config.yml b/contracts/admin/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/contracts/admin/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/admin/Cargo.toml b/contracts/admin/Cargo.toml deleted file mode 100644 index 7f7c619..0000000 --- a/contracts/admin/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "admin" -version = "0.2.0" -authors = ["sbeem ", "scrtreddev "] -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -debug-print = ["shade-protocol/debug-print"] - -[dependencies] -# Needs to be in contract dependency in order for build to work -shade-protocol = { path = "../../packages/shade_protocol", default-features = false, features = ["admin_impl"] } -cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" } - -[dev-dependencies] -rstest = "0.15" -shade-protocol = { path = "../../packages/shade_protocol", features = ["multi-test"] } -shade-multi-test = { version = "0.1.0", path = "../../packages/multi_test", features = [ "admin" ] } diff --git a/contracts/admin/Makefile b/contracts/admin/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/contracts/admin/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/admin/README.md b/contracts/admin/README.md deleted file mode 100644 index 5bba3b6..0000000 --- a/contracts/admin/README.md +++ /dev/null @@ -1,127 +0,0 @@ -# Admin Auth Contract -* [Introduction](#Introduction) -* [Sections](#Sections) - * [Init](#Init) - * [Admin](#Admin) - * Messages - * [AddContract](#AddContract) - * [RemoveContract](#RemoveContract) - * [AddAuthorization](#AddAuthorization) - * [RemoveAuthorization](#RemoveAuthorization) - * [AddSuper](#AddSuper) - * [RemoveSuper](#RemoveSuper) - * [User](#User) - * Queries - * [GetSuperAdmins](#GetSuperAdmins) - * [GetContracts](#GetContracts) - * [GetAuthorizedUsers](#GetAuthorizedUsers) - * [ValidateAdminPermission](#ValidateAdminPermission) -# Introduction -This contract is used to centrally authorize the owners of a contracts. A contract can query the Shade Admin Contract to confirm whether the original caller has the relevant permissions against the calling contract. - -# Sections - -## Admin - -### Messages -#### AddContract -##### Request -Add a contract. -| Name | Type | Description | optional | -|------------------|--------|---------------------------------|----------| -| contract | String | Address of contract to be added | no | - -#### RemoveContract -##### Request -Remove a contract. -| Name | Type | Description | optional | -|------------------|--------|-----------------------------------|----------| -| contract | String | Address of contract to be removed | no | - -#### AddAuthorization -##### Request -Authorize a user with admin perms for the inputted contract. -| Name | Type | Description | optional | -|------------------|--------|----------------------------------------------|----------| -| contract | String | Address of contract | no | -| admin | String | Address of user to be given admin privileges | no | - -#### RemoveAuthorization -##### Request -Deauthorize a user for the inputted contract. -| Name | Type | Description | optional | -|------------------|--------|------------------------------------------|----------| -| contract | String | Address of contract | no | -| admin | String | Address of user to lose admin privileges | no | - -#### AddSuper -##### Request -Authorize a user to be given super-admin perms. -| Name | Type | Description | optional | -|---------------|--------|----------------------------------------------------|----------| -| super_address | String | Address of user to be given super-admin privileges | no | - -#### RemoveSuper -##### Request -Deauthorize a user from super-admin perms. -| Name | Type | Description | optional | -|---------------|--------|------------------------------------------------|----------| -| super_address | String | Address of user to lose super-admin privileges | no | - - -## User - -### Queries - -#### GetSuperAdmins -Gets a list of the super-admin addresses. -##### Response -```json -{ - "SuperAdminResponse": { - "super_admins": "Vector of strings of the super-admin addresses" - } -} -``` - -#### GetContracts -Gets a list of all of the contracts and the users' that have perms over them. -##### Response -```json -{ - "ContractsResponse": { - "contracts": "Vector containing tuples of the contract addresses and a vector of strings of the authorized users" - } -} -``` - -#### GetAuthorizedUsers -Gets a vector of strings of the users' that have perms for the inputted contract address. -##### Request -| Name | Type | Description | optional | -|------------------|--------|---------------------|----------| -| contract | String | Address of contract | no | -##### Response -```json -{ - "AuthorizedUsersResponse": { - "authorized_users": "Vector of strings of the authorized users", - } -} -``` - -#### ValidateAdminPermission -Determines if inputted admin has admin perms over contract. -##### Request -| Name | Type | Description | optional | -|------------------|--------|---------------------------------|----------| -| contract | String | Address of contract | no | -| admin | String | Address of user to be validated | no | -##### Response -```json -{ - "ValidateAdminPermissionResponse": { - "error_msg": "Option determining if user has perms", - } -} -``` \ No newline at end of file diff --git a/contracts/admin/src/contract.rs b/contracts/admin/src/contract.rs deleted file mode 100644 index 666d521..0000000 --- a/contracts/admin/src/contract.rs +++ /dev/null @@ -1,107 +0,0 @@ -use shade_protocol::{ - admin::{ - errors::unauthorized_super, AdminAuthStatus, AdminsResponse, ConfigResponse, ExecuteMsg, - InstantiateMsg, PermissionsResponse, QueryMsg, - }, - c_std::{ - shd_entry_point, to_binary, Addr, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, - StdResult, Storage, - }, - utils::pad_handle_result, -}; - -use crate::{ - execute::{ - try_self_destruct, try_toggle_status, try_transfer_super, try_update_registry, - try_update_registry_bulk, - }, - query::query_validate_permission, - shared::{ADMINS, PERMISSIONS, STATUS, SUPER}, -}; - -pub const RESPONSE_BLOCK_SIZE: usize = 256; - -#[cfg_attr(not(feature = "library"), shd_entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - let super_admin = msg.super_admin.unwrap_or_else(|| info.sender.to_string()); - let super_admin_addr = deps.api.addr_validate(super_admin.as_str())?; - SUPER.save(deps.storage, &super_admin_addr)?; - - ADMINS.save(deps.storage, &Vec::new())?; - STATUS.save(deps.storage, &AdminAuthStatus::Active)?; - - let res = Response::new() - .add_attribute("action", "initialized") - .add_attribute("superadmin", &info.sender); - Ok(res) -} - -#[cfg_attr(not(feature = "library"), shd_entry_point)] -pub fn execute( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> StdResult { - // Only the super user can execute anything on this contract. - is_super(deps.storage, &info.sender)?; - // Super user is assumed to have been verified by this point. - pad_handle_result( - match msg { - ExecuteMsg::UpdateRegistry { action } => { - try_update_registry(deps.storage, deps.api, action) - } - ExecuteMsg::UpdateRegistryBulk { actions } => try_update_registry_bulk(deps, actions), - ExecuteMsg::TransferSuper { new_super } => try_transfer_super(deps, new_super), - ExecuteMsg::SelfDestruct {} => try_self_destruct(deps), - ExecuteMsg::ToggleStatus { new_status } => try_toggle_status(deps, new_status), - }, - RESPONSE_BLOCK_SIZE, - ) -} - -fn is_super(storage: &dyn Storage, address: &Addr) -> StdResult<()> { - let super_admin = SUPER.load(storage)?; - if super_admin == *address { - Ok(()) - } else { - Err(unauthorized_super(address.as_str())) - } -} - -#[cfg_attr(not(feature = "library"), shd_entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - Ok(match msg { - QueryMsg::GetConfig {} => to_binary(&ConfigResponse { - super_admin: SUPER.load(deps.storage)?, - status: STATUS.load(deps.storage)?, - }), - QueryMsg::ValidateAdminPermission { permission, user } => { - to_binary(&query_validate_permission(deps, permission, user)?) - } - QueryMsg::GetAdmins {} => { - STATUS - .load(deps.storage)? - .not_shutdown()? - .not_under_maintenance()?; - to_binary(&AdminsResponse { - admins: ADMINS.load(deps.storage)?, - }) - } - QueryMsg::GetPermissions { user } => { - STATUS - .load(deps.storage)? - .not_shutdown()? - .not_under_maintenance()?; - let validated_user = deps.api.addr_validate(user.as_str())?; - to_binary(&PermissionsResponse { - permissions: PERMISSIONS.load(deps.storage, &validated_user)?, - }) - } - }?) -} diff --git a/contracts/admin/src/execute.rs b/contracts/admin/src/execute.rs deleted file mode 100644 index f23d22d..0000000 --- a/contracts/admin/src/execute.rs +++ /dev/null @@ -1,167 +0,0 @@ -use crate::shared::{validate_permissions, ADMINS, PERMISSIONS, STATUS, SUPER}; -use shade_protocol::admin::errors::{no_permission, unregistered_admin}; -use shade_protocol::admin::{AdminAuthStatus, RegistryAction}; -use shade_protocol::c_std::{Addr, Api, DepsMut, Response, StdResult, Storage}; - -/// Performs one registry update. Cannot be run during a shutdown. -pub fn try_update_registry( - store: &mut dyn Storage, - api: &dyn Api, - action: RegistryAction, -) -> StdResult { - STATUS.load(store)?.not_shutdown()?; - let mut admins = ADMINS.load(store)?; - resolve_registry_action(store, &mut admins, api, action)?; - ADMINS.save(store, &admins)?; - Ok(Response::default()) -} - -/// Performs bulk registry updates. Cannot be run during a shutdown. -pub fn try_update_registry_bulk( - deps: DepsMut, - actions: Vec, -) -> StdResult { - STATUS.load(deps.storage)?.not_shutdown()?; - let mut admins = ADMINS.load(deps.storage)?; - for action in actions { - resolve_registry_action(deps.storage, &mut admins, deps.api, action)?; - } - ADMINS.save(deps.storage, &admins)?; - Ok(Response::default()) -} - -pub fn try_transfer_super(deps: DepsMut, new_super: String) -> StdResult { - let valid_super = deps.api.addr_validate(new_super.as_str())?; - // If you're trying to transfer the super permissions to someone who hasn't been registered as an admin, - // it won't work. This is a safeguard. - let mut admins = ADMINS.load(deps.storage)?; - if !admins.contains(&valid_super) { - return Err(unregistered_admin(valid_super.as_str())); - } else { - // Update the super and remove them from the admin list. - SUPER.save(deps.storage, &valid_super)?; - delete_admin(deps.storage, &mut admins, deps.api, new_super)?; - ADMINS.save(deps.storage, &admins)?; - } - Ok(Response::default()) -} - -pub fn try_self_destruct(deps: DepsMut) -> StdResult { - // Clear permissions - let admins = ADMINS.load(deps.storage)?; - admins - .iter() - .for_each(|admin| PERMISSIONS.remove(deps.storage, admin)); - // Clear admins - ADMINS.save(deps.storage, &vec![])?; - // Disable contract - STATUS.save(deps.storage, &AdminAuthStatus::Shutdown)?; - Ok(Response::default()) -} - -pub fn try_toggle_status(deps: DepsMut, new_status: AdminAuthStatus) -> StdResult { - STATUS.update(deps.storage, |_| -> StdResult<_> { Ok(new_status) })?; - Ok(Response::default()) -} - -fn resolve_registry_action( - store: &mut dyn Storage, - admins: &mut Vec, - api: &dyn Api, - action: RegistryAction, -) -> StdResult<()> { - match action { - RegistryAction::RegisterAdmin { user } => register_admin(store, admins, api, user), - RegistryAction::GrantAccess { permissions, user } => { - grant_access(store, api, admins, permissions, user) - } - RegistryAction::RevokeAccess { permissions, user } => { - revoke_access(store, api, admins, permissions, user) - } - RegistryAction::DeleteAdmin { user } => delete_admin(store, admins, api, user), - }?; - Ok(()) -} - -fn register_admin( - store: &mut dyn Storage, - admins: &mut Vec, - api: &dyn Api, - user: String, -) -> StdResult<()> { - let user_addr = api.addr_validate(user.as_str())?; - if !admins.contains(&user_addr) { - // Create an empty permissions for them and add their address to the registered array. - admins.push(user_addr.clone()); - PERMISSIONS.save(store, &user_addr, &vec![])?; - }; - Ok(()) -} - -fn delete_admin( - store: &mut dyn Storage, - admins: &mut Vec, - api: &dyn Api, - user: String, -) -> StdResult<()> { - let user_addr = api.addr_validate(user.as_str())?; - if admins.contains(&user_addr) { - // Delete admin from list. - admins.retain(|x| x.ne(&user_addr)); - // Delete their permissions. - PERMISSIONS.remove(store, &user_addr); - }; - Ok(()) -} - -fn grant_access( - store: &mut dyn Storage, - api: &dyn Api, - admins: &[Addr], - mut permissions: Vec, - user: String, -) -> StdResult<()> { - let user = api.addr_validate(user.as_str())?; - validate_permissions(permissions.as_slice())?; - verify_registered(admins, &user)?; - PERMISSIONS.update(store, &user, |old_perms| -> StdResult<_> { - match old_perms { - Some(mut old_perms) => { - permissions.retain(|c| !old_perms.contains(c)); - old_perms.append(&mut permissions); - Ok(old_perms) - } - None => Err(no_permission(user.as_str())), - } - })?; - Ok(()) -} - -fn revoke_access( - store: &mut dyn Storage, - api: &dyn Api, - admins: &[Addr], - permissions: Vec, - user: String, -) -> StdResult<()> { - let user = api.addr_validate(user.as_str())?; - validate_permissions(permissions.as_slice())?; - verify_registered(admins, &user)?; - PERMISSIONS.update(store, &user, |old_perms| -> StdResult<_> { - match old_perms { - Some(mut old_perms) => { - old_perms.retain(|c| !permissions.contains(c)); - Ok(old_perms) - } - None => Err(no_permission(user.as_str())), - } - })?; - Ok(()) -} - -fn verify_registered(admins: &[Addr], user: &Addr) -> StdResult<()> { - if !admins.contains(user) { - return Err(no_permission(user.as_str())); - } - Ok(()) -} diff --git a/contracts/admin/src/lib.rs b/contracts/admin/src/lib.rs deleted file mode 100644 index 6258ab5..0000000 --- a/contracts/admin/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod contract; -pub mod execute; -pub mod query; -pub mod shared; -#[cfg(test)] -mod test; diff --git a/contracts/admin/src/query.rs b/contracts/admin/src/query.rs deleted file mode 100644 index 0d7b144..0000000 --- a/contracts/admin/src/query.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::shared::{is_valid_permission, PERMISSIONS, STATUS, SUPER}; -use shade_protocol::{ - admin::{errors::unregistered_admin, ValidateAdminPermissionResponse}, - c_std::{Deps, StdResult}, -}; - -/// Checks if the user has the requested permission. Permissions are case sensitive. -pub fn query_validate_permission( - deps: Deps, - permission: String, - user: String, -) -> StdResult { - STATUS - .load(deps.storage)? - .not_shutdown()? - .not_under_maintenance()?; - is_valid_permission(permission.as_str())?; - let valid_user = deps.api.addr_validate(user.as_str())?; - let super_admin = SUPER.load(deps.storage)?; - - let has_permission: bool; - - // Super admin has all permissions. The permissions don't need to have been created and assigned to the super admin beforehand. We do this because we assume that the super admin is secure (like a multi-sig or the main governance contract) so it would be a hassle to whitelist every permission we want them to have. - if valid_user == super_admin { - has_permission = true; - } else { - let permissions = PERMISSIONS.may_load(deps.storage, &valid_user)?; - match permissions { - Some(permissions) => { - if permissions.iter().any(|perm| permission.eq(perm)) { - has_permission = true; - } else { - has_permission = false; - } - } - None => return Err(unregistered_admin(valid_user.as_str())), - } - } - Ok(ValidateAdminPermissionResponse { has_permission }) -} diff --git a/contracts/admin/src/shared.rs b/contracts/admin/src/shared.rs deleted file mode 100644 index cb73a4b..0000000 --- a/contracts/admin/src/shared.rs +++ /dev/null @@ -1,35 +0,0 @@ -use shade_protocol::c_std::Addr; -use shade_protocol::utils::storage::plus::{Item, Map}; -use shade_protocol::{ - admin::{errors::invalid_permission_format, AdminAuthStatus}, - c_std::StdResult, -}; - -/// Maps user to permissions for which they have user. -pub const PERMISSIONS: Map<&Addr, Vec> = Map::new("permissions"); -/// List of all admins. -pub const ADMINS: Item> = Item::new("admins"); -/// Super user. -pub const SUPER: Item = Item::new("super"); -/// Whether or not this contract can be consumed. -pub const STATUS: Item = Item::new("is_active"); - -pub fn validate_permissions(permissions: &[String]) -> StdResult<()> { - for permission in permissions { - is_valid_permission(permission.as_str())?; - } - Ok(()) -} - -pub fn is_valid_permission(permission: &str) -> StdResult<()> { - if permission.len() <= 10 { - return Err(invalid_permission_format(permission)); - } - let valid_chars = permission.bytes().all(|byte| { - (b'A'..=b'Z').contains(&byte) || (b'0'..=b'9').contains(&byte) || b'_'.eq(&byte) - }); - if !valid_chars { - return Err(invalid_permission_format(permission)); - } - Ok(()) -} diff --git a/contracts/admin/src/test.rs b/contracts/admin/src/test.rs deleted file mode 100644 index fa6eb5c..0000000 --- a/contracts/admin/src/test.rs +++ /dev/null @@ -1,351 +0,0 @@ -use crate::shared::is_valid_permission; -use rstest::*; -use shade_multi_test::multi::admin::Admin; -use shade_protocol::{ - admin::{ - AdminAuthStatus, AdminsResponse, ConfigResponse, ExecuteMsg, InstantiateMsg, - PermissionsResponse, QueryMsg, RegistryAction, ValidateAdminPermissionResponse, - }, - c_std::Addr, - multi_test::App, - utils::{ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -#[rstest] -#[case("VAULT", false)] -#[case("test", false)] -#[case("VAULT_", false)] -#[case("VAULT_TARGET", true)] -#[case("VAULT_TARG3T_2", true)] -#[case("", false)] -#[case("*@#$*!*#!#!#****", false)] -#[case("VAULT_TARGET_addr", false)] -fn test_is_valid_permission(#[case] permission: String, #[case] is_valid: bool) { - let resp = is_valid_permission(permission.as_str()); - if is_valid { - assert!(resp.is_ok()); - } else { - assert!(resp.is_err()); - } -} - -#[rstest] -#[case(AdminAuthStatus::Active, vec![true, true, true, false, true, true, true])] -#[case(AdminAuthStatus::Maintenance, vec![true, true, true, false, true, true, true])] -#[case(AdminAuthStatus::Shutdown, vec![false, false, false, false, false, false, true])] -fn test_status(#[case] status: AdminAuthStatus, #[case] expect_success: Vec) { - //init - let mut chain: App = App::default(); - let contract = InstantiateMsg { super_admin: None } - .test_init( - Admin::default(), - &mut chain, - Addr::unchecked("admin"), - "admin_contract", - &[], - ) - .unwrap(); - //set state - ExecuteMsg::ToggleStatus { new_status: status } - .test_exec(&contract, &mut chain, Addr::unchecked("admin"), &[]) - .unwrap(); - - //register 'super' as admin - let action = RegistryAction::RegisterAdmin { - user: "super".to_string(), - }; - let result = ExecuteMsg::UpdateRegistry { - action: action.clone(), - } - .test_exec(&contract, &mut chain, Addr::unchecked("admin"), &[]); - assert_eq!(&result.is_ok(), expect_success.get(0).unwrap()); - - //test bulk update - let actions = vec![action.clone()]; - let result = ExecuteMsg::UpdateRegistryBulk { actions }.test_exec( - &contract, - &mut chain, - Addr::unchecked("admin"), - &[], - ); - assert_eq!(&result.is_ok(), expect_success.get(1).unwrap()); - - //set super admin to 'super' - let result = ExecuteMsg::TransferSuper { - new_super: "super".to_string(), - } - .test_exec(&contract, &mut chain, Addr::unchecked("admin"), &[]); - assert_eq!(&result.is_ok(), expect_success.get(2).unwrap()); - - //register 'admin' as admin without being the super user - let action = RegistryAction::RegisterAdmin { - user: "admin".to_string(), - }; - let result = ExecuteMsg::UpdateRegistry { - action: action.clone(), - } - .test_exec(&contract, &mut chain, Addr::unchecked("admin"), &[]); - assert_eq!(&result.is_ok(), expect_success.get(3).unwrap()); - - //register admin as admin with correct permissions - let action = RegistryAction::RegisterAdmin { - user: "admin".to_string(), - }; - let result = ExecuteMsg::UpdateRegistry { - action: action.clone(), - } - .test_exec(&contract, &mut chain, Addr::unchecked("super"), &[]); - assert_eq!(&result.is_ok(), expect_success.get(4).unwrap()); - - //set super admin to 'admin' - let result = ExecuteMsg::TransferSuper { - new_super: "admin".to_string(), - } - .test_exec(&contract, &mut chain, Addr::unchecked("super"), &[]); - assert_eq!(&result.is_ok(), expect_success.get(5).unwrap()); - - //self destruct - let result = - ExecuteMsg::SelfDestruct {}.test_exec(&contract, &mut chain, Addr::unchecked("admin"), &[]); - assert_eq!(&result.is_ok(), expect_success.get(6).unwrap()); -} - -#[rstest] -#[case(vec!["test", "blah"], vec!["test", "blah"], vec![false, false])] -#[case(vec!["test", "blah", "aaaa", "bbbb", "cccc"], vec!["test", "bbbb"], vec![false, true, true, false, true])] -fn test_admin( - #[case] admins_to_add: Vec<&str>, - #[case] admins_to_remove: Vec<&str>, - #[case] expected_in_final_admins: Vec, -) { - //init - let mut chain = App::default(); - - let admin_contract = InstantiateMsg { super_admin: None } - .test_init( - Admin::default(), - &mut chain, - Addr::unchecked("admin"), - "admin_contract", - &[], - ) - .unwrap(); - - //check config - let config: ConfigResponse = QueryMsg::GetConfig {} - .test_query(&admin_contract, &chain) - .unwrap(); - assert_eq!(config.super_admin.as_str(), "admin"); - assert_eq!(config.status, AdminAuthStatus::Active); - - //read admins - let response: AdminsResponse = QueryMsg::GetAdmins {} - .test_query(&admin_contract, &chain) - .unwrap(); - assert!(response.admins.is_empty()); - - //add admins - for admin in admins_to_add.iter() { - ExecuteMsg::UpdateRegistry { - action: RegistryAction::RegisterAdmin { - user: admin.to_string(), - }, - } - .test_exec(&admin_contract, &mut chain, Addr::unchecked("admin"), &[]) - .unwrap(); - } - - //read admins - let response: AdminsResponse = QueryMsg::GetAdmins {} - .test_query(&admin_contract, &chain) - .unwrap(); - let admin_list = response.admins; - let admin_list_str: Vec = admin_list.into_iter().map(|x| x.to_string()).collect(); - for admin in admins_to_add.iter() { - assert!(admin_list_str.contains(&admin.to_string())); - } - - //remove some admins - for admin in admins_to_remove.iter() { - ExecuteMsg::UpdateRegistry { - action: RegistryAction::DeleteAdmin { - user: admin.to_string(), - }, - } - .test_exec(&admin_contract, &mut chain, Addr::unchecked("admin"), &[]) - .unwrap(); - } - - //read admins - let response: AdminsResponse = QueryMsg::GetAdmins {} - .test_query(&admin_contract, &chain) - .unwrap(); - let admin_list = response.admins; - let admin_list_str: Vec = admin_list.into_iter().map(|x| x.to_string()).collect(); - for (i, admin) in admins_to_add.iter().enumerate() { - assert_eq!( - &admin_list_str.contains(&admin.to_string()), - expected_in_final_admins.get(i).unwrap() - ); - } - - //remove all admins with batch - let mut actions = vec![]; - for admin in &admins_to_add { - actions.push(RegistryAction::DeleteAdmin { - user: admin.to_string(), - }); - } - - ExecuteMsg::UpdateRegistryBulk { actions } - .test_exec(&admin_contract, &mut chain, Addr::unchecked("admin"), &[]) - .unwrap(); - - //read admins - let response: AdminsResponse = QueryMsg::GetAdmins {} - .test_query(&admin_contract, &chain) - .unwrap(); - let admin_list = response.admins; - let admin_list_str: Vec = admin_list.into_iter().map(|x| x.to_string()).collect(); - for admin in &admins_to_add { - assert_eq!(&admin_list_str.contains(&admin.to_string()), &false); - } -} - -#[rstest] -#[case( -vec![ -("user", vec!["SOME_TARGET"]), -("places", vec!["PLACE_SAN_JUAN", "PLACE_NEW_YORK", "PLACE_CAPRI_ISLAND"]), -("not_admin", vec!["SOME_TARGET_ONE", "SOME_TARGET_TWO", "TARGET_THREE"]) -], -vec![ -("places", vec!["PLACE_NEW_YORK"]), -("not_admin", vec!["SOME_TARGET_ONE", "TARGET_THREE"]), -("user", vec!["SOME_TARGET"]), -] -)] -fn test_permissions( - #[case] permissions: Vec<(&str, Vec<&str>)>, - #[case] revoke_permissions: Vec<(&str, Vec<&str>)>, -) { - let mut chain = App::default(); - - let admin = InstantiateMsg { super_admin: None } - .test_init( - Admin::default(), - &mut chain, - Addr::unchecked("admin"), - "admin_contract", - &[], - ) - .unwrap(); - - let mut actions = vec![]; - for permission in permissions.iter() { - actions.append(&mut vec![ - RegistryAction::RegisterAdmin { - user: permission.0.to_string(), - }, - RegistryAction::GrantAccess { - permissions: permission.1.iter().map(|&i| i.to_string()).collect(), - user: permission.0.to_string(), - }, - ]) - } - - // Check that only super admin chan do this - assert!(ExecuteMsg::UpdateRegistryBulk { - actions: actions.clone() - } - .test_exec(&admin, &mut chain, Addr::unchecked("user"), &[]) - .is_err()); - - assert!(ExecuteMsg::UpdateRegistryBulk { actions } - .test_exec(&admin, &mut chain, Addr::unchecked("admin"), &[]) - .is_ok()); - - // Confirm that all permissions are set - for permission in permissions.iter() { - // Check that the permissions are correctly returned - let stored_permissions: PermissionsResponse = QueryMsg::GetPermissions { - user: permission.0.to_string(), - } - .test_query(&admin, &chain) - .unwrap(); - - assert_eq!(stored_permissions.permissions.len(), permission.1.len()); - for perm in permission.1.iter() { - assert!(stored_permissions.permissions.contains(&perm.to_string())); - - // Check that no other permission is "accepted" - let res: ValidateAdminPermissionResponse = QueryMsg::ValidateAdminPermission { - permission: perm.to_string(), - user: permission.0.to_string(), - } - .test_query(&admin, &chain) - .unwrap(); - assert!(res.has_permission); - } - } - - // Remove permissions - let revoke_actions: Vec = revoke_permissions - .iter() - .map(|permission| RegistryAction::RevokeAccess { - permissions: permission.1.iter().map(|&item| item.to_string()).collect(), - user: permission.0.to_string(), - }) - .collect(); - - assert!(ExecuteMsg::UpdateRegistryBulk { - actions: revoke_actions.clone() - } - .test_exec(&admin, &mut chain, Addr::unchecked("user"), &[]) - .is_err()); - - assert!(ExecuteMsg::UpdateRegistryBulk { - actions: revoke_actions - } - .test_exec(&admin, &mut chain, Addr::unchecked("admin"), &[]) - .is_ok()); - - for permission in permissions.iter() { - // Check that the permissions are correctly returned - let stored_permissions: PermissionsResponse = QueryMsg::GetPermissions { - user: permission.0.to_string(), - } - .test_query(&admin, &chain) - .unwrap(); - - for perm in permission.1.iter() { - let mut assertion: Option = None; - for p in revoke_permissions.iter() { - if p.0 == permission.0 { - assertion = Some(!p.1.contains(perm)); - - assert_eq!( - stored_permissions.permissions.len(), - permission.1.len().wrapping_sub(p.1.len()) - ); - break; - } - } - assert!(assertion.is_some(), "Never found the required item"); - - assert_eq!( - stored_permissions.permissions.contains(&perm.to_string()), - assertion.unwrap() - ); - - // Check that no other permission is "accepted" - let res: ValidateAdminPermissionResponse = QueryMsg::ValidateAdminPermission { - permission: perm.to_string(), - user: permission.0.to_string(), - } - .test_query(&admin, &chain) - .unwrap(); - assert_eq!(res.has_permission, assertion.unwrap()); - } - } -} diff --git a/contracts/airdrop/Cargo.toml b/contracts/airdrop/Cargo.toml index 29518b2..d05f744 100644 --- a/contracts/airdrop/Cargo.toml +++ b/contracts/airdrop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "airdrop" -version = "0.1.0" +version = "0.1.1" authors = ["Guy Garcia "] edition = "2018" @@ -23,9 +23,8 @@ backtraces = ["shade-protocol/backtraces"] debug-print = ["shade-protocol/debug-print"] [dependencies] -ethereum-verify = { version = "0.1.0", path = "../../packages/ethereum_veri"} +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["airdrop"] } +ethereum-verify = { version = "0.1.0", path = "../../packages/ethereum_verify"} hex = "0.4" -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ - "airdrop" -] } +sha2 = { version = "0.10.2", default-features = false } rs_merkle = { git = "https://github.com/FloppyDisck/rs-merkle", branch = "node_export" } \ No newline at end of file diff --git a/contracts/airdrop/src/contract.rs b/contracts/airdrop/src/contract.rs index 12fdef8..cb74e9b 100644 --- a/contracts/airdrop/src/contract.rs +++ b/contracts/airdrop/src/contract.rs @@ -1,38 +1,20 @@ use crate::{ handle::{ try_account, - try_add_tasks, try_claim, - try_claim_decay, - try_complete_task, try_disable_permit_key, try_set_viewing_key, try_update_config, + // try_claim_decay, }, query, state::{config_w, decay_claimed_w, total_claimed_w}, }; use shade_protocol::{ - airdrop::{ - claim_info::RequiredTask, - errors::{invalid_dates, invalid_task_percentage}, - Config, - ExecuteMsg, - InstantiateMsg, - QueryMsg, - }, + airdrop::{errors::invalid_dates, Config, ExecuteMsg, InstantiateMsg, QueryMsg}, c_std::{ - shd_entry_point, - to_binary, - Binary, - Deps, - DepsMut, - Env, - MessageInfo, - Response, - StdError, - StdResult, - Uint128, + shd_entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, + StdResult, Uint128, }, utils::{pad_handle_result, pad_query_result}, }; @@ -47,24 +29,6 @@ pub fn instantiate( info: MessageInfo, msg: InstantiateMsg, ) -> StdResult { - // Setup task claim - // let mut task_claim = vec![RequiredTask { - // address: env.contract.address.clone(), - // percent: msg.default_claim, - // }]; - // let mut claim = msg.task_claim; - // task_claim.append(&mut claim); - - // // Validate claim percentage - // let mut count = Uint128::zero(); - // for claim in task_claim.iter() { - // count += claim.percent; - // } - - // if count > Uint128::new(100u128) { - // return Err(invalid_task_percentage(count.to_string().as_str())); - // } - let start_date = match msg.start_date { None => env.block.time.seconds(), Some(date) => date, @@ -113,17 +77,15 @@ pub fn instantiate( contract: env.contract.address, dump_address: msg.dump_address, airdrop_snip20: msg.airdrop_token.clone(), - airdrop_snip20_optional: msg.airdrop_token_optional.clone(), + airdrop_snip20_optional: msg.airdrop_snip20_optional.clone(), airdrop_amount: msg.airdrop_amount, - // task_claim, start_date, end_date: msg.end_date, - // decay_start: msg.decay_start, + decay_start: msg.decay_start, merkle_root: msg.merkle_root, total_accounts: msg.total_accounts, - max_amount: msg.max_amount, - query_rounding: msg.query_rounding, claim_msg_plaintext: msg.claim_msg_plaintext, + query_rounding: msg.query_rounding, }; config_w(deps.storage).save(&config)?; @@ -131,6 +93,7 @@ pub fn instantiate( // Initialize claim amount total_claimed_w(deps.storage).save(&Uint128::zero())?; + // clawback function?? decay_claimed_w(deps.storage).save(&false)?; Ok(Response::new()) @@ -146,7 +109,6 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S query_rounding: redeem_step_size, start_date, end_date, - decay_start: start_decay, .. } => try_update_config( deps, @@ -157,22 +119,31 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S redeem_step_size, start_date, end_date, - start_decay, ), - // ExecuteMsg::AddTasks { tasks, .. } => try_add_tasks(deps, &env, &info, tasks), - // ExecuteMsg::CompleteTask { address, .. } => { - // try_complete_task(deps, &env, &info, address) - // } - // ExecuteMsg::Account { - // addresses, - // partial_tree, - // .. - // } => try_account(deps, &env, &info, addresses, partial_tree), + ExecuteMsg::Account { + eth_pubkey, + amount, + addresses, + .. + } => try_account( + deps, + &env, + &info, + eth_pubkey, + amount.unwrap_or_default(), + addresses, + ), ExecuteMsg::DisablePermitKey { key, .. } => { try_disable_permit_key(deps, &env, &info, key) } ExecuteMsg::SetViewingKey { key, .. } => try_set_viewing_key(deps, &env, &info, key), - ExecuteMsg::Claim { amount, eth_pubkey, eth_sig, proof, .. } => try_claim(deps, &env, &info, amount, eth_pubkey, eth_sig, proof), + ExecuteMsg::Claim { + amount, + eth_pubkey, + eth_sig, + proof, + .. + } => try_claim(deps, &env, &info, amount, eth_pubkey, eth_sig, proof), // ExecuteMsg::ClaimDecay { .. } => try_claim_decay(deps, &env, &info), }, RESPONSE_BLOCK_SIZE, @@ -186,16 +157,15 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::Config {} => to_binary(&query::config(deps)?), QueryMsg::Dates { current_date } => to_binary(&query::dates(deps, current_date)?), QueryMsg::TotalClaimed {} => to_binary(&query::total_claimed(deps)?), - // QueryMsg::Account { - // permit, - // current_date, - // } => to_binary(&query::account(deps, permit, current_date)?), + QueryMsg::Account { permit, eth_pubkey } => { + to_binary(&query::account(deps, permit, eth_pubkey)?) + } QueryMsg::AccountWithKey { account, key, - current_date, - } => to_binary(&query::account_with_key(deps, account, key, current_date)?), + eth_pubkey, + } => to_binary(&query::account_with_key(deps, account, key, eth_pubkey)?), }, RESPONSE_BLOCK_SIZE, ) -} \ No newline at end of file +} diff --git a/contracts/airdrop/src/handle.rs b/contracts/airdrop/src/handle.rs index b1c5a2c..cf2cad8 100644 --- a/contracts/airdrop/src/handle.rs +++ b/contracts/airdrop/src/handle.rs @@ -1,7 +1,7 @@ use crate::state::{ account_r, - account_total_claimed_r, - account_total_claimed_w, + // account_total_claimed_r, + // account_total_claimed_w, account_viewkey_w, account_w, address_in_account_w, @@ -10,54 +10,36 @@ use crate::state::{ config_r, config_w, decay_claimed_w, + eth_pubkey_claim_r, + eth_pubkey_claim_w, + eth_pubkey_in_account_w, revoke_permit, total_claimed_r, total_claimed_w, validate_address_permit, }; -use rs_merkle::{algorithms::Sha256, Hasher, MerkleProof}; +use hex::decode_to_slice; +use rs_merkle::{algorithms::Sha256, Hasher}; +use sha2::Digest; use shade_protocol::{ airdrop::{ account::{Account, AccountKey, AddressProofMsg, AddressProofPermit}, - claim_info::RequiredTask, errors::{ - account_does_not_exist, - address_already_in_account, - airdrop_ended, - airdrop_not_started, - claim_too_high, - decay_claimed, - decay_not_set, - expected_memo, - invalid_dates, - invalid_partial_tree, - invalid_task_percentage, - not_admin, - nothing_to_claim, - unexpected_error, + account_does_not_exist, address_already_in_account, airdrop_ended, airdrop_not_started, + already_claimed, decay_claimed, decay_not_set, expected_memo, failed_verification, + invalid_dates, not_admin, nothing_to_claim, unexpected_error, wrong_length, }, - Config, - ExecuteAnswer, + Config, ExecuteAnswer, }, c_std::{ - from_binary, - to_binary, - Addr, - Api, - Binary, - Decimal, - DepsMut, - Env, - MessageInfo, - Response, - StdResult, - Storage, - Uint128, + ensure_eq, from_binary, to_binary, Addr, Api, Binary, Decimal, DepsMut, Env, MessageInfo, + Response, StdResult, Storage, Uint128, }, query_authentication::viewing_keys::ViewingKey, snip20::helpers::send_msg, utils::generic_response::{ResponseStatus, ResponseStatus::Success}, }; +use std::{convert::TryInto, fmt::Write}; #[allow(clippy::too_many_arguments)] pub fn try_update_config( @@ -69,7 +51,6 @@ pub fn try_update_config( query_rounding: Option, start_date: Option, end_date: Option, - decay_start: Option, ) -> StdResult { let config = config_r(deps.storage).load()?; // Check if admin @@ -112,228 +93,123 @@ pub fn try_update_config( )); } } - if let Some(start_decay) = decay_start { - if start_date > start_decay { - return Err(invalid_dates( - "Decay", - start_decay.to_string().as_str(), - "before", - "StartDate", - start_date.to_string().as_str(), - )); - } - } else if let Some(start_decay) = state.decay_start { - if start_date > start_decay { - return Err(invalid_dates( - "Decay", - start_decay.to_string().as_str(), - "before", - "StartDate", - start_date.to_string().as_str(), - )); - } - } state.start_date = start_date; } if let Some(end_date) = end_date { // Avoid date collisions - if let Some(decay_start) = decay_start { - if decay_start > end_date { - return Err(invalid_dates( - "EndDate", - end_date.to_string().as_str(), - "before", - "Decay", - decay_start.to_string().as_str(), - )); - } - } else if let Some(decay_start) = state.decay_start { - if decay_start > end_date { - return Err(invalid_dates( - "EndDate", - end_date.to_string().as_str(), - "before", - "Decay", - decay_start.to_string().as_str(), - )); - } - } - state.end_date = Some(end_date); } - if decay_start.is_some() { - state.decay_start = decay_start - } Ok(state) })?; Ok(Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { status: Success })?)) } -// pub fn try_add_tasks( -// deps: DepsMut, -// _env: &Env, -// info: &MessageInfo, -// tasks: Vec, -// ) -> StdResult { -// let config = config_r(deps.storage).load()?; -// // Check if admin -// if info.sender != config.admin { -// return Err(not_admin(config.admin.as_str())); -// } - -// config_w(deps.storage).update(|mut config| { -// let mut task_list = tasks; -// config.task_claim.append(&mut task_list); - -// //Validate that they do not exceed 100 -// let mut count = Uint128::zero(); -// for task in config.task_claim.iter() { -// count += task.percent; -// } - -// if count > Uint128::new(100u128) { -// return Err(invalid_task_percentage(count.to_string().as_str())); -// } - -// Ok(config) -// })?; - -// Ok(Response::new().set_data(to_binary(&ExecuteAnswer::AddTask { status: Success })?)) -// } - -// pub fn try_account( -// deps: DepsMut, -// env: &Env, -// info: &MessageInfo, -// addresses: Vec, -// partial_tree: Vec, -// ) -> StdResult { -// // Check if airdrop active -// let config = config_r(deps.storage).load()?; - -// // Check that airdrop hasn't ended -// available(&config, env)?; - -// // Setup account -// let sender = info.sender.to_string(); - -// // These variables are setup to facilitate updating -// let updating_account: bool; -// let old_claim_amount: Uint128; - -// let mut account = match account_r(deps.storage).may_load(sender.as_bytes())? { -// None => { -// updating_account = false; -// old_claim_amount = Uint128::zero(); -// let mut account = Account::default(); - -// // Validate permits -// try_add_account_addresses( -// deps.storage, -// deps.api, -// &config, -// &info.sender, -// &mut account, -// addresses.clone(), -// partial_tree.clone(), -// )?; - -// // Add default claim at index 0 -// account_total_claimed_w(deps.storage).save(sender.as_bytes(), &Uint128::zero())?; -// claim_status_w(deps.storage, 0).save(sender.as_bytes(), &false)?; - -// account -// } -// Some(acc) => { -// updating_account = true; -// old_claim_amount = acc.total_claimable; -// acc -// } -// }; - -// // Claim airdrop -// let mut messages = vec![]; - -// // let (completed_percentage, unclaimed_percentage) = -// // update_tasks(deps.storage, &config, sender.clone())?; - -// let - -// redeem_amount = claim_tokens( -// deps.storage, -// env, -// info, -// &config, -// &account, -// amount -// )?; - - -// // Update account after claim to calculate difference -// if updating_account { -// // Validate permits -// try_add_account_addresses( -// deps.storage, -// deps.api, -// &config, -// &info.sender, -// &mut account, -// addresses.clone(), -// partial_tree.clone(), -// )?; -// } - -// if updating_account && completed_percentage > Uint128::zero() { -// // Calculate the total new address amount -// let added_address_total = account.total_claimable.checked_sub(old_claim_amount)?; -// account_total_claimed_w(deps.storage).update(sender.as_bytes(), |claimed| { -// if let Some(claimed) = claimed { -// let new_redeem: Uint128; -// if completed_percentage == Uint128::new(100u128) { -// new_redeem = -// added_address_total * decay_factor(env.block.time.seconds(), &config); -// } else { -// new_redeem = completed_percentage -// .multiply_ratio(added_address_total, Uint128::new(100u128)) -// * decay_factor(env.block.time.seconds(), &config); -// } - -// redeem_amount += new_redeem; -// Ok(claimed + new_redeem) -// } else { -// Err(unexpected_error()) -// } -// })?; -// } - -// if redeem_amount > Uint128::zero() { -// total_claimed_w(deps.storage) -// .update(|claimed| -> StdResult { Ok(claimed + redeem_amount) })?; - -// messages.push(send_msg( -// info.sender.clone(), -// redeem_amount.into(), -// None, -// None, -// None, -// &config.airdrop_snip20, -// )?); -// } - -// // Save account -// account_w(deps.storage).save(sender.as_bytes(), &account)?; - -// Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Account { -// status: ResponseStatus::Success, -// total: account.total_claimable, -// claimed: account_total_claimed_r(deps.storage).load(sender.to_string().as_bytes())?, -// // Will always be 0 since rewards are automatically claimed here -// finished_tasks: finished_tasks(deps.storage, sender.clone())?, -// addresses: account.addresses, -// })?)) -// } +pub fn try_account( + deps: DepsMut, + env: &Env, + info: &MessageInfo, + eth_pubkey: String, + amount: Uint128, + addresses: Vec, +) -> StdResult { + // Check if airdrop active + let config = config_r(deps.storage).load()?; + + // Check that airdrop hasn't ended + available(&config, env)?; + + // Setup account by cosmos signer + let sender = info.sender.to_string(); + + // These variables are setup to facilitate updating + let updating_account: bool; + + // define the msg senders account + let mut account: Account = match account_r(deps.storage).may_load(sender.clone().as_bytes())? { + None => { + updating_account = false; + // setup a new account with addresses & eth_pubkey + let mut account = Account::default(); + + // Validate permits + try_add_account_addresses( + deps.storage, + deps.api, + &config, + &info.sender, + &mut account, + addresses.clone(), + eth_pubkey.clone(), + )?; + + // we setup an unchecked eth_pubkey for now. We will verify this eth_pubkey during + // the claim msg, and will update to verified eth_pubkey. + + // sets the accounts eth_pubkey claim status to false. note we always check claim function when checking + // the signed msg with the stored address, never both or isolated; + // (in order for this function to be called, sender.clone was required for reading the account details, + // preventing the ability to determine if a eth_pubkey not yours has claimed)* + claim_status_w(deps.storage, 0).save(account.eth_pubkey.as_bytes(), &false)?; + + account + } + Some(acc) => { + updating_account = true; + acc + } + }; + + // Claim airdrop + let mut messages = vec![]; + let mut redeem_amount = Uint128::zero(); + + if account.claimed == false { + redeem_amount = claim_tokens(deps.storage, ð_pubkey, &amount)?; + } else { + return Err(nothing_to_claim()); + } + + // Update account after claim to calculate difference, + // and to save eth_pubkey as saved to the contract state. + if updating_account { + // Validate permits + try_add_account_addresses( + deps.storage, + deps.api, + &config, + &info.sender, + &mut account, + addresses.clone(), + eth_pubkey, + )?; + } + + total_claimed_w(deps.storage) + .update(|claimed| -> StdResult { Ok(claimed + redeem_amount) })?; + + messages.push(send_msg( + info.sender.clone(), + redeem_amount.into(), + None, + None, + None, + &config.airdrop_snip20, + )?); + + // Save account + account_w(deps.storage).save(sender.as_bytes(), &account)?; + claim_status_w(deps.storage, 0).save(account.eth_pubkey.as_bytes(), &true)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Account { + status: ResponseStatus::Success, + claimed: eth_pubkey_claim_r(deps.storage) + .load(account.eth_pubkey.to_string().as_bytes())?, + addresses: account.addresses, + eth_pubkey: account.eth_pubkey, + })?)) +} pub fn try_disable_permit_key( deps: DepsMut, @@ -366,33 +242,7 @@ pub fn try_set_viewing_key( ) } -// pub fn try_complete_task( -// deps: DepsMut, -// _env: &Env, -// info: &MessageInfo, -// account: Addr, -// ) -> StdResult { -// let config = config_r(deps.storage).load()?; - -// for (i, task) in config.task_claim.iter().enumerate() { -// if task.address == info.sender { -// claim_status_w(deps.storage, i).update( -// account.to_string().as_bytes(), -// |status| -> StdResult { -// // If there was a state then ignore -// if let Some(status) = status { -// Ok(status) -// } else { -// Ok(false) -// } -// }, -// )?; -// } -// } - -// Ok(Response::new().set_data(to_binary(&ExecuteAnswer::CompleteTask { status: Success })?)) -// } - +// claim an airdrop, privately. Must have created an account & signing keys prior pub fn try_claim( deps: DepsMut, env: &Env, @@ -402,56 +252,61 @@ pub fn try_claim( eth_sig: String, proof: Vec, ) -> StdResult { - let config = config_r(deps.storage).load()?; + let config: Config = config_r(deps.storage).load()?; // Check that airdrop hasn't ended available(&config, env)?; - // Validate eth_signature - validation::validate_claim( &deps, + // Get account from the msg sender, restricting access to query account via eth_pubkey + // as well as verify eth sig was generated with account.eth_pubkey + let sender = info.sender.clone(); + let account = account_r(deps.storage).load(sender.clone().as_bytes())?; + + // validate eth_signature + validation::validate_claim( + &deps, info.clone(), - eth_pubkey.clone(), + account.eth_pubkey.clone(), // uses the saved account eth_pubkey eth_sig, config.clone(), )?; - // Get account - let sender = info.sender.clone(); - // let account = account_r(deps.storage).load(sender.to_string().as_bytes())?; - - - // Calculate airdrop - // let (completed_percentage, unclaimed_percentage) = - // update_tasks(deps.storage, &config, sender.to_string())?; - - // if unclaimed_percentage == Uint128::zero() { - // return Err(nothing_to_claim()); - // } - - let redeem_amount = claim_tokens( - deps.storage, - env, - info, - &config, - &sender, - &amount, - // completed_percentage, - // unclaimed_percentage, - )?; + // generate merkleTree leaf with eth_pubkey & amount + let user_input = format!("{}{}", account.eth_pubkey, amount); + let hash = sha2::Sha256::digest(user_input.as_bytes()) + .as_slice() + .try_into() + .map_err(|_| nothing_to_claim())?; + + let hash: [u8; 32] = proof.into_iter().try_fold(hash, |hash, p| { + let mut proof_buf = [0; 32]; + hex::decode_to_slice(p, &mut proof_buf).unwrap(); + let mut hashes = [hash, proof_buf]; + hashes.sort_unstable(); + sha2::Sha256::digest(&hashes.concat()) + .as_slice() + .try_into() + .map_err(|_| wrong_length()) + })?; + + let merkle = config.merkle_root.clone(); + let mut root_buf: [u8; 32] = [0; 32]; + decode_to_slice(merkle, &mut root_buf).unwrap(); + ensure_eq!(root_buf, hash, failed_verification()); - // let redeem_amount = amount.clone(); + // check if eth_pubkey has already claimed + let redeem_amount = claim_tokens(deps.storage, ð_pubkey, &amount)?; - // If we want to have 100% allocation claim in 1tx, redeem_amount = amount.clone(); + // update global claimed amount total_claimed_w(deps.storage) .update(|claimed| -> StdResult { Ok(claimed + redeem_amount) })?; Ok(Response::new() .set_data(to_binary(&ExecuteAnswer::Claim { status: ResponseStatus::Success, - total: account.total_claimable, - claimed: account_total_claimed_r(deps.storage).load(sender.to_string().as_bytes())?, - // finished_tasks: finished_tasks(deps.storage, sender.to_string())?, + claimed: eth_pubkey_claim_r(deps.storage).load(eth_pubkey.as_bytes())?, addresses: account.addresses, + eth_pubkey, })?) .add_message(send_msg( sender.clone(), @@ -467,216 +322,134 @@ pub fn try_claim( None, None, None, - &config.airdrop_snip20, + &config.airdrop_snip20_optional, )?)) } -// pub fn try_claim_decay(deps: DepsMut, env: &Env, _info: &MessageInfo) -> StdResult { -// let config = config_r(deps.storage).load()?; - -// // Check if airdrop ended -// if let Some(end_date) = config.end_date { -// if let Some(dump_address) = config.dump_address { -// if env.block.time.seconds() > end_date { -// decay_claimed_w(deps.storage).update(|claimed| { -// if claimed { -// Err(decay_claimed()) -// } else { -// Ok(true) -// } -// })?; - -// let total_claimed = total_claimed_r(deps.storage).load()?; -// let send_total = config.airdrop_amount.checked_sub(total_claimed)?; -// let messages = vec![send_msg( -// dump_address.clone(), -// send_total.into(), -// None, -// None, -// None, -// &config.airdrop_snip20, -// )?]; - -// return Ok(Response::new() -// .set_data(to_binary(&ExecuteAnswer::ClaimDecay { status: Success })?)); -// } -// } -// } - -// Err(decay_not_set()) -// } - -// pub fn finished_tasks(storage: &dyn Storage, account: String) -> StdResult> { -// let mut finished_tasks = vec![]; -// let config = config_r(storage).load()?; - -// for (index, _task) in config.task_claim.iter().enumerate() { -// match claim_status_r(storage, index).may_load(account.as_bytes())? { -// None => {} -// Some(_) => { -// finished_tasks.push(config.task_claim[index].clone()); -// } -// } -// } - -// Ok(finished_tasks) -// } - -// /// Gets task information and sets them -// pub fn update_tasks( -// storage: &mut dyn Storage, -// config: &Config, -// sender: String, -// ) -> StdResult<(Uint128, Uint128)> { -// // Calculate eligible tasks -// let mut completed_percentage = Uint128::zero(); -// let mut unclaimed_percentage = Uint128::zero(); -// for (index, task) in config.task_claim.iter().enumerate() { -// // Check if task has been completed -// let state = claim_status_r(storage, index).may_load(sender.as_bytes())?; - -// match state { -// // Ignore if none -// None => {} -// Some(claimed) => { -// completed_percentage += task.percent; -// if !claimed { -// // Set claim status to true since we're going to claim it now -// claim_status_w(storage, index).save(sender.as_bytes(), &true)?; - -// unclaimed_percentage += task.percent; -// } -// } -// } -// } - -// Ok((completed_percentage, unclaimed_percentage)) -// } +pub fn try_claim_decay(deps: DepsMut, env: &Env, _info: &MessageInfo) -> StdResult { + let config = config_r(deps.storage).load()?; + + // Check if airdrop ended + if let Some(end_date) = config.end_date { + if let Some(dump_address) = config.dump_address { + if env.block.time.seconds() > end_date { + decay_claimed_w(deps.storage).update(|claimed| { + if claimed { + Err(decay_claimed()) + } else { + Ok(true) + } + })?; + + let total_claimed = total_claimed_r(deps.storage).load()?; + let send_total = config.airdrop_amount.checked_sub(total_claimed)?; + let messages = vec![send_msg( + dump_address.clone(), + send_total.into(), + None, + None, + None, + &config.airdrop_snip20, + )?]; + + return Ok(Response::new() + .set_data(to_binary(&ExecuteAnswer::ClaimDecay { status: Success })?)); + } + } + } + + Err(decay_not_set()) +} pub fn claim_tokens( storage: &mut dyn Storage, - env: &Env, - info: &MessageInfo, + eth_pubkey: &String, + amount: &Uint128, +) -> StdResult { + // Save the eth_pubkey to state, if eth_pubkey already exist, error on claim + eth_pubkey_claim_w(storage).update(eth_pubkey.as_bytes(), |claimed| { + if let Some(_claimed) = claimed { + Err(already_claimed()) + } else { + Ok(true) + } + })?; + Ok(*amount) +} + +/// Validates all of the information and updates relevant states +pub fn try_add_account_addresses( + storage: &mut dyn Storage, + api: &dyn Api, config: &Config, sender: &Addr, - amount: Uint128, -) -> StdResult { - // send_amount - let sender = info.sender.to_string(); + account: &mut Account, + addresses: Vec, + eth_pubkey: String, +) -> StdResult<()> { + // Setup the items to validate + let mut leaves_to_validate: Vec<(usize, [u8; 32])> = vec![]; + + // Iterate addresses + for permit in addresses.iter() { + if let Some(memo) = permit.memo.clone() { + let params: AddressProofMsg = from_binary(&Binary::from_base64(&memo)?)?; + + // Avoid verifying sender + if ¶ms.address != sender { + // Check permit legitimacy + validate_address_permit(storage, api, permit, ¶ms, config.contract.clone())?; + } - // Amount to be redeemed - let mut redeem_amount = amount.to; - - // Update total claimed and calculate claimable - // account_total_claimed_w(storage).update(sender.as_bytes(), |claimed| { - // if let Some(claimed) = claimed { - // // This solves possible uToken inaccuracies - // if completed_percentage == Uint128::new(100u128) { - // redeem_amount = account.total_claimable.checked_sub(claimed)?; - // } else { - // redeem_amount = unclaimed_percentage - // .multiply_ratio(account.total_claimable, Uint128::new(100u128)); - // } - - // // Update redeem amount with the decay multiplier - // redeem_amount = redeem_amount * decay_factor(env.block.time.seconds(), config); - - // Ok(claimed + redeem_amount) - // } else { - // Err(account_does_not_exist()) - // } - // })?; - - Ok(redeem_amount) -} + // Update address if its not in an account + address_in_account_w(storage).update( + params.address.to_string().as_bytes(), + |state| -> StdResult { + if state.is_some() { + return Err(address_already_in_account(params.address.as_str())); + } + + Ok(true) + }, + )?; + + // Update eth_pubkey if its not in an account + eth_pubkey_in_account_w(storage).update( + eth_pubkey.to_string().as_bytes(), + |state| -> StdResult { + if state.is_some() { + return Err(address_already_in_account(eth_pubkey.as_str())); + } + Ok(true) + }, + )?; + + // Add account as a leaf + let leaf_hash = + Sha256::hash((params.address.to_string() + ¶ms.amount.to_string()).as_bytes()); + leaves_to_validate.push((params.index as usize, leaf_hash)); + + // If valid then add to account array and sum total amount + account.addresses.push(params.address); + account.eth_pubkey = eth_pubkey.clone(); + } else { + return Err(expected_memo()); + } + } + + // Need to sort by index in order for the proof to work + leaves_to_validate.sort_by_key(|item| item.0); + + let mut indices: Vec = vec![]; + let mut leaves: Vec<[u8; 32]> = vec![]; -// /// Validates all of the information and updates relevant states -// pub fn try_add_account_addresses( -// storage: &mut dyn Storage, -// api: &dyn Api, -// config: &Config, -// sender: &Addr, -// account: &mut Account, -// addresses: Vec, -// partial_tree: Vec, -// ) -> StdResult<()> { -// // Setup the items to validate -// let mut leaves_to_validate: Vec<(usize, [u8; 32])> = vec![]; - -// // Iterate addresses -// for permit in addresses.iter() { -// if let Some(memo) = permit.memo.clone() { -// let params: AddressProofMsg = from_binary(&Binary::from_base64(&memo)?)?; - -// // Avoid verifying sender -// if ¶ms.address != sender { -// // Check permit legitimacy -// validate_address_permit(storage, api, permit, ¶ms, config.contract.clone())?; -// } - -// // Check that airdrop amount does not exceed maximum -// if params.amount > config.max_amount { -// return Err(claim_too_high( -// params.amount.to_string().as_str(), -// config.max_amount.to_string().as_str(), -// )); -// } - -// // Update address if its not in an account -// address_in_account_w(storage).update( -// params.address.to_string().as_bytes(), -// |state| -> StdResult { -// if state.is_some() { -// return Err(address_already_in_account(params.address.as_str())); -// } - -// Ok(true) -// }, -// )?; - -// // Add account as a leaf -// let leaf_hash = -// Sha256::hash((params.address.to_string() + ¶ms.amount.to_string()).as_bytes()); -// leaves_to_validate.push((params.index as usize, leaf_hash)); - -// // If valid then add to account array and sum total amount -// account.addresses.push(params.address); -// account.total_claimable += params.amount; -// } else { -// return Err(expected_memo()); -// } -// } - -// // Need to sort by index in order for the proof to work -// leaves_to_validate.sort_by_key(|item| item.0); - -// let mut indices: Vec = vec![]; -// let mut leaves: Vec<[u8; 32]> = vec![]; - -// for leaf in leaves_to_validate.iter() { -// indices.push(leaf.0); -// leaves.push(leaf.1); -// } - -// // Convert partial tree from base64 to binary -// let mut partial_tree_binary: Vec<[u8; 32]> = vec![]; -// for node in partial_tree.iter() { -// let mut arr: [u8; 32] = Default::default(); -// arr.clone_from_slice(node.as_slice()); -// partial_tree_binary.push(arr); -// } - -// // Prove that user is in airdrop -// let proof = MerkleProof::::new(partial_tree_binary); -// // Convert to a fixed length array without messing up the contract -// let mut root: [u8; 32] = Default::default(); -// root.clone_from_slice(config.merkle_root.as_slice()); -// if !proof.verify(root, &indices, &leaves, config.total_accounts as usize) { -// return Err(invalid_partial_tree()); -// } - -// Ok(()) -// } + for leaf in leaves_to_validate.iter() { + indices.push(leaf.0); + leaves.push(leaf.1); + } + + Ok(()) +} pub fn available(config: &Config, env: &Env) -> StdResult<()> { let current_time = env.block.time.seconds(); @@ -700,17 +473,6 @@ pub fn available(config: &Config, env: &Env) -> StdResult<()> { Ok(()) } -/// Get the multiplier for decay, will return 1 when decay isnt in effect. -pub fn decay_factor(current_time: u64, config: &Config) -> Decimal { - // Calculate redeem amount after applying decay - if let Some(decay_start) = config.decay_start { - if current_time >= decay_start { - return inverse_normalizer(decay_start, current_time, config.end_date.unwrap()); - } - } - Decimal::one() -} - /// Get the inverse normalized value [0,1] of x between [min, max] pub fn inverse_normalizer(min: u64, x: u64, max: u64) -> Decimal { Decimal::from_ratio(max - x, max - min) @@ -722,7 +484,6 @@ mod validation { use ethereum_verify::verify_ethereum_text; use shade_protocol::c_std::StdError; - pub fn compute_plaintext_msg(config: &Config, info: MessageInfo) -> String { str::replace( &config.claim_msg_plaintext, diff --git a/contracts/airdrop/src/query.rs b/contracts/airdrop/src/query.rs index 6780f15..1e93216 100644 --- a/contracts/airdrop/src/query.rs +++ b/contracts/airdrop/src/query.rs @@ -1,20 +1,17 @@ -use crate::{ - handle::decay_factor, - state::{ - account_r, - account_total_claimed_r, - account_viewkey_r, - claim_status_r, - config_r, - decay_claimed_r, - total_claimed_r, - validate_account_permit, - }, +use crate::state::{ + account_r, + // account_total_claimed_r, + account_viewkey_r, + claim_status_r, + config_r, + decay_claimed_r, + eth_pubkey_claim_r, + total_claimed_r, + validate_account_permit, }; use shade_protocol::{ airdrop::{ account::{AccountKey, AccountPermit}, - claim_info::RequiredTask, errors::invalid_viewing_key, QueryAnswer, }, @@ -33,87 +30,50 @@ pub fn dates(deps: Deps, current_date: Option) -> StdResult { Ok(QueryAnswer::Dates { start: config.start_date, end: config.end_date, - decay_start: config.decay_start, - decay_factor: current_date.map(|date| Uint128::new(100u128) * decay_factor(date, &config)), }) } pub fn total_claimed(deps: Deps) -> StdResult { let claimed: Uint128; let total_claimed = total_claimed_r(deps.storage).load()?; - if decay_claimed_r(deps.storage).load()? { - claimed = total_claimed; - } else { - let config = config_r(deps.storage).load()?; - claimed = total_claimed.checked_div(config.query_rounding)? * config.query_rounding; - } + + claimed = total_claimed; Ok(QueryAnswer::TotalClaimed { claimed }) } +// returns account information for an eth_pubkey fn account_information( deps: Deps, account_address: Addr, - current_date: Option, + eth_pubkey: String, ) -> StdResult { let account = account_r(deps.storage).load(account_address.to_string().as_bytes())?; // Calculate eligible tasks - let config = config_r(deps.storage).load()?; - let mut finished_tasks: Vec = vec![]; - let mut completed_percentage = Uint128::zero(); - let mut unclaimed_percentage = Uint128::zero(); - for (index, task) in config.task_claim.iter().enumerate() { - // Check if task has been completed - let state = - claim_status_r(deps.storage, index).may_load(account_address.to_string().as_bytes())?; + // let config = config_r(deps.storage).load()?; - match state { - // Ignore if none - None => {} - Some(claimed) => { - finished_tasks.push(task.clone()); - if !claimed { - unclaimed_percentage += task.percent; - } else { - completed_percentage += task.percent; - } - } - } - } + // Check if eth address has claimed + let claim_state = eth_pubkey_claim_r(deps.storage).may_load(account.eth_pubkey.as_bytes())?; - let mut unclaimed: Uint128; - - if unclaimed_percentage == Uint128::new(100u128) { - unclaimed = account.total_claimable; - } else { - unclaimed = - unclaimed_percentage.multiply_ratio(account.total_claimable, Uint128::new(100u128)); - } - - if let Some(time) = current_date { - unclaimed = unclaimed * decay_factor(time, &config); - } + // match claim_state { + // // Ignore if none + // None => {} + // Some(claimed) => {} + // } Ok(QueryAnswer::Account { - total: account.total_claimable, - claimed: account_total_claimed_r(deps.storage) - .load(account_address.to_string().as_bytes())?, - unclaimed, - finished_tasks, + claimed: claim_state.unwrap(), addresses: account.addresses, + eth_pubkey: account.eth_pubkey, }) } -pub fn account( - deps: Deps, - permit: AccountPermit, - current_date: Option, -) -> StdResult { +pub fn account(deps: Deps, permit: AccountPermit, eth_pubkey: String) -> StdResult { let config = config_r(deps.storage).load()?; account_information( deps, validate_account_permit(deps, &permit, config.contract)?, - current_date, + eth_pubkey, ) } @@ -121,7 +81,7 @@ pub fn account_with_key( deps: Deps, account: Addr, key: String, - current_date: Option, + eth_pubkey: String, ) -> StdResult { // Validate address let stored_hash = account_viewkey_r(deps.storage).load(account.to_string().as_bytes())?; @@ -130,5 +90,5 @@ pub fn account_with_key( return Err(invalid_viewing_key()); } - account_information(deps, account, current_date) + account_information(deps, account, eth_pubkey) } diff --git a/contracts/airdrop/src/state.rs b/contracts/airdrop/src/state.rs index acf98dc..2c8177c 100644 --- a/contracts/airdrop/src/state.rs +++ b/contracts/airdrop/src/state.rs @@ -31,9 +31,12 @@ pub static CONFIG_KEY: &[u8] = b"config"; pub static DECAY_CLAIMED_KEY: &[u8] = b"decay_claimed"; pub static CLAIM_STATUS_KEY: &[u8] = b"claim_status_"; pub static REWARD_IN_ACCOUNT_KEY: &[u8] = b"reward_in_account"; +// Save a list of eth_pubkeys to prevent double claims +pub static ETH_PUBKEY_IN_ACCOUNT_KEY: &[u8] = b"eth_pubkey_in_account"; +pub static ETH_PUBKEY: &str = "eth_pubkeys"; pub static ACCOUNTS_KEY: &[u8] = b"accounts"; pub static TOTAL_CLAIMED_KEY: &[u8] = b"total_claimed"; -pub static USER_TOTAL_CLAIMED_KEY: &[u8] = b"user_total_claimed"; +// pub static USER_TOTAL_CLAIMED_KEY: &[u8] = b"user_total_claimed"; pub static ACCOUNT_PERMIT_KEY: &str = "account_permit_key"; pub static ACCOUNT_VIEWING_KEY: &[u8] = b"account_viewing_key"; @@ -62,6 +65,15 @@ pub fn address_in_account_w(storage: &mut dyn Storage) -> Bucket { bucket(storage, REWARD_IN_ACCOUNT_KEY) } +// Is address added to an account +pub fn eth_pubkey_in_account_r(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, ETH_PUBKEY_IN_ACCOUNT_KEY) +} + +pub fn eth_pubkey_in_account_w(storage: &mut dyn Storage) -> Bucket { + bucket(storage, ETH_PUBKEY_IN_ACCOUNT_KEY) +} + // airdrop account pub fn account_r(storage: &dyn Storage) -> ReadonlyBucket { bucket_read(storage, ACCOUNTS_KEY) @@ -93,15 +105,26 @@ pub fn total_claimed_w(storage: &mut dyn Storage) -> Singleton { singleton(storage, TOTAL_CLAIMED_KEY) } -// Total account claimed -pub fn account_total_claimed_r(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, USER_TOTAL_CLAIMED_KEY) +// Eth pubkey claimed +pub fn eth_pubkey_claim_r(storage: &dyn Storage) -> ReadonlyBucket { + let key = ETH_PUBKEY.to_string(); + bucket_read(storage, key.as_bytes()) } -pub fn account_total_claimed_w(storage: &mut dyn Storage) -> Bucket { - bucket(storage, USER_TOTAL_CLAIMED_KEY) +pub fn eth_pubkey_claim_w(storage: &mut dyn Storage) -> Bucket { + let key = ETH_PUBKEY.to_string(); + bucket(storage, key.as_bytes()) } +// Total account claimed +// pub fn account_total_claimed_r(storage: &dyn Storage) -> ReadonlyBucket { +// bucket_read(storage, USER_TOTAL_CLAIMED_KEY) +// } + +// pub fn account_total_claimed_w(storage: &mut dyn Storage) -> Bucket { +// bucket(storage, USER_TOTAL_CLAIMED_KEY) +// } + // Account viewing key pub fn account_viewkey_r(storage: &dyn Storage) -> ReadonlyBucket<[u8; 32]> { bucket_read(storage, ACCOUNT_VIEWING_KEY) diff --git a/contracts/basic_staking/.cargo/config b/contracts/basic_staking/.cargo/config deleted file mode 100644 index 882fe08..0000000 --- a/contracts/basic_staking/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[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/basic_staking/.circleci/config.yml b/contracts/basic_staking/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/contracts/basic_staking/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/basic_staking/Cargo.toml b/contracts/basic_staking/Cargo.toml deleted file mode 100644 index 4c830ee..0000000 --- a/contracts/basic_staking/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "basic_staking" -version = "0.1.0" -authors = ["Jack Swenson "] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] - -[dependencies] -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ - "basic_staking", - "math", - "storage_plus", - "admin", - "airdrop", -] } -cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" } - -[dev-dependencies] -shade-multi-test = { path = "../../packages/multi_test", features = [ - "basic_staking", - "snip20", - "query_auth", - "admin" -] } diff --git a/contracts/basic_staking/Makefile b/contracts/basic_staking/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/contracts/basic_staking/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/basic_staking/README.md b/contracts/basic_staking/README.md deleted file mode 100644 index d41a979..0000000 --- a/contracts/basic_staking/README.md +++ /dev/null @@ -1,83 +0,0 @@ -# Snip20 Staking -* [Introduction](#Introduction) -* [Sections](#Sections) - * [Init](#Init) - * [Interface](#Interface) - * Messages - * [Receive](#Receive) - * [UpdateConfig](#UpdateConfig) - * [RegisterRewards](#RegisterRewards) - * [Unbond](#Unbond) - * [Withdraw](#Withdraw) - * [Claim](#Claim) - * [Compound](#Compound) - * [CancelRewardPool](#CancelRewardPool) - * [TransferStake](#TransferStake) - * Queries - * [Config](#Config) - * [StakeToken](#StakeToken) - * [StakingInfo](#StakingInfo) - * [TotalStaked](#TotalStaked) - * [RewardTokens](#RewardTokens) - * [RewardPools](#RewardPools) - * [Balance](#Balance) - * [Staked](#Staked) - * [Rewards](#Rewards) - * [Unbonding](#Unbonding) - -# Introduction -This contract allows users to lock up their 'stake_token', with a configurable unbonding period. Staking users will earn rewards from all active reward pools based on their stake amount / total staked. -Rewards will be initialized by sending in an amount of tokens to be emitted, with start/end timestamps for the rewards period. -Reward pools can be initialized with any registered reward token (admin-only registration). Admins can always init a reward pool (known as 'official'), there is also a configurable 'max_user_pools' that determines how many pools are allowed at 1 time that can be initialized permissionlessly (by any user) - -# Sections - -## Init -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -| admin_auth | Contract | shade admin authentication contract -| query_auth | Contract | shade query authentication contract -| stake_token | Contract | token that will be deposited for staking -| unbond_period | Uint128 | How long it takes to unbond funds in seconds -| max_user_pools | Uint128 | How many permissionless pools are allowed -| reward_cancel_threshold | Uint128 | Percentage of rewards that must be claimed for a reward pool to be cancelled without 'force' -| viewing_key | String | Contract viewing key for snip20's - -## Interface - -### Messages -#### UpdateConfig -Updates the given values -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -| admin_auth | Contract | shade admin authentication contract -| query_auth | Contract | shade query authentication contract -| unbond_period | Uint128 | How long it takes to unbond funds in seconds -| max_user_pools | Uint128 | How many permissionless pools are allowed -| reward_cancel_threshold | Uint128 | Percentage of rewards that must be claimed for a reward pool to be cancelled without 'force' - -##### Response -```json -{ - "update_config": { - "status": "success" - } -} -``` - -### Queries - -#### Config -Gets the contract's configuration variables -##### Response -```json -{ - "config": { - "config": { - "owner": "Owner address", - } - } -} -``` diff --git a/contracts/basic_staking/src/contract.rs b/contracts/basic_staking/src/contract.rs deleted file mode 100644 index 5649695..0000000 --- a/contracts/basic_staking/src/contract.rs +++ /dev/null @@ -1,212 +0,0 @@ -use shade_protocol::{ - basic_staking::{Auth, AuthPermit, Config, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg}, - c_std::{ - shd_entry_point, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, - StdError, StdResult, Uint128, - }, - query_auth::helpers::{authenticate_permit, authenticate_vk, PermitAuthentication}, - snip20::helpers::{register_receive, set_viewing_key_msg}, - utils::{asset::Contract, pad_handle_result}, -}; - -use crate::{execute, query, storage::*}; - -pub const RESPONSE_BLOCK_SIZE: usize = 256; - -#[shd_entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - CONFIG.save( - deps.storage, - &Config { - admin_auth: msg.admin_auth.into_valid(deps.api)?, - query_auth: msg.query_auth.into_valid(deps.api)?, - airdrop: match msg.airdrop { - Some(airdrop) => Some(airdrop.into_valid(deps.api)?), - None => None, - }, - unbond_period: msg.unbond_period, - max_user_pools: msg.max_user_pools, - }, - )?; - - let stake_token = msg.stake_token.into_valid(deps.api)?; - - STAKE_TOKEN.save(deps.storage, &stake_token)?; - VIEWING_KEY.save(deps.storage, &msg.viewing_key)?; - - REWARD_TOKENS.save(deps.storage, &vec![stake_token.clone()])?; - REWARD_POOLS.save(deps.storage, &vec![])?; - MAX_POOL_ID.save(deps.storage, &Uint128::zero())?; - - TRANSFER_WL.save(deps.storage, &vec![])?; - - TOTAL_STAKED.save(deps.storage, &Uint128::zero())?; - - let resp = Response::new().add_messages(vec![ - set_viewing_key_msg(msg.viewing_key, None, &stake_token)?, - register_receive(env.contract.code_hash, None, &stake_token)?, - ]); - - Ok(resp) -} - -#[shd_entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - pad_handle_result( - match msg { - ExecuteMsg::UpdateConfig { - admin_auth, - query_auth, - airdrop, - unbond_period, - max_user_pools, - padding, - } => execute::update_config( - deps, - env, - info, - admin_auth, - query_auth, - airdrop, - unbond_period, - max_user_pools, - ), - ExecuteMsg::RegisterRewards { token, padding } => { - let api = deps.api; - execute::register_reward(deps, env, info, token.into_valid(api)?) - } - ExecuteMsg::AddTransferWhitelist { user, padding } => { - let api = deps.api; - execute::add_transfer_whitelist(deps, env, info, api.addr_validate(&user)?) - } - ExecuteMsg::RemoveTransferWhitelist { user, padding } => { - let api = deps.api; - execute::rm_transfer_whitelist(deps, env, info, api.addr_validate(&user)?) - } - ExecuteMsg::Receive { - sender, - from, - amount, - msg, - .. - } => execute::receive(deps, env, info, sender, from, amount, msg), - ExecuteMsg::Claim { padding } => execute::claim(deps, env, info), - ExecuteMsg::Unbond { - amount, - compound, - padding, - } => execute::unbond(deps, env, info, amount, compound.unwrap_or(false)), - ExecuteMsg::Withdraw { ids, padding } => { - execute::withdraw(deps, env, info.clone(), ids) - } - ExecuteMsg::Compound { padding } => execute::compound(deps, env, info), - ExecuteMsg::EndRewardPool { id, force, padding } => { - execute::end_reward_pool(deps, env, info, id, force.unwrap_or(false)) - } - ExecuteMsg::TransferStake { - amount, - recipient, - compound, - padding, - } => { - let api = deps.api; - execute::transfer_stake( - deps, - env, - info, - amount, - api.addr_validate(&recipient)?, - compound.unwrap_or(false), - ) - } - }, - RESPONSE_BLOCK_SIZE, - ) -} - -pub fn authenticate(deps: Deps, auth: Auth, query_auth: Contract) -> StdResult { - match auth { - Auth::ViewingKey { key, address } => { - let address = deps.api.addr_validate(&address)?; - if !authenticate_vk(address.clone(), key, &deps.querier, &query_auth)? { - return Err(StdError::generic_err("Invalid Viewing Key")); - } - Ok(address) - } - Auth::Permit(permit) => { - let res: PermitAuthentication = - authenticate_permit(permit, &deps.querier, query_auth)?; - if res.revoked { - return Err(StdError::generic_err("Permit Revoked")); - } - Ok(res.sender) - } - } -} - -#[shd_entry_point] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query::config(deps)?), - QueryMsg::StakeToken {} => to_binary(&query::stake_token(deps)?), - QueryMsg::StakingInfo {} => to_binary(&query::staking_info(deps)?), - QueryMsg::TotalStaked {} => to_binary(&query::total_staked(deps)?), - QueryMsg::RewardTokens {} => to_binary(&query::reward_tokens(deps)?), - QueryMsg::RewardPools {} => to_binary(&query::reward_pools(deps)?), - QueryMsg::Balance { - auth, - unbonding_ids, - } => { - let config = CONFIG.load(deps.storage)?; - let user = authenticate(deps, auth, config.query_auth)?; - let unbonding_ids = match unbonding_ids { - Some(ids) => ids, - None => { - if let Some(ids) = USER_UNBONDING_IDS.may_load(deps.storage, user.clone())? { - ids - } else { - vec![] - } - } - }; - to_binary(&query::user_balance( - deps, - env, - user.clone(), - unbonding_ids, - )?) - } - QueryMsg::Staked { auth } => { - let config = CONFIG.load(deps.storage)?; - to_binary(&query::user_staked( - deps, - authenticate(deps, auth, config.query_auth)?, - )?) - } - QueryMsg::Rewards { auth } => { - let config = CONFIG.load(deps.storage)?; - to_binary(&query::user_rewards( - deps, - env, - authenticate(deps, auth, config.query_auth)?, - )?) - } - QueryMsg::Unbonding { auth, ids } => { - let config = CONFIG.load(deps.storage)?; - let user = authenticate(deps, auth, config.query_auth)?; - to_binary(&query::user_unbondings( - deps, - user.clone(), - ids.unwrap_or(USER_UNBONDING_IDS.load(deps.storage, user)?), - )?) - } - QueryMsg::TransferWhitelist {} => to_binary(&QueryAnswer::TransferWhitelist { - whitelist: TRANSFER_WL.load(deps.storage)?, - }), - } -} diff --git a/contracts/basic_staking/src/execute.rs b/contracts/basic_staking/src/execute.rs deleted file mode 100644 index e637533..0000000 --- a/contracts/basic_staking/src/execute.rs +++ /dev/null @@ -1,938 +0,0 @@ -use shade_protocol::{ - admin::helpers::{admin_is_valid, validate_admin, AdminPermissions}, - basic_staking::{Action, ExecuteAnswer, RewardPoolInternal, Unbonding}, - c_std::{ - from_binary, to_binary, Addr, Binary, DepsMut, Env, MessageInfo, Response, StdError, - StdResult, Storage, Uint128, - }, - contract_interfaces::airdrop::ExecuteMsg::CompleteTask, - snip20::helpers::{register_receive, send_msg, set_viewing_key_msg}, - utils::{ - asset::{Contract, RawContract}, - generic_response::ResponseStatus, - ExecuteCallback, - }, -}; - -use crate::storage::*; -use std::cmp::{max, min}; - -pub fn update_config( - deps: DepsMut, - _env: Env, - info: MessageInfo, - admin_auth: Option, - query_auth: Option, - airdrop: Option, - unbond_period: Option, - max_user_pools: Option, -) -> StdResult { - let mut config = CONFIG.load(deps.storage)?; - - validate_admin( - &deps.querier, - AdminPermissions::StakingAdmin, - info.sender.to_string(), - &config.admin_auth, - )?; - - if let Some(admin_auth) = admin_auth { - config.admin_auth = admin_auth.into_valid(deps.api)?; - } - - if let Some(query_auth) = query_auth { - config.query_auth = query_auth.into_valid(deps.api)?; - } - - if let Some(airdrop) = airdrop { - config.airdrop = Some(airdrop.into_valid(deps.api)?); - } - - if let Some(unbond_period) = unbond_period { - config.unbond_period = unbond_period; - } - - if let Some(max_user_pools) = max_user_pools { - config.max_user_pools = max_user_pools; - } - - CONFIG.save(deps.storage, &config)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn register_reward( - deps: DepsMut, - env: Env, - info: MessageInfo, - token: Contract, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - validate_admin( - &deps.querier, - AdminPermissions::StakingAdmin, - info.sender.to_string(), - &config.admin_auth, - )?; - - let mut reward_tokens = REWARD_TOKENS.load(deps.storage)?; - - if reward_tokens.contains(&token) { - return Err(StdError::generic_err("Reward token already registered")); - } - - reward_tokens.push(token.clone()); - REWARD_TOKENS.save(deps.storage, &reward_tokens)?; - - Ok(Response::new() - .add_messages(vec![ - set_viewing_key_msg(VIEWING_KEY.load(deps.storage)?, None, &token)?, - register_receive(env.contract.code_hash, None, &token)?, - ]) - .set_data(to_binary(&ExecuteAnswer::RegisterRewards { - status: ResponseStatus::Success, - })?)) -} - -pub fn receive( - deps: DepsMut, - env: Env, - info: MessageInfo, - _sender: Addr, - from: Addr, - amount: Uint128, - msg: Option, -) -> StdResult { - let now = Uint128::new(env.block.time.seconds() as u128); - - match msg { - Some(m) => match from_binary(&m)? { - Action::Stake { - compound, - airdrop_task, - } => { - let stake_token = STAKE_TOKEN.load(deps.storage)?; - if info.sender != stake_token.address { - return Err(StdError::generic_err(format!( - "Invalid Stake Token: {}", - info.sender - ))); - } - - let compound = compound.unwrap_or(false); - - let total_staked = TOTAL_STAKED.load(deps.storage)?; - - let mut reward_pools = - update_rewards(env.clone(), &REWARD_POOLS.load(deps.storage)?, total_staked); - - let mut response = Response::new(); - - let mut compound_amount = Uint128::zero(); - - let user_staked = USER_STAKED - .may_load(deps.storage, from.clone())? - .unwrap_or(Uint128::zero()); - - if !user_staked.is_zero() { - // Claim Rewards - for reward_pool in reward_pools.iter_mut() { - let reward_claimed = reward_pool_claim( - deps.storage, - from.clone(), - user_staked, - &reward_pool, - )?; - reward_pool.claimed += reward_claimed; - if compound && reward_pool.token == stake_token { - // Compound stake_token rewards - compound_amount += reward_claimed; - } else { - // Claim if not compound or not stake token rewards - response = response - .add_message(send_msg( - info.sender.clone(), - reward_claimed, - None, - None, - None, - &reward_pool.token, - )?) - .add_attribute( - reward_pool.token.address.to_string(), - reward_claimed, - ); - } - } - } else { - for reward_pool in reward_pools.iter() { - // make sure user rewards start now - USER_REWARD_PER_TOKEN_PAID.save( - deps.storage, - user_pool_key(from.clone(), reward_pool.id), - &reward_pool.reward_per_token, - )?; - } - } - - // Send airdrop message - if let Some(true) = airdrop_task { - let config = CONFIG.load(deps.storage)?; - if let Some(airdrop) = config.airdrop { - response = response.add_message( - CompleteTask { - address: from.clone(), - padding: None, - } - .to_cosmos_msg(&airdrop, vec![])?, - ); - } else { - return Err(StdError::generic_err("No airdrop contract configured")); - } - } - - if compound_amount > Uint128::zero() { - response = response.add_attribute("compounded", compound_amount); - } - - USER_STAKED.save( - deps.storage, - from.clone(), - &(user_staked + amount + compound_amount), - )?; - TOTAL_STAKED.save(deps.storage, &(total_staked + amount + compound_amount))?; - - REWARD_POOLS.save(deps.storage, &reward_pools.clone())?; - - Ok(response.set_data(to_binary(&ExecuteAnswer::Stake { - staked: user_staked + amount, - status: ResponseStatus::Success, - })?)) - } - Action::Rewards { start, end } => { - let reward_tokens = REWARD_TOKENS.load(deps.storage)?; - - if let Some(token) = reward_tokens - .iter() - .find(|contract| contract.address == info.sender) - { - // Disallow end before start - if start >= end { - return Err(StdError::generic_err("'start' must be after 'end'")); - } - - // Disallow retro-active emissions (maybe could allow?) - if start < now { - return Err(StdError::generic_err("Cannot start emitting in the past")); - } - - let mut reward_pools = REWARD_POOLS.load(deps.storage)?; - - let config = CONFIG.load(deps.storage)?; - let is_admin = match admin_is_valid( - &deps.querier, - AdminPermissions::StakingAdmin, - from.to_string(), - &config.admin_auth, - ) { - Ok(_) => true, - Err(_) => false, - }; - - // check user_pool limit - if !is_admin { - let user_pools_count = reward_pools - .iter() - .filter(|pool| !pool.official) - .collect::>() - .len(); - if user_pools_count as u128 >= config.max_user_pools.u128() { - return Err(StdError::generic_err("Max user pools exceeded")); - } - } - - let new_id = MAX_POOL_ID.load(deps.storage)? + Uint128::new(1); - MAX_POOL_ID.save(deps.storage, &new_id)?; - - // Tokens per second emitted from this pool - let rate = amount * Uint128::new(10u128.pow(18)) / (end - start); - - reward_pools.push(RewardPoolInternal { - id: new_id, - amount, - start, - end, - token: token.clone(), - rate, - reward_per_token: Uint128::zero(), - claimed: Uint128::zero(), - last_update: now, - creator: from, - official: is_admin, - }); - REWARD_POOLS.save(deps.storage, &reward_pools)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Rewards { - status: ResponseStatus::Success, - })?)) - } else { - return Err(StdError::generic_err(format!( - "Invalid Reward: {}", - info.sender - ))); - } - } - }, - None => { - return Err(StdError::generic_err("No action provided")); - } - } -} - -pub fn reward_per_token(total_staked: Uint128, now: u64, pool: &RewardPoolInternal) -> Uint128 { - if total_staked.is_zero() { - return Uint128::zero(); - } - - let start = max(pool.last_update, pool.start); - let end = min(pool.end, Uint128::new(now as u128)); - - if start > end { - return pool.reward_per_token; - } - - pool.reward_per_token + (((end - start) * pool.rate) / total_staked) -} - -pub fn rewards_earned( - user_staked: Uint128, - reward_per_token: Uint128, - user_reward_per_token_paid: Uint128, -) -> Uint128 { - user_staked * (reward_per_token - user_reward_per_token_paid) / Uint128::new(10u128.pow(18)) -} - -/* - * Returns new reward_per_token - */ -pub fn updated_reward_pool( - reward_pool: &RewardPoolInternal, - total_staked: Uint128, - now: u64, -) -> RewardPoolInternal { - let mut pool = reward_pool.clone(); - pool.reward_per_token = reward_per_token(total_staked, now, &reward_pool); - pool.last_update = min(reward_pool.end, Uint128::new(now as u128)); - pool -} - -pub fn update_rewards( - env: Env, - reward_pools: &Vec, - total_staked: Uint128, -) -> Vec { - reward_pools - .iter() - .map(|pool| updated_reward_pool(pool, total_staked, env.block.time.seconds())) - .collect() -} - -/* returns the earned rewards - * Reward must be sent buy calling code - */ -pub fn reward_pool_claim( - storage: &mut dyn Storage, - user: Addr, - user_staked: Uint128, - reward_pool: &RewardPoolInternal, -) -> StdResult { - let user_reward_per_token_paid = USER_REWARD_PER_TOKEN_PAID - .may_load(storage, user_pool_key(user.clone(), reward_pool.id))? - .unwrap_or(Uint128::zero()); - - let user_reward = rewards_earned( - user_staked, - reward_pool.reward_per_token, - user_reward_per_token_paid, - ); - - USER_REWARD_PER_TOKEN_PAID.save( - storage, - user_pool_key(user.clone(), reward_pool.id), - &reward_pool.reward_per_token, - )?; - - Ok(user_reward) -} - -pub fn claim(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { - let user_staked = USER_STAKED.load(deps.storage, info.sender.clone())?; - - if user_staked.is_zero() { - return Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Claim { - status: ResponseStatus::Success, - })?)); - } - - let total_staked = TOTAL_STAKED.load(deps.storage)?; - - let now = env.block.time.seconds(); - - let mut reward_pools = - update_rewards(env.clone(), &REWARD_POOLS.load(deps.storage)?, total_staked); - - let mut response = Response::new(); - - for reward_pool in reward_pools.iter_mut() { - let reward_claimed = - reward_pool_claim(deps.storage, info.sender.clone(), user_staked, &reward_pool)?; - - reward_pool.claimed += reward_claimed; - - response = response - .add_message(send_msg( - info.sender.clone(), - reward_claimed, - None, - None, - None, - &reward_pool.token, - )?) - .add_attribute(reward_pool.token.address.to_string(), reward_claimed); - } - - REWARD_POOLS.save(deps.storage, &reward_pools)?; - - Ok(response.set_data(to_binary(&ExecuteAnswer::Claim { - //claimed: - status: ResponseStatus::Success, - })?)) -} - -pub fn unbond( - deps: DepsMut, - env: Env, - info: MessageInfo, - amount: Uint128, - compound: bool, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if amount.is_zero() { - return Err(StdError::generic_err("Must unbond non-zero amount")); - } - - if let Some(mut user_staked) = USER_STAKED.may_load(deps.storage, info.sender.clone())? { - // if not compounding, check staked >= unbond amount - if !compound && user_staked < amount { - return Err(StdError::generic_err(format!( - "Cannot unbond {}, only {} staked", - amount, user_staked - ))); - } - - let now = env.block.time.seconds(); - - let mut total_staked = TOTAL_STAKED.load(deps.storage)?; - - let mut reward_pools = - update_rewards(env.clone(), &REWARD_POOLS.load(deps.storage)?, total_staked); - - let stake_token = STAKE_TOKEN.load(deps.storage)?; - let mut compound_amount = Uint128::zero(); - - let mut response = Response::new(); - - // Claim/Compound rewards - for reward_pool in reward_pools.iter_mut() { - let reward_claimed = - reward_pool_claim(deps.storage, info.sender.clone(), user_staked, &reward_pool)?; - reward_pool.claimed += reward_claimed; - - if compound && reward_pool.token == stake_token { - // Compound stake_token rewards - compound_amount += reward_claimed; - } else { - // Claim if not compound or not stake token rewards - response = response - .add_message(send_msg( - info.sender.clone(), - reward_claimed, - None, - None, - None, - &reward_pool.token, - )?) - .add_attribute(reward_pool.token.address.to_string(), reward_claimed); - } - } - - // if compounding, check staked + compounded >= unbond amount - if user_staked + compound_amount < amount { - return Err(StdError::generic_err(format!( - "Cannot unbond {}, only {} staked after compounding", - amount, - user_staked + compound_amount, - ))); - } - if compound_amount > Uint128::zero() { - response = response.add_attribute("compounded", compound_amount); - } - - user_staked = (user_staked + compound_amount) - amount; - total_staked = (total_staked + compound_amount) - amount; - - TOTAL_STAKED.save(deps.storage, &total_staked)?; - USER_STAKED.save(deps.storage, info.sender.clone(), &user_staked)?; - REWARD_POOLS.save(deps.storage, &reward_pools)?; - - let mut user_unbonding_ids = USER_UNBONDING_IDS - .may_load(deps.storage, info.sender.clone())? - .unwrap_or(vec![]); - - let next_id = *user_unbonding_ids.iter().max().unwrap_or(&Uint128::zero()) + Uint128::one(); - - user_unbonding_ids.push(next_id); - USER_UNBONDING_IDS.save(deps.storage, info.sender.clone(), &user_unbonding_ids)?; - - USER_UNBONDING.save( - deps.storage, - user_unbonding_key(info.sender, next_id), - &Unbonding { - id: next_id, - amount, - complete: Uint128::new(now as u128) + config.unbond_period, - }, - )?; - - Ok(response.set_data(to_binary(&ExecuteAnswer::Unbond { - id: next_id, - unbonded: amount, - status: ResponseStatus::Success, - })?)) - } else { - return Err(StdError::generic_err("User is not a staker")); - } -} - -pub fn withdraw( - deps: DepsMut, - env: Env, - info: MessageInfo, - ids: Option>, -) -> StdResult { - let mut user_unbonding_ids = USER_UNBONDING_IDS - .may_load(deps.storage, info.sender.clone())? - .unwrap_or(vec![]); - - // If null ids, use all user unbondings - let ids = ids.unwrap_or(user_unbonding_ids.clone()); - - let now = Uint128::new(env.block.time.seconds() as u128); - - let mut withdrawn_ids = vec![]; - let mut withdrawn_amount = Uint128::zero(); - - for id in ids.into_iter() { - if let Some(unbonding) = - USER_UNBONDING.may_load(deps.storage, user_unbonding_key(info.sender.clone(), id))? - { - if now >= unbonding.complete { - withdrawn_amount += unbonding.amount; - withdrawn_ids.push(id); - } - } else { - return Err(StdError::generic_err(format!("Bad ID {}", id))); - } - } - - if withdrawn_amount.is_zero() { - return Ok(Response::new() - .add_attribute("withdrawn", withdrawn_amount) - .set_data(to_binary(&ExecuteAnswer::Withdraw { - withdrawn: withdrawn_amount, - status: ResponseStatus::Success, - })?)); - } - - // Sort lists so the operation is O(n) - withdrawn_ids.sort(); - user_unbonding_ids.sort(); - - // To store remaining ids - let mut new_unbonding_ids = vec![]; - - // Maps to the withdrawn_ids list - let mut withdrawn_i = 0; - - for i in 0..user_unbonding_ids.len() { - // If all withdrawn handled, or it doesn't collide with withdrawn - if withdrawn_i >= withdrawn_ids.len() || user_unbonding_ids[i] != withdrawn_ids[withdrawn_i] - { - new_unbonding_ids.push(user_unbonding_ids[i]); - } else { - // advance withdrawn index - withdrawn_i += 1; - } - } - - USER_UNBONDING_IDS.save(deps.storage, info.sender.clone(), &new_unbonding_ids)?; - - Ok(Response::new() - .add_message(send_msg( - info.sender, - withdrawn_amount, - None, - None, - None, - &STAKE_TOKEN.load(deps.storage)?, - )?) - .add_attribute("withdrawn", withdrawn_amount) - .set_data(to_binary(&ExecuteAnswer::Withdraw { - withdrawn: withdrawn_amount, - status: ResponseStatus::Success, - })?)) -} - -pub fn compound(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { - let mut response = Response::new(); - - let user_staked = USER_STAKED - .may_load(deps.storage, info.sender.clone())? - .unwrap_or(Uint128::zero()); - - if user_staked.is_zero() { - return Err(StdError::generic_err("User has no stake")); - } - - let total_staked = TOTAL_STAKED.load(deps.storage)?; - let mut reward_pools = - update_rewards(env.clone(), &REWARD_POOLS.load(deps.storage)?, total_staked); - let stake_token = STAKE_TOKEN.load(deps.storage)?; - - let mut compound_amount = Uint128::zero(); - - for reward_pool in reward_pools.iter_mut() { - let reward_claimed = - reward_pool_claim(deps.storage, info.sender.clone(), user_staked, &reward_pool)?; - reward_pool.claimed += reward_claimed; - - if reward_pool.token == stake_token { - // Compound stake_token rewards - compound_amount += reward_claimed; - } else { - // Claim non-stake_token rewards - response = response - .add_message(send_msg( - info.sender.clone(), - reward_claimed, - None, - None, - None, - &reward_pool.token, - )?) - .add_attribute(reward_pool.token.address.to_string(), reward_claimed); - } - } - REWARD_POOLS.save(deps.storage, &reward_pools)?; - - if compound_amount > Uint128::zero() { - response = response.add_attribute("compounded", compound_amount); - } - - USER_STAKED.save( - deps.storage, - info.sender.clone(), - &(user_staked + compound_amount), - )?; - TOTAL_STAKED.save(deps.storage, &(total_staked + compound_amount))?; - - Ok(response.set_data(to_binary(&ExecuteAnswer::Compound { - compounded: compound_amount, - status: ResponseStatus::Success, - })?)) -} - -pub fn end_reward_pool( - deps: DepsMut, - env: Env, - info: MessageInfo, - id: Uint128, - force: bool, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - validate_admin( - &deps.querier, - AdminPermissions::StakingAdmin, - info.sender.to_string(), - &config.admin_auth, - )?; - - let total_staked = TOTAL_STAKED.load(deps.storage)?; - let mut reward_pools = - update_rewards(env.clone(), &REWARD_POOLS.load(deps.storage)?, total_staked); - - // Amount of rewards pulled from contract - let mut extract_amount = Uint128::zero(); - - let now = Uint128::new(env.block.time.seconds() as u128); - - let pool_i = match reward_pools.iter().position(|p| p.id == id) { - Some(i) => i, - None => { - return Err(StdError::generic_err("Could not match id")); - } - }; - - // Remove reward pool, will edit & push it later - let mut reward_pool = reward_pools.remove(pool_i); - - // Delete reward pool if it hasn't started - let deleted = if reward_pool.start > now { - println!("DELETING BEFORE START"); - extract_amount = reward_pool.amount; - true - } - // Reward pool hasn't ended, trim off un-emitted tokens & edit pool to end now - else if reward_pool.end > now { - // remove rewards from now -> end - extract_amount = reward_pool.rate * (reward_pool.end - now) / Uint128::new(10u128.pow(18)); - println!("EXTRACTING {}", extract_amount); - reward_pool.end = now; - reward_pool.amount -= extract_amount; - - if reward_pool.claimed == reward_pool.amount { - true - } else { - reward_pools.push(reward_pool.clone()); - false - } - } - // Delete reward pool if reward pool is fully claimed, or forced - else if reward_pool.claimed == reward_pool.amount || force { - extract_amount += reward_pool.amount - reward_pool.claimed; - true - } else { - return Err(StdError::generic_err( - "Reward pool is complete but claims are still pending", - )); - }; - - REWARD_POOLS.save(deps.storage, &reward_pools)?; - - Ok(Response::new() - .add_message(send_msg( - info.sender, - extract_amount, - None, - None, - None, - &reward_pool.token, - )?) - .set_data(to_binary(&ExecuteAnswer::EndRewardPool { - deleted, - extracted: extract_amount, - status: ResponseStatus::Success, - })?)) -} - -pub fn add_transfer_whitelist( - deps: DepsMut, - _env: Env, - info: MessageInfo, - user: Addr, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - validate_admin( - &deps.querier, - AdminPermissions::StakingAdmin, - info.sender.to_string(), - &config.admin_auth, - )?; - - let mut whitelist = TRANSFER_WL.load(deps.storage)?; - - if whitelist.contains(&user) { - return Err(StdError::generic_err("User already whitelisted")); - } - - whitelist.push(user); - - TRANSFER_WL.save(deps.storage, &whitelist)?; - - Ok( - Response::default().set_data(to_binary(&ExecuteAnswer::AddTransferWhitelist { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn rm_transfer_whitelist( - deps: DepsMut, - _env: Env, - info: MessageInfo, - user: Addr, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - validate_admin( - &deps.querier, - AdminPermissions::StakingAdmin, - info.sender.to_string(), - &config.admin_auth, - )?; - - let mut whitelist = TRANSFER_WL.load(deps.storage)?; - - match whitelist.iter().position(|u| *u == user) { - Some(i) => { - whitelist.remove(i); - } - None => { - return Err(StdError::generic_err("User not in whitelist")); - } - } - - TRANSFER_WL.save(deps.storage, &whitelist)?; - - Ok( - Response::default().set_data(to_binary(&ExecuteAnswer::AddTransferWhitelist { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn transfer_stake( - deps: DepsMut, - env: Env, - info: MessageInfo, - amount: Uint128, - recipient: Addr, - compound: bool, -) -> StdResult { - let whitelist = TRANSFER_WL.load(deps.storage)?; - - if !whitelist.contains(&info.sender) { - return Err(StdError::generic_err(format!( - "Transfer Stake not allowed for {}", - info.sender - ))); - } - - // Claim/Compound for sending user - let total_staked = TOTAL_STAKED.load(deps.storage)?; - - let mut reward_pools = - update_rewards(env.clone(), &REWARD_POOLS.load(deps.storage)?, total_staked); - - let stake_token = STAKE_TOKEN.load(deps.storage)?; - - let mut response = Response::new(); - - let sender_staked = USER_STAKED - .may_load(deps.storage, info.sender.clone())? - .unwrap_or(Uint128::zero()); - - if sender_staked == Uint128::zero() { - return Err(StdError::generic_err("Cannot transfer with 0 staked")); - } - - let mut sender_compound_amount = Uint128::zero(); - - // Claim/Compound rewards for Sender - for reward_pool in reward_pools.iter_mut() { - println!("reward pool claim sender"); - let reward_claimed = reward_pool_claim( - deps.storage, - info.sender.clone(), - sender_staked, - &reward_pool, - )?; - println!("POST reward pool claim sender"); - reward_pool.claimed += reward_claimed; - - if compound && reward_pool.token == stake_token { - // Compound stake_token rewards - sender_compound_amount += reward_claimed; - } else { - // Claim if not compound or not stake token rewards - response = response - .add_message(send_msg( - info.sender.clone(), - reward_claimed, - None, - None, - None, - &reward_pool.token, - )?) - .add_attribute(reward_pool.token.address.to_string(), reward_claimed); - } - } - - if sender_staked + sender_compound_amount < amount { - return Err(StdError::generic_err(format!( - "Cannot transfer {}, only {} available", - amount, - sender_staked + sender_compound_amount - ))); - } - - println!("sender compound amount {}", sender_compound_amount); - - if sender_compound_amount > Uint128::zero() { - response = response.add_attribute("compounded", sender_compound_amount); - } - - // Adjust sender staked - USER_STAKED.save( - deps.storage, - info.sender, - &(sender_staked + sender_compound_amount - amount), - )?; - - // Claim for receiving user - let recipient_staked = USER_STAKED - .may_load(deps.storage, recipient.clone())? - .unwrap_or(Uint128::zero()); - - // Claim rewards for Receiver (no compound) - for reward_pool in reward_pools.iter_mut() { - println!("reward pool claim recipient"); - let reward_claimed = reward_pool_claim( - deps.storage, - recipient.clone(), - recipient_staked, - &reward_pool, - )?; - reward_pool.claimed += reward_claimed; - - // Claim if not compound or not stake token rewards - println!( - "SENDING RECIPIETN REWARD {} {}", - reward_claimed, - recipient.clone() - ); - response = response.add_message(send_msg( - recipient.clone(), - reward_claimed, - None, - None, - None, - &reward_pool.token, - )?); - } - - // Adjust recipient staked - USER_STAKED.save(deps.storage, recipient, &(recipient_staked + amount))?; - - Ok(response.set_data(to_binary(&ExecuteAnswer::TransferStake { - transferred: amount, - status: ResponseStatus::Success, - })?)) -} diff --git a/contracts/basic_staking/src/lib.rs b/contracts/basic_staking/src/lib.rs deleted file mode 100644 index 25ee0d9..0000000 --- a/contracts/basic_staking/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod contract; -pub mod execute; -pub mod query; -pub mod storage; diff --git a/contracts/basic_staking/src/query.rs b/contracts/basic_staking/src/query.rs deleted file mode 100644 index 4243ed4..0000000 --- a/contracts/basic_staking/src/query.rs +++ /dev/null @@ -1,215 +0,0 @@ -use shade_protocol::{ - basic_staking::{QueryAnswer, Reward, RewardPool, RewardPoolInternal, StakingInfo}, - c_std::{Addr, Deps, Env, StdError, StdResult, Uint128}, -}; - -use crate::{ - execute::{reward_per_token, rewards_earned}, - storage::*, -}; - -pub fn config(deps: Deps) -> StdResult { - Ok(QueryAnswer::Config { - config: CONFIG.load(deps.storage)?, - }) -} - -pub fn stake_token(deps: Deps) -> StdResult { - Ok(QueryAnswer::StakeToken { - token: STAKE_TOKEN.load(deps.storage)?.address, - }) -} - -pub fn staking_info(deps: Deps) -> StdResult { - Ok(QueryAnswer::StakingInfo { - info: StakingInfo { - stake_token: STAKE_TOKEN.load(deps.storage)?.address, - total_staked: TOTAL_STAKED.load(deps.storage)?, - unbond_period: CONFIG.load(deps.storage)?.unbond_period, - reward_pools: REWARD_POOLS - .load(deps.storage)? - .into_iter() - .map( - |RewardPoolInternal { - id, - amount, - start, - end, - token, - rate, - official, - .. - }| RewardPool { - id, - amount, - start, - end, - token, - rate, - official, - }, - ) - .collect(), - }, - }) -} - -pub fn total_staked(deps: Deps) -> StdResult { - Ok(QueryAnswer::TotalStaked { - amount: TOTAL_STAKED.load(deps.storage)?, - }) -} - -pub fn reward_tokens(deps: Deps) -> StdResult { - Ok(QueryAnswer::RewardTokens { - tokens: REWARD_TOKENS - .load(deps.storage)? - .iter() - .map(|contract| contract.address.clone()) - .collect(), - }) -} - -pub fn reward_pools(deps: Deps) -> StdResult { - Ok(QueryAnswer::RewardPools { - rewards: REWARD_POOLS - .load(deps.storage)? - .into_iter() - .map( - |RewardPoolInternal { - id, - amount, - start, - end, - token, - rate, - official, - .. - }| RewardPool { - id, - amount, - start, - end, - token, - rate, - official, - }, - ) - .collect(), - }) -} - -pub fn user_balance( - deps: Deps, - env: Env, - user: Addr, - unbonding_ids: Vec, -) -> StdResult { - let mut unbondings = vec![]; - - for unbonding_id in unbonding_ids.iter() { - if let Some(unbonding) = USER_UNBONDING.may_load( - deps.storage, - user_unbonding_key(user.clone(), *unbonding_id), - )? { - unbondings.push(unbonding); - } else { - return Err(StdError::generic_err(format!( - "Bad Unbonding ID {}", - unbonding_id - ))); - } - } - - let mut rewards = vec![]; - - if let Some(user_staked) = USER_STAKED.may_load(deps.storage, user.clone())? { - if user_staked.is_zero() { - return Ok(QueryAnswer::Balance { - staked: user_staked, - rewards, - unbondings, - }); - } - let reward_pools = REWARD_POOLS.load(deps.storage)?; - let total_staked = TOTAL_STAKED.load(deps.storage)?; - let now = env.block.time.seconds(); - - for reward_pool in reward_pools { - let user_reward_per_token_paid = USER_REWARD_PER_TOKEN_PAID - .may_load(deps.storage, user_pool_key(user.clone(), reward_pool.id))? - .unwrap_or(Uint128::zero()); - let reward_per_token = reward_per_token(total_staked, now, &reward_pool); - let rewards_earned = - rewards_earned(user_staked, reward_per_token, user_reward_per_token_paid); - if !rewards_earned.is_zero() { - rewards.push(Reward { - token: reward_pool.token, - amount: rewards_earned, - }); - } - } - - Ok(QueryAnswer::Balance { - staked: user_staked, - rewards, - unbondings, - }) - } else { - Ok(QueryAnswer::Balance { - staked: Uint128::zero(), - rewards, - unbondings, - }) - } -} - -pub fn user_staked(deps: Deps, user: Addr) -> StdResult { - Ok(QueryAnswer::Staked { - amount: USER_STAKED - .may_load(deps.storage, user)? - .unwrap_or(Uint128::zero()), - }) -} - -pub fn user_rewards(deps: Deps, env: Env, user: Addr) -> StdResult { - let mut rewards = vec![]; - - if let Some(user_staked) = USER_STAKED.may_load(deps.storage, user.clone())? { - if user_staked.is_zero() { - return Ok(QueryAnswer::Rewards { rewards }); - } - let reward_pools = REWARD_POOLS.load(deps.storage)?; - let total_staked = TOTAL_STAKED.load(deps.storage)?; - let now = env.block.time.seconds(); - - for reward_pool in reward_pools { - let user_reward_per_token_paid = USER_REWARD_PER_TOKEN_PAID - .may_load(deps.storage, user_pool_key(user.clone(), reward_pool.id))? - .unwrap_or(Uint128::zero()); - let reward_per_token = reward_per_token(total_staked, now, &reward_pool); - rewards.push(Reward { - token: reward_pool.token, - amount: rewards_earned(user_staked, reward_per_token, user_reward_per_token_paid), - }); - } - } - - Ok(QueryAnswer::Rewards { rewards }) -} - -pub fn user_unbondings(deps: Deps, user: Addr, ids: Vec) -> StdResult { - let mut unbondings = vec![]; - - for id in ids.iter() { - if let Some(unbonding) = - USER_UNBONDING.may_load(deps.storage, user_unbonding_key(user.clone(), *id))? - { - unbondings.push(unbonding); - } else { - return Err(StdError::generic_err(format!("Bad ID {}", id))); - } - } - - Ok(QueryAnswer::Unbonding { unbondings }) -} diff --git a/contracts/basic_staking/src/storage.rs b/contracts/basic_staking/src/storage.rs deleted file mode 100644 index 23065b4..0000000 --- a/contracts/basic_staking/src/storage.rs +++ /dev/null @@ -1,34 +0,0 @@ -use shade_protocol::{ - basic_staking, - c_std::{Addr, Uint128}, - utils::asset::Contract, -}; - -use shade_protocol::secret_storage_plus::{Item, Map}; - -pub const CONFIG: Item = Item::new("config"); -pub const STAKE_TOKEN: Item = Item::new("stake_token"); -pub const VIEWING_KEY: Item = Item::new("viewing_key"); - -// Whitelist for transferring stake -pub const TRANSFER_WL: Item> = Item::new("transfer_whitelist"); - -pub const TOTAL_STAKED: Item = Item::new("total_stake"); - -pub const REWARD_TOKENS: Item> = Item::new("reward_tokens"); -pub const REWARD_POOLS: Item> = Item::new("reward_pools"); - -pub const USER_STAKED: Map = Map::new("user_stake"); - -pub fn user_unbonding_key(user: Addr, unbond_id: Uint128) -> String { - format!("{}-{}", user, unbond_id) -} -pub const USER_UNBONDING_IDS: Map> = Map::new("user_unbonding_ids"); -pub const USER_UNBONDING: Map = Map::new("user_unbonding"); -pub const MAX_POOL_ID: Item = Item::new("max_pool_id"); - -pub fn user_pool_key(user: Addr, pool_id: Uint128) -> String { - format!("{}-{}", user, pool_id) -} - -pub const USER_REWARD_PER_TOKEN_PAID: Map = Map::new("user_reward_per_token_paid"); diff --git a/contracts/basic_staking/tests/bad_stake_token.rs b/contracts/basic_staking/tests/bad_stake_token.rs deleted file mode 100644 index b7aede7..0000000 --- a/contracts/basic_staking/tests/bad_stake_token.rs +++ /dev/null @@ -1,347 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -// Add other adapters here as they come -fn bad_stake_token( - stake_amount: Uint128, - unbond_period: Uint128, - reward_amount: Uint128, - reward_start: Uint128, - reward_end: Uint128, -) { - let mut app = App::default(); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let staking_user = Addr::unchecked("staker"); - let reward_user = Addr::unchecked("reward_user"); - - let stake_token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![snip20::InitialBalance { - amount: stake_amount, - address: staking_user.to_string(), - }]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - let reward_token = snip20::InstantiateMsg { - name: "reward_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![ - snip20::InitialBalance { - amount: reward_amount, - address: reward_user.to_string(), - }, - snip20::InitialBalance { - amount: stake_amount, - address: staking_user.to_string(), - }, - ]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - // set staking_user viewing keys - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&stake_token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&reward_token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - // set staking user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // set reward user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - airdrop: None, - stake_token: stake_token.clone().into(), - unbond_period, - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - - // Pre-staking user stake amount - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Stake funds - match (snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: basic_staking.code_hash.clone().into(), - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&reward_token, &mut app, staking_user.clone(), &[])) - { - Ok(_) => panic!("Allowed to stake bad tokens"), - Err(e) => {} - } - - // Post-staking user balance - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "Post-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Register Reward Token - basic_staking::ExecuteMsg::RegisterRewards { - token: reward_token.clone().into(), - padding: None, - } - .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) - .unwrap(); - - // Init Rewards - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: reward_amount, - msg: Some( - to_binary(&basic_staking::Action::Rewards { - start: reward_start, - end: reward_end, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&reward_token, &mut app, reward_user.clone(), &[]) - .unwrap(); - // Stake funds - match (snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&reward_token, &mut app, staking_user.clone(), &[])) - { - Ok(_) => panic!("Allowed to stake bad tokens after registering rewards"), - Err(e) => {} - } -} - -macro_rules! bad_stake_token { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - stake_amount, - unbond_period, - reward_amount, - reward_start, - reward_end, - ) = $value; - bad_stake_token( - stake_amount, - unbond_period, - reward_amount, - reward_start, - reward_end, - ) - } - )* - } -} - -bad_stake_token! { - bad_stake_token_0: ( - Uint128::new(1), // stake_amount - Uint128::new(100), // unbond_period - Uint128::new(100), // reward_amount - Uint128::new(0), // reward_start (0-*) - Uint128::new(100), // reward_end - ), - bad_stake_token_1: ( - Uint128::new(100), - Uint128::new(100), - Uint128::new(1000), - Uint128::new(0), - Uint128::new(100), - ), - bad_stake_token_2: ( - Uint128::new(1000), - Uint128::new(100), - Uint128::new(300), - Uint128::new(0), - Uint128::new(100), - ), - bad_stake_token_3: ( - Uint128::new(10), - Uint128::new(100), - Uint128::new(50000), - Uint128::new(0), - Uint128::new(2500000), - ), - bad_stake_token_4: ( - Uint128::new(1234567), - Uint128::new(10000), - Uint128::new(500), - Uint128::new(0), - Uint128::new(10000), - ), - bad_stake_token_5: ( - Uint128::new(99999999999), - Uint128::new(100), - Uint128::new(8192), - Uint128::new(20), - Uint128::new(8000), - ), -} diff --git a/contracts/basic_staking/tests/end_reward_pool_after_end_claimed.rs b/contracts/basic_staking/tests/end_reward_pool_after_end_claimed.rs deleted file mode 100644 index 7d6b480..0000000 --- a/contracts/basic_staking/tests/end_reward_pool_after_end_claimed.rs +++ /dev/null @@ -1,290 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -#[test] -fn end_reward_pool_after_end() { - let mut app = App::default(); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let staking_user = Addr::unchecked("staker"); - let reward_user = Addr::unchecked("reward_user"); - - let stake_amount = Uint128::new(100); - let reward_start = Uint128::new(100); - let reward_end = Uint128::new(200); - let reward_amount = Uint128::new(100); - let unbond_period = Uint128::zero(); - - let token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![ - snip20::InitialBalance { - amount: stake_amount, - address: staking_user.to_string(), - }, - snip20::InitialBalance { - amount: reward_amount, - address: reward_user.to_string(), - }, - ]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - // set staking_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - // set admin_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, admin_user.clone(), &[]) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - // set staking user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // set reward user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - airdrop: None, - stake_token: token.clone().into(), - unbond_period, - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - - // Pre-staking user stake amount - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Stake funds - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Post-staking user balance - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, stake_amount, "Post-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Init Rewards - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: reward_amount, - msg: Some( - to_binary(&basic_staking::Action::Rewards { - start: reward_start, - end: reward_end, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, reward_user.clone(), &[]) - .unwrap(); - - // Check reward pool - let pool_id = match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); - assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); - assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); - rewards[0].id - } - _ => { - panic!("Staking balance query failed"); - } - }; - - let end_period = (reward_end + Uint128::new(100)); - - // move to middle of emissions period - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(end_period.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - // Claim rewards - basic_staking::ExecuteMsg::Claim { padding: None } - .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // End reward pool - basic_staking::ExecuteMsg::EndRewardPool { - id: pool_id, - force: Some(false), - padding: None, - } - .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) - .unwrap(); - - // Check reward pool was removed - match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - assert_eq!(rewards.len(), 0, "Reward pool removed bc fully claimed"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Admin user that cancelled should receive un-emitted funds - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: admin_user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!( - amount, - Uint128::zero(), - "No pending emissions when cancelled", - ); - } - _ => { - panic!("admin snip20 balance query failed"); - } - }; -} diff --git a/contracts/basic_staking/tests/end_reward_pool_after_end_unclaimed.rs b/contracts/basic_staking/tests/end_reward_pool_after_end_unclaimed.rs deleted file mode 100644 index 97064af..0000000 --- a/contracts/basic_staking/tests/end_reward_pool_after_end_unclaimed.rs +++ /dev/null @@ -1,288 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -#[test] -fn end_reward_pool_after_end() { - let mut app = App::default(); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let staking_user = Addr::unchecked("staker"); - let reward_user = Addr::unchecked("reward_user"); - - let stake_amount = Uint128::new(100); - let reward_start = Uint128::new(100); - let reward_end = Uint128::new(200); - let reward_amount = Uint128::new(100); - let unbond_period = Uint128::zero(); - - let token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![ - snip20::InitialBalance { - amount: stake_amount, - address: staking_user.to_string(), - }, - snip20::InitialBalance { - amount: reward_amount, - address: reward_user.to_string(), - }, - ]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - // set staking_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - // set admin_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, admin_user.clone(), &[]) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - // set staking user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // set reward user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - airdrop: None, - stake_token: token.clone().into(), - unbond_period, - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - - // Pre-staking user stake amount - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Stake funds - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Post-staking user balance - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, stake_amount, "Post-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Init Rewards - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: reward_amount, - msg: Some( - to_binary(&basic_staking::Action::Rewards { - start: reward_start, - end: reward_end, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, reward_user.clone(), &[]) - .unwrap(); - - // Check reward pool - let pool_id = match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); - assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); - assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); - rewards[0].id - } - _ => { - panic!("Staking balance query failed"); - } - }; - - let end_period = (reward_end + Uint128::new(100)); - - // move to middle of emissions period - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(end_period.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - // End reward pool - match (basic_staking::ExecuteMsg::EndRewardPool { - id: pool_id, - force: Some(false), - padding: None, - }) - .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) - { - Ok(_) => panic!("Reward pool cancelled before fully claimed"), - Err(_) => {} - } - - // Check reward pool was not removed - match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - assert_eq!(rewards.len(), 1, "Unclaimed reward pool was not removed"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Admin user that cancelled should receive un-emitted funds - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: admin_user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!( - amount, - Uint128::zero(), - "No pending emissions when cancelled", - ); - } - _ => { - panic!("admin snip20 balance query failed"); - } - }; -} diff --git a/contracts/basic_staking/tests/end_reward_pool_after_end_unclaimed_force.rs b/contracts/basic_staking/tests/end_reward_pool_after_end_unclaimed_force.rs deleted file mode 100644 index 781e273..0000000 --- a/contracts/basic_staking/tests/end_reward_pool_after_end_unclaimed_force.rs +++ /dev/null @@ -1,386 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -#[test] -fn end_reward_pool_after_end() { - let mut app = App::default(); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let staking_user = Addr::unchecked("staker"); - let reward_user = Addr::unchecked("reward_user"); - - let stake_amount = Uint128::new(100); - let reward_start = Uint128::new(100); - let reward_end = Uint128::new(200); - let reward_amount = Uint128::new(100); - let unbond_period = Uint128::zero(); - - let token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![ - snip20::InitialBalance { - amount: stake_amount, - address: staking_user.to_string(), - }, - snip20::InitialBalance { - amount: reward_amount, - address: reward_user.to_string(), - }, - ]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - let second_token = snip20::InstantiateMsg { - name: "reward_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKNB".into(), - decimals: 6, - initial_balances: Some(vec![snip20::InitialBalance { - amount: reward_amount, - address: reward_user.to_string(), - }]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - // set staking_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - // set admin_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, admin_user.clone(), &[]) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - // set staking user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // set reward user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - airdrop: None, - stake_token: token.clone().into(), - unbond_period, - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - - // Pre-staking user stake amount - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Stake funds - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Post-staking user balance - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, stake_amount, "Post-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Init Rewards - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: reward_amount, - msg: Some( - to_binary(&basic_staking::Action::Rewards { - start: reward_start, - end: reward_end, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, reward_user.clone(), &[]) - .unwrap(); - - // Check reward pool - let pool_id = match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); - assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); - assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); - rewards[0].id - } - _ => { - panic!("Staking balance query failed"); - } - }; - - let end_period = (reward_end + Uint128::new(100)); - - // move to middle of emissions period - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(end_period.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - // End reward pool - basic_staking::ExecuteMsg::EndRewardPool { - id: pool_id, - force: Some(true), - padding: None, - } - .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) - .unwrap(); - - // Check reward pool was removed - match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - assert_eq!(rewards.len(), 0, "Unclaimed reward pool force removed"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Admin user that cancelled should receive un-emitted funds - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: admin_user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!( - amount, reward_amount, - "pending emissions when cancelled sent to admin", - ); - } - _ => { - panic!("admin snip20 balance query failed"); - } - }; - - // Register Reward Token - basic_staking::ExecuteMsg::RegisterRewards { - token: second_token.clone().into(), - padding: None, - } - .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) - .unwrap(); - - let second_reward_start = Uint128::new(10000); - let second_reward_end = Uint128::new(100000); - - // Setup new rewards with new token - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: reward_amount, - msg: Some( - to_binary(&basic_staking::Action::Rewards { - start: second_reward_start, - end: second_reward_end, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&second_token, &mut app, reward_user.clone(), &[]) - .unwrap(); - - // Check reward pool - match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - println!("{:?}", rewards); - assert_eq!(rewards.len(), 1, "Second reward pool exists"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(second_reward_end.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - // Check queries work - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - println!("Rewards {:?}", rewards); - assert_eq!(rewards.len(), 1, "Second rewards pool"); - // panic!("OOF"); - } - _ => { - panic!("Staking balance query failed"); - } - }; -} diff --git a/contracts/basic_staking/tests/end_reward_pool_before_end_claimed.rs b/contracts/basic_staking/tests/end_reward_pool_before_end_claimed.rs deleted file mode 100644 index 33ca7bb..0000000 --- a/contracts/basic_staking/tests/end_reward_pool_before_end_claimed.rs +++ /dev/null @@ -1,291 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -#[test] -fn end_reward_pool_before_end_claimed() { - let mut app = App::default(); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let staking_user = Addr::unchecked("staker"); - let reward_user = Addr::unchecked("reward_user"); - - let stake_amount = Uint128::new(100); - let reward_start = Uint128::new(100); - let reward_end = Uint128::new(200); - let reward_amount = Uint128::new(100); - let unbond_period = Uint128::zero(); - - let token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![ - snip20::InitialBalance { - amount: stake_amount, - address: staking_user.to_string(), - }, - snip20::InitialBalance { - amount: reward_amount, - address: reward_user.to_string(), - }, - ]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - // set staking_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - // set admin_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, admin_user.clone(), &[]) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - // set staking user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // set reward user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - airdrop: None, - stake_token: token.clone().into(), - unbond_period, - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - - // Pre-staking user stake amount - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Stake funds - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Post-staking user balance - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, stake_amount, "Post-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Init Rewards - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: reward_amount, - msg: Some( - to_binary(&basic_staking::Action::Rewards { - start: reward_start, - end: reward_end, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, reward_user.clone(), &[]) - .unwrap(); - - // Check reward pool - let pool_id = match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); - assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); - assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); - rewards[0].id - } - _ => { - panic!("Staking balance query failed"); - } - }; - - let mid_period = reward_start + ((reward_end - reward_start) / Uint128::new(2)); - println!("mid period {}", mid_period); - - // move to middle of emissions period - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(mid_period.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - // Claim rewards - basic_staking::ExecuteMsg::Claim { padding: None } - .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // End reward pool - basic_staking::ExecuteMsg::EndRewardPool { - id: pool_id, - force: Some(false), - padding: None, - } - .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) - .unwrap(); - - // Check reward pool was removed - match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - assert_eq!(rewards.len(), 0, "Reward pool removed bc fully claimed"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Admin user that cancelled should receive un-emitted funds - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: admin_user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!( - amount, - reward_amount / Uint128::new(2), - "Cancelled rewards sent to cancelling admin user" - ); - } - _ => { - panic!("admin snip20 balance query failed"); - } - }; -} diff --git a/contracts/basic_staking/tests/end_reward_pool_before_end_unclaimed.rs b/contracts/basic_staking/tests/end_reward_pool_before_end_unclaimed.rs deleted file mode 100644 index 46e223b..0000000 --- a/contracts/basic_staking/tests/end_reward_pool_before_end_unclaimed.rs +++ /dev/null @@ -1,291 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -#[test] -fn end_reward_pool_before_end_unclaimed() { - let mut app = App::default(); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let staking_user = Addr::unchecked("staker"); - let reward_user = Addr::unchecked("reward_user"); - - let stake_amount = Uint128::new(100); - let reward_start = Uint128::new(100); - let reward_end = Uint128::new(200); - let reward_amount = Uint128::new(100); - let unbond_period = Uint128::zero(); - - let token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![ - snip20::InitialBalance { - amount: stake_amount, - address: staking_user.to_string(), - }, - snip20::InitialBalance { - amount: reward_amount, - address: reward_user.to_string(), - }, - ]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - // set staking_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - // set admin_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, admin_user.clone(), &[]) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - // set staking user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // set reward user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - airdrop: None, - stake_token: token.clone().into(), - unbond_period, - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - - // Pre-staking user stake amount - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Stake funds - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Post-staking user balance - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, stake_amount, "Post-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Init Rewards - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: reward_amount, - msg: Some( - to_binary(&basic_staking::Action::Rewards { - start: reward_start, - end: reward_end, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, reward_user.clone(), &[]) - .unwrap(); - - // Check reward pool - let pool_id = match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); - assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); - assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); - rewards[0].id - } - _ => { - panic!("Staking balance query failed"); - } - }; - - let mid_period = reward_start + ((reward_end - reward_start) / Uint128::new(2)); - println!("mid period {}", mid_period); - - // move to middle of emissions period - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(mid_period.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - // End reward pool - basic_staking::ExecuteMsg::EndRewardPool { - id: pool_id, - force: Some(false), - padding: None, - } - .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) - .unwrap(); - - // Check reward pool was removed - match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - assert_eq!(rewards.len(), 1, "Reward pool remains until fully claimed"); - assert_eq!( - rewards[0].amount, - reward_amount / Uint128::new(2), - "Reward pool half remaining", - ); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Admin user that cancelled should receive un-emitted funds - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: admin_user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!( - amount, - reward_amount / Uint128::new(2), - "Cancelled rewards sent to cancelling admin user" - ); - } - _ => { - panic!("admin snip20 balance query failed"); - } - }; -} diff --git a/contracts/basic_staking/tests/end_reward_pool_before_start.rs b/contracts/basic_staking/tests/end_reward_pool_before_start.rs deleted file mode 100644 index e15b12b..0000000 --- a/contracts/basic_staking/tests/end_reward_pool_before_start.rs +++ /dev/null @@ -1,273 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -// Add other adapters here as they come -#[test] -fn end_reward_pool_before_start() { - let mut app = App::default(); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let staking_user = Addr::unchecked("staker"); - let reward_user = Addr::unchecked("reward_user"); - - let stake_amount = Uint128::new(100); - let reward_start = Uint128::new(100); - let reward_end = Uint128::new(200); - let reward_amount = Uint128::new(100); - let unbond_period = Uint128::zero(); - - let token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![ - snip20::InitialBalance { - amount: stake_amount, - address: staking_user.to_string(), - }, - snip20::InitialBalance { - amount: reward_amount, - address: reward_user.to_string(), - }, - ]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - // set staking_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - // set admin_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, admin_user.clone(), &[]) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - // set staking user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // set reward user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - airdrop: None, - stake_token: token.clone().into(), - unbond_period, - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - - // Pre-staking user stake amount - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Stake funds - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Post-staking user balance - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, stake_amount, "Post-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Init Rewards - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: reward_amount, - msg: Some( - to_binary(&basic_staking::Action::Rewards { - start: reward_start, - end: reward_end, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, reward_user.clone(), &[]) - .unwrap(); - - // Check reward pool - let pool_id = match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); - assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); - assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); - rewards[0].id - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // End reward pool - basic_staking::ExecuteMsg::EndRewardPool { - id: pool_id, - force: Some(false), - padding: None, - } - .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) - .unwrap(); - - // Check reward pool was removed - match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - assert_eq!(rewards.len(), 0, "No reward pools after cancelling"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Admin user that cancelled should receive un-emitted funds - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: admin_user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, reward_amount, "Rewards cancelled to admin user"); - } - _ => { - panic!("admin snip20 balance query failed"); - } - }; -} diff --git a/contracts/basic_staking/tests/multi_staker.rs b/contracts/basic_staking/tests/multi_staker.rs deleted file mode 100644 index d31f047..0000000 --- a/contracts/basic_staking/tests/multi_staker.rs +++ /dev/null @@ -1,523 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -// Add other adapters here as they come -fn multi_staker_single_pool( - stake_amounts: Vec, - unbond_period: Uint128, - reward_amount: Uint128, - reward_start: Uint128, - reward_end: Uint128, - expected_rewards: Vec, -) { - let mut app = App::default(); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let reward_user = Addr::unchecked("reward_user"); - let staking_users = stake_amounts - .iter() - .enumerate() - .map(|(i, x)| Addr::unchecked(format!("staker-{}", i))) - .collect::>(); - - let mut initial_balances = staking_users - .iter() - .zip(stake_amounts.clone().into_iter()) - .map(|(user, amount)| snip20::InitialBalance { - amount, - address: user.to_string(), - }) - .collect::>(); - - initial_balances.push(snip20::InitialBalance { - amount: reward_amount, - address: reward_user.to_string(), - }); - - let token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(initial_balances), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - // set staking_users viewing key - for user in staking_users.clone().into_iter() { - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, user.clone(), &[]) - .unwrap(); - - // set staking user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, user.clone(), &[]) - .unwrap(); - } - - // set reward user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - airdrop: None, - stake_token: token.clone().into(), - unbond_period, - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - println!("BASIC STAKING {}", basic_staking.address); - - for (user, stake_amount) in staking_users.iter().zip(stake_amounts.clone().into_iter()) { - // Pre-staking user balance - match (basic_staking::QueryMsg::Staked { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Staked { amount } => { - assert_eq!(amount, Uint128::zero(), "Pre-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Stake funds - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, user.clone(), &[]) - .unwrap(); - - // Post-staking user balance - match (basic_staking::QueryMsg::Staked { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Staked { amount } => { - assert_eq!(amount, stake_amount, "Post-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - } - - // Init Rewards - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: reward_amount, - msg: Some( - to_binary(&basic_staking::Action::Rewards { - start: reward_start, - end: reward_end, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, reward_user.clone(), &[]) - .unwrap(); - - // reward user has no stake - match (basic_staking::QueryMsg::Staked { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: reward_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Staked { amount } => { - assert_eq!(amount, Uint128::zero(), "Reward User Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Check reward pool - match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); - assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); - assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Move forward to reward start - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(reward_start.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - for user in staking_users.iter() { - // No rewards should be pending - match (basic_staking::QueryMsg::Rewards { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Rewards { rewards } => { - assert_eq!(rewards.len(), 1, "Rewards length at beginning"); - assert_eq!( - rewards[0].amount, - Uint128::zero(), - "Rewards claimable at beginning" - ); - } - _ => { - panic!("Staking rewards query failed"); - } - }; - } - - let reward_duration = reward_end - reward_start; - - // Move to middle of reward period - println!("Fast-forward to reward middle"); - app.set_block(BlockInfo { - height: 2, - time: Timestamp::from_seconds((reward_start.u128() + reward_duration.u128() / 2) as u64), - chain_id: "chain_id".to_string(), - }); - - for (user, reward) in staking_users - .iter() - .zip(expected_rewards.clone().into_iter()) - { - // Half-ish rewards should be pending - match (basic_staking::QueryMsg::Rewards { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Rewards { rewards } => { - assert_eq!(rewards.len(), 1, "Rewards length in the middle"); - let amount = rewards[0].amount; - let expected = reward / Uint128::new(2); - assert!( - amount >= expected - Uint128::one() && amount <= expected, - "Rewards claimable in the middle within error of 1 unit token {} != {}", - amount, - expected - ); - } - _ => { - panic!("Staking rewards query failed"); - } - }; - } - - // Move to end of rewards - println!("Fast-forward to reward end"); - app.set_block(BlockInfo { - height: 3, - time: Timestamp::from_seconds(reward_end.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - for ((user, amount), reward) in staking_users - .iter() - .zip(stake_amounts.clone().into_iter()) - .zip(expected_rewards.clone().into_iter()) - { - // All rewards should be pending - match (basic_staking::QueryMsg::Rewards { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Rewards { rewards } => { - assert_eq!(rewards.len(), 1, "Rewards length in the middle"); - let amount = rewards[0].amount; - assert!( - amount >= reward - Uint128::one() && amount <= reward, - "Rewards claimable at the end within error of 1 unit token {} != {}", - amount, - reward, - ); - } - _ => { - panic!("Staking rewards query failed"); - } - }; - - // Claim rewards - basic_staking::ExecuteMsg::Claim { padding: None } - .test_exec(&basic_staking, &mut app, user.clone(), &[]) - .unwrap(); - - // Check rewards were claimed - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert!( - amount >= reward - Uint128::one() && amount <= reward, - "Rewards claimed at the end within error of 1 unit token {} != {}", - amount, - reward, - ); - } - _ => { - panic!("Snip20 balance query failed"); - } - }; - - // Unbond - basic_staking::ExecuteMsg::Unbond { - amount, - compound: None, - padding: None, - } - .test_exec(&basic_staking, &mut app, user.clone(), &[]) - .unwrap(); - - // All funds should be unbonding - match (basic_staking::QueryMsg::Unbonding { - ids: None, - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Unbonding { unbondings } => { - assert_eq!(unbondings.len(), 1, "1 unbonding"); - assert_eq!(unbondings[0].amount, amount, "Unbonding full amount"); - assert_eq!( - unbondings[0].complete, - reward_end + unbond_period, - "Unbonding complete expectedt" - ); - } - _ => { - panic!("Staking unbonding query failed"); - } - }; - } - - println!("Fast-forward to end of unbonding period"); - app.set_block(BlockInfo { - height: 10, - time: Timestamp::from_seconds((reward_end + unbond_period).u128() as u64), - chain_id: "chain_id".to_string(), - }); - - for ((user, stake_amount), reward) in staking_users - .iter() - .zip(stake_amounts.clone().into_iter()) - .zip(expected_rewards.clone().into_iter()) - { - // Withdraw unbonding - basic_staking::ExecuteMsg::Withdraw { - ids: None, - padding: None, - } - .test_exec(&basic_staking, &mut app, user.clone(), &[]) - .unwrap(); - - // Check unbonding withdrawn - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - let expected = stake_amount + reward; - assert!( - amount >= expected - Uint128::one() && amount <= expected, - "Final user balance within error of 1 unit token {} != {}", - amount, - expected, - ); - } - _ => { - panic!("Snip20 balance query failed"); - } - }; - } -} - -macro_rules! multi_staker_single_pool { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - stake_amounts, - unbond_period, - reward_amount, - reward_start, - reward_end, - expected_rewards, - ) = $value; - multi_staker_single_pool( - stake_amounts, - unbond_period, - reward_amount, - reward_start, - reward_end, - expected_rewards, - ) - } - )* - } -} - -multi_staker_single_pool! { - multi_staker_single_pool_0: ( - vec![ // stake_amount - Uint128::new(1), - Uint128::new(10), - ], - Uint128::new(100), // unbond_period - Uint128::new(100), // reward_amount - Uint128::new(0), // reward_start (0-*) - Uint128::new(100), // reward_end - vec![ // expected rewards - Uint128::new(10), - Uint128::new(90), - ], - ), - multi_staker_single_pool_1: ( - vec![ // stake_amount - Uint128::new(33), - Uint128::new(33), - Uint128::new(34), - ], - Uint128::new(100), // unbond_period - Uint128::new(100), // reward_amount - Uint128::new(0), // reward_start (0-*) - Uint128::new(100), // reward_end - vec![ // expected rewards - Uint128::new(33), - Uint128::new(33), - Uint128::new(34), - ], - ), -} diff --git a/contracts/basic_staking/tests/non_admin_access.rs b/contracts/basic_staking/tests/non_admin_access.rs deleted file mode 100644 index b00972a..0000000 --- a/contracts/basic_staking/tests/non_admin_access.rs +++ /dev/null @@ -1,186 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -#[test] -fn non_admin_access() { - let mut app = App::default(); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let non_admin_user = Addr::unchecked("nonadmin"); - let token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - airdrop: None, - stake_token: token.clone().into(), - unbond_period: Uint128::zero(), - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - - match (basic_staking::ExecuteMsg::UpdateConfig { - admin_auth: None, - query_auth: None, - airdrop: None, - unbond_period: None, - max_user_pools: None, - padding: None, - } - .test_exec(&basic_staking, &mut app, non_admin_user.clone(), &[])) - { - Ok(_) => panic!("Register Rewards by non-admin"), - Err(e) => { - assert!( - e.source() - .unwrap() - .to_string() - .contains("unregistered_admin"), - "Not an admin error" - ); - } - } - - match (basic_staking::ExecuteMsg::RegisterRewards { - token: RawContract { - address: "any_token".to_string(), - code_hash: "any_hash".to_string(), - }, - padding: None, - } - .test_exec(&basic_staking, &mut app, non_admin_user.clone(), &[])) - { - Ok(_) => panic!("Register Rewards by non-admin"), - Err(e) => { - assert!( - e.source() - .unwrap() - .to_string() - .contains("unregistered_admin"), - "Not an admin error" - ); - } - } - - match (basic_staking::ExecuteMsg::EndRewardPool { - id: Uint128::zero(), - force: None, - padding: None, - } - .test_exec(&basic_staking, &mut app, non_admin_user.clone(), &[])) - { - Ok(_) => panic!("End reward pool by non-admin"), - Err(e) => { - assert!( - e.source() - .unwrap() - .to_string() - .contains("unregistered_admin"), - "Not an admin error" - ); - } - } - - match (basic_staking::ExecuteMsg::AddTransferWhitelist { - user: "any_user".to_string(), - padding: None, - } - .test_exec(&basic_staking, &mut app, non_admin_user.clone(), &[])) - { - Ok(_) => panic!("End reward pool by non-admin"), - Err(e) => { - assert!( - e.source() - .unwrap() - .to_string() - .contains("unregistered_admin"), - "Not an admin error" - ); - } - } - - match (basic_staking::ExecuteMsg::RemoveTransferWhitelist { - user: "any_user".to_string(), - padding: None, - } - .test_exec(&basic_staking, &mut app, non_admin_user.clone(), &[])) - { - Ok(_) => panic!("End reward pool by non-admin"), - Err(e) => { - assert!( - e.source() - .unwrap() - .to_string() - .contains("unregistered_admin"), - "Not an admin error" - ); - } - } -} diff --git a/contracts/basic_staking/tests/non_stake_rewards.rs b/contracts/basic_staking/tests/non_stake_rewards.rs deleted file mode 100644 index 4d0bd03..0000000 --- a/contracts/basic_staking/tests/non_stake_rewards.rs +++ /dev/null @@ -1,610 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -// Add other adapters here as they come -fn non_stake_rewards( - stake_amount: Uint128, - unbond_period: Uint128, - reward_amount: Uint128, - reward_start: Uint128, - reward_end: Uint128, -) { - let mut app = App::default(); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let staking_user = Addr::unchecked("staker"); - let reward_user = Addr::unchecked("reward_user"); - - let stake_token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![snip20::InitialBalance { - amount: stake_amount, - address: staking_user.to_string(), - }]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - let reward_token = snip20::InstantiateMsg { - name: "reward_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![snip20::InitialBalance { - amount: reward_amount, - address: reward_user.to_string(), - }]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - // set staking_user viewing keys - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&stake_token, &mut app, staking_user.clone(), &[]) - .unwrap(); - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&reward_token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - // set staking user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // set reward user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - airdrop: None, - stake_token: stake_token.clone().into(), - unbond_period, - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - println!("BASIC STAKING {}", basic_staking.address); - - // Pre-staking user stake amount - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Stake funds - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&stake_token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Post-staking user balance - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, stake_amount, "Post-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Register Reward Token - basic_staking::ExecuteMsg::RegisterRewards { - token: reward_token.clone().into(), - padding: None, - } - .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) - .unwrap(); - - // Init Rewards - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: reward_amount, - msg: Some( - to_binary(&basic_staking::Action::Rewards { - start: reward_start, - end: reward_end, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&reward_token, &mut app, reward_user.clone(), &[]) - .unwrap(); - - // reward user has no stake - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: reward_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "Reward User Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Check reward pool - match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); - assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); - assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Move forward to reward start - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(reward_start.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - // No rewards should be pending - match (basic_staking::QueryMsg::Rewards { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Rewards { rewards } => { - assert_eq!(rewards.len(), 1, "Rewards length at beginning"); - assert_eq!( - rewards[0].amount, - Uint128::zero(), - "Rewards claimable at beginning" - ); - } - _ => { - panic!("Staking rewards query failed"); - } - }; - - let reward_duration = reward_end - reward_start; - - // Move to middle of reward period - println!("Fast-forward to reward middle"); - app.set_block(BlockInfo { - height: 2, - time: Timestamp::from_seconds((reward_start.u128() + reward_duration.u128() / 2) as u64), - chain_id: "chain_id".to_string(), - }); - - // Half-ish rewards should be pending - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(rewards.len(), 1, "rewards length in the middle"); - let amount = rewards[0].amount; - let expected = reward_amount / Uint128::new(2); - assert!( - amount >= expected - Uint128::one() && amount <= expected, - "Rewards claimable in the middle within error of 1 unit token {} != {}", - amount, - expected - ); - } - _ => { - panic!("Staking rewards query failed"); - } - }; - - // Move to end of rewards - println!("Fast-forward to reward end"); - app.set_block(BlockInfo { - height: 3, - time: Timestamp::from_seconds(reward_end.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - // All rewards should be pending - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(rewards.len(), 1, "rewards length at end"); - let amount = rewards[0].amount; - assert!( - amount >= reward_amount - Uint128::one() && amount <= reward_amount, - "Rewards claimable at the end within error of 1 unit token {} != {}", - amount, - reward_amount, - ); - } - _ => { - panic!("Staking rewards query failed"); - } - }; - - // Claim rewards - basic_staking::ExecuteMsg::Compound { padding: None } - .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Check rewards were received - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }) - .test_query(&reward_token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert!( - amount >= reward_amount - Uint128::one() && amount <= reward_amount, - "Rewards claimed at the end within error of 1 unit token {} != {}", - amount, - reward_amount, - ); - } - _ => { - panic!("Snip20 balance query failed"); - } - }; - - // Unbond - basic_staking::ExecuteMsg::Unbond { - amount: stake_amount, - compound: None, - padding: None, - } - .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) - .unwrap(); - - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "0 staked after unbonding"); - assert_eq!(rewards, vec![], "0 rewards after unbonding"); - - assert_eq!(unbondings.len(), 1, "1 unbonding"); - assert_eq!(unbondings[0].amount, stake_amount, "Unbonding full amount"); - assert_eq!( - unbondings[0].complete, - reward_end + unbond_period, - "Unbonding complete expectedt" - ); - } - _ => { - panic!("Staking unbonding query failed"); - } - }; - println!("Fast-forward to end of unbonding period"); - app.set_block(BlockInfo { - height: 10, - time: Timestamp::from_seconds((reward_end + unbond_period).u128() as u64), - chain_id: "chain_id".to_string(), - }); - - basic_staking::ExecuteMsg::Withdraw { - ids: None, - padding: None, - } - .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) - .unwrap(); - - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "Final Staked Balance"); - assert_eq!(rewards, vec![], "Final Rewards"); - assert_eq!(unbondings, vec![], "Final Unbondings"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Check user stake token balance - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }) - .test_query(&stake_token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!( - amount, stake_amount, - "Final user balance of stake token {}, expected {}", - amount, stake_amount, - ); - } - _ => { - panic!("Snip20 balance query failed"); - } - }; - - // Check user reward token balance - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }) - .test_query(&reward_token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert!( - amount >= reward_amount - Uint128::one() - && amount <= reward_amount + Uint128::one(), - "Final user balance of reward token {}, expected {}", - amount, - reward_amount, - ); - } - _ => { - panic!("Snip20 balance query failed"); - } - }; -} - -macro_rules! non_stake_rewards { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - stake_amount, - unbond_period, - reward_amount, - reward_start, - reward_end, - ) = $value; - non_stake_rewards( - stake_amount, - unbond_period, - reward_amount, - reward_start, - reward_end, - ) - } - )* - } -} - -non_stake_rewards! { - non_stake_rewards_0: ( - Uint128::new(1), // stake_amount - Uint128::new(100), // unbond_period - Uint128::new(100), // reward_amount - Uint128::new(0), // reward_start (0-*) - Uint128::new(100), // reward_end - ), - non_stake_rewards_1: ( - Uint128::new(100), - Uint128::new(100), - Uint128::new(1000), - Uint128::new(0), - Uint128::new(100), - ), - non_stake_rewards_2: ( - Uint128::new(1000), - Uint128::new(100), - Uint128::new(300), - Uint128::new(0), - Uint128::new(100), - ), - non_stake_rewards_3: ( - Uint128::new(10), - Uint128::new(100), - Uint128::new(50000), - Uint128::new(0), - Uint128::new(2500000), - ), - non_stake_rewards_4: ( - Uint128::new(1234567), - Uint128::new(10000), - Uint128::new(500), - Uint128::new(0), - Uint128::new(10000), - ), - non_stake_rewards_5: ( - Uint128::new(99999999999), - Uint128::new(100), - Uint128::new(8192), - Uint128::new(20), - Uint128::new(8000), - ), -} diff --git a/contracts/basic_staking/tests/single_staker.rs b/contracts/basic_staking/tests/single_staker.rs deleted file mode 100644 index 01d6e90..0000000 --- a/contracts/basic_staking/tests/single_staker.rs +++ /dev/null @@ -1,593 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -// Add other adapters here as they come -fn single_staker_single_pool( - stake_amount: Uint128, - unbond_period: Uint128, - reward_amount: Uint128, - reward_start: Uint128, - reward_end: Uint128, -) { - let mut app = App::default(); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let staking_user = Addr::unchecked("staker"); - let reward_user = Addr::unchecked("reward_user"); - - let token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![ - snip20::InitialBalance { - amount: stake_amount, - address: staking_user.to_string(), - }, - snip20::InitialBalance { - amount: reward_amount, - address: reward_user.to_string(), - }, - ]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - // set staking_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - // set staking user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // set reward user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - airdrop: None, - stake_token: token.clone().into(), - unbond_period, - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - println!("BASIC STAKING {}", basic_staking.address); - - // Pre-staking user stake amount - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "Pre-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Stake funds - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Post-staking user balance - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, stake_amount, "Post-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Post staking total staked - match (basic_staking::QueryMsg::TotalStaked {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::TotalStaked { amount } => { - assert_eq!(amount, stake_amount, "Post staking total staked"); - } - _ => { - panic!("Total Staked query failed"); - } - }; - - // Verify stake token - match (basic_staking::QueryMsg::StakeToken {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::StakeToken { token: stake_token } => { - assert_eq!(stake_token, token.address); - } - _ => { - panic!("Total Staked query failed"); - } - }; - - // Stake token should be registered - match (basic_staking::QueryMsg::RewardTokens {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardTokens { tokens } => { - assert_eq!(tokens.len(), 1, "Only stake token registered"); - assert_eq!(tokens[0], token.address); - } - _ => { - panic!("Total Staked query failed"); - } - }; - - // Init Rewards - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: reward_amount, - msg: Some( - to_binary(&basic_staking::Action::Rewards { - start: reward_start, - end: reward_end, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, reward_user.clone(), &[]) - .unwrap(); - - // reward user has no stake - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: reward_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "Reward User Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Check reward pool - match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); - assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); - assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Move forward to reward start - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(reward_start.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - // No rewards should be pending - match (basic_staking::QueryMsg::Rewards { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Rewards { rewards } => { - assert_eq!(rewards.len(), 1, "Rewards length at beginning"); - assert_eq!( - rewards[0].amount, - Uint128::zero(), - "Rewards claimable at beginning" - ); - } - _ => { - panic!("Staking rewards query failed"); - } - }; - - let reward_duration = reward_end - reward_start; - - // Move to middle of reward period - println!("Fast-forward to reward middle"); - app.set_block(BlockInfo { - height: 2, - time: Timestamp::from_seconds((reward_start.u128() + reward_duration.u128() / 2) as u64), - chain_id: "chain_id".to_string(), - }); - - // Half-ish rewards should be pending - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(rewards.len(), 1, "rewards length in the middle"); - let amount = rewards[0].amount; - let expected = reward_amount / Uint128::new(2); - assert!( - amount >= expected - Uint128::one() && amount <= expected, - "Rewards claimable in the middle within error of 1 unit token {} != {}", - amount, - expected - ); - } - _ => { - panic!("Staking rewards query failed"); - } - }; - - // Move to end of rewards - println!("Fast-forward to reward end"); - app.set_block(BlockInfo { - height: 3, - time: Timestamp::from_seconds(reward_end.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - // All rewards should be pending - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(rewards.len(), 1, "rewards length at end"); - let amount = rewards[0].amount; - assert!( - amount >= reward_amount - Uint128::one() && amount <= reward_amount, - "Rewards claimable at the end within error of 1 unit token {} != {}", - amount, - reward_amount, - ); - } - _ => { - panic!("Staking rewards query failed"); - } - }; - - // Claim rewards - basic_staking::ExecuteMsg::Claim { padding: None } - .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Check rewards were received - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert!( - amount >= reward_amount - Uint128::one() && amount <= reward_amount, - "Rewards claimed at the end within error of 1 unit token {} != {}", - amount, - reward_amount, - ); - } - _ => { - panic!("Snip20 balance query failed"); - } - }; - - // Unbond - basic_staking::ExecuteMsg::Unbond { - amount: stake_amount, - compound: None, - padding: None, - } - .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) - .unwrap(); - - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "0 staked after unbonding"); - assert_eq!(rewards, vec![], "0 rewards after unbonding"); - - assert_eq!(unbondings.len(), 1, "1 unbonding"); - assert_eq!(unbondings[0].amount, stake_amount, "Unbonding full amount"); - assert_eq!( - unbondings[0].complete, - reward_end + unbond_period, - "Unbonding complete expectedt" - ); - } - _ => { - panic!("Staking unbonding query failed"); - } - }; - println!("Fast-forward to end of unbonding period"); - app.set_block(BlockInfo { - height: 10, - time: Timestamp::from_seconds((reward_end + unbond_period).u128() as u64), - chain_id: "chain_id".to_string(), - }); - - basic_staking::ExecuteMsg::Withdraw { - ids: None, - padding: None, - } - .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) - .unwrap(); - - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "Final Staked Balance"); - assert_eq!(rewards, vec![], "Final Rewards"); - assert_eq!(unbondings, vec![], "Final Unbondings"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Check snip20 received by user - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - let expected = stake_amount + reward_amount; - assert!( - amount >= expected - Uint128::one() && amount <= expected, - "Final user balance within error of 1 unit token {} != {}", - amount, - expected, - ); - } - _ => { - panic!("Snip20 balance query failed"); - } - }; -} - -macro_rules! single_staker_single_pool { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - stake_amount, - unbond_period, - reward_amount, - reward_start, - reward_end, - ) = $value; - single_staker_single_pool( - stake_amount, - unbond_period, - reward_amount, - reward_start, - reward_end, - ) - } - )* - } -} - -single_staker_single_pool! { - single_staker_single_pool_0: ( - Uint128::new(1), // stake_amount - Uint128::new(100), // unbond_period - Uint128::new(100), // reward_amount - Uint128::new(0), // reward_start (0-*) - Uint128::new(100), // reward_end - ), - single_staker_single_pool_1: ( - Uint128::new(100), - Uint128::new(100), - Uint128::new(1000), - Uint128::new(0), - Uint128::new(100), - ), - single_staker_single_pool_2: ( - Uint128::new(1000), - Uint128::new(100), - Uint128::new(300), - Uint128::new(0), - Uint128::new(100), - ), - single_staker_single_pool_3: ( - Uint128::new(10), - Uint128::new(100), - Uint128::new(50000), - Uint128::new(0), - Uint128::new(2500000), - ), - single_staker_single_pool_4: ( - Uint128::new(1234567), - Uint128::new(10000), - Uint128::new(500), - Uint128::new(0), - Uint128::new(10000), - ), - single_staker_single_pool_5: ( - Uint128::new(99999999999), - Uint128::new(100), - Uint128::new(8192), - Uint128::new(20), - Uint128::new(8000), - ), -} diff --git a/contracts/basic_staking/tests/single_staker_compounding.rs b/contracts/basic_staking/tests/single_staker_compounding.rs deleted file mode 100644 index 2eed6ed..0000000 --- a/contracts/basic_staking/tests/single_staker_compounding.rs +++ /dev/null @@ -1,588 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -// Add other adapters here as they come -fn single_staker_compounding( - stake_amount: Uint128, - unbond_period: Uint128, - reward_amount: Uint128, - reward_start: Uint128, - reward_end: Uint128, -) { - let mut app = App::default(); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let staking_user = Addr::unchecked("staker"); - let reward_user = Addr::unchecked("reward_user"); - - let token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![ - snip20::InitialBalance { - amount: stake_amount, - address: staking_user.to_string(), - }, - snip20::InitialBalance { - amount: reward_amount, - address: reward_user.to_string(), - }, - ]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - // set staking_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - // set staking user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // set reward user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - airdrop: None, - stake_token: token.clone().into(), - unbond_period, - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - println!("BASIC STAKING {}", basic_staking.address); - - // Pre-staking user balance - match (basic_staking::QueryMsg::Staked { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Staked { amount } => { - assert_eq!(amount, Uint128::zero(), "Pre-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Stake half funds (half after some rewards to compound on stake) - let first_amount = stake_amount / Uint128::new(2); - let second_amount = stake_amount - first_amount; - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: first_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Post-staking user balance - match (basic_staking::QueryMsg::Staked { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Staked { amount } => { - assert_eq!(amount, first_amount, "Post First Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Init Rewards - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: reward_amount, - msg: Some( - to_binary(&basic_staking::Action::Rewards { - start: reward_start, - end: reward_end, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, reward_user.clone(), &[]) - .unwrap(); - - // reward user has no stake - match (basic_staking::QueryMsg::Staked { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: reward_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Staked { amount } => { - assert_eq!(amount, Uint128::zero(), "Reward User Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Check reward pool - match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - println!("init rewards {:?}", rewards); - assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); - assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); - assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Move forward to reward start - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(reward_start.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - // No rewards should be pending - match (basic_staking::QueryMsg::Rewards { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Rewards { rewards } => { - assert_eq!(rewards.len(), 1, "Rewards length at beginning"); - assert_eq!( - rewards[0].amount, - Uint128::zero(), - "Rewards claimable at beginning" - ); - } - _ => { - panic!("Staking rewards query failed"); - } - }; - - let reward_duration = reward_end - reward_start; - - // Move to middle of reward period - println!("Fast-forward to reward middle"); - app.set_block(BlockInfo { - height: 2, - time: Timestamp::from_seconds((reward_start.u128() + reward_duration.u128() / 2) as u64), - chain_id: "chain_id".to_string(), - }); - - let expected_mid_rewards = reward_amount / Uint128::new(2); - let mut mid_rewards = Uint128::zero(); - - // Half-ish rewards should be pending - match (basic_staking::QueryMsg::Rewards { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Rewards { rewards } => { - assert_eq!(rewards.len(), 1, "rewards length in the middle"); - let amount = rewards[0].amount; - mid_rewards = amount; - assert!( - amount >= expected_mid_rewards - Uint128::one() && amount <= expected_mid_rewards, - "Rewards claimable in the middle within error of 1 unit token {} != {}", - amount, - expected_mid_rewards - ); - } - _ => { - panic!("Staking rewards query failed"); - } - }; - - // Stake & compound - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: second_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: Some(true), - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Re-query rewards - match (basic_staking::QueryMsg::Rewards { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Rewards { rewards } => { - assert_eq!( - rewards.len(), - 1, - "rewards length in the middle post-compound" - ); - assert_eq!( - rewards[0].amount, - Uint128::zero(), - "Rewards claimable post-compound" - ); - } - _ => { - panic!("Staking rewards query failed"); - } - }; - // Stake balance reflects compounded rewards - match (basic_staking::QueryMsg::Staked { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Staked { amount } => { - println!("PRE COMPOUND STAKED {}", amount); - let amount = amount.u128(); - let expected = (stake_amount + mid_rewards).u128(); - assert!( - amount <= expected && expected <= amount, - "Reward User Stake Balance post-compound" - ); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Move to end of rewards - println!("Fast-forward to reward end"); - app.set_block(BlockInfo { - height: 3, - time: Timestamp::from_seconds(reward_end.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - let current_rewards: Uint128; - // All rewards should be pending - match (basic_staking::QueryMsg::Rewards { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Rewards { rewards } => { - assert_eq!(rewards.len(), 1, "rewards length at end"); - current_rewards = rewards[0].amount; - println!("CUR REWARDS {}", current_rewards); - println!( - "reward amount {} mid rewards {}", - reward_amount, mid_rewards - ); - let expected = reward_amount - mid_rewards; - assert!( - current_rewards >= expected - Uint128::new(2) - && current_rewards <= expected + Uint128::new(2), - "Rewards claimable at the end within error of 2 unit tokens {} != {}", - current_rewards, - expected, - ); - } - _ => { - panic!("Staking rewards query failed"); - } - }; - - // Compound rewards - basic_staking::ExecuteMsg::Compound { padding: None } - .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Check rewards were compounded - match (basic_staking::QueryMsg::Staked { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Staked { amount } => { - assert_eq!( - amount, - stake_amount + current_rewards + mid_rewards, - "Post compound staked {}, {}", - stake_amount, - current_rewards - ); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Unbond - basic_staking::ExecuteMsg::Unbond { - amount: stake_amount + current_rewards + mid_rewards, - compound: None, - padding: None, - } - .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // All rewards should be pending - match (basic_staking::QueryMsg::Unbonding { - ids: None, - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Unbonding { unbondings } => { - assert_eq!(unbondings.len(), 1, "1 unbonding"); - assert_eq!( - unbondings[0].amount, - stake_amount + current_rewards + mid_rewards, - "Unbonding full amount" - ); - assert_eq!( - unbondings[0].complete, - reward_end + unbond_period, - "Unbonding complete expectedt" - ); - } - _ => { - panic!("Staking unbonding query failed"); - } - }; - - println!("Fast-forward to end of unbonding period"); - app.set_block(BlockInfo { - height: 10, - time: Timestamp::from_seconds((reward_end + unbond_period).u128() as u64), - chain_id: "chain_id".to_string(), - }); - - basic_staking::ExecuteMsg::Withdraw { - ids: None, - padding: None, - } - .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Check unbonding withdrawn - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - let expected = stake_amount + reward_amount; - assert!( - amount >= expected - Uint128::new(2) && amount <= expected, - "Final user balance within error of 2 unit tokens {} != {}", - amount, - expected, - ); - } - _ => { - panic!("Snip20 balance query failed"); - } - }; -} - -macro_rules! single_staker_compounding { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - stake_amount, - unbond_period, - reward_amount, - reward_start, - reward_end, - ) = $value; - single_staker_compounding( - stake_amount, - unbond_period, - reward_amount, - reward_start, - reward_end, - ) - } - )* - } -} - -single_staker_compounding! { - single_staker_compounding_0: ( - Uint128::new(2), // stake_amount - Uint128::new(100), // unbond_period - Uint128::new(100), // reward_amount - Uint128::new(0), // reward_start (0-*) - Uint128::new(100), // reward_end - ), - single_staker_compounding_1: ( - Uint128::new(100), - Uint128::new(100), - Uint128::new(1000), - Uint128::new(0), - Uint128::new(100), - ), - single_staker_compounding_2: ( - Uint128::new(1000), - Uint128::new(100), - Uint128::new(300), - Uint128::new(0), - Uint128::new(100), - ), - single_staker_compounding_3: ( - Uint128::new(10), - Uint128::new(100), - Uint128::new(50000), - Uint128::new(0), - Uint128::new(2500000), - ), - single_staker_compounding_4: ( - Uint128::new(1234567), - Uint128::new(10000), - Uint128::new(500), - Uint128::new(0), - Uint128::new(10000), - ), - single_staker_compounding_5: ( - Uint128::new(99999999999), - Uint128::new(100), - Uint128::new(8192), - Uint128::new(20), - Uint128::new(8000), - ), -} diff --git a/contracts/basic_staking/tests/stake_0_total_bug.rs b/contracts/basic_staking/tests/stake_0_total_bug.rs deleted file mode 100644 index 538e20f..0000000 --- a/contracts/basic_staking/tests/stake_0_total_bug.rs +++ /dev/null @@ -1,445 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -// Add other adapters here as they come -fn stake_0_total_bug( - stake_amount: Uint128, - unbond_period: Uint128, - reward_amount: Uint128, - reward_start: Uint128, - reward_end: Uint128, -) { - let mut app = App::default(); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let staking_user = Addr::unchecked("staker"); - let reward_user = Addr::unchecked("reward_user"); - - let token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![ - snip20::InitialBalance { - amount: stake_amount, - address: staking_user.to_string(), - }, - snip20::InitialBalance { - amount: reward_amount, - address: reward_user.to_string(), - }, - ]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - // set staking_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - // set staking user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // set reward user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - airdrop: None, - stake_token: token.clone().into(), - unbond_period, - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - println!("BASIC STAKING {}", basic_staking.address); - - // Init Rewards - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: reward_amount, - msg: Some( - to_binary(&basic_staking::Action::Rewards { - start: reward_start, - end: reward_end, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, reward_user.clone(), &[]) - .unwrap(); - - // reward user has no stake - match (basic_staking::QueryMsg::Staked { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: reward_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Staked { amount } => { - assert_eq!(amount, Uint128::zero(), "Reward User Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Check reward pool - match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); - assert_eq!(rewards[0].start, reward_start, "Reward Pool Start"); - assert_eq!(rewards[0].end, reward_end, "Reward Pool End"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Stake funds - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Post-staking user balance - match (basic_staking::QueryMsg::Staked { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Staked { amount } => { - assert_eq!(amount, stake_amount, "Post-Stake Balance"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Move forward to reward start - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(reward_start.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - // No rewards should be pending - match (basic_staking::QueryMsg::Rewards { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Rewards { rewards } => { - assert_eq!(rewards.len(), 1, "Rewards length at beginning"); - assert_eq!( - rewards[0].amount, - Uint128::zero(), - "Rewards claimable at beginning" - ); - } - _ => { - panic!("Staking rewards query failed"); - } - }; - - let reward_duration = reward_end - reward_start; - - // Move to middle of reward period - println!("Fast-forward to reward middle"); - app.set_block(BlockInfo { - height: 2, - time: Timestamp::from_seconds((reward_start.u128() + reward_duration.u128() / 2) as u64), - chain_id: "chain_id".to_string(), - }); - - // Half-ish rewards should be pending - match (basic_staking::QueryMsg::Rewards { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Rewards { rewards } => { - assert_eq!(rewards.len(), 1, "rewards length in the middle"); - let amount = rewards[0].amount; - let expected = reward_amount / Uint128::new(2); - assert!( - amount >= expected - Uint128::one() && amount <= expected, - "Rewards claimable in the middle within error of 1 unit token {} != {}", - amount, - expected - ); - } - _ => { - panic!("Staking rewards query failed"); - } - }; - - // Move to end of rewards - println!("Fast-forward to reward end"); - app.set_block(BlockInfo { - height: 3, - time: Timestamp::from_seconds(reward_end.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - // All rewards should be pending - match (basic_staking::QueryMsg::Rewards { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Rewards { rewards } => { - assert_eq!(rewards.len(), 1, "rewards length at end"); - let amount = rewards[0].amount; - assert!( - amount >= reward_amount - Uint128::one() && amount <= reward_amount, - "Rewards claimable at the end within error of 1 unit token {} != {}", - amount, - reward_amount, - ); - } - _ => { - panic!("Staking rewards query failed"); - } - }; - - // Claim rewards - basic_staking::ExecuteMsg::Claim { padding: None } - .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Check rewards were claimed - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert!( - amount >= reward_amount - Uint128::one() && amount <= reward_amount, - "Rewards claimed at the end within error of 1 unit token {} != {}", - amount, - reward_amount, - ); - } - _ => { - panic!("Snip20 balance query failed"); - } - }; - - // Unbond - basic_staking::ExecuteMsg::Unbond { - amount: stake_amount, - compound: None, - padding: None, - } - .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // All rewards should be pending - match (basic_staking::QueryMsg::Unbonding { - ids: None, - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Unbonding { unbondings } => { - assert_eq!(unbondings.len(), 1, "1 unbonding"); - assert_eq!(unbondings[0].amount, stake_amount, "Unbonding full amount"); - assert_eq!( - unbondings[0].complete, - reward_end + unbond_period, - "Unbonding complete expectedt" - ); - } - _ => { - panic!("Staking unbonding query failed"); - } - }; - println!("Fast-forward to end of unbonding period"); - app.set_block(BlockInfo { - height: 10, - time: Timestamp::from_seconds((reward_end + unbond_period).u128() as u64), - chain_id: "chain_id".to_string(), - }); - - basic_staking::ExecuteMsg::Withdraw { - ids: None, - padding: None, - } - .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Check unbonding withdrawn - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - let expected = stake_amount + reward_amount; - assert!( - amount >= expected - Uint128::one() && amount <= expected, - "Final user balance within error of 1 unit token {} != {}", - amount, - expected, - ); - } - _ => { - panic!("Snip20 balance query failed"); - } - }; -} - -macro_rules! stake_0_total_bug { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - stake_amount, - unbond_period, - reward_amount, - reward_start, - reward_end, - ) = $value; - stake_0_total_bug( - stake_amount, - unbond_period, - reward_amount, - reward_start, - reward_end, - ) - } - )* - } -} - -stake_0_total_bug! { - stake_0_total_bug_0: ( - Uint128::new(1), // stake_amount - Uint128::new(100), // unbond_period - Uint128::new(100), // reward_amount - Uint128::new(0), // reward_start (0-*) - Uint128::new(100), // reward_end - ), -} diff --git a/contracts/basic_staking/tests/transfer_stake.rs b/contracts/basic_staking/tests/transfer_stake.rs deleted file mode 100644 index 1f77069..0000000 --- a/contracts/basic_staking/tests/transfer_stake.rs +++ /dev/null @@ -1,454 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -// Add other adapters here as they come -fn transfer_stake(stake_amount: Uint128, transfer_amount: Uint128) { - let mut app = App::default(); - - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let staking_user = Addr::unchecked("staker"); - let transfer_user = Addr::unchecked("transfer"); - - let reward_user = Addr::unchecked("reward_user"); - let reward_amount = Uint128::new(100000000); - let reward_start = Uint128::new(100); - let reward_end = Uint128::new(1000); - - let token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![ - snip20::InitialBalance { - amount: stake_amount, - address: staking_user.to_string(), - }, - snip20::InitialBalance { - amount: transfer_amount, - address: transfer_user.to_string(), - }, - snip20::InitialBalance { - amount: reward_amount, - address: reward_user.to_string(), - }, - ]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - // set staking_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // set transfer_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, transfer_user.clone(), &[]) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - // set staking user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) - .unwrap(); - // set transfer user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, transfer_user.clone(), &[]) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - stake_token: token.clone().into(), - airdrop: None, - unbond_period: Uint128::zero(), - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - - // Init Rewards - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: reward_amount, - msg: Some( - to_binary(&basic_staking::Action::Rewards { - start: reward_start, - end: reward_end, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, reward_user.clone(), &[]) - .unwrap(); - - // Stake staking user - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Stake transfer user - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: transfer_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, transfer_user.clone(), &[]) - .unwrap(); - - // Skip to end of rewards - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(reward_end.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - // Transfer should fail, not on whitelist - match (basic_staking::ExecuteMsg::TransferStake { - amount: transfer_amount, - recipient: staking_user.clone().into(), - compound: Some(false), - padding: None, - } - .test_exec(&basic_staking, &mut app, transfer_user.clone(), &[])) - { - Err(_) => {} - Ok(_) => { - panic!("Shouldn't allow transfer if source not on whitelist"); - } - } - - // Add transfer user to whitelist - basic_staking::ExecuteMsg::AddTransferWhitelist { - user: transfer_user.clone().into(), - padding: None, - } - .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) - .unwrap(); - - // Check whitelist - match (basic_staking::QueryMsg::TransferWhitelist {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::TransferWhitelist { whitelist } => { - assert_eq!( - whitelist, - vec![transfer_user.clone()], - "Whitelist contains transfer user" - ); - } - _ => { - panic!("Unexpected transfer whitelist response"); - } - } - - // check balances to verify rewards claim - let stake_user_reward = match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, stake_amount, "Pre-Transfer recipient balance"); - rewards[0].amount - } - _ => { - panic!("Staking balance query failed"); - } - }; - - let transfer_user_reward = match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: transfer_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!( - staked, transfer_amount, - "Pre-Transfer transfer user balance" - ); - rewards[0].amount - } - _ => { - panic!("Staking balance query failed"); - } - }; - - basic_staking::ExecuteMsg::TransferStake { - amount: transfer_amount, - recipient: staking_user.clone().into(), - compound: Some(false), - padding: None, - } - .test_exec(&basic_staking, &mut app, transfer_user.clone(), &[]) - .unwrap(); - - // check staking user balance after transfer - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!( - staked, - stake_amount + transfer_amount, - "Post-Transfer recipient balance" - ); - assert_eq!(rewards.len(), 0, "Recipient rewards claimed"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - // Check recipient rewards claimed - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, stake_user_reward, "Recipient rewards claimed",); - } - _ => { - panic!("Snip20 balance query failed"); - } - }; - - // check transfer user balance after transfer - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: transfer_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "Post-Transfer sender balance"); - assert_eq!(rewards.len(), 0, "Sender rewards claimed"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Check sender rewards claimed - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: transfer_user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, transfer_user_reward, "Sender rewards claimed"); - } - _ => { - panic!("Snip20 balance query failed"); - } - }; - - // Remove from whitelist - basic_staking::ExecuteMsg::RemoveTransferWhitelist { - user: transfer_user.clone().into(), - padding: None, - } - .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) - .unwrap(); - - // Check whitelist - match (basic_staking::QueryMsg::TransferWhitelist {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::TransferWhitelist { whitelist } => { - assert!(whitelist.is_empty(), "Whitelist empty after removal"); - } - _ => { - panic!("Unexpected transfer whitelist response"); - } - } -} - -macro_rules! transfer_stake { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - stake_amount, - transfer_amount, - ) = $value; - transfer_stake( - stake_amount, - transfer_amount, - ) - } - )* - } -} - -transfer_stake! { - transfer_stake_0: ( - Uint128::new(1), // stake_amount - Uint128::new(1), // transfer_amount - ), - transfer_stake_1: ( - Uint128::new(100), - Uint128::new(100), - ), - transfer_stake_2: ( - Uint128::new(1000), - Uint128::new(1000), - ), - transfer_stake_3: ( - Uint128::new(10), - Uint128::new(10), - ), - transfer_stake_4: ( - Uint128::new(1234567), - Uint128::new(1234567), - ), - transfer_stake_5: ( - Uint128::new(99999999999), - Uint128::new(99999999999), - ), -} diff --git a/contracts/basic_staking/tests/transfer_stake_compound.rs b/contracts/basic_staking/tests/transfer_stake_compound.rs deleted file mode 100644 index 722a849..0000000 --- a/contracts/basic_staking/tests/transfer_stake_compound.rs +++ /dev/null @@ -1,454 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -// Add other adapters here as they come -fn transfer_stake(stake_amount: Uint128, transfer_amount: Uint128) { - let mut app = App::default(); - - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let staking_user = Addr::unchecked("staker"); - let transfer_user = Addr::unchecked("transfer"); - - let reward_user = Addr::unchecked("reward_user"); - let reward_amount = Uint128::new(100000000); - let reward_start = Uint128::new(100); - let reward_end = Uint128::new(1000); - - let token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![ - snip20::InitialBalance { - amount: stake_amount, - address: staking_user.to_string(), - }, - snip20::InitialBalance { - amount: transfer_amount, - address: transfer_user.to_string(), - }, - snip20::InitialBalance { - amount: reward_amount, - address: reward_user.to_string(), - }, - ]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - // set staking_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // set transfer_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, transfer_user.clone(), &[]) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - // set staking user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) - .unwrap(); - // set transfer user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, transfer_user.clone(), &[]) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - stake_token: token.clone().into(), - airdrop: None, - unbond_period: Uint128::zero(), - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - - // Init Rewards - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: reward_amount, - msg: Some( - to_binary(&basic_staking::Action::Rewards { - start: reward_start, - end: reward_end, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, reward_user.clone(), &[]) - .unwrap(); - - // Stake staking user - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Stake transfer user - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: transfer_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, transfer_user.clone(), &[]) - .unwrap(); - - // Skip to end of rewards - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(reward_end.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - // Transfer should fail, not on whitelist - match (basic_staking::ExecuteMsg::TransferStake { - amount: transfer_amount, - recipient: staking_user.clone().into(), - compound: Some(true), - padding: None, - } - .test_exec(&basic_staking, &mut app, transfer_user.clone(), &[])) - { - Err(_) => {} - Ok(_) => { - panic!("Shouldn't allow transfer if source not on whitelist"); - } - } - - // Add transfer user to whitelist - basic_staking::ExecuteMsg::AddTransferWhitelist { - user: transfer_user.clone().into(), - padding: None, - } - .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) - .unwrap(); - - // Check whitelist - match (basic_staking::QueryMsg::TransferWhitelist {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::TransferWhitelist { whitelist } => { - assert_eq!( - whitelist, - vec![transfer_user.clone()], - "Whitelist contains transfer user" - ); - } - _ => { - panic!("Unexpected transfer whitelist response"); - } - } - - // check balances to verify rewards claim - let stake_user_reward = match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, stake_amount, "Pre-Transfer recipient balance"); - rewards[0].amount - } - _ => { - panic!("Staking balance query failed"); - } - }; - - let transfer_user_reward = match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: transfer_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!( - staked, transfer_amount, - "Pre-Transfer transfer user balance" - ); - rewards[0].amount - } - _ => { - panic!("Staking balance query failed"); - } - }; - - basic_staking::ExecuteMsg::TransferStake { - amount: transfer_amount, - recipient: staking_user.clone().into(), - compound: Some(true), - padding: None, - } - .test_exec(&basic_staking, &mut app, transfer_user.clone(), &[]) - .unwrap(); - - // check staking user balance after transfer - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!( - staked, - stake_amount + transfer_amount, - "Post-Transfer recipient balance" - ); - assert_eq!(rewards.len(), 0, "Recipient rewards claimed"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - // Check recipient rewards claimed - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, stake_user_reward, "Recipient rewards claimed",); - } - _ => { - panic!("Snip20 balance query failed"); - } - }; - - // check transfer user balance after transfer - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: transfer_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, transfer_user_reward, "Post-Transfer sender balance"); - assert_eq!(rewards.len(), 0, "Sender rewards compounded"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Check sender rewards compounded - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: transfer_user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, Uint128::zero(), "Sender no balance bc compound"); - } - _ => { - panic!("Snip20 balance query failed"); - } - }; - - // Remove from whitelist - basic_staking::ExecuteMsg::RemoveTransferWhitelist { - user: transfer_user.clone().into(), - padding: None, - } - .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) - .unwrap(); - - // Check whitelist - match (basic_staking::QueryMsg::TransferWhitelist {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::TransferWhitelist { whitelist } => { - assert!(whitelist.is_empty(), "Whitelist empty after removal"); - } - _ => { - panic!("Unexpected transfer whitelist response"); - } - } -} - -macro_rules! transfer_stake { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - stake_amount, - transfer_amount, - ) = $value; - transfer_stake( - stake_amount, - transfer_amount, - ) - } - )* - } -} - -transfer_stake! { - transfer_stake_0: ( - Uint128::new(1), // stake_amount - Uint128::new(1), // transfer_amount - ), - transfer_stake_1: ( - Uint128::new(100), - Uint128::new(100), - ), - transfer_stake_2: ( - Uint128::new(1000), - Uint128::new(1000), - ), - transfer_stake_3: ( - Uint128::new(10), - Uint128::new(10), - ), - transfer_stake_4: ( - Uint128::new(1234567), - Uint128::new(1234567), - ), - transfer_stake_5: ( - Uint128::new(99999999999), - Uint128::new(99999999999), - ), -} diff --git a/contracts/basic_staking/tests/transfer_stake_sender_no_stake.rs b/contracts/basic_staking/tests/transfer_stake_sender_no_stake.rs deleted file mode 100644 index 3fed621..0000000 --- a/contracts/basic_staking/tests/transfer_stake_sender_no_stake.rs +++ /dev/null @@ -1,232 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -// Add other adapters here as they come -fn transfer_stake(stake_amount: Uint128, transfer_amount: Uint128) { - let mut app = App::default(); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let receiving_user = Addr::unchecked("staker"); - let sending_user = Addr::unchecked("transfer"); - - let token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![ - snip20::InitialBalance { - amount: stake_amount, - address: receiving_user.to_string(), - }, - snip20::InitialBalance { - amount: transfer_amount, - address: sending_user.to_string(), - }, - ]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - // set receiving_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, receiving_user.clone(), &[]) - .unwrap(); - - // set sending_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, sending_user.clone(), &[]) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - // set staking user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, receiving_user.clone(), &[]) - .unwrap(); - - // set transfer user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, sending_user.clone(), &[]) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - stake_token: token.clone().into(), - airdrop: None, - unbond_period: Uint128::zero(), - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - println!("BASIC STAKING {}", basic_staking.address); - - // Stake staking user - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, receiving_user.clone(), &[]) - .unwrap(); - - // Transfer should fail, not on whitelist - match (basic_staking::ExecuteMsg::TransferStake { - amount: transfer_amount, - recipient: receiving_user.clone().into(), - compound: Some(true), - padding: None, - } - .test_exec(&basic_staking, &mut app, sending_user.clone(), &[])) - { - Err(_) => {} - Ok(_) => { - panic!("Shouldn't allow transfer if source not on whitelist"); - } - } - - // Add transfer user to whitelist - basic_staking::ExecuteMsg::AddTransferWhitelist { - user: sending_user.clone().into(), - padding: None, - } - .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) - .unwrap(); - - match (basic_staking::ExecuteMsg::TransferStake { - amount: transfer_amount, - recipient: receiving_user.clone().into(), - compound: Some(true), - padding: None, - } - .test_exec(&basic_staking, &mut app, sending_user.clone(), &[])) - { - Ok(_) => { - panic!("Transfer success with no balance!"); - } - Err(e) => {} - } -} - -macro_rules! transfer_stake { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - stake_amount, - transfer_amount, - ) = $value; - transfer_stake( - stake_amount, - transfer_amount, - ) - } - )* - } -} - -transfer_stake! { - transfer_stake_0: ( - Uint128::new(1), // stake_amount - Uint128::new(1), // transfer_amount - ), - transfer_stake_1: ( - Uint128::new(100), - Uint128::new(100), - ), - transfer_stake_2: ( - Uint128::new(1000), - Uint128::new(1000), - ), - transfer_stake_3: ( - Uint128::new(10), - Uint128::new(10), - ), - transfer_stake_4: ( - Uint128::new(1234567), - Uint128::new(1234567), - ), - transfer_stake_5: ( - Uint128::new(99999999999), - Uint128::new(99999999999), - ), -} diff --git a/contracts/basic_staking/tests/unbonding_withdrawals.rs b/contracts/basic_staking/tests/unbonding_withdrawals.rs deleted file mode 100644 index d1f962f..0000000 --- a/contracts/basic_staking/tests/unbonding_withdrawals.rs +++ /dev/null @@ -1,316 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -// Add other adapters here as they come -fn unbonding_withdrawals( - stake_amount: Uint128, - unbond_period: Uint128, - unbonding_amounts: Vec, - withdraw_order: Vec, -) { - let mut app = App::default(); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let staking_user = Addr::unchecked("staker"); - let reward_user = Addr::unchecked("reward_user"); - - let token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![snip20::InitialBalance { - amount: stake_amount, - address: staking_user.to_string(), - }]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - // set staking_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - // set staking user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // set reward user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - airdrop: None, - stake_token: token.clone().into(), - unbond_period, - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - println!("BASIC STAKING {}", basic_staking.address); - - // Stake funds - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - let mut now = 50; - let mut unbonded = Uint128::zero(); - - // Setup timestamp - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(now), - chain_id: "chain_id".to_string(), - }); - - // Perform each unbonding - for unbond_amount in unbonding_amounts.iter() { - // Unbond - basic_staking::ExecuteMsg::Unbond { - amount: unbond_amount.clone(), - compound: None, - padding: None, - } - .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) - .unwrap(); - } - - // Check that unbonding list is as expected - let unbonding_ids = match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!( - unbondings - .iter() - .map(|u| u.amount) - .collect::>(), - unbonding_amounts, - "unbondings order as expectedt" - ); - unbondings.iter().map(|u| u.id).collect::>() - } - _ => { - panic!("Balance query failed"); - vec![] - } - }; - - println!("Fast-forward to end of unbonding period"); - app.set_block(BlockInfo { - height: 10, - time: Timestamp::from_seconds(now + unbond_period.u128() as u64), - chain_id: "chain_id".to_string(), - }); - - let mut withdrawn_ids = vec![]; - - for i in withdraw_order.into_iter() { - // Withdraw - basic_staking::ExecuteMsg::Withdraw { - ids: Some(vec![unbonding_ids[i]]), - padding: None, - } - .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) - .unwrap(); - - withdrawn_ids.push(unbonding_ids[i]); - - let expected_unbonding_ids = unbonding_ids - .clone() - .into_iter() - .filter(|id| !withdrawn_ids.contains(id)) - .collect::>(); - - // Check that unbonding list is as expected - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!( - unbondings.iter().map(|u| u.id).collect::>(), - expected_unbonding_ids, - "unbondings order as expected after withdrawing", - ); - } - _ => { - panic!("Balance query failed"); - } - }; - } - - // Check snip20 received by user - match (snip20::QueryMsg::Balance { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, stake_amount, "Final user balance",); - } - _ => { - panic!("Snip20 balance query failed"); - } - }; -} - -macro_rules! unbonding_withdrawals { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - stake_amount, - unbond_period, - unbonding_amounts, - withdraw_order, - ) = $value; - unbonding_withdrawals( - stake_amount, - unbond_period, - unbonding_amounts, - withdraw_order, - ) - } - )* - } -} - -unbonding_withdrawals! { - unbonding_withdrawals_0: ( - Uint128::new(100), // stake_amount - Uint128::new(100), // unbond_period - vec![ - Uint128::new(10), - Uint128::new(50), - Uint128::new(17), - Uint128::new(23), - ], - vec![0, 2, 1, 3], - ), - unbonding_withdrawals_1: ( - Uint128::new(1000), // stake_amount - Uint128::new(100), // unbond_period - vec![ - Uint128::new(10), - Uint128::new(50), - Uint128::new(17), - Uint128::new(23), - Uint128::new(100), - Uint128::new(700), - Uint128::new(50), - Uint128::new(50), - ], - vec![7, 5, 6, 2, 4, 3, 1, 0], - ), -} diff --git a/contracts/basic_staking/tests/unstake_softlock_bug.rs b/contracts/basic_staking/tests/unstake_softlock_bug.rs deleted file mode 100644 index b9c5e98..0000000 --- a/contracts/basic_staking/tests/unstake_softlock_bug.rs +++ /dev/null @@ -1,340 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -// Add other adapters here as they come -fn unstake_softlock_bug(stake_amount: Uint128, unbond_period: Uint128, reward_amount: Uint128) { - let mut app = App::default(); - - let mut now = 0; - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(now), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - let staking_user = Addr::unchecked("staker"); - let reward_user = Addr::unchecked("reward_user"); - - let extra_amount = Uint128::new(100000000000); - - let token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![ - snip20::InitialBalance { - amount: stake_amount + extra_amount, - address: staking_user.to_string(), - }, - snip20::InitialBalance { - amount: reward_amount, - address: reward_user.to_string(), - }, - ]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - // set staking_user viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - // set staking user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // set reward user VK - query_auth::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&query_contract, &mut app, reward_user.clone(), &[]) - .unwrap(); - - // Init Staking - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.into(), - query_auth: query_contract.into(), - airdrop: None, - stake_token: token.clone().into(), - unbond_period, - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - println!("BASIC STAKING {}", basic_staking.address); - - now = 10; - app.set_block(BlockInfo { - height: 10, - time: Timestamp::from_seconds(now), - chain_id: "chain_id".to_string(), - }); - - let reward_end = now + 100000000; - - // Init Rewards - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: reward_amount, - msg: Some( - to_binary(&basic_staking::Action::Rewards { - start: Uint128::new(now as u128), - end: Uint128::new(reward_end as u128), - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, reward_user.clone(), &[]) - .unwrap(); - - // Check reward pool - match (basic_staking::QueryMsg::RewardPools {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::RewardPools { rewards } => { - assert_eq!(rewards[0].amount, reward_amount, "Reward Pool Amount"); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Stake funds - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Generate some rewards - now += reward_end / 4; - app.set_block(BlockInfo { - height: 10, - time: Timestamp::from_seconds(now), - chain_id: "chain_id".to_string(), - }); - - // Post-staking user balance - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, stake_amount, "Post-Stake Balance"); - /* - panic!( - "REWARDS {}", - rewards.iter().map(|r| r.amount).sum::() - ); - */ - } - _ => { - panic!("Staking balance query failed"); - } - }; - - // Unbond all funds - basic_staking::ExecuteMsg::Unbond { - amount: stake_amount, - compound: None, - padding: None, - } - .test_exec(&basic_staking, &mut app, staking_user.clone(), &[]) - .unwrap(); - - // Check balance reflects unbonding - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, Uint128::zero(), "Post-Unbond stake amount p"); - assert_eq!(unbondings.len(), 1, "Post unbond unbondings"); - assert_eq!( - unbondings[0].amount, stake_amount, - "Post unbond amount unbonding" - ); - } - _ => { - panic!("Staking balance query failed"); - } - }; - - println!("Fast-forward to end of unbonding period"); - - now += unbond_period.u128() as u64; - app.set_block(BlockInfo { - height: 10, - time: Timestamp::from_seconds(now), - chain_id: "chain_id".to_string(), - }); - - // Restake - snip20::ExecuteMsg::Send { - recipient: basic_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: stake_amount, - msg: Some( - to_binary(&basic_staking::Action::Stake { - compound: None, - airdrop_task: None, - }) - .unwrap(), - ), - memo: None, - padding: None, - } - .test_exec(&token, &mut app, staking_user.clone(), &[]) - .unwrap(); - - println!("Checking balance after restake..."); - match (basic_staking::QueryMsg::Balance { - auth: basic_staking::Auth::ViewingKey { - key: viewing_key.clone(), - address: staking_user.clone().into(), - }, - unbonding_ids: None, - }) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Balance { - staked, - rewards, - unbondings, - } => { - assert_eq!(staked, stake_amount, "Re-Staked Balance"); - // assert_eq!(rewards, vec![], "Re-staked Rewards"); - // assert_eq!(unbondings, vec![], "Final Unbondings"); - } - _ => { - panic!("Staking balance query failed"); - } - }; -} - -macro_rules! unstake_softlock_bug { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - stake_amount, - unbond_period, - reward_amount, - ) = $value; - unstake_softlock_bug( - stake_amount, - unbond_period, - reward_amount, - ) - } - )* - } -} - -unstake_softlock_bug! { - unstake_softlock_bug_0: ( - Uint128::new(100), // stake_amount - Uint128::new(100), // unbond_period - Uint128::new(100000000000), // reward_amount - ), -} diff --git a/contracts/basic_staking/tests/update_config.rs b/contracts/basic_staking/tests/update_config.rs deleted file mode 100644 index ffb5e8d..0000000 --- a/contracts/basic_staking/tests/update_config.rs +++ /dev/null @@ -1,127 +0,0 @@ -use shade_protocol::c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}; - -use shade_protocol::{ - contract_interfaces::{basic_staking, query_auth, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - basic_staking::BasicStaking, - query_auth::QueryAuth, - snip20::Snip20, -}; - -#[test] -fn update_config() { - let mut app = App::default(); - - // init block time for predictable behavior - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(0), - chain_id: "chain_id".to_string(), - }); - - let viewing_key = "unguessable".to_string(); - let admin_user = Addr::unchecked("admin"); - - let token = snip20::InstantiateMsg { - name: "stake_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "STKN".into(), - decimals: 6, - initial_balances: Some(vec![]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let query_contract = query_auth::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - prng_seed: to_binary("").ok().unwrap(), - } - .test_init( - QueryAuth::default(), - &mut app, - admin_user.clone(), - "query_auth", - &[], - ) - .unwrap(); - - let basic_staking = basic_staking::InstantiateMsg { - admin_auth: admin_contract.clone().into(), - query_auth: query_contract.clone().into(), - airdrop: None, - stake_token: token.clone().into(), - unbond_period: Uint128::zero(), - max_user_pools: Uint128::one(), - viewing_key: viewing_key.clone(), - } - .test_init( - BasicStaking::default(), - &mut app, - admin_user.clone(), - "basic_staking", - &[], - ) - .unwrap(); - - let mut config_match = match (basic_staking::QueryMsg::Config {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Config { config } => config, - _ => panic!("Config query failed"), - }; - - config_match.admin_auth = query_contract.clone().into(); - config_match.query_auth = admin_contract.clone().into(); - config_match.airdrop = Some(admin_contract.clone().into()); - config_match.unbond_period = Uint128::new(100); - config_match.max_user_pools = Uint128::new(10); - - // update config fields - basic_staking::ExecuteMsg::UpdateConfig { - admin_auth: Some(config_match.admin_auth.clone().into()), - query_auth: Some(config_match.query_auth.clone().into()), - airdrop: Some(RawContract { - address: admin_contract.address.to_string(), - code_hash: admin_contract.code_hash, - }), - unbond_period: Some(config_match.unbond_period.clone()), - max_user_pools: Some(config_match.max_user_pools.clone()), - padding: None, - } - .test_exec(&basic_staking, &mut app, admin_user.clone(), &[]) - .unwrap(); - - match (basic_staking::QueryMsg::Config {}) - .test_query(&basic_staking, &app) - .unwrap() - { - basic_staking::QueryAnswer::Config { config } => { - assert_eq!(config_match.clone(), config); - } - _ => panic!("Config query failed"), - } -} diff --git a/contracts/dao/scrt_staking/.cargo/config b/contracts/dao/scrt_staking/.cargo/config deleted file mode 100644 index 882fe08..0000000 --- a/contracts/dao/scrt_staking/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[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/dao/scrt_staking/.circleci/config.yml b/contracts/dao/scrt_staking/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/contracts/dao/scrt_staking/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/dao/scrt_staking/Cargo.toml b/contracts/dao/scrt_staking/Cargo.toml deleted file mode 100644 index 0e644e9..0000000 --- a/contracts/dao/scrt_staking/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "scrt_staking" -version = "0.1.0" -authors = ["Jack Swenson "] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] - -[dependencies] -shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ - "adapter", - "dao", - "scrt_staking", - "treasury", - "math", - "storage_plus", -] } -cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" } - -[dev-dependencies] -shade-multi-test = { path = "../../../packages/multi_test", features = [ - "scrt_staking", - "snip20", - "admin" -] } diff --git a/contracts/dao/scrt_staking/Makefile b/contracts/dao/scrt_staking/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/contracts/dao/scrt_staking/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/dao/scrt_staking/README.md b/contracts/dao/scrt_staking/README.md deleted file mode 100644 index eb0af80..0000000 --- a/contracts/dao/scrt_staking/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# sSCRT Staking Contract -* [Introduction](#Introduction) -* [Sections](#Sections) - * [Init](#Init) - * [DAO Adapter](/packages/shade_protocol/src/DAO_ADAPTER.md) - * [Interface](#Interface) - * Messages - * [Receive](#Receive) - * [UpdateConfig](#UpdateConfig) - * Queries - * [Config](#Config) - * [Delegations](#Delegations) - -# Introduction -The sSCRT Staking contract receives sSCRT, redeems it for SCRT, then stakes it with a validator that falls within the criteria it has been configured with. The configured `treasury` will receive all funds from claiming rewards/unbonding. - -# Sections - -## Init -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|admin | Addr | contract owner/admin; a valid bech32 address; -|treasury | Addr | contract designated to receive all outgoing funds -|sscrt | Contract | sSCRT Snip-20 contract to accept for redemption/staking, all other funds will error -|validator_bounds | ValidatorBounds | criteria defining an acceptable validator to stake with -|viewing_key | String | Viewing Key to be set for any relevant SNIP-20 - -## Interface - -### Messages -#### UpdateConfig -Updates the given values -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|owner | Addr | contract owner/admin; a valid bech32 address; -|treasury | Addr | contract designated to receive all outgoing funds -|sscrt | Contract | sSCRT Snip-20 contract to accept for redemption/staking, all other funds will error -|validator_bounds | ValidatorBounds | criteria defining an acceptable validator to stake with - -##### Response -```json -{ - "update_config": { - "status": "success" - } -} -``` - - -### Queries - -#### Config -Gets the contract's configuration variables -##### Response -```json -{ - "config": { - "config": { - "owner": "Owner address", - } - } -} -``` diff --git a/contracts/dao/scrt_staking/src/contract.rs b/contracts/dao/scrt_staking/src/contract.rs deleted file mode 100644 index 31af03e..0000000 --- a/contracts/dao/scrt_staking/src/contract.rs +++ /dev/null @@ -1,116 +0,0 @@ -use shade_protocol::{ - c_std::{ - entry_point, - to_binary, - Binary, - Deps, - DepsMut, - Env, - MessageInfo, - Response, - StdResult, - Uint128, - }, - dao::{ - adapter, - scrt_staking::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}, - }, - snip20::helpers::{register_receive, set_viewing_key_msg}, -}; - -use crate::{ - execute, - query, - storage::{CONFIG, SELF_ADDRESS, UNBONDING, VIEWING_KEY}, -}; - -#[entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - let config = Config { - admin_auth: msg.admin_auth.into_valid(deps.api)?, - sscrt: msg.sscrt.into_valid(deps.api)?, - owner: deps.api.addr_validate(msg.owner.as_str())?, - validator_bounds: msg.validator_bounds, - }; - - CONFIG.save(deps.storage, &config)?; - - SELF_ADDRESS.save(deps.storage, &env.contract.address)?; - VIEWING_KEY.save(deps.storage, &msg.viewing_key)?; - UNBONDING.save(deps.storage, &Uint128::zero())?; - - let resp = Response::new().add_messages(vec![ - set_viewing_key_msg(msg.viewing_key, None, &config.sscrt)?, - register_receive(env.contract.code_hash, None, &config.sscrt)?, - ]); - - Ok(resp) -} - -#[entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - ExecuteMsg::Receive { - sender, - from, - amount, - msg, - .. - } => { - let sender = deps.api.addr_validate(&sender)?; - let from = deps.api.addr_validate(&from)?; - execute::receive(deps, env, info, sender, from, amount, msg) - } - ExecuteMsg::UpdateConfig { config } => execute::try_update_config(deps, env, info, config), - ExecuteMsg::Adapter(adapter) => match adapter { - adapter::SubExecuteMsg::Unbond { asset, amount } => { - let asset = deps.api.addr_validate(&asset)?; - execute::unbond(deps, env, info, asset, amount) - } - adapter::SubExecuteMsg::Claim { asset } => { - let asset = deps.api.addr_validate(&asset)?; - execute::claim(deps, env, info, asset) - } - adapter::SubExecuteMsg::Update { asset } => { - let asset = deps.api.addr_validate(&asset)?; - execute::update(deps, env, info, asset) - } - }, - } -} - -#[entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query::config(deps)?), - QueryMsg::Delegations {} => to_binary(&query::delegations(deps)?), - QueryMsg::Rewards {} => to_binary(&query::rewards(deps)?), - QueryMsg::Adapter(adapter) => match adapter { - adapter::SubQueryMsg::Balance { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::balance(deps, asset)?) - } - adapter::SubQueryMsg::Claimable { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::claimable(deps, asset)?) - } - adapter::SubQueryMsg::Unbonding { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::unbonding(deps, asset)?) - } - adapter::SubQueryMsg::Unbondable { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::unbondable(deps, asset)?) - } - adapter::SubQueryMsg::Reserves { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::reserves(deps, asset)?) - } - }, - } -} diff --git a/contracts/dao/scrt_staking/src/execute.rs b/contracts/dao/scrt_staking/src/execute.rs deleted file mode 100644 index 1ee3fbc..0000000 --- a/contracts/dao/scrt_staking/src/execute.rs +++ /dev/null @@ -1,424 +0,0 @@ -use shade_protocol::{ - admin::helpers::{validate_admin, AdminPermissions}, - c_std::{ - to_binary, - Addr, - Binary, - Coin, - CosmosMsg, - Deps, - DepsMut, - DistributionMsg, - Env, - MessageInfo, - Response, - StakingMsg, - StdError, - StdResult, - Uint128, - Validator, - }, -}; - -use shade_protocol::snip20::helpers::redeem_msg; - -use shade_protocol::{ - dao::{ - adapter, - scrt_staking::{Config, ExecuteAnswer, ValidatorBounds}, - }, - utils::{ - asset::{scrt_balance, Contract}, - generic_response::ResponseStatus, - wrap::{unwrap, wrap_and_send}, - }, -}; - -use crate::{ - query, - storage::{CONFIG, SELF_ADDRESS, UNBONDING}, -}; - -pub fn receive( - deps: DepsMut, - env: Env, - info: MessageInfo, - _sender: Addr, - _from: Addr, - amount: Uint128, - _msg: Option, -) -> StdResult { - //panic!("scrt staking Received {}", amount); - - let config = CONFIG.load(deps.storage)?; - - if info.sender != config.sscrt.address { - return Err(StdError::generic_err("Only accepts sSCRT")); - } - - let validator = choose_validator(deps, env.block.time.seconds())?; - - Ok(Response::new() - .add_messages(vec![ - redeem_msg(amount, None, None, &config.sscrt)?, - CosmosMsg::Staking(StakingMsg::Delegate { - validator: validator.address.clone(), - amount: Coin { - amount, - denom: "uscrt".to_string(), - }, - }), - ]) - .set_data(to_binary(&ExecuteAnswer::Receive { - status: ResponseStatus::Success, - validator, - })?)) -} - -pub fn try_update_config( - deps: DepsMut, - _env: Env, - info: MessageInfo, - config: Config, -) -> StdResult { - let cur_config = CONFIG.load(deps.storage)?; - - validate_admin( - &deps.querier, - AdminPermissions::ScrtStakingAdmin, - &info.sender, - &cur_config.admin_auth, - )?; - - // Save new info - CONFIG.save(deps.storage, &config)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { - status: ResponseStatus::Success, - })?), - ) -} - -/* Claim rewards and restake, hold enough for pending unbondings - * Send reserves unbonded funds to treasury - */ -pub fn update(deps: DepsMut, env: Env, _info: MessageInfo, asset: Addr) -> StdResult { - let mut messages = vec![]; - //let asset = deps.api.addr_validate(asset.as_str())?; - - let config = CONFIG.load(deps.storage)?; - - if asset != config.sscrt.address { - return Err(StdError::generic_err("Unrecognized Asset")); - } - - let scrt_balance = scrt_balance(deps.as_ref(), SELF_ADDRESS.load(deps.storage)?)?; - - // Claim Rewards - let rewards = query::rewards(deps.as_ref())?; - if !rewards.is_zero() { - messages.append(&mut withdraw_rewards(deps.as_ref())?); - } - - let mut stake_amount = rewards + scrt_balance; - let unbonding = UNBONDING.load(deps.storage)?; - - // Don't restake funds that unbonded - if unbonding < stake_amount { - stake_amount = stake_amount - unbonding; - } else { - stake_amount = Uint128::zero(); - } - - if stake_amount > Uint128::zero() { - let validator = choose_validator(deps, env.block.time.seconds())?; - messages.push(CosmosMsg::Staking(StakingMsg::Delegate { - validator: validator.address.clone(), - amount: Coin { - amount: stake_amount, - denom: "uscrt".to_string(), - }, - })); - } - - Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Update { - status: ResponseStatus::Success, - }, - )?)) -} - -pub fn unbond( - deps: DepsMut, - _env: Env, - info: MessageInfo, - asset: Addr, - amount: Uint128, -) -> StdResult { - /* Unbonding to the scrt staking contract - * Once scrt is on balance sheet, treasury can claim - * and this contract will take all scrt->sscrt and send - */ - - //let asset = deps.api.addr_validate(asset.as_str())?; - let config = CONFIG.load(deps.storage)?; - - if validate_admin( - &deps.querier, - AdminPermissions::ScrtStakingAdmin, - &info.sender, - &config.admin_auth, - ) - .is_err() - && config.owner != info.sender - { - return Err(StdError::generic_err("Unauthorized")); - } - - if asset != config.sscrt.address { - return Err(StdError::generic_err("Unrecognized Asset")); - } - - let self_address = SELF_ADDRESS.load(deps.storage)?; - let delegations = query::delegations(deps.as_ref())?; - - let delegated = Uint128::new( - delegations - .iter() - .map(|d| d.amount.amount.u128()) - .sum::(), - ); - let scrt_balance = scrt_balance(deps.as_ref(), self_address)?; - let rewards = query::rewards(deps.as_ref())?; - - let mut messages = vec![]; - - if !rewards.is_zero() { - messages.append(&mut withdraw_rewards(deps.as_ref())?); - } - - let mut undelegated = vec![]; - - let prev_unbonding = UNBONDING.load(deps.storage)?; - - let mut total = delegated + scrt_balance + rewards + delegated; - total -= prev_unbonding; - - if total - prev_unbonding < amount { - return Err(StdError::generic_err(format!( - "Total Unbond amount {} greater than delegated {}; rew {}, bal {}", - amount, delegated, rewards, scrt_balance - ))); - } - - let mut unbonding = amount; - let mut total_unbonding = amount + prev_unbonding; - let reserves = scrt_balance + rewards; - - if total_unbonding.is_zero() { - return Ok( - Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Unbond { - status: ResponseStatus::Success, - amount: unbonding, - })?), - ); - } - - // Send full unbonding - if total_unbonding <= reserves { - messages.append(&mut wrap_and_send( - unbonding, - config.owner, - config.sscrt, - None, - )?); - total_unbonding = Uint128::zero(); - } - // Send all reserves - else if !reserves.is_zero() { - messages.append(&mut wrap_and_send( - reserves, - config.owner, - config.sscrt, - None, - )?); - total_unbonding -= reserves; - } - - UNBONDING.save(deps.storage, &total_unbonding)?; - - while !total_unbonding.is_zero() { - // Unbond from largest validator first - let max_delegation = delegations.iter().max_by_key(|d| { - if undelegated.contains(&d.validator) { - Uint128::zero() - } else { - d.amount.amount - } - }); - - // No more delegated funds to unbond - match max_delegation { - None => { - break; - } - Some(delegation) => { - if undelegated.contains(&delegation.validator) - || delegation.amount.amount.clone() == Uint128::zero() - { - break; - } - - // This delegation isn't enough to fully unbond - if delegation.amount.amount.clone() < unbonding - && !delegation.amount.amount.clone().is_zero() - { - messages.push(CosmosMsg::Staking(StakingMsg::Undelegate { - validator: delegation.validator.clone(), - amount: delegation.amount.clone(), - })); - unbonding = unbonding - delegation.amount.amount.clone(); - undelegated.push(delegation.validator.clone()); - } else if !delegation.amount.amount.clone().is_zero() { - messages.push(CosmosMsg::Staking(StakingMsg::Undelegate { - validator: delegation.validator.clone(), - amount: Coin { - denom: delegation.amount.denom.clone(), - amount: unbonding, - }, - })); - unbonding = Uint128::zero(); - undelegated.push(delegation.validator.clone()); - } - } - } - } - - Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Unbond { - status: ResponseStatus::Success, - amount: unbonding, - }, - )?)) -} - -pub fn withdraw_rewards(deps: Deps) -> StdResult> { - let mut messages = vec![]; - let address = SELF_ADDRESS.load(deps.storage)?; - - for delegation in deps.querier.query_all_delegations(address.clone())? { - messages.push(CosmosMsg::Distribution( - DistributionMsg::WithdrawDelegatorReward { - validator: delegation.validator, - }, - )); - } - - Ok(messages) -} - -pub fn unwrap_and_stake( - _deps: DepsMut, - amount: Uint128, - validator: Validator, - token: Contract, -) -> StdResult> { - Ok(vec![ - // unwrap - unwrap(amount, token.clone())?, - // Stake - CosmosMsg::Staking(StakingMsg::Delegate { - validator: validator.address.clone(), - amount: Coin { - amount, - denom: "uscrt".to_string(), - }, - }), - ]) -} - -/* Claims completed unbondings, wraps them, - * and returns them to treasury - */ -pub fn claim(deps: DepsMut, _env: Env, _info: MessageInfo, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - //let asset = deps.api.addr_validate(asset.as_str())?; - if asset != config.sscrt.address { - return Err(StdError::generic_err("Unrecognized Asset")); - } - - let mut messages = vec![]; - - let unbond_amount = UNBONDING.load(deps.storage)?; - let claim_amount; - - let scrt_balance = scrt_balance(deps.as_ref(), SELF_ADDRESS.load(deps.storage)?)?; - - if scrt_balance >= unbond_amount { - claim_amount = unbond_amount; - } else { - // Claim Rewards - let rewards = query::rewards(deps.as_ref())?; - - if !rewards.is_zero() { - //assert!(false, "withdraw rewards"); - messages.append(&mut withdraw_rewards(deps.as_ref())?); - } - - if rewards + scrt_balance >= unbond_amount { - claim_amount = unbond_amount; - } else { - claim_amount = rewards + scrt_balance; - } - } - - if !claim_amount.is_zero() { - messages.append(&mut wrap_and_send( - claim_amount, - config.owner, - config.sscrt, - None, - )?); - - //assert!(false, "u - claim_amount: {} - {}", unbond_amount, claim_amount); - let u = UNBONDING.load(deps.storage)?; - UNBONDING.save(deps.storage, &(u - claim_amount))?; - } - - Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Claim { - status: ResponseStatus::Success, - amount: claim_amount, - }, - )?)) -} - -pub fn choose_validator(deps: DepsMut, seed: u64) -> StdResult { - let mut validators = deps.querier.query_all_validators()?; - - // filter down to viable candidates - if let Some(bounds) = (CONFIG.load(deps.storage)?).validator_bounds { - let mut candidates = vec![]; - - for validator in validators { - if is_validator_inbounds(&validator, &bounds) { - candidates.push(validator); - } - } - - validators = candidates; - } - - if validators.is_empty() { - return Err(StdError::generic_err("No validators within bounds")); - } - - // seed will likely be env.block.time.seconds() - Ok(validators[(seed % validators.len() as u64) as usize].clone()) -} - -pub fn is_validator_inbounds(validator: &Validator, bounds: &ValidatorBounds) -> bool { - validator.commission <= bounds.max_commission && validator.commission >= bounds.min_commission -} diff --git a/contracts/dao/scrt_staking/src/lib.rs b/contracts/dao/scrt_staking/src/lib.rs deleted file mode 100644 index cce0227..0000000 --- a/contracts/dao/scrt_staking/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod contract; -pub mod execute; -pub mod query; -pub mod storage; - -#[cfg(test)] -mod test; diff --git a/contracts/dao/scrt_staking/src/query.rs b/contracts/dao/scrt_staking/src/query.rs deleted file mode 100644 index b9da002..0000000 --- a/contracts/dao/scrt_staking/src/query.rs +++ /dev/null @@ -1,182 +0,0 @@ -use shade_protocol::{ - c_std::{Addr, Delegation, Deps, StdError, StdResult, Uint128}, - dao::{adapter, scrt_staking::QueryAnswer}, - utils::asset::scrt_balance, -}; - -use crate::storage::*; - -pub fn config(deps: Deps) -> StdResult { - Ok(QueryAnswer::Config { - config: CONFIG.load(deps.storage)?, - }) -} - -pub fn delegations(deps: Deps) -> StdResult> { - deps.querier - .query_all_delegations(SELF_ADDRESS.load(deps.storage)?) -} - -pub fn rewards(deps: Deps) -> StdResult { - let self_address = SELF_ADDRESS.load(deps.storage)?; - - let mut rewards = Uint128::zero(); - - // TODO change to stargate query - for d in deps.querier.query_all_delegations(self_address.clone())? { - if let Some(delegation) = deps - .querier - .query_delegation(self_address.clone(), d.validator.clone())? - { - for coin in delegation.accumulated_rewards { - if coin.denom != "uscrt" { - // TODO send to treasury - return Err(StdError::generic_err("Non-scrt coin in rewards!")); - } - rewards += coin.amount; - } - } else { - return Err(StdError::generic_err(format!( - "No delegation to {} but it was in storage", - d.validator - ))); - } - } - - Ok(rewards) -} - -pub fn balance(deps: Deps, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if asset != config.sscrt.address { - return Err(StdError::generic_err(format!( - "Unrecognized Asset {}", - asset - ))); - } - - let delegated = Uint128::new( - delegations(deps)? - .into_iter() - .map(|d| d.amount.amount.u128()) - .sum::(), - ); - - let scrt_balance = scrt_balance(deps, SELF_ADDRESS.load(deps.storage)?)?; - - let rewards = rewards(deps)?; - - Ok(adapter::QueryAnswer::Balance { - amount: delegated + rewards + scrt_balance, - }) -} - -pub fn claimable(deps: Deps, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if asset != config.sscrt.address { - return Err(StdError::generic_err(format!( - "Unrecognized Asset {}", - asset - ))); - } - - let scrt_balance = scrt_balance(deps, SELF_ADDRESS.load(deps.storage)?)?; - let rewards = rewards(deps)?; - //assert!(false, "balance {}", scrt_balance); - let unbonding = UNBONDING.load(deps.storage)?; - //assert!(false, "unbonding {}", unbonding); - - let mut amount = scrt_balance + rewards; - if amount > unbonding { - amount = unbonding; - } - - Ok(adapter::QueryAnswer::Claimable { amount }) -} - -pub fn unbonding(deps: Deps, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if asset != config.sscrt.address { - return Err(StdError::generic_err(format!( - "Unrecognized Asset {}", - asset - ))); - } - - let scrt_balance = scrt_balance(deps, SELF_ADDRESS.load(deps.storage)?)?; - - let rewards = rewards(deps)?; - let mut unbonding = UNBONDING.load(deps.storage)?; - - if unbonding >= (scrt_balance + rewards) { - unbonding -= scrt_balance + rewards; - } else { - unbonding = Uint128::zero(); - } - Ok(adapter::QueryAnswer::Unbonding { amount: unbonding }) -} - -pub fn unbondable(deps: Deps, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if asset != config.sscrt.address { - return Err(StdError::generic_err(format!( - "Unrecognized Asset {}", - asset - ))); - } - - /* TODO: issues since we cant query unbondings - * While assets are unbonding they don't reflect anywhere in balance - * Once the unbonding funds are here they will show, making it difficult to present - * unbondable funds that arent being currently unbonded - */ - let delegated = Uint128::new( - delegations(deps)? - .into_iter() - .map(|d| d.amount.amount.u128()) - .sum::(), - ); - - let scrt_balance = scrt_balance(deps, SELF_ADDRESS.load(deps.storage)?)?; - let rewards = rewards(deps)?; - - let unbonding = UNBONDING.load(deps.storage)?; - - let mut unbondable = delegated; - - if unbonding < scrt_balance + rewards { - unbondable += scrt_balance + rewards - unbonding; - } - - /*TODO: Query current unbondings - * u >= 7 = 0 - * u < 7 = unbondable - */ - Ok(adapter::QueryAnswer::Unbondable { amount: unbondable }) -} - -pub fn reserves(deps: Deps, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if asset != config.sscrt.address { - return Err(StdError::generic_err(format!( - "Unrecognized Asset {}", - asset - ))); - } - - let scrt_balance = scrt_balance(deps, SELF_ADDRESS.load(deps.storage)?)?; - - let rewards = rewards(deps)?; - - if !scrt_balance.is_zero() { - assert!(false, "scrt bal {}", scrt_balance); - } - Ok(adapter::QueryAnswer::Reserves { - amount: scrt_balance + rewards, - }) -} diff --git a/contracts/dao/scrt_staking/src/storage.rs b/contracts/dao/scrt_staking/src/storage.rs deleted file mode 100644 index 2726ffd..0000000 --- a/contracts/dao/scrt_staking/src/storage.rs +++ /dev/null @@ -1,9 +0,0 @@ -use shade_protocol::c_std::{Addr, Uint128}; -use shade_protocol::dao::scrt_staking; - -use shade_protocol::secret_storage_plus::Item; - -pub const CONFIG: Item = Item::new("config"); -pub const SELF_ADDRESS: Item = Item::new("self_address"); -pub const VIEWING_KEY: Item = Item::new("viewing_key"); -pub const UNBONDING: Item = Item::new("unbonding"); diff --git a/contracts/dao/scrt_staking/src/test.rs b/contracts/dao/scrt_staking/src/test.rs deleted file mode 100644 index 04225b9..0000000 --- a/contracts/dao/scrt_staking/src/test.rs +++ /dev/null @@ -1,46 +0,0 @@ -/* -#[cfg(test)] -pub mod tests { - use shade_protocol::c_std::{ - testing::{ - mock_dependencies, mock_env, MockStorage, MockApi, MockQuerier - }, - Addr, - coins, from_binary, StdError, Uint128, - DepsMut, - }; - use shade_protocol::{ - treasury::{ - QueryAnswer, InstantiateMsg, ExecuteMsg, - QueryMsg, - }, - asset::Contract, - }; - - use crate::{ - contract::{ - init, handle, query, - }, - }; - - fn create_contract(address: &str, code_hash: &str) -> Contract { - let env = mock_env(address.to_string(), &[]); - return Contract{ - address: info.sender, - code_hash: code_hash.to_string() - } - } - - fn dummy_init(admin: String, viewing_key: String) -> Extern { - let mut deps = mock_dependencies(20, &[]); - let msg = InstantiateMsg { - admin: Option::from(Addr(admin.clone())), - viewing_key, - }; - let env = mock_env(admin, &coins(1000, "earth")); - let _res = init(&mut deps, env, info, msg).unwrap(); - - return deps - } -} -*/ diff --git a/contracts/dao/scrt_staking/tests/integration.rs b/contracts/dao/scrt_staking/tests/integration.rs deleted file mode 100644 index f9300f9..0000000 --- a/contracts/dao/scrt_staking/tests/integration.rs +++ /dev/null @@ -1,420 +0,0 @@ -use shade_multi_test::multi::admin::init_admin_auth; -use shade_protocol::c_std::{to_binary, Addr, Coin, Delegation, Uint128}; - -use shade_protocol::{ - contract_interfaces::{ - dao::{adapter, scrt_staking}, - snip20, - }, - utils::{ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{scrt_staking::ScrtStaking, snip20::Snip20}; -use shade_protocol::multi_test::{App, StakingSudo, SudoMsg}; - -// Add other adapters here as they come -fn basic_scrt_staking_integration( - deposit: Uint128, - rewards: Uint128, - expected_scrt_staking: Uint128, -) { - let mut app = App::default(); - - let viewing_key = "unguessable".to_string(); - let admin = Addr::unchecked("admin"); - let validator = Addr::unchecked("validator"); - let admin_auth = init_admin_auth(&mut app, &admin); - let token = snip20::InstantiateMsg { - name: "secretSCRT".into(), - admin: Some("admin".into()), - symbol: "SSCRT".into(), - decimals: 6, - initial_balances: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - query_auth: None, - } - .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) - .unwrap(); - - let scrt_staking = scrt_staking::InstantiateMsg { - admin_auth: admin_auth.into(), - owner: admin.clone().into(), - sscrt: token.clone().into(), - validator_bounds: None, - viewing_key: viewing_key.clone(), - } - .test_init( - ScrtStaking::default(), - &mut app, - admin.clone(), - "scrt_staking", - &[], - ) - .unwrap(); - println!("SCRT STAKING ADDR {}", scrt_staking.address); - - app.sudo(SudoMsg::Staking(StakingSudo::AddValidator { - validator: validator.to_string().clone(), - })) - .unwrap(); - - // set admin owner key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - if !deposit.is_zero() { - let deposit_coin = Coin { - denom: "uscrt".into(), - amount: deposit, - }; - app.init_modules(|router, _, storage| { - router - .bank - .init_balance(storage, &admin.clone(), vec![deposit_coin.clone()]) - .unwrap(); - }); - - // Wrap L1 into tokens - snip20::ExecuteMsg::Deposit { padding: None } - .test_exec(&token, &mut app, admin.clone(), &vec![deposit_coin]) - .unwrap(); - - // Deposit funds in scrt staking - snip20::ExecuteMsg::Send { - recipient: scrt_staking.address.to_string().clone(), - recipient_code_hash: None, - amount: deposit, - msg: None, - memo: None, - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Delegations - let delegations: Vec = scrt_staking::QueryMsg::Delegations {} - .test_query(&scrt_staking, &app) - .unwrap(); - assert!( - !delegations.is_empty(), - "empty delegations! {}", - delegations.len() - ); - } - - // reserves should be 0 (all staked) - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap() - { - adapter::QueryAnswer::Reserves { amount } => { - assert_eq!(amount, Uint128::zero(), "Reserves Pre-Rewards"); - } - _ => panic!("Query failed"), - }; - - // Balance - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap() - { - adapter::QueryAnswer::Balance { amount } => { - assert_eq!(amount, deposit, "Balance Pre-Rewards"); - } - _ => panic!("Query failed"), - }; - - // Rewards - let cur_rewards: Uint128 = scrt_staking::QueryMsg::Rewards {} - .test_query(&scrt_staking, &app) - .unwrap(); - assert_eq!(cur_rewards, Uint128::zero(), "Rewards Pre-add"); - - //ensemble.add_rewards(rewards); - app.sudo(SudoMsg::Staking(StakingSudo::AddRewards { - amount: Coin { - amount: rewards, - denom: "uscrt".into(), - }, - })) - .unwrap(); - /* - let block = app.block_info(); - app.init_modules(|router, api, storage| { - router.staking.add_rewards( - api, - storage, - router, - &block, - Coin { amount: rewards, denom: "uscrt".into() }, - ).unwrap(); - }); - */ - - // Rewards - let cur_rewards: Uint128 = scrt_staking::QueryMsg::Rewards {} - .test_query(&scrt_staking, &app) - .unwrap(); - - if deposit.is_zero() { - assert_eq!(cur_rewards, Uint128::zero(), "Rewards Post-add"); - } else { - assert_eq!(cur_rewards, rewards, "Rewards Post-add"); - } - - // reserves should be rewards - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap() - { - adapter::QueryAnswer::Reserves { amount } => { - if deposit.is_zero() { - assert_eq!(amount, Uint128::zero(), "Reserves Post-Rewards"); - } else { - assert_eq!(amount, rewards, "Reserves Post-Rewards"); - } - } - _ => panic!("Query failed"), - }; - - // Balance - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap() - { - adapter::QueryAnswer::Balance { amount } => { - if deposit.is_zero() { - assert_eq!(amount, Uint128::zero(), "Balance Post-Rewards"); - } else { - assert_eq!(amount, deposit + rewards, "Balance Post-Rewards"); - } - } - _ => panic!("Query failed"), - }; - - // Update SCRT Staking - adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { - asset: token.address.to_string().clone().to_string(), - }) - .test_exec(&scrt_staking, &mut app, admin.clone(), &[]) - .unwrap(); - - // reserves/rewards should be staked - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap() - { - adapter::QueryAnswer::Reserves { amount } => { - assert_eq!(amount, Uint128::zero(), "Reserves Post-Update"); - } - _ => panic!("Query failed"), - }; - - // Balance - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap() - { - adapter::QueryAnswer::Balance { amount } => { - assert_eq!(amount, expected_scrt_staking, "Balance Post-Update"); - } - _ => panic!("Query failed"), - }; - - // Claimable - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap() - { - adapter::QueryAnswer::Claimable { amount } => { - assert_eq!(amount, Uint128::zero(), "Claimable Pre-Unbond"); - } - _ => panic!("Query failed"), - }; - - // Unbondable - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbondable { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap() - { - adapter::QueryAnswer::Unbondable { amount } => { - assert_eq!(amount, expected_scrt_staking, "Unbondable Pre-Unbond"); - } - _ => panic!("Query failed"), - }; - - println!("SCRT STAKING ADDR {}", scrt_staking.address); - // Unbond all - adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Unbond { - amount: expected_scrt_staking, - asset: token.address.to_string().clone().to_string(), - }) - .test_exec(&scrt_staking, &mut app, admin.clone(), &[]) - .unwrap(); - println!("SCRT STAKING ADDR {}", scrt_staking.address); - - // Unbonding - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbonding { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap() - { - adapter::QueryAnswer::Unbonding { amount } => { - assert_eq!(amount, expected_scrt_staking, "Unbonding Pre fast forward"); - } - _ => panic!("Query failed"), - }; - - // Claimable - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap() - { - adapter::QueryAnswer::Claimable { amount } => { - assert_eq!(amount, Uint128::zero(), "Claimable Pre unbond fast forward"); - } - _ => panic!("Query failed"), - }; - - app.sudo(SudoMsg::Staking(StakingSudo::FastForwardUndelegate {})) - .unwrap(); - - // Claimable - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap() - { - adapter::QueryAnswer::Claimable { amount } => { - if deposit.is_zero() { - assert_eq!(amount, Uint128::zero(), "Claimable post fast forward"); - } else { - assert_eq!(amount, deposit + rewards, "Claimable post fast forward"); - } - } - _ => panic!("Query failed"), - }; - - // Claim - adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Claim { - asset: token.address.to_string().clone().to_string(), - }) - .test_exec(&scrt_staking, &mut app, admin.clone(), &[]) - .unwrap(); - - // Reserves - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap() - { - adapter::QueryAnswer::Reserves { amount } => { - assert_eq!(amount, Uint128::zero(), "Reserves Post Claim"); - } - _ => panic!("Query failed"), - }; - - // Balance - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap() - { - adapter::QueryAnswer::Balance { amount } => { - assert_eq!(amount, Uint128::zero(), "Balance Post Claim"); - } - _ => panic!("Query failed"), - }; - - // ensure wrapped tokens were returned - match (snip20::QueryMsg::Balance { - address: admin.to_string().clone(), - key: viewing_key.clone(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - if deposit.is_zero() { - assert_eq!(amount.u128(), 0u128, "Final User balance"); - } else { - assert_eq!( - amount.u128(), - deposit.u128() + rewards.u128(), - "Final user balance" - ); - } - } - _ => { - panic!("snip20 balance query failed"); - } - }; -} - -macro_rules! basic_scrt_staking_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - deposit, - rewards, - expected_scrt_staking, - ) = $value; - basic_scrt_staking_integration(deposit, rewards, expected_scrt_staking); - } - )* - } -} - -basic_scrt_staking_tests! { - basic_scrt_staking_0: ( - Uint128::new(100), // deposit - Uint128::new(0), // rewards - Uint128::new(100), // balance - ), - basic_scrt_staking_1: ( - Uint128::new(100), // deposit - Uint128::new(50), // rewards - Uint128::new(150), // balance - ), - basic_scrt_staking_no_deposit: ( - Uint128::new(0), // deposit - Uint128::new(1000), // rewards - Uint128::new(0), // balance - ), -} diff --git a/contracts/dao/scrt_staking/tests/unit.rs b/contracts/dao/scrt_staking/tests/unit.rs deleted file mode 100644 index ea3e8b7..0000000 --- a/contracts/dao/scrt_staking/tests/unit.rs +++ /dev/null @@ -1,36 +0,0 @@ -/* -use cosmwasm_std::{ - coins, from_binary, to_binary, - Extern, Addr, StdError, - Binary, StdResult, HandleResponse, Env, - InitResponse, Uint128, -}; - -#[test] -fn test_function(param0, param1) { - assert_eq!(param0, param1); -} - -macro_rules! test_function_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let (param0, param1) = $value; - test_function(param0, param1); - } - )* - } -} - -test_function_tests! { - test_function_0: ( - Uint128(1), - Uint128(1), - ), - test_function_1: ( - Uint128(1), - Uint128(2), - ), -} -*/ diff --git a/contracts/dao/stkd_scrt/.cargo/config b/contracts/dao/stkd_scrt/.cargo/config deleted file mode 100644 index 882fe08..0000000 --- a/contracts/dao/stkd_scrt/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[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/dao/stkd_scrt/.circleci/config.yml b/contracts/dao/stkd_scrt/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/contracts/dao/stkd_scrt/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/dao/stkd_scrt/Cargo.toml b/contracts/dao/stkd_scrt/Cargo.toml deleted file mode 100644 index 061fa8b..0000000 --- a/contracts/dao/stkd_scrt/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -name = "stkd_scrt" -version = "0.1.0" -authors = ["Jack Swenson "] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] - -[dependencies] -shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ - "adapter", - "dao", - "stkd_scrt", - "treasury", - "math", - "storage_plus", -] } - -[dev-dependencies] -shade-multi-test = { path = "../../../packages/multi_test", features = [ - "scrt_staking", - "snip20", - "admin" -] } diff --git a/contracts/dao/stkd_scrt/Makefile b/contracts/dao/stkd_scrt/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/contracts/dao/stkd_scrt/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/dao/stkd_scrt/README.md b/contracts/dao/stkd_scrt/README.md deleted file mode 100644 index eb0af80..0000000 --- a/contracts/dao/stkd_scrt/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# sSCRT Staking Contract -* [Introduction](#Introduction) -* [Sections](#Sections) - * [Init](#Init) - * [DAO Adapter](/packages/shade_protocol/src/DAO_ADAPTER.md) - * [Interface](#Interface) - * Messages - * [Receive](#Receive) - * [UpdateConfig](#UpdateConfig) - * Queries - * [Config](#Config) - * [Delegations](#Delegations) - -# Introduction -The sSCRT Staking contract receives sSCRT, redeems it for SCRT, then stakes it with a validator that falls within the criteria it has been configured with. The configured `treasury` will receive all funds from claiming rewards/unbonding. - -# Sections - -## Init -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|admin | Addr | contract owner/admin; a valid bech32 address; -|treasury | Addr | contract designated to receive all outgoing funds -|sscrt | Contract | sSCRT Snip-20 contract to accept for redemption/staking, all other funds will error -|validator_bounds | ValidatorBounds | criteria defining an acceptable validator to stake with -|viewing_key | String | Viewing Key to be set for any relevant SNIP-20 - -## Interface - -### Messages -#### UpdateConfig -Updates the given values -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|owner | Addr | contract owner/admin; a valid bech32 address; -|treasury | Addr | contract designated to receive all outgoing funds -|sscrt | Contract | sSCRT Snip-20 contract to accept for redemption/staking, all other funds will error -|validator_bounds | ValidatorBounds | criteria defining an acceptable validator to stake with - -##### Response -```json -{ - "update_config": { - "status": "success" - } -} -``` - - -### Queries - -#### Config -Gets the contract's configuration variables -##### Response -```json -{ - "config": { - "config": { - "owner": "Owner address", - } - } -} -``` diff --git a/contracts/dao/stkd_scrt/src/contract.rs b/contracts/dao/stkd_scrt/src/contract.rs deleted file mode 100644 index 392d266..0000000 --- a/contracts/dao/stkd_scrt/src/contract.rs +++ /dev/null @@ -1,108 +0,0 @@ -use shade_protocol::{ - c_std::{ - shd_entry_point, - to_binary, - Binary, - Deps, - DepsMut, - Env, - MessageInfo, - Response, - StdResult, - }, - dao::{ - adapter, - stkd_scrt::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}, - }, - snip20::helpers::{register_receive, set_viewing_key_msg}, - utils::generic_response::ResponseStatus, -}; - -use crate::{execute, query, storage::*}; - -#[shd_entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - let config = Config { - admin_auth: msg.admin_auth.into_valid(deps.api)?, - sscrt: msg.sscrt.into_valid(deps.api)?, - owner: deps.api.addr_validate(msg.owner.as_str())?, - staking_derivatives: msg.staking_derivatives.into_valid(deps.api)?, - }; - - CONFIG.save(deps.storage, &config)?; - - SELF_ADDRESS.save(deps.storage, &env.contract.address)?; - VIEWING_KEY.save(deps.storage, &msg.viewing_key)?; - - Ok(Response::new().add_messages(vec![ - set_viewing_key_msg(msg.viewing_key, None, &config.sscrt)?, - register_receive(env.contract.code_hash, None, &config.sscrt)?, - ])) -} - -#[shd_entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - ExecuteMsg::Receive { - sender, - from, - amount, - msg, - .. - } => { - let sender = deps.api.addr_validate(&sender)?; - let from = deps.api.addr_validate(&from)?; - execute::receive(deps, env, info, sender, from, amount, msg) - } - ExecuteMsg::UpdateConfig { config } => execute::try_update_config(deps, env, info, config), - ExecuteMsg::Adapter(adapter) => match adapter { - adapter::SubExecuteMsg::Unbond { asset, amount } => { - let asset = deps.api.addr_validate(&asset)?; - execute::unbond(deps, env, info, asset, amount) - } - adapter::SubExecuteMsg::Claim { asset } => { - let asset = deps.api.addr_validate(&asset)?; - execute::claim(deps, env, info, asset) - } - adapter::SubExecuteMsg::Update { asset: _ } => Ok(Response::new().set_data(to_binary( - &adapter::ExecuteAnswer::Update { - status: ResponseStatus::Success, - }, - )?)), - }, - } -} - -#[shd_entry_point] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query::config(deps)?), - QueryMsg::Adapter(adapter) => match adapter { - adapter::SubQueryMsg::Balance { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::balance(deps, env, asset)?) - } - adapter::SubQueryMsg::Claimable { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::claimable(deps, env, asset)?) - } - adapter::SubQueryMsg::Unbonding { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::unbonding(deps, env, asset)?) - } - adapter::SubQueryMsg::Unbondable { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::unbondable(deps, env, asset)?) - } - adapter::SubQueryMsg::Reserves { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::reserves(deps, env, asset)?) - } - }, - } -} diff --git a/contracts/dao/stkd_scrt/src/execute.rs b/contracts/dao/stkd_scrt/src/execute.rs deleted file mode 100644 index 0876590..0000000 --- a/contracts/dao/stkd_scrt/src/execute.rs +++ /dev/null @@ -1,158 +0,0 @@ -use shade_protocol::{ - admin::helpers::{validate_admin, AdminPermissions}, - c_std::{ - to_binary, - Addr, - Binary, - DepsMut, - Env, - MessageInfo, - Response, - StdError, - StdResult, - Uint128, - }, -}; - -use shade_protocol::{ - dao::{ - adapter, - stkd_scrt::{staking_derivatives, Config, ExecuteAnswer}, - }, - utils::{ - generic_response::ResponseStatus, - wrap::{unwrap, wrap_and_send}, - }, -}; - -use crate::storage::*; - -pub fn receive( - deps: DepsMut, - _env: Env, - info: MessageInfo, - _sender: Addr, - _from: Addr, - amount: Uint128, - _msg: Option, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if info.sender != config.sscrt.address { - return Err(StdError::generic_err("Only accepts sSCRT")); - } - - // Unwrap & stake - Ok(Response::new() - .add_message(unwrap(amount, config.sscrt.clone())?) - .add_message(staking_derivatives::stake_msg( - amount, - &config.staking_derivatives, - )?) - .set_data(to_binary(&ExecuteAnswer::Receive { - status: ResponseStatus::Success, - })?)) -} - -pub fn try_update_config( - deps: DepsMut, - _env: Env, - info: MessageInfo, - config: Config, -) -> StdResult { - let cur_config = CONFIG.load(deps.storage)?; - - validate_admin( - &deps.querier, - AdminPermissions::ScrtStakingAdmin, - &info.sender, - &cur_config.admin_auth, - )?; - - // Save new info - CONFIG.save(deps.storage, &config)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn unbond( - deps: DepsMut, - _env: Env, - info: MessageInfo, - asset: Addr, - amount: Uint128, -) -> StdResult { - /* Unbonding to the scrt staking contract - * Once scrt is on balance sheet, treasury can claim - * and this contract will take all scrt->sscrt and send - */ - let config = CONFIG.load(deps.storage)?; - - if validate_admin( - &deps.querier, - AdminPermissions::ScrtStakingAdmin, - &info.sender, - &config.admin_auth, - ) - .is_err() - && config.owner != info.sender - { - return Err(StdError::generic_err("Unauthorized")); - } - - if asset != config.sscrt.address { - return Err(StdError::generic_err("Unrecognized Asset")); - } - - Ok(Response::new() - .add_message(staking_derivatives::unbond_msg( - amount, - &config.staking_derivatives, - )?) - .set_data(to_binary(&adapter::ExecuteAnswer::Unbond { - status: ResponseStatus::Success, - amount, - })?)) -} - -/* Claims completed unbondings, wraps them, - * and returns them to treasury - */ -pub fn claim(deps: DepsMut, env: Env, _info: MessageInfo, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if asset != config.sscrt.address { - return Err(StdError::generic_err("Unrecognized Asset")); - } - - let claimable = staking_derivatives::holdings_query( - &deps.querier, - env.contract.address, - VIEWING_KEY.load(deps.storage)?, - env.block.time.seconds(), - &config.staking_derivatives, - )? - .claimable_scrt; - - let mut messages = vec![]; - if !claimable.is_zero() { - messages.push(staking_derivatives::claim_msg(&config.staking_derivatives)?); - messages.append(&mut wrap_and_send( - claimable, - config.owner, - config.sscrt, - None, - )?); - } - - Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Claim { - status: ResponseStatus::Success, - amount: claimable, - }, - )?)) -} diff --git a/contracts/dao/stkd_scrt/src/lib.rs b/contracts/dao/stkd_scrt/src/lib.rs deleted file mode 100644 index cce0227..0000000 --- a/contracts/dao/stkd_scrt/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod contract; -pub mod execute; -pub mod query; -pub mod storage; - -#[cfg(test)] -mod test; diff --git a/contracts/dao/stkd_scrt/src/query.rs b/contracts/dao/stkd_scrt/src/query.rs deleted file mode 100644 index a53da5c..0000000 --- a/contracts/dao/stkd_scrt/src/query.rs +++ /dev/null @@ -1,124 +0,0 @@ -use shade_protocol::{ - c_std::{Addr, Deps, Env, StdError, StdResult, Uint128}, - dao::{ - adapter, - stkd_scrt::{staking_derivatives, QueryAnswer}, - }, -}; - -use crate::storage::*; - -pub fn config(deps: Deps) -> StdResult { - Ok(QueryAnswer::Config { - config: CONFIG.load(deps.storage)?, - }) -} - -pub fn balance(deps: Deps, env: Env, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if asset != config.sscrt.address { - return Err(StdError::generic_err(format!( - "Unrecognized Asset {}", - asset - ))); - } - - let holdings = staking_derivatives::holdings_query( - &deps.querier, - env.contract.address, - VIEWING_KEY.load(deps.storage)?, - env.block.time.seconds(), - &config.staking_derivatives, - )?; - - Ok(adapter::QueryAnswer::Balance { - amount: holdings.claimable_scrt - + holdings.unbonding_scrt - + holdings.token_balance_value_in_scrt, - }) -} - -pub fn claimable(deps: Deps, env: Env, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if asset != config.sscrt.address { - return Err(StdError::generic_err(format!( - "Unrecognized Asset {}", - asset - ))); - } - - let holdings = staking_derivatives::holdings_query( - &deps.querier, - env.contract.address, - VIEWING_KEY.load(deps.storage)?, - env.block.time.seconds(), - &config.staking_derivatives, - )?; - - Ok(adapter::QueryAnswer::Claimable { - amount: holdings.claimable_scrt, - }) -} - -pub fn unbonding(deps: Deps, env: Env, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if asset != config.sscrt.address { - return Err(StdError::generic_err(format!( - "Unrecognized Asset {}", - asset - ))); - } - - let holdings = staking_derivatives::holdings_query( - &deps.querier, - env.contract.address, - VIEWING_KEY.load(deps.storage)?, - env.block.time.seconds(), - &config.staking_derivatives, - )?; - - Ok(adapter::QueryAnswer::Unbonding { - amount: holdings.unbonding_scrt, - }) -} - -pub fn unbondable(deps: Deps, env: Env, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if asset != config.sscrt.address { - return Err(StdError::generic_err(format!( - "Unrecognized Asset {}", - asset - ))); - } - - let holdings = staking_derivatives::holdings_query( - &deps.querier, - env.contract.address, - VIEWING_KEY.load(deps.storage)?, - env.block.time.seconds(), - &config.staking_derivatives, - )?; - - Ok(adapter::QueryAnswer::Unbondable { - amount: holdings.token_balance_value_in_scrt, - }) -} - -pub fn reserves(deps: Deps, _env: Env, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if asset != config.sscrt.address { - return Err(StdError::generic_err(format!( - "Unrecognized Asset {}", - asset - ))); - } - - Ok(adapter::QueryAnswer::Reserves { - amount: Uint128::zero(), - }) -} diff --git a/contracts/dao/stkd_scrt/src/storage.rs b/contracts/dao/stkd_scrt/src/storage.rs deleted file mode 100644 index 2795df2..0000000 --- a/contracts/dao/stkd_scrt/src/storage.rs +++ /dev/null @@ -1,7 +0,0 @@ -use shade_protocol::{c_std::Addr, dao::stkd_scrt}; - -use shade_protocol::secret_storage_plus::Item; - -pub const CONFIG: Item = Item::new("config"); -pub const SELF_ADDRESS: Item = Item::new("self_address"); -pub const VIEWING_KEY: Item = Item::new("viewing_key"); diff --git a/contracts/dao/stkd_scrt/src/test.rs b/contracts/dao/stkd_scrt/src/test.rs deleted file mode 100644 index 04225b9..0000000 --- a/contracts/dao/stkd_scrt/src/test.rs +++ /dev/null @@ -1,46 +0,0 @@ -/* -#[cfg(test)] -pub mod tests { - use shade_protocol::c_std::{ - testing::{ - mock_dependencies, mock_env, MockStorage, MockApi, MockQuerier - }, - Addr, - coins, from_binary, StdError, Uint128, - DepsMut, - }; - use shade_protocol::{ - treasury::{ - QueryAnswer, InstantiateMsg, ExecuteMsg, - QueryMsg, - }, - asset::Contract, - }; - - use crate::{ - contract::{ - init, handle, query, - }, - }; - - fn create_contract(address: &str, code_hash: &str) -> Contract { - let env = mock_env(address.to_string(), &[]); - return Contract{ - address: info.sender, - code_hash: code_hash.to_string() - } - } - - fn dummy_init(admin: String, viewing_key: String) -> Extern { - let mut deps = mock_dependencies(20, &[]); - let msg = InstantiateMsg { - admin: Option::from(Addr(admin.clone())), - viewing_key, - }; - let env = mock_env(admin, &coins(1000, "earth")); - let _res = init(&mut deps, env, info, msg).unwrap(); - - return deps - } -} -*/ diff --git a/contracts/dao/stkd_scrt/tests/integration.rs b/contracts/dao/stkd_scrt/tests/integration.rs deleted file mode 100644 index 4065456..0000000 --- a/contracts/dao/stkd_scrt/tests/integration.rs +++ /dev/null @@ -1,331 +0,0 @@ -/*use shade_multi_test::multi::admin::init_admin_auth; -use shade_protocol::c_std::{to_binary, Addr, Coin, Decimal, Delegation, Uint128, Validator}; - -use shade_protocol::{ - contract_interfaces::{ - dao::{adapter, stkd_scrt}, - snip20, - }, - utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{snip20::Snip20, stkd_scrt::StkdScrt}; -use shade_protocol::multi_test::{App, StakingSudo, SudoMsg}; - -fn bonded_adapter_test(deposit: Uint128, rewards: Uint128, reserves: Uint128, balance: Uint128) { - let mut app = App::default(); - - let viewing_key = "unguessable".to_string(); - let admin = Addr::unchecked("admin"); - let validator = Addr::unchecked("validator"); - let admin_auth = init_admin_auth(&mut app, &admin); - let token = snip20::InstantiateMsg { - name: "secretSCRT".into(), - admin: Some("admin".into()), - symbol: "SSCRT".into(), - decimals: 6, - initial_balances: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - query_auth: None, - } - .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) - .unwrap(); - - let stkd_scrt = stkd_scrt::InstantiateMsg { - admin_auth: admin_auth.into(), - owner: admin.clone().into(), - sscrt: token.clone().into(), - validator_bounds: None, - viewing_key: viewing_key.clone(), - } - .test_init( - ScrtStaking::default(), - &mut app, - admin.clone(), - "stkd_scrt", - &[], - ) - .unwrap(); - - app.sudo(SudoMsg::Staking(StakingSudo::AddValidator { - validator: validator.to_string().clone(), - })) - .unwrap(); - - //TODO deploy staking_derivatives - - // set admin owner key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - let deposit_coin = Coin { - denom: "uscrt".into(), - amount: deposit, - }; - app.init_modules(|router, _, storage| { - router - .bank - .init_balance(storage, &admin.clone(), vec![deposit_coin.clone()]) - .unwrap(); - }); - - // Wrap L1 into tokens - snip20::ExecuteMsg::Deposit { padding: None } - .test_exec(&token, &mut app, admin.clone(), &vec![deposit_coin]) - .unwrap(); - - // Send funds to adapter - snip20::ExecuteMsg::Send { - recipient: stkd_scrt.address.to_string().clone(), - recipient_code_hash: None, - amount: deposit, - msg: None, - memo: None, - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // reserves - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { - asset: token.address.to_string().clone(), - }) - .test_query(&stkd_scrt, &app) - .unwrap() - { - adapter::QueryAnswer::Reserves { amount } => { - assert_eq!(amount, reserves, "Reserves Pre-Rewards"); - } - _ => panic!("Query failed"), - }; - - // Balance - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&stkd_scrt, &app) - .unwrap() - { - adapter::QueryAnswer::Balance { amount } => { - assert_eq!(amount, balance, "Balance Pre-Rewards"); - } - _ => panic!("Query failed"), - }; - - // Rewards - let cur_rewards: Uint128 = stkd_scrt::QueryMsg::Rewards {} - .test_query(&stkd_scrt, &app) - .unwrap(); - assert_eq!(cur_rewards, Uint128::zero(), "Rewards Pre-add"); - - //ensemble.add_rewards(rewards); - app.sudo(SudoMsg::Staking(StakingSudo::AddRewards { - amount: Coin { - amount: rewards, - denom: "uscrt".into(), - }, - })) - .unwrap(); - - // Reserves - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { - asset: token.address.to_string().clone(), - }) - .test_query(&stkd_scrt, &app) - .unwrap() - { - adapter::QueryAnswer::Reserves { amount } => { - assert_eq!(amount, reserves, "Reserves Post-Rewards"); - } - _ => panic!("Query failed"), - }; - - // Balance - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&stkd_scrt, &app) - .unwrap() - { - adapter::QueryAnswer::Balance { amount } => { - assert_eq!(amount, deposit + rewards, "Balance Post-Rewards"); - } - _ => panic!("Query failed"), - }; - - // Claimable - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { - asset: token.address.to_string().clone(), - }) - .test_query(&stkd_scrt, &app) - .unwrap() - { - adapter::QueryAnswer::Claimable { amount } => { - assert_eq!(amount, Uint128::zero(), "Claimable Pre-Unbond"); - } - _ => panic!("Query failed"), - }; - - // Unbondable - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbondable { - asset: token.address.to_string().clone(), - }) - .test_query(&stkd_scrt, &app) - .unwrap() - { - adapter::QueryAnswer::Unbondable { amount } => { - assert_eq!(amount, balance, "Unbondable Pre-Unbond"); - } - _ => panic!("Query failed"), - }; - - // Unbond all - adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Unbond { - amount: balance, - asset: token.address.to_string().clone().to_string(), - }) - .test_exec(&stkd_scrt, &mut app, admin.clone(), &[]) - .unwrap(); - println!("SCRT STAKING ADDR {}", stkd_scrt.address); - - // Unbonding - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbonding { - asset: token.address.to_string().clone(), - }) - .test_query(&stkd_scrt, &app) - .unwrap() - { - adapter::QueryAnswer::Unbonding { amount } => { - assert_eq!(amount, deposit, "Unbonding Pre fast forward"); - } - _ => panic!("Query failed"), - }; - - // Claimable - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { - asset: token.address.to_string().clone(), - }) - .test_query(&stkd_scrt, &app) - .unwrap() - { - adapter::QueryAnswer::Claimable { amount } => { - assert_eq!(amount, Uint128::zero(), "Claimable Pre unbond fast forward"); - } - _ => panic!("Query failed"), - }; - - app.sudo(SudoMsg::Staking(StakingSudo::FastForwardUndelegate {})) - .unwrap(); - - // Claimable - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { - asset: token.address.to_string().clone(), - }) - .test_query(&stkd_scrt, &app) - .unwrap() - { - adapter::QueryAnswer::Claimable { amount } => { - assert_eq!(amount, deposit, "Claimable post fast forward"); - } - _ => panic!("Query failed"), - }; - - // Claim - adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Claim { - asset: token.address.to_string().clone().to_string(), - }) - .test_exec(&stkd_scrt, &mut app, admin.clone(), &[]) - .unwrap(); - - // Reserves - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { - asset: token.address.to_string().clone(), - }) - .test_query(&stkd_scrt, &app) - .unwrap() - { - adapter::QueryAnswer::Reserves { amount } => { - assert_eq!(amount, Uint128::zero(), "Reserves Post Claim"); - } - _ => panic!("Query failed"), - }; - - // Balance - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&stkd_scrt, &app) - .unwrap() - { - adapter::QueryAnswer::Balance { amount } => { - assert_eq!(amount, Uint128::zero(), "Balance Post Claim"); - } - _ => panic!("Query failed"), - }; - - // Unbonding - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbonding { - asset: token.address.to_string().clone(), - }) - .test_query(&stkd_scrt, &app) - .unwrap() - { - adapter::QueryAnswer::Unbonding { amount } => { - assert_eq!(amount, Uint128::zero(), "Unbonding Post Claim"); - } - _ => panic!("Query failed"), - }; - - // ensure wrapped tokens were returned - match (snip20::QueryMsg::Balance { - address: admin.to_string().clone(), - key: viewing_key.clone(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, deposit + rewards, "Final User balance"); - } - _ => { - panic!("snip20 balance query failed"); - } - }; -} - -macro_rules! basic_stkd_scrt_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - deposit, - rewards, - expected_stkd_scrt, - ) = $value; - basic_stkd_scrt_integration(deposit, rewards, expected_stkd_scrt); - } - )* - } -} - -basic_stkd_scrt_tests! { - basic_stkd_scrt_0: ( - Uint128::new(100), // deposit - Uint128::new(10), // rewards - Uint128::new(100), // reserves - Uint128::new(100), // balance - ), -}*/ diff --git a/contracts/dao/stkd_scrt/tests/unit.rs b/contracts/dao/stkd_scrt/tests/unit.rs deleted file mode 100644 index ea3e8b7..0000000 --- a/contracts/dao/stkd_scrt/tests/unit.rs +++ /dev/null @@ -1,36 +0,0 @@ -/* -use cosmwasm_std::{ - coins, from_binary, to_binary, - Extern, Addr, StdError, - Binary, StdResult, HandleResponse, Env, - InitResponse, Uint128, -}; - -#[test] -fn test_function(param0, param1) { - assert_eq!(param0, param1); -} - -macro_rules! test_function_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let (param0, param1) = $value; - test_function(param0, param1); - } - )* - } -} - -test_function_tests! { - test_function_0: ( - Uint128(1), - Uint128(1), - ), - test_function_1: ( - Uint128(1), - Uint128(2), - ), -} -*/ diff --git a/contracts/dao/treasury/.cargo/config b/contracts/dao/treasury/.cargo/config deleted file mode 100644 index 882fe08..0000000 --- a/contracts/dao/treasury/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[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/dao/treasury/.circleci/config.yml b/contracts/dao/treasury/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/contracts/dao/treasury/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/dao/treasury/Cargo.toml b/contracts/dao/treasury/Cargo.toml deleted file mode 100644 index 921b460..0000000 --- a/contracts/dao/treasury/Cargo.toml +++ /dev/null @@ -1,47 +0,0 @@ -[package] -name = "treasury" -version = "0.1.0" -authors = ["Jackson Swenson ", "Jack Sisson ./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 - -# 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/dao/treasury/README.md b/contracts/dao/treasury/README.md deleted file mode 100644 index cf14494..0000000 --- a/contracts/dao/treasury/README.md +++ /dev/null @@ -1,172 +0,0 @@ -# Treasury -* [Introduction](#Introduction) -* [Sections](#Sections) - * [Init](#Init) - * [DAO Adapter](/packages/shade_protocol/src/DAO_ADAPTER.md) - * [Interface](#Interface) - * Messages - * [Receive](#Receive) - * [UpdateConfig](#UpdateConfig) - * [RegisterAsset](#RegisterAsset) - * [RegisterManager](#RegisterManager) - * [Allowance](#Allowance) - * [AddAccount](#AddAccount) - * [CloseAccount](#CloseAccount) - * Queries - * [Config](#Config) - * [Assets](#Assets) - * [Allowances](#Allowances) - * [CurrentAllowances](#CurrentAllowances) - * [Allowance](#Allowance) - * [Account](#Account) -# Introduction -The treasury contract holds network funds from things such as mint commission and pending airdrop funds - -# Sections - -## Init -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|admin | string | contract owner/admin; a valid bech32 address; Controls funds -|viewing_key | string | viewing key for all registered snip20 assets -|sscrt | Contract | sSCRT contract for wrapping & unwrapping - -## Interface - -### Messages - -#### UpdateConfig -Updates the given values -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|config | string | New config to be set for the contract - -##### Response -```json -{ - "update_config": { - "status": "success" - } -} -``` - -#### RegisterAsset -Registers a SNIP-20 compliant asset since [RegisterReceive](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md#RegisterReceive) is called. - -Note: Will return an error if there's an asset with that address already registered. -##### Request -|Name |Type |Description | optional | -|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| -|contract | Contract | Type explained [here](#Contract) | no | -##### Response -```json -{ - "register_asset": { - "status": "success" - } -} -``` - -### Queries - -#### Config -Gets the contract's configuration -##### Response -```json -{ - "config": { - "config": { - "admin": "admin address", - "sscrt": { - "address": "", - "code_hash": "", - }, - } - } -} -``` - -#### Assets -List of assets supported -##### Response -```json -{ - "assets": { - "assets": ["asset address", ...] - } -} -``` - -#### Allowances -List of configured allowances for things like treasury_manager & rewards -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|asset | Addr | Asset to query balance of -##### Response -```json -{ - "allowances": { - "allowances": [ - { - "allowance": ... - }, - ...] - } -} -``` - -#### Allowance -List of configured allowances for things like treasury_manager & rewards -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|asset | Addr | Asset to query allowance for -|spender | Addr | Spender of allowance -##### Response -```json -{ - "allowances": { - "allowances": [ - { - "allowance": ... - }, - ... - ] - } -} -``` - -#### Accounts -List of account holders -##### Response -```json -{ - "accounts": { - "accounts": ["address0", ...], - } -} -``` - -#### Account -Balance of a given account holders assets (e.g. SHD staking) -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|holder | Addr | Holder of the account -|asset | Addr | Asset to query balance of -##### Response -```json -{ - "account": { - "account": { - "balances": Uint128, - "unbondings": Uint128, - "claimable": Uint128, - "status": ("active"|"disabled"|"closed"|"transferred"), - } - } -} -``` diff --git a/contracts/dao/treasury/Untitled Diagram.drawio b/contracts/dao/treasury/Untitled Diagram.drawio deleted file mode 100644 index 513b976..0000000 --- a/contracts/dao/treasury/Untitled Diagram.drawio +++ /dev/null @@ -1 +0,0 @@ -UzV2zq1wL0osyPDNT0nNUTV2VTV2LsrPL4GwciucU3NyVI0MMlNUjV1UjYwMgFjVyA2HrCFY1qAgsSg1rwSLBiADYTaQg2Y1AA== \ No newline at end of file diff --git a/contracts/dao/treasury/src/contract.rs b/contracts/dao/treasury/src/contract.rs deleted file mode 100644 index 0de8ab0..0000000 --- a/contracts/dao/treasury/src/contract.rs +++ /dev/null @@ -1,128 +0,0 @@ -use crate::{execute, query, storage::*}; -use shade_protocol::{ - c_std::{ - shd_entry_point, - to_binary, - Addr, - Binary, - Deps, - DepsMut, - Env, - MessageInfo, - Response, - StdResult, - }, - dao::treasury::{Config, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, RunLevel}, - utils::asset::Contract, -}; - -#[shd_entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - CONFIG.save(deps.storage, &Config { - admin_auth: msg.admin_auth.into_valid(deps.api)?, - multisig: deps.api.addr_validate(&msg.multisig)?, - })?; - - VIEWING_KEY.save(deps.storage, &msg.viewing_key)?; - RUN_LEVEL.save(deps.storage, &RunLevel::Normal)?; - - Ok(Response::new()) -} - -#[shd_entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - ExecuteMsg::Receive { - sender, - from, - amount, - msg, - .. - } => { - let sender = deps.api.addr_validate(&sender)?; - let from = deps.api.addr_validate(&from)?; - execute::receive(deps, env, info, sender, from, amount, msg) - } - ExecuteMsg::UpdateConfig { - admin_auth, - multisig, - } => execute::try_update_config(deps, env, info, admin_auth, multisig), - ExecuteMsg::RegisterAsset { contract } => { - let contract = contract.into_valid(deps.api)?; - execute::try_register_asset(deps, &env, info, &contract) - } - ExecuteMsg::RegisterManager { contract } => { - let mut contract = contract.into_valid(deps.api)?; - execute::register_manager(deps, &env, info, &mut contract) - } - ExecuteMsg::RegisterWrap { denom, contract } => { - let contract = contract.into_valid(deps.api)?; - execute::register_wrap(deps, &env, info, denom, &contract) - } - ExecuteMsg::Allowance { - asset, - allowance, - refresh_now, - } => { - let asset = deps.api.addr_validate(&asset)?; - let allowance = allowance.valid(deps.api)?; - execute::allowance(deps, &env, info, asset, allowance, refresh_now) - } - ExecuteMsg::Update { asset } => { - let asset = deps.api.addr_validate(&asset)?; - execute::update(deps, &env, info, asset) - } - ExecuteMsg::SetRunLevel { run_level } => { - execute::set_run_level(deps, &env, info, run_level) - } - ExecuteMsg::WrapCoins {} => execute::wrap_coins(deps, &env, info), - } -} - -#[shd_entry_point] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query::config(deps)?), - QueryMsg::Assets {} => to_binary(&query::assets(deps)?), - QueryMsg::Allowances { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::allowances(deps, asset)?) - } - QueryMsg::Allowance { asset, spender } => { - let asset = deps.api.addr_validate(&asset)?; - let spender = deps.api.addr_validate(&spender)?; - to_binary(&query::allowance(deps, env, asset, spender)?) - } - QueryMsg::RunLevel => to_binary(&QueryAnswer::RunLevel { - run_level: RUN_LEVEL.load(deps.storage)?, - }), - //TODO: parse string & format manually to accept all valid date formats - QueryMsg::Metrics { - date, - epoch, - period, - } => to_binary(&query::metrics(deps, env, date, epoch, period)?), - QueryMsg::Balance { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::balance(deps, env, asset)?) - } - QueryMsg::BatchBalance { assets } => { - let mut val_assets = vec![]; - - for a in assets { - val_assets.push(deps.api.addr_validate(&a)?); - } - - to_binary(&query::batch_balance(deps, env, val_assets)?) - } - QueryMsg::Reserves { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::reserves(deps, env, asset)?) - } - } -} diff --git a/contracts/dao/treasury/src/execute.rs b/contracts/dao/treasury/src/execute.rs deleted file mode 100644 index de391b2..0000000 --- a/contracts/dao/treasury/src/execute.rs +++ /dev/null @@ -1,795 +0,0 @@ -use crate::storage::*; -use shade_protocol::{ - c_std::{ - to_binary, - Addr, - Binary, - DepsMut, - Env, - MessageInfo, - Response, - StdError, - StdResult, - Uint128, - }, - contract_interfaces::{ - admin::helpers::{validate_admin, AdminPermissions}, - dao::{ - manager, - treasury::{ - Action, - Allowance, - AllowanceMeta, - AllowanceType, - Context, - ExecuteAnswer, - Metric, - RunLevel, - }, - }, - snip20, - }, - snip20::helpers::{ - allowance_query, - balance_query, - decrease_allowance_msg, - increase_allowance_msg, - register_receive, - send_msg, - set_viewing_key_msg, - }, - utils::{ - asset::{Contract, RawContract}, - cycle::{exceeds_cycle, parse_utc_datetime, utc_from_seconds, utc_now, Cycle}, - generic_response::ResponseStatus, - wrap::wrap_coin, - }, -}; -use std::collections::HashMap; - -const ONE_HUNDRED_PERCENT: Uint128 = Uint128::new(10u128.pow(18u32)); - -pub fn receive( - deps: DepsMut, - env: Env, - info: MessageInfo, - _sender: Addr, - from: Addr, - amount: Uint128, - _msg: Option, -) -> StdResult { - // Only store metrics for registered assets - if ASSET.may_load(deps.storage, info.sender.clone())?.is_some() { - METRICS.push(deps.storage, env.block.time, Metric { - action: Action::FundsReceived, - context: Context::Receive, - timestamp: env.block.time.seconds(), - token: info.sender, - amount, - user: from, - })?; - } - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Receive { - status: ResponseStatus::Success, - })?)) -} - -pub fn try_update_config( - deps: DepsMut, - _env: Env, - info: MessageInfo, - admin_auth: Option, - multisig: Option, -) -> StdResult { - let mut config = CONFIG.load(deps.storage)?; - - validate_admin( - &deps.querier, - AdminPermissions::TreasuryAdmin, - &info.sender, - &config.admin_auth, - )?; - - if let Some(admin_auth) = admin_auth { - config.admin_auth = admin_auth.into_valid(deps.api)?; - } - if let Some(multisig) = multisig { - config.multisig = deps.api.addr_validate(&multisig)?; - } - - CONFIG.save(deps.storage, &config)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { - config, - status: ResponseStatus::Success, - })?), - ) -} - -pub fn update(deps: DepsMut, env: &Env, info: MessageInfo, asset: Addr) -> StdResult { - match RUN_LEVEL.load(deps.storage)? { - RunLevel::Migrating => { - let config = CONFIG.load(deps.storage)?; - validate_admin( - &deps.querier, - AdminPermissions::TreasuryAdmin, - &info.sender, - &config.admin_auth, - )?; - migrate(deps, env, info, asset) - } - RunLevel::Deactivated => { - return Err(StdError::generic_err("Contract Deactivated")); - } - RunLevel::Normal => rebalance(deps, env, info, asset), - } -} - -fn rebalance(deps: DepsMut, env: &Env, _info: MessageInfo, asset: Addr) -> StdResult { - let viewing_key = VIEWING_KEY.load(deps.storage)?; - - let full_asset = match ASSET.may_load(deps.storage, asset.clone())? { - Some(a) => a, - None => { - return Err(StdError::generic_err("Not a registered asset")); - } - }; - - let mut allowances = ALLOWANCES.load(deps.storage, asset.clone())?; - - let mut total_balance = balance_query( - &deps.querier, - env.contract.address.clone(), - viewing_key.clone(), - &full_asset.contract.clone(), - )?; - let mut token_balance = total_balance; - - // { spender: (balance, allowance) } - let mut metadata: HashMap = HashMap::new(); - - let mut messages = vec![]; - let mut metrics = vec![]; - - let now = utc_now(&env); - - // allowances marked for removal - let mut stale_allowances = vec![]; - - for (i, a) in allowances.clone().iter().enumerate() { - let manager = MANAGER.may_load(deps.storage, a.spender.clone())?; - let mut claimable = Uint128::zero(); - let mut unbonding = Uint128::zero(); - let mut balance = Uint128::zero(); - // we can only get some of these numbers when it's a treasury manager - if let Some(m) = manager.clone() { - claimable = manager::claimable_query( - deps.querier, - &asset.clone(), - env.contract.address.clone(), - m.clone(), - )?; - // claim when not zero - if !claimable.is_zero() { - messages.push(manager::claim_msg(&asset.clone(), m.clone())?); - metrics.push(Metric { - action: Action::Claim, - context: Context::Rebalance, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: claimable, - user: m.address.clone(), - }); - } - - unbonding = manager::unbonding_query( - deps.querier, - &asset.clone(), - env.contract.address.clone(), - m.clone(), - )?; - - balance = manager::balance_query( - deps.querier, - &asset.clone(), - env.contract.address.clone(), - m, - )? - } - - // can allways get allowance for everyone - let allowance = allowance_query( - &deps.querier, - env.contract.address.clone(), - a.spender.clone(), - viewing_key.clone(), - 1, - &full_asset.contract.clone(), - )? - .allowance; - - if token_balance > allowance { - token_balance -= allowance; - } else { - token_balance = Uint128::zero(); - } - - // if all of these are zero then we need to remove the allowance at the end of the fn - if balance.is_zero() - && unbonding.is_zero() - && claimable.is_zero() - && allowance.is_zero() - && a.amount.is_zero() - { - stale_allowances.push(i); - } - - metadata.insert(a.spender.clone(), (balance, allowance)); - total_balance += balance + unbonding; - } - - /* Amounts given priority sice the array is sorted - * portions are calculated after amounts are taken from total - */ - for (i, allowance) in allowances.clone().iter().enumerate() { - let last_refresh = parse_utc_datetime(&allowance.last_refresh)?; - - // Refresh allowance if cycle is exceeded - if !exceeds_cycle(&now, &last_refresh, allowance.cycle.clone()) { - // Once allowances need 1 refresh if last_refresh == 'null' - if allowance.cycle == Cycle::Once { - if last_refresh.timestamp() != 0 { - if stale_allowances.iter().find(|&&x| x == i) == None { - stale_allowances.push(i); - stale_allowances.sort(); - } - continue; - } - } else { - continue; - } - } - - allowances[i].last_refresh = now.to_rfc3339(); - - // calculate the desired amount for the manager - let desired_amount = match allowance.allowance_type { - AllowanceType::Amount => { - // reduce total_balance so amount allowances are not used in the calculation for - // portion allowances - if total_balance >= allowance.amount { - total_balance -= allowance.amount; - } else { - total_balance = Uint128::zero(); - } - allowance.amount - } - AllowanceType::Portion => { - // This just gives a ratio of total balance where allowance.amount is the percent - total_balance.multiply_ratio(allowance.amount, ONE_HUNDRED_PERCENT) - } - }; - - let (balance, cur_allowance) = metadata[&allowance.spender]; - let total = balance + cur_allowance; - - // calculate threshold - let threshold = desired_amount.multiply_ratio(allowance.tolerance, ONE_HUNDRED_PERCENT); - - match desired_amount.cmp(&total) { - // Decrease Allowance - std::cmp::Ordering::Less => { - // decrease is cur_allow + bal - allow.amount because the current amount of funds - // the spender has access to is it's current allowance plus it balance, so to - // find the decrease, we subtract that by the amount the allowance is set to - let mut decrease = total - desired_amount; - // threshold check - if decrease <= threshold { - continue; - } - // Allowance fully covers amount needed - if cur_allowance >= decrease { - if !decrease.is_zero() { - messages.push(decrease_allowance_msg( - allowance.spender.clone(), - decrease, - None, - None, - 1, - &full_asset.contract.clone(), - vec![], - )?); - token_balance += decrease; - metrics.push(Metric { - action: Action::DecreaseAllowance, - context: Context::Rebalance, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: decrease, - user: allowance.spender.clone(), - }); - } - } - // Reduce allowance then unbond - else { - if !cur_allowance.is_zero() { - messages.push(decrease_allowance_msg( - allowance.spender.clone(), - cur_allowance, - None, - None, - 1, - &full_asset.contract.clone(), - vec![], - )?); - token_balance += cur_allowance; - metrics.push(Metric { - action: Action::DecreaseAllowance, - context: Context::Rebalance, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: cur_allowance, - user: allowance.spender.clone(), - }); - } - - decrease -= cur_allowance; - - // Unbond remaining - if !decrease.is_zero() { - if let Some(m) = - MANAGER.may_load(deps.storage, allowance.spender.clone())? - { - messages.push(manager::unbond_msg( - &asset.clone(), - decrease, - m.clone(), - )?); - metrics.push(Metric { - action: Action::Unbond, - context: Context::Rebalance, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: decrease, - user: m.address.clone(), - }); - } - } - } - } - // Increase Allowance - std::cmp::Ordering::Greater => { - let mut increase = desired_amount - total; - if increase > token_balance { - increase = token_balance; - } - token_balance -= increase; - - // threshold check - if increase <= threshold { - continue; - } - if !increase.is_zero() { - messages.push(increase_allowance_msg( - allowance.spender.clone(), - increase, - None, - None, - 1, - &full_asset.contract.clone(), - vec![], - )?); - metrics.push(Metric { - action: Action::IncreaseAllowance, - context: Context::Rebalance, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: increase, - user: allowance.spender.clone(), - }); - } - } - _ => {} - } - } - - if !stale_allowances.is_empty() { - for index in stale_allowances.iter().rev() { - allowances.remove(index.clone()); - } - } - ALLOWANCES.save(deps.storage, asset.clone(), &allowances)?; - - METRICS.append(deps.storage, env.block.time, &mut metrics)?; - - Ok(Response::new() - .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::Rebalance { - status: ResponseStatus::Success, - })?)) -} - -pub fn migrate(deps: DepsMut, env: &Env, _info: MessageInfo, asset: Addr) -> StdResult { - let mut messages = vec![]; - let mut metrics = vec![]; - - let allowances = ALLOWANCES.load(deps.storage, asset.clone())?; - let full_asset = ASSET.load(deps.storage, asset.clone())?; - let viewing_key = VIEWING_KEY.load(deps.storage)?; - - let mut claimed = Uint128::zero(); - - for allowance in allowances { - if let Some(m) = MANAGER.may_load(deps.storage, allowance.spender.clone())? { - // TODO store in metadata object for re-use - let unbondable = manager::unbondable_query( - deps.querier, - &asset, - env.contract.address.clone(), - m.clone(), - )?; - - // Unbond all if any - if !unbondable.is_zero() { - messages.push(manager::unbond_msg(&asset, unbondable, m.clone())?); - metrics.push(Metric { - action: Action::Unbond, - context: Context::Migration, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: unbondable, - user: m.address.clone(), - }); - } - - let claimable = manager::claimable_query( - deps.querier, - &asset, - env.contract.address.clone(), - m.clone(), - )?; - - // Claim if any - if !claimable.is_zero() { - messages.push(manager::claim_msg(&asset, m.clone())?); - metrics.push(Metric { - action: Action::Claim, - context: Context::Migration, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: claimable, - user: m.address.clone(), - }); - claimed += claimable; - } - } - - let cur_allowance = allowance_query( - &deps.querier, - env.contract.address.clone(), - allowance.spender.clone(), - viewing_key.clone(), - 1, - &full_asset.contract.clone(), - )? - .allowance; - - // Reduce all allowance if any - if !cur_allowance.is_zero() { - messages.push(decrease_allowance_msg( - allowance.spender.clone(), - cur_allowance, - None, - None, - 1, - &full_asset.contract.clone(), - vec![], - )?); - metrics.push(Metric { - action: Action::DecreaseAllowance, - context: Context::Migration, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: cur_allowance, - user: allowance.spender.clone(), - }); - } - } - - // Send full balance to multisig - let balance = balance_query( - &deps.querier, - env.contract.address.clone(), - viewing_key.clone(), - &full_asset.contract.clone(), - )?; - - if !(balance + claimed).is_zero() { - let config = CONFIG.load(deps.storage)?; - - //TODO: send to super admin from admin_auth -- remove multisig from config - messages.push(send_msg( - config.multisig.clone(), - balance + claimed, - None, - None, - None, - &full_asset.contract.clone(), - )?); - metrics.push(Metric { - action: Action::SendFunds, - context: Context::Migration, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: balance + claimed, - user: config.multisig.clone(), - }); - } - - METRICS.append(deps.storage, env.block.time, &mut metrics)?; - - Ok(Response::new() - .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::Migration { - status: ResponseStatus::Success, - })?)) -} - -pub fn set_run_level( - deps: DepsMut, - _env: &Env, - info: MessageInfo, - run_level: RunLevel, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - // TODO force super-admin? - validate_admin( - &deps.querier, - AdminPermissions::TreasuryAdmin, - &info.sender, - &config.admin_auth, - )?; - - RUN_LEVEL.save(deps.storage, &run_level)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RunLevel { run_level })?)) -} - -pub fn try_register_asset( - deps: DepsMut, - env: &Env, - info: MessageInfo, - contract: &Contract, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - validate_admin( - &deps.querier, - AdminPermissions::TreasuryAdmin, - &info.sender, - &config.admin_auth, - )?; - - ASSET_LIST.push(deps.storage, &contract.address.clone())?; - - ASSET.save( - deps.storage, - contract.address.clone(), - &snip20::helpers::fetch_snip20(contract, &deps.querier)?, - )?; - - ALLOWANCES.save(deps.storage, contract.address.clone(), &Vec::new())?; - - Ok(Response::new() - .add_message(register_receive( - env.contract.code_hash.clone(), - None, - contract, - )?) - .add_message(set_viewing_key_msg( - VIEWING_KEY.load(deps.storage)?, - None, - &contract.clone(), - )?) - .set_data(to_binary(&ExecuteAnswer::RegisterAsset { - status: ResponseStatus::Success, - })?)) -} - -pub fn register_wrap( - deps: DepsMut, - _env: &Env, - info: MessageInfo, - denom: String, - contract: &Contract, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - validate_admin( - &deps.querier, - AdminPermissions::TreasuryAdmin, - &info.sender, - &config.admin_auth, - )?; - - // Asset must be registered - if let Some(a) = ASSET.may_load(deps.storage, contract.address.clone())? { - // Deposit mut be enabled - if let Some(conf) = a.token_config { - if conf.deposit_enabled { - WRAP.save(deps.storage, denom, &contract.address)?; - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::RegisterWrap { - status: ResponseStatus::Success, - })?), - ) - } else { - Err(StdError::generic_err("Asset deposit not enabled")) - } - } else { - Err(StdError::generic_err("Asset has no token config")) - } - } else { - Err(StdError::generic_err("Unrecognized Asset")) - } -} - -pub fn register_manager( - deps: DepsMut, - _env: &Env, - info: MessageInfo, - contract: &mut Contract, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - validate_admin( - &deps.querier, - AdminPermissions::TreasuryAdmin, - &info.sender, - &config.admin_auth, - )?; - - // Ensure it isn't already registered - if let Some(_) = MANAGER.may_load(deps.storage, contract.address.clone())? { - return Err(StdError::generic_err("Manager already registered")); - } - - MANAGER.save(deps.storage, contract.address.clone(), &contract)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::RegisterManager { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn allowance( - deps: DepsMut, - _env: &Env, - info: MessageInfo, - asset: Addr, - allowance: Allowance, - refresh_now: bool, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - validate_admin( - &deps.querier, - AdminPermissions::TreasuryAdmin, - &info.sender, - &config.admin_auth, - )?; - - if ASSET.may_load(deps.storage, asset.clone())?.is_none() { - return Err(StdError::generic_err("Not a registered asset")); - } - - if allowance.tolerance >= ONE_HUNDRED_PERCENT { - return Err(StdError::generic_err(format!( - "Tolerance {} >= 100%", - allowance.tolerance - ))); - } - - let mut allowances = ALLOWANCES - .may_load(deps.storage, asset.clone())? - .unwrap_or(vec![]); - - // This will cause allowance refresh asap, changed below if !refresh_now - let mut last_refresh = utc_from_seconds(0).to_rfc3339(); - - // remove duplicated allowance - match allowances - .iter() - .position(|a| a.spender == allowance.spender) - { - Some(i) => { - if !refresh_now { - last_refresh = allowances[i].last_refresh.clone(); - } - allowances.swap_remove(i); - } - None => {} - }; - - allowances.push(AllowanceMeta { - spender: allowance.spender.clone(), - amount: allowance.amount, - cycle: allowance.cycle, - allowance_type: allowance.allowance_type.clone(), - // "zero/null" datetime, guarantees refresh next update - last_refresh, - tolerance: allowance.tolerance, - }); - - // ensure that the portion allocations don't go above 100% - if allowances - .iter() - .map(|a| { - if a.allowance_type == AllowanceType::Portion { - a.amount - } else { - Uint128::zero() - } - }) - .sum::() - > ONE_HUNDRED_PERCENT - { - return Err(StdError::generic_err( - "Invalid allowance total exceeding 100%", - )); - } - - // Sort list before going into storage - allowances.sort_by(|a, b| match a.allowance_type { - AllowanceType::Amount => match b.allowance_type { - AllowanceType::Amount => std::cmp::Ordering::Equal, - AllowanceType::Portion => std::cmp::Ordering::Less, - }, - AllowanceType::Portion => match b.allowance_type { - AllowanceType::Amount => std::cmp::Ordering::Greater, - AllowanceType::Portion => std::cmp::Ordering::Equal, - }, - }); - - ALLOWANCES.save(deps.storage, asset, &allowances)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::Allowance { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn wrap_coins(deps: DepsMut, env: &Env, info: MessageInfo) -> StdResult { - let coins = deps.querier.query_all_balances(&env.contract.address)?; - - let mut messages = vec![]; - let mut success = vec![]; - let mut failed = vec![]; - - for coin in coins { - if let Some(asset) = WRAP.may_load(deps.storage, coin.denom.clone())? { - let token = ASSET.load(deps.storage, asset)?; - messages.push(wrap_coin(coin.clone(), token.contract.clone())?); - success.push(coin.clone()); - METRICS.push(deps.storage, env.block.time, Metric { - action: Action::Wrap, - context: Context::Wrap, - timestamp: env.block.time.seconds(), - token: token.contract.address, - amount: coin.amount, - user: info.sender.clone(), - })?; - } else { - failed.push(coin); - } - } - - Ok(Response::new() - .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::WrapCoins { success, failed })?)) -} diff --git a/contracts/dao/treasury/src/lib.rs b/contracts/dao/treasury/src/lib.rs deleted file mode 100644 index 25ee0d9..0000000 --- a/contracts/dao/treasury/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod contract; -pub mod execute; -pub mod query; -pub mod storage; diff --git a/contracts/dao/treasury/src/query.rs b/contracts/dao/treasury/src/query.rs deleted file mode 100644 index 3c242a5..0000000 --- a/contracts/dao/treasury/src/query.rs +++ /dev/null @@ -1,173 +0,0 @@ -use crate::storage::*; -use shade_protocol::{ - c_std::{Addr, Deps, Env, StdError, StdResult, Uint128}, - contract_interfaces::dao::{adapter, manager, treasury}, - snip20::helpers::{allowance_query, balance_query}, - utils::{asset::Contract, cycle::parse_utc_datetime, storage::plus::period_storage::Period}, -}; -use std::collections::HashSet; - -pub fn config(deps: Deps) -> StdResult { - Ok(treasury::QueryAnswer::Config { - config: CONFIG.load(deps.storage)?, - }) -} - -pub fn metrics( - deps: Deps, - env: Env, - date: Option, - epoch: Option, - period: Period, -) -> StdResult { - if date.is_some() && epoch.is_some() { - return Err(StdError::generic_err("cannot pass both epoch and date")); - } - let key = { - if let Some(d) = date { - parse_utc_datetime(&d)?.timestamp() as u64 - } else if let Some(e) = epoch { - e.u128() as u64 - } else { - env.block.time.seconds() - } - }; - Ok(treasury::QueryAnswer::Metrics { - metrics: METRICS.load_period(deps.storage, key, period)?, - }) -} - -pub fn batch_balance(deps: Deps, env: Env, assets: Vec) -> StdResult> { - let mut balances = vec![]; - let mut managers: HashSet = HashSet::new(); - - for asset in assets.clone() { - let full_asset = match ASSET.may_load(deps.storage, asset.clone())? { - Some(a) => a, - None => { - return Err(StdError::generic_err("Unrecognized Asset")); - } - }; - - let balance = balance_query( - &deps.querier, - env.contract.address.clone(), - VIEWING_KEY.load(deps.storage)?, - &full_asset.contract.clone(), - )?; - - balances.push(balance); - - // build list of unique managers to query balances - for allowance in ALLOWANCES.load(deps.storage, asset.clone())? { - if let Some(m) = MANAGER.may_load(deps.storage, allowance.spender)? { - managers.insert(m); - } - } - } - for manager in managers { - let manager_balances = manager::batch_balance_query( - deps.querier, - &assets.clone(), - env.contract.address.clone(), - manager, - )?; - balances = balances - .into_iter() - .zip(manager_balances.into_iter()) - .map(|(a, b)| a + b) - .collect(); - } - - Ok(balances) -} - -pub fn balance(deps: Deps, env: Env, asset: Addr) -> StdResult { - let full_asset = match ASSET.may_load(deps.storage, asset.clone())? { - Some(a) => a, - None => { - return Err(StdError::generic_err("Unrecognized Asset")); - } - }; - - let allowances = ALLOWANCES.load(deps.storage, asset.clone())?; - - let mut balance = balance_query( - &deps.querier, - env.contract.address.clone(), - VIEWING_KEY.load(deps.storage)?, - &full_asset.contract.clone(), - )?; - - for allowance in allowances { - if let Some(m) = MANAGER.may_load(deps.storage, allowance.spender)? { - balance += manager::balance_query( - deps.querier, - &asset.clone(), - env.contract.address.clone(), - m, - )?; - } - } - Ok(adapter::QueryAnswer::Balance { amount: balance }) -} - -pub fn reserves(deps: Deps, env: Env, asset: Addr) -> StdResult { - //TODO: restrict to admin? - - let full_asset = match ASSET.may_load(deps.storage, asset.clone())? { - Some(a) => a, - None => { - return Err(StdError::generic_err("Unrecognized Asset")); - } - }; - - let reserves = balance_query( - &deps.querier, - env.contract.address.clone(), - VIEWING_KEY.load(deps.storage)?, - &full_asset.contract.clone(), - )?; - - Ok(adapter::QueryAnswer::Reserves { amount: reserves }) -} - -pub fn allowance( - deps: Deps, - env: Env, - asset: Addr, - spender: Addr, -) -> StdResult { - let key = VIEWING_KEY.load(deps.storage)?; - - let full_asset = match ASSET.may_load(deps.storage, asset.clone())? { - Some(a) => a, - None => { - return Err(StdError::generic_err("Unrecognized Asset")); - } - }; - - return Ok(treasury::QueryAnswer::Allowance { - amount: allowance_query( - &deps.querier, - env.contract.address, - spender.clone(), - key, - 1, - &full_asset.contract.clone(), - )? - .allowance, - }); -} - -pub fn assets(deps: Deps) -> StdResult { - Ok(treasury::QueryAnswer::Assets { - assets: ASSET_LIST.iter(deps.storage).collect(), - }) -} - -pub fn allowances(deps: Deps, asset: Addr) -> StdResult { - Ok(treasury::QueryAnswer::Allowances { - allowances: ALLOWANCES.may_load(deps.storage, asset)?.unwrap_or(vec![]), - }) -} diff --git a/contracts/dao/treasury/src/storage.rs b/contracts/dao/treasury/src/storage.rs deleted file mode 100644 index 339d21b..0000000 --- a/contracts/dao/treasury/src/storage.rs +++ /dev/null @@ -1,27 +0,0 @@ -use shade_protocol::{ - c_std::Addr, - dao::treasury::{AllowanceMeta, Config, Metric, RunLevel}, - secret_storage_plus::{Item, Map}, - snip20::helpers::Snip20Asset, - utils::{ - asset::Contract, - storage::plus::{iter_item::IterItem, period_storage::PeriodStorage}, - }, -}; - -pub const CONFIG: Item = Item::new("config"); -pub const VIEWING_KEY: Item = Item::new("viewing_key"); - -pub const ASSET_LIST: IterItem = IterItem::new_override("asset_list", "asset_list_2"); -pub const ASSET: Map = Map::new("asset"); - -// { denom: snip20 } -pub const WRAP: Map = Map::new("wrap"); - -pub const MANAGER: Map = Map::new("managers"); -pub const ALLOWANCES: Map> = Map::new("allowances"); - -pub const RUN_LEVEL: Item = Item::new("runlevel"); - -pub const METRICS: PeriodStorage = - PeriodStorage::new("metrics-all", "metrics-recent", "metrics-timed"); diff --git a/contracts/dao/treasury/tests/dao/equilibrium.rs b/contracts/dao/treasury/tests/dao/equilibrium.rs deleted file mode 100644 index 4866b2f..0000000 --- a/contracts/dao/treasury/tests/dao/equilibrium.rs +++ /dev/null @@ -1,314 +0,0 @@ -use shade_multi_test::interfaces::{ - dao::{ - init_dao, - mock_adapter_complete_unbonding, - system_balance_reserves, - system_balance_unbondable, - }, - treasury, - treasury_manager, - utils::{DeployedContracts, SupportedContracts}, -}; -use shade_protocol::{ - c_std::Uint128, - contract_interfaces::dao::{treasury::AllowanceType, treasury_manager::AllocationType}, - multi_test::App, - utils::cycle::Cycle, -}; - -pub fn equilibrium_test( - is_instant_unbond: bool, - initial_bals: (Uint128, Vec<(Uint128, Vec)>), -) { - let mut app = App::default(); - let mut contracts = DeployedContracts::new(); - let num_managers = 8; - init_dao( - &mut app, - "admin", - &mut contracts, - Uint128::new(1500), - "SSCRT", - vec![ - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - ], - vec![Cycle::Constant; 8], - vec![ - Uint128::new(5 * 10u128.pow(16)), // Poriton - 5% - Uint128::new(30), // Amount - 30 - Uint128::new(15 * 10u128.pow(16)), // Portion - 15% - Uint128::new(40), // Amount - 40 - Uint128::new(25 * 10u128.pow(16)), // Poriton - 25% - Uint128::new(50), // Amount - 50 - Uint128::new(35 * 10u128.pow(16)), // Portion - 35% - Uint128::new(20), // Amount - 20 - ], // Allowance amount - vec![Uint128::zero(); 8], - vec![ - vec![ - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - ]; - 8 - ], - vec![ - vec![ - Uint128::new(1), // Amount - 1 - Uint128::new(4 * 10u128.pow(16)), // Portion - 4% - Uint128::new(2), // Amount - 2 - Uint128::new(16 * 10u128.pow(16)), //Portion - 16% - Uint128::new(3), // Amount - 3 - Uint128::new(1 * 10u128.pow(17)), //Portion - 10% - Uint128::new(4), // Amount - 4 - Uint128::new(2 * 10u128.pow(17)), // Portion - 20% - ]; - 8 - ], - vec![vec![Uint128::zero(); 8]; 8], - is_instant_unbond, - true, - ) - .unwrap(); - for i in 0..20 { - let bals = { - if is_instant_unbond { - system_balance_reserves(&app, &contracts, "SSCRT") - } else { - system_balance_unbondable(&app, &contracts, "SSCRT") - } - }; - assert_eq!(bals, initial_bals, "loop: {}", i); - for tm in 0..num_managers { - treasury_manager::update_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(tm), - ) - .unwrap(); - } - treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); - if !is_instant_unbond { - let mut k = 0; - for _i in 0..num_managers { - for _j in 0..num_managers { - println!("{}", k); - mock_adapter_complete_unbonding( - &mut app, - "admin", - &contracts, - SupportedContracts::MockAdapter(k), - ) - .unwrap(); - k += 1; - } - k += 1; - } - } - } -} - -macro_rules! dao_tests_migration { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - is_instant_unbond, - initial_bals, - ) = $value; - equilibrium_test( - is_instant_unbond, - initial_bals, - ); - } - )* - } -} - -dao_tests_migration! ( - dao_test_equilibrium_instant_unbond: ( - true, - (Uint128::new(857), vec![ - (Uint128::new(0), vec![ // used - 38 - Uint128::new(1), - Uint128::new(2), - Uint128::new(2), - Uint128::new(9), - Uint128::new(3), - Uint128::new(5), - Uint128::new(4), - Uint128::new(11), - ]), - (Uint128::new(0), vec![ // used - 18 - Uint128::new(1), - Uint128::new(0), - Uint128::new(2), - Uint128::new(3), - Uint128::new(3), - Uint128::new(2), - Uint128::new(4), - Uint128::new(4), - ]), - (Uint128::new(0), vec![ // used - 105 - Uint128::new(1), - Uint128::new(7), - Uint128::new(2), - Uint128::new(31), - Uint128::new(3), - Uint128::new(19), - Uint128::new(4), - Uint128::new(38), - ]), - (Uint128::new(0), vec![ // used - 25 - Uint128::new(1), - Uint128::new(1), - Uint128::new(2), - Uint128::new(4), - Uint128::new(3), - Uint128::new(3), - Uint128::new(4), - Uint128::new(6), - ]), - (Uint128::new(0), vec![ // used - 174 - Uint128::new(1), - Uint128::new(13), - Uint128::new(2), - Uint128::new(52), - Uint128::new(3), - Uint128::new(33), - Uint128::new(4), - Uint128::new(66), - ]), - (Uint128::new(0), vec![ // used - 29 - Uint128::new(1), - Uint128::new(1), - Uint128::new(2), - Uint128::new(6), - Uint128::new(3), - Uint128::new(4), - Uint128::new(4), - Uint128::new(8), - ]), - (Uint128::new(0), vec![ // used - 241 - Uint128::new(1), - Uint128::new(18), - Uint128::new(2), - Uint128::new(74), - Uint128::new(3), - Uint128::new(46), - Uint128::new(4), - Uint128::new(93), - ]), - (Uint128::new(0), vec![ // used - 14 - Uint128::new(1), - Uint128::new(0), - Uint128::new(2), - Uint128::new(1), - Uint128::new(3), - Uint128::new(1), - Uint128::new(4), - Uint128::new(2), - ]), - ]), - ), - dao_test_equilibrium_non_instant_unbond: ( - false, - (Uint128::new(857), vec![ - (Uint128::new(0), vec![ // used - 38 - Uint128::new(1), - Uint128::new(2), - Uint128::new(2), - Uint128::new(9), - Uint128::new(3), - Uint128::new(5), - Uint128::new(4), - Uint128::new(11), - ]), - (Uint128::new(0), vec![ // used - 18 - Uint128::new(1), - Uint128::new(0), - Uint128::new(2), - Uint128::new(3), - Uint128::new(3), - Uint128::new(2), - Uint128::new(4), - Uint128::new(4), - ]), - (Uint128::new(0), vec![ // used - 105 - Uint128::new(1), - Uint128::new(7), - Uint128::new(2), - Uint128::new(31), - Uint128::new(3), - Uint128::new(19), - Uint128::new(4), - Uint128::new(38), - ]), - (Uint128::new(0), vec![ // used - 25 - Uint128::new(1), - Uint128::new(1), - Uint128::new(2), - Uint128::new(4), - Uint128::new(3), - Uint128::new(3), - Uint128::new(4), - Uint128::new(6), - ]), - (Uint128::new(0), vec![ // used - 174 - Uint128::new(1), - Uint128::new(13), - Uint128::new(2), - Uint128::new(52), - Uint128::new(3), - Uint128::new(33), - Uint128::new(4), - Uint128::new(66), - ]), - (Uint128::new(0), vec![ // used - 29 - Uint128::new(1), - Uint128::new(1), - Uint128::new(2), - Uint128::new(6), - Uint128::new(3), - Uint128::new(4), - Uint128::new(4), - Uint128::new(8), - ]), - (Uint128::new(0), vec![ // used - 241 - Uint128::new(1), - Uint128::new(18), - Uint128::new(2), - Uint128::new(74), - Uint128::new(3), - Uint128::new(46), - Uint128::new(4), - Uint128::new(93), - ]), - (Uint128::new(0), vec![ // used - 14 - Uint128::new(1), - Uint128::new(0), - Uint128::new(2), - Uint128::new(1), - Uint128::new(3), - Uint128::new(1), - Uint128::new(4), - Uint128::new(2), - ]), - ]), - ), -); diff --git a/contracts/dao/treasury/tests/dao/gains_losses.rs b/contracts/dao/treasury/tests/dao/gains_losses.rs deleted file mode 100644 index 60ee116..0000000 --- a/contracts/dao/treasury/tests/dao/gains_losses.rs +++ /dev/null @@ -1,1012 +0,0 @@ -use shade_multi_test::interfaces::{ - dao::{ - init_dao, - mock_adapter_complete_unbonding, - mock_adapter_sub_tokens, - system_balance_reserves, - system_balance_unbondable, - }, - snip20, - treasury, - treasury_manager, - utils::{DeployedContracts, SupportedContracts}, -}; -use shade_protocol::{ - c_std::Uint128, - contract_interfaces::dao::{treasury::AllowanceType, treasury_manager::AllocationType}, - multi_test::App, - utils::cycle::Cycle, -}; - -pub fn dao_int_gains_losses( - initial_treasury_bal: Uint128, - allow_type: Vec, - t_cycle: Vec, - allow_amount: Vec, - allow_tolerance: Vec, - alloc_type: Vec>, - alloc_amount: Vec>, - alloc_tolerance: Vec>, - is_instant_unbond: bool, - expected_after_init: (Uint128, Vec<(Uint128, Vec)>), - snip20_send_amount: Uint128, - adapters_to_send_to: Vec, - is_adapters_gain: Vec, - expected_in_between_updates: (Uint128, Vec<(Uint128, Vec)>), - expected_after_updates: (Uint128, Vec<(Uint128, Vec)>), -) { - let mut app = App::default(); - let mut contracts = DeployedContracts::new(); - let num_managers = allow_type.len(); - init_dao( - &mut app, - "admin", - &mut contracts, - initial_treasury_bal, - "SSCRT", - allow_type, - t_cycle, - allow_amount, - allow_tolerance, - alloc_type, - alloc_amount.clone(), - alloc_tolerance, - is_instant_unbond, - true, - ) - .unwrap(); - let bals = { - if is_instant_unbond { - system_balance_reserves(&app, &contracts, "SSCRT") - } else { - system_balance_unbondable(&app, &contracts, "SSCRT") - } - }; - assert_eq!(bals, expected_after_init, "AFTER INITIALIZATION"); - for (i, adap) in adapters_to_send_to.clone().iter().enumerate() { - if is_adapters_gain[i] { - snip20::send_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - contracts - .get(&SupportedContracts::MockAdapter(adap.clone())) - .unwrap() - .address - .to_string(), - snip20_send_amount, - None, - ) - .unwrap(); - } else { - mock_adapter_sub_tokens( - &mut app, - "admin", - &contracts, - snip20_send_amount, - SupportedContracts::MockAdapter(adap.clone()), - ) - .unwrap(); - } - } - // Needs 2 full cycles to reballance fully - for tm in 0..num_managers { - treasury_manager::update_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(tm), - ) - .unwrap(); - } - treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); - let bals = { - if is_instant_unbond { - let sys_bal = system_balance_reserves(&app, &contracts, "SSCRT"); - assert_eq!(sys_bal, expected_in_between_updates, "AFTER FIRST UPDATE"); - for tm in 0..num_managers { - treasury_manager::update_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(tm), - ) - .unwrap(); - } - treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); - for tm in 0..num_managers { - treasury_manager::update_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(tm), - ) - .unwrap(); - } - treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); - system_balance_reserves(&app, &contracts, "SSCRT") - } else { - let _sys_bal = system_balance_unbondable(&app, &contracts, "SSCRT"); - //assert_eq!(sys_bal, expected_in_between_updates, "AFTER FIRST UPDATE"); - let mut k = 0; - for i in 0..num_managers { - for _j in 0..alloc_amount[i].len() { - mock_adapter_complete_unbonding( - &mut app, - "admin", - &contracts, - SupportedContracts::MockAdapter(k), - ) - .unwrap(); - k += 1; - } - k += 1; - } - for tm in 0..num_managers { - treasury_manager::update_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(tm), - ) - .unwrap(); - } - treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); - for tm in 0..num_managers { - treasury_manager::update_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(tm), - ) - .unwrap(); - } - treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); - let mut k = 0; - for i in 0..num_managers { - for _j in 0..alloc_amount[i].len() { - println!("{}", k); - mock_adapter_complete_unbonding( - &mut app, - "admin", - &contracts, - SupportedContracts::MockAdapter(k), - ) - .unwrap(); - k += 1; - } - k += 1; - } - for tm in 0..num_managers { - treasury_manager::update_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(tm), - ) - .unwrap(); - } - treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); - for tm in 0..num_managers { - treasury_manager::update_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(tm), - ) - .unwrap(); - } - treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); - //update_dao(&mut app, "admin", &contracts, "SSCRT", num_managers); - system_balance_unbondable(&app, &contracts, "SSCRT") - } - }; - assert_eq!(bals, expected_after_updates, "AFTER BOTH UPDATES"); -} - -macro_rules! dao_tests_gains_losses { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - initial_treasury_bal, - allow_type, - t_cycle, - allow_amount, - allow_tolerance, - alloc_type, - alloc_amount, - alloc_tolerance, - is_instant_unbond, - expected_after_init, - snip20_send_amount, - adapters_to_send_to, - is_adapters_gain, - expected_in_between_updates, - expected_after_updates, - ) = $value; - dao_int_gains_losses( - initial_treasury_bal, - allow_type, - t_cycle, - allow_amount, - allow_tolerance, - alloc_type, - alloc_amount, - alloc_tolerance, - is_instant_unbond, - expected_after_init, - snip20_send_amount, - adapters_to_send_to, - is_adapters_gain, - expected_in_between_updates, - expected_after_updates, - ); - } - )* - } -} - -dao_tests_gains_losses! { - dao_test_gains:( - Uint128::new(1000), - vec![AllowanceType::Portion], - vec![Cycle::Constant], - vec![ - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - ], // Allowance amount - vec![Uint128::zero()], - vec![vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount, - ]], - vec![vec![ - Uint128::new(6 * 10u128.pow(17)), - Uint128::new(5), - Uint128::new(2 * 10u128.pow(17)), - Uint128::new(15), - ]], - vec![vec![Uint128::zero(); 4]], - true, - (Uint128::new(516), vec![(Uint128::new(0), vec![ - Uint128::new(348), - Uint128::new(5), - Uint128::new(116), - Uint128::new(15), - ])]), - Uint128::new(100), - vec![0, 1, 2], - vec![true, true, true], - (Uint128::new(520), vec![(Uint128::new(56), vec![ - Uint128::new(528), - Uint128::new(5), - Uint128::new(176), - Uint128::new(15), - ])]), - (Uint128::new(520), vec![(Uint128::new(152), vec![ - Uint128::new(456), - Uint128::new(5), - Uint128::new(152), - Uint128::new(15), - ])]), - ), - dao_test_gains_4_managers: ( - Uint128::new(1000), - vec![ - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - ], - vec![Cycle::Constant; 4], - vec![ - Uint128::new(50), // Amount - 50 - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - Uint128::new(100), // Amount - 100 - Uint128::new(2 * 10u128.pow(17)), // Portion - 40% - ], // Allowance amount - vec![Uint128::zero(); 4], - vec![ - vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount - ]; - 4 - ], - vec![ - vec![ - Uint128::new(6 * 10u128.pow(17)), - Uint128::new(5), - Uint128::new(2 * 10u128.pow(17)), - Uint128::new(15) - ]; - 4 - ], - vec![vec![Uint128::zero(); 4]; 4], - true, - (Uint128::new(320), vec![ - (Uint128::new(0), vec![ - Uint128::new(18), - Uint128::new(5), - Uint128::new(6), - Uint128::new(15), - ]), - (Uint128::new(0), vec![ - Uint128::new(294), - Uint128::new(5), - Uint128::new(98), - Uint128::new(15), - ]), - (Uint128::new(0), vec![ - Uint128::new(48), - Uint128::new(5), - Uint128::new(16), - Uint128::new(15), - ]), - (Uint128::new(0), vec![ - Uint128::new(90), - Uint128::new(5), - Uint128::new(30), - Uint128::new(15), - ]), - ]), - Uint128::new(100), - vec![0, 1, 2, 3, 5, 7, 10, 12, 16], - vec![true; 11], - (Uint128::new(528), vec![ - (Uint128::new(180), vec![ - Uint128::new(18), - Uint128::new(5), - Uint128::new(12), - Uint128::new(15), - ]), - (Uint128::new(60), vec![ - Uint128::new(414), - Uint128::new(5), - Uint128::new(138), - Uint128::new(15), - ]), - (Uint128::new(140), vec![ - Uint128::new(60), - Uint128::new(5), - Uint128::new(20), - Uint128::new(15), - ]), - (Uint128::new(100), vec![ - Uint128::new(120), - Uint128::new(5), - Uint128::new(30), - Uint128::new(15), - ]), - ]), - (Uint128::new(622), vec![ - (Uint128::new(6), vec![ - Uint128::new(18), - Uint128::new(5), - Uint128::new(6), - Uint128::new(15), - ]), - (Uint128::new(0), vec![ - Uint128::new(618), - Uint128::new(5), - Uint128::new(206), - Uint128::new(15), - ]), - (Uint128::new(16), vec![ - Uint128::new(48), - Uint128::new(5), - Uint128::new(16), - Uint128::new(15), - ]), - (Uint128::new(0), vec![ - Uint128::new(198), - Uint128::new(5), - Uint128::new(66), - Uint128::new(15), - ]), - ]), - ), - dao_test_losses: ( - Uint128::new(1000), - vec![ - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - ], - vec![Cycle::Constant; 4], - vec![ - Uint128::new(50), // Amount - 50 - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - Uint128::new(100), // Amount - 100 - Uint128::new(2 * 10u128.pow(17)), // Portion - 40% - ], // Allowance amount - vec![Uint128::zero(); 4], - vec![ - vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount - ]; - 4 - ], - vec![ - vec![ - Uint128::new(6 * 10u128.pow(17)), - Uint128::new(5), - Uint128::new(2 * 10u128.pow(17)), - Uint128::new(15) - ]; - 4 - ], - vec![vec![Uint128::zero(); 4]; 4], - true, - (Uint128::new(320), vec![ - (Uint128::new(0), vec![ - Uint128::new(18), - Uint128::new(5), - Uint128::new(6), - Uint128::new(15), - ]), - (Uint128::new(0), vec![ - Uint128::new(294), - Uint128::new(5), - Uint128::new(98), - Uint128::new(15), - ]), - (Uint128::new(0), vec![ - Uint128::new(48), - Uint128::new(5), - Uint128::new(16), - Uint128::new(15), - ]), - (Uint128::new(0), vec![ - Uint128::new(90), - Uint128::new(5), - Uint128::new(30), - Uint128::new(15), - ]), - ]), - Uint128::new(5), - vec![0, 1, 2, 3, 5, 7, 10, 12, 16], - vec![false; 9], - (Uint128::new(303), vec![ - (Uint128::new(7), vec![ - Uint128::new(6), - Uint128::new(5), - Uint128::new(1), - Uint128::new(11), - ]), - (Uint128::new(1), vec![ - Uint128::new(288), - Uint128::new(5), - Uint128::new(96), - Uint128::new(15), - ]), - (Uint128::new(1), vec![ - Uint128::new(42), - Uint128::new(5), - Uint128::new(14), - Uint128::new(15), - ]), - (Uint128::new(4), vec![ - Uint128::new(87), - Uint128::new(5), - Uint128::new(29), - Uint128::new(15), - ]), - ]), - (Uint128::new(282), vec![ - (Uint128::new(0), vec![ - Uint128::new(18), - Uint128::new(5), - Uint128::new(6), - Uint128::new(15), - ]), - (Uint128::new(16), vec![ - Uint128::new(277), - Uint128::new(5), - Uint128::new(92), - Uint128::new(15), - ]), - (Uint128::new(0), vec![ - Uint128::new(48), - Uint128::new(5), - Uint128::new(16), - Uint128::new(15), - ]), - (Uint128::new(8), vec![ - Uint128::new(84), - Uint128::new(5), - Uint128::new(28), - Uint128::new(15), - ]), - ]), - ), - dao_test_losses_and_gains: ( - Uint128::new(1500), - vec![ - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - ], - vec![Cycle::Constant; 4], - vec![ - Uint128::new(200), // Amount - 50 - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - Uint128::new(300), // Amount - 100 - Uint128::new(3 * 10u128.pow(17)), // Portion - 40% - ], // Allowance amount - vec![Uint128::zero(); 4], - vec![ - vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount - ]; - 4 - ], - vec![ - vec![ - Uint128::new(6 * 10u128.pow(17)), - Uint128::new(50), - Uint128::new(2 * 10u128.pow(17)), - Uint128::new(75) - ]; - 4 - ], - vec![vec![Uint128::zero(); 4]; 4], - true, - (Uint128::new(280), vec![ - (Uint128::new(0), vec![ - Uint128::new(45), - Uint128::new(50), - Uint128::new(15), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(285), - Uint128::new(50), - Uint128::new(95), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(105), - Uint128::new(50), - Uint128::new(35), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(105), - Uint128::new(50), - Uint128::new(35), - Uint128::new(75), - ]), - ]), - Uint128::new(50), - vec![0, 1, 2, 3, 5, 7, 10, 12, 16], - vec![true, false, true, false, false, true, true, true, false], - (Uint128::new(200), vec![ - (Uint128::new(100), vec![ - Uint128::new(45), - Uint128::new(15), - Uint128::new(15), - Uint128::new(25), - ]), - (Uint128::new(50), vec![ - Uint128::new(285), - Uint128::new(50), - Uint128::new(95), - Uint128::new(75), - ]), - (Uint128::new(45), vec![ - Uint128::new(132), - Uint128::new(50), - Uint128::new(43), - Uint128::new(75), - ]), - (Uint128::new(40), vec![ - Uint128::new(75), - Uint128::new(35), - Uint128::new(25), - Uint128::new(75), - ]), - ]), - (Uint128::new(218), vec![ - (Uint128::new(15), vec![ - Uint128::new(45), - Uint128::new(50), - Uint128::new(15), - Uint128::new(75), - ]), - (Uint128::new(26), vec![ - Uint128::new(303), - Uint128::new(50), - Uint128::new(101), - Uint128::new(75), - ]), - (Uint128::new(35), vec![ - Uint128::new(105), - Uint128::new(50), - Uint128::new(35), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(114), - Uint128::new(50), - Uint128::new(38), - Uint128::new(75), - ]), - ]), - ), - dao_test_gains_4_managers_with_unbond: ( - Uint128::new(1000), - vec![ - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - ], - vec![Cycle::Constant; 4], - vec![ - Uint128::new(50), // Amount - 50 - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - Uint128::new(100), // Amount - 100 - Uint128::new(2 * 10u128.pow(17)), // Portion - 40% - ], // Allowance amount - vec![Uint128::zero(); 4], - vec![ - vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount - ]; - 4 - ], - vec![ - vec![ - Uint128::new(6 * 10u128.pow(17)), - Uint128::new(5), - Uint128::new(2 * 10u128.pow(17)), - Uint128::new(15) - ]; - 4 - ], - vec![vec![Uint128::zero(); 4]; 4], - false, - (Uint128::new(320), vec![ - (Uint128::new(0), vec![ - Uint128::new(18), - Uint128::new(5), - Uint128::new(6), - Uint128::new(15), - ]), - (Uint128::new(0), vec![ - Uint128::new(294), - Uint128::new(5), - Uint128::new(98), - Uint128::new(15), - ]), - (Uint128::new(0), vec![ - Uint128::new(48), - Uint128::new(5), - Uint128::new(16), - Uint128::new(15), - ]), - (Uint128::new(0), vec![ - Uint128::new(90), - Uint128::new(5), - Uint128::new(30), - Uint128::new(15), - ]), - ]), - Uint128::new(100), - vec![0, 1, 2, 3, 5, 7, 10, 12, 16], - vec![true; 11], - (Uint128::new(528), vec![ - (Uint128::new(180), vec![ - Uint128::new(18), - Uint128::new(5), - Uint128::new(12), - Uint128::new(15), - ]), - (Uint128::new(60), vec![ - Uint128::new(414), - Uint128::new(5), - Uint128::new(138), - Uint128::new(15), - ]), - (Uint128::new(140), vec![ - Uint128::new(60), - Uint128::new(5), - Uint128::new(20), - Uint128::new(15), - ]), - (Uint128::new(100), vec![ - Uint128::new(120), - Uint128::new(5), - Uint128::new(30), - Uint128::new(15), - ]), - ]), - (Uint128::new(622), vec![ - (Uint128::new(6), vec![ - Uint128::new(18), - Uint128::new(5), - Uint128::new(6), - Uint128::new(15), - ]), - (Uint128::new(0), vec![ - Uint128::new(618), - Uint128::new(5), - Uint128::new(206), - Uint128::new(15), - ]), - (Uint128::new(16), vec![ - Uint128::new(48), - Uint128::new(5), - Uint128::new(16), - Uint128::new(15), - ]), - (Uint128::new(0), vec![ - Uint128::new(198), - Uint128::new(5), - Uint128::new(66), - Uint128::new(15), - ]), - ]), - ), - dao_test_losses_with_unbond: ( - Uint128::new(1000), - vec![ - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - ], - vec![Cycle::Constant; 4], - vec![ - Uint128::new(50), // Amount - 50 - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - Uint128::new(100), // Amount - 100 - Uint128::new(2 * 10u128.pow(17)), // Portion - 40% - ], // Allowance amount - vec![Uint128::zero(); 4], - vec![ - vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount - ]; - 4 - ], - vec![ - vec![ - Uint128::new(6 * 10u128.pow(17)), - Uint128::new(5), - Uint128::new(2 * 10u128.pow(17)), - Uint128::new(15) - ]; - 4 - ], - vec![vec![Uint128::zero(); 4]; 4], - false, - (Uint128::new(320), vec![ - (Uint128::new(0), vec![ - Uint128::new(18), - Uint128::new(5), - Uint128::new(6), - Uint128::new(15), - ]), - (Uint128::new(0), vec![ - Uint128::new(294), - Uint128::new(5), - Uint128::new(98), - Uint128::new(15), - ]), - (Uint128::new(0), vec![ - Uint128::new(48), - Uint128::new(5), - Uint128::new(16), - Uint128::new(15), - ]), - (Uint128::new(0), vec![ - Uint128::new(90), - Uint128::new(5), - Uint128::new(30), - Uint128::new(15), - ]), - ]), - Uint128::new(5), - vec![0, 1, 2, 3, 5, 7, 10, 12, 16], - vec![false; 9], - (Uint128::new(303), vec![ - (Uint128::new(7), vec![ - Uint128::new(6), - Uint128::new(5), - Uint128::new(1), - Uint128::new(11), - ]), - (Uint128::new(1), vec![ - Uint128::new(288), - Uint128::new(5), - Uint128::new(96), - Uint128::new(15), - ]), - (Uint128::new(1), vec![ - Uint128::new(42), - Uint128::new(5), - Uint128::new(14), - Uint128::new(15), - ]), - (Uint128::new(4), vec![ - Uint128::new(87), - Uint128::new(5), - Uint128::new(29), - Uint128::new(15), - ]), - ]), - (Uint128::new(275), vec![ - (Uint128::new(6), vec![ - Uint128::new(18), - Uint128::new(5), - Uint128::new(6), - Uint128::new(15), - ]), - (Uint128::new(16), vec![ - Uint128::new(277), - Uint128::new(5), - Uint128::new(92), - Uint128::new(15), - ]), - (Uint128::new(1), vec![ - Uint128::new(48), - Uint128::new(5), - Uint128::new(16), - Uint128::new(15), - ]), - (Uint128::new(8), vec![ - Uint128::new(84), - Uint128::new(5), - Uint128::new(28), - Uint128::new(15), - ]), - ]), - ), - dao_test_losses_and_gains_with_unbond: ( - Uint128::new(1500), - vec![ - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - ], - vec![Cycle::Constant; 4], - vec![ - Uint128::new(200), // Amount - 50 - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - Uint128::new(300), // Amount - 100 - Uint128::new(3 * 10u128.pow(17)), // Portion - 40% - ], // Allowance amount - vec![Uint128::zero(); 4], - vec![ - vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount - ]; - 4 - ], - vec![ - vec![ - Uint128::new(6 * 10u128.pow(17)), - Uint128::new(50), - Uint128::new(2 * 10u128.pow(17)), - Uint128::new(75) - ]; - 4 - ], - vec![vec![Uint128::zero(); 4]; 4], - false, - (Uint128::new(280), vec![ - (Uint128::new(0), vec![ - Uint128::new(45), - Uint128::new(50), - Uint128::new(15), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(285), - Uint128::new(50), - Uint128::new(95), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(105), - Uint128::new(50), - Uint128::new(35), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(105), - Uint128::new(50), - Uint128::new(35), - Uint128::new(75), - ]), - ]), - Uint128::new(50), - vec![0, 1, 2, 3, 5, 7, 10, 12, 16], - vec![true, false, true, false, false, true, true, true, false], - (Uint128::new(200), vec![ - (Uint128::new(100), vec![ - Uint128::new(45), - Uint128::new(15), - Uint128::new(15), - Uint128::new(25), - ]), - (Uint128::new(50), vec![ - Uint128::new(285), - Uint128::new(50), - Uint128::new(95), - Uint128::new(75), - ]), - (Uint128::new(45), vec![ - Uint128::new(132), - Uint128::new(50), - Uint128::new(43), - Uint128::new(75), - ]), - (Uint128::new(40), vec![ - Uint128::new(75), - Uint128::new(35), - Uint128::new(25), - Uint128::new(75), - ]), - ]), - (Uint128::new(156), vec![ - (Uint128::new(15), vec![ - Uint128::new(45), - Uint128::new(50), - Uint128::new(15), - Uint128::new(75), - ]), - (Uint128::new(50), vec![ - Uint128::new(303), - Uint128::new(50), - Uint128::new(101), - Uint128::new(75), - ]), - (Uint128::new(35), vec![ - Uint128::new(105), - Uint128::new(50), - Uint128::new(35), - Uint128::new(75), - ]), - (Uint128::new(38), vec![ - Uint128::new(114), - Uint128::new(50), - Uint128::new(38), - Uint128::new(75), - ]), - ]), - ), -} diff --git a/contracts/dao/treasury/tests/dao/mod.rs b/contracts/dao/treasury/tests/dao/mod.rs deleted file mode 100644 index fe8b4df..0000000 --- a/contracts/dao/treasury/tests/dao/mod.rs +++ /dev/null @@ -1,322 +0,0 @@ -pub mod equilibrium; -pub mod gains_losses; - -use shade_multi_test::interfaces::{ - dao::{ - init_dao, - mock_adapter_complete_unbonding, - system_balance_reserves, - system_balance_unbondable, - update_dao, - }, - treasury, - treasury_manager, - utils::{DeployedContracts, SupportedContracts}, -}; -use shade_protocol::{ - c_std::Uint128, - contract_interfaces::dao::{self, treasury::AllowanceType, treasury_manager::AllocationType}, - multi_test::App, - utils::cycle::Cycle, -}; - -pub fn dao_int_test( - initial_treasury_bal: Uint128, - snip20_symbol: &str, - allow_amount: Vec, - allow_type: Vec, - cycle: Vec, - allow_tolerance: Vec, - expected_allowance: Vec, - alloc_amount: Vec>, - alloc_type: Vec>, - alloc_tolerance: Vec>, - is_instant_unbond: bool, - expected_treasury: Uint128, - expected_manager: Vec, - expected_adapter: Vec>, -) { - let mut app = App::default(); - let mut contracts = DeployedContracts::new(); - let num_managers = allow_amount.len(); - init_dao( - &mut app, - "admin", - &mut contracts, - initial_treasury_bal, - snip20_symbol.clone(), - allow_type.clone(), - cycle.clone(), - allow_amount.clone(), - allow_tolerance.clone(), - alloc_type.clone(), - alloc_amount.clone(), - alloc_tolerance.clone(), - is_instant_unbond, - true, - ) - .unwrap(); - //query assets - let assets_query_res = treasury::assets_query(&app, &contracts).unwrap(); - assert!( - assets_query_res.contains( - &contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .address - ) - ); - //query allowance - for i in 0..num_managers { - assert_eq!( - expected_allowance[i], - treasury::allowance_query( - &app, - &contracts, - snip20_symbol, - SupportedContracts::TreasuryManager(i) - ) - .unwrap(), - "Treasury->Manager Allowance", - ); - } - let mut bals; - if is_instant_unbond { - bals = system_balance_reserves(&app, &contracts, snip20_symbol); - } else { - bals = system_balance_unbondable(&app, &contracts, snip20_symbol); - } - assert_eq!(bals.0, expected_treasury); - for (i, manager_tuples) in bals.1.iter().enumerate() { - assert_eq!(manager_tuples.0, expected_manager[i]); - for (j, adapter_bals) in manager_tuples.1.iter().enumerate() { - assert_eq!(adapter_bals.clone(), expected_adapter[i][j]); - } - } - let mut k = 0; - for i in 0..num_managers { - treasury::allowance_exec( - &mut app, - "admin", - &contracts, - snip20_symbol, - i, - allow_type[i].clone(), - cycle[i].clone(), - Uint128::zero(), - allow_tolerance[i].clone(), - true, - ) - .unwrap(); - for j in 0..alloc_amount[i].len() { - treasury_manager::allocate_exec( - &mut app, - "admin", - &contracts, - snip20_symbol, - Some(j.to_string()), - &SupportedContracts::MockAdapter(k), - alloc_type[i][j].clone(), - Uint128::zero(), - alloc_tolerance[i][j].clone(), - i, - ) - .unwrap(); - k += 1; - } - k += 1; - } - - update_dao(&mut app, "admin", &contracts, "SSCRT", num_managers).unwrap(); - treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); - if !is_instant_unbond { - k = 0; - for i in 0..num_managers { - for _j in 0..alloc_amount[i].len() { - println!("{}", k); - mock_adapter_complete_unbonding( - &mut app, - "admin", - &contracts, - SupportedContracts::MockAdapter(k), - ) - .unwrap(); - k += 1; - } - k += 1; - } - update_dao(&mut app, "admin", &contracts, "SSCRT", num_managers).unwrap(); - bals = system_balance_unbondable(&app, &contracts, "SSCRT"); - } else { - bals = system_balance_reserves(&app, &contracts, "SSCRT"); - } - assert_eq!(bals.0, initial_treasury_bal); - for (_i, manager_tuples) in bals.1.iter().enumerate() { - assert_eq!(manager_tuples.0, Uint128::zero()); - for (_j, adapter_bals) in manager_tuples.1.iter().enumerate() { - assert_eq!(adapter_bals.clone(), Uint128::zero()); - } - } - treasury::update_exec(&mut app, "admin", &contracts, "SSCRT").unwrap(); - assert_eq!( - std::vec::Vec::::new(), - treasury::allowances_query(&app, &contracts, "SSCRT",).unwrap() - ); -} - -macro_rules! dao_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - initial_treasury_bal, - snip20_symbol, - allow_amount, - allow_type, - cycle, - allow_tolerance, - expected_allowance, - alloc_amount, - alloc_type, - alloc_tolerance, - is_instant_unbond, - expected_treasury, - expected_manager, - expected_adapter, - ) = $value; - dao_int_test( - initial_treasury_bal, - snip20_symbol, - allow_amount, - allow_type, - cycle, - allow_tolerance, - expected_allowance, - alloc_amount, - alloc_type, - alloc_tolerance, - is_instant_unbond, - expected_treasury, - expected_manager, - expected_adapter, - ); - } - )* - } -} - -dao_tests! { - dao_test_0:( - Uint128::new(1_000_000), - "SSCRT", - vec![Uint128::new(5 * 10u128.pow(17))], - vec![AllowanceType::Portion], - vec![Cycle::Constant], - vec![Uint128::zero()], - vec![Uint128::new(90_000)], - vec![vec![Uint128::new(1 * 10u128.pow(17)), Uint128::new(400_000)]], - vec![vec![AllocationType::Portion, AllocationType::Amount]], - vec![vec![Uint128::zero(), Uint128::zero()]], - true, - Uint128::new(590_000), - vec![Uint128::new(0)], - vec![vec![Uint128::new(10_000), Uint128::new(400_000)]], - ), - dao_test_1:( - Uint128::new(100_000_000), - "SSCRT", - vec![Uint128::new(50_000_000), Uint128::new(40_000_000)], - vec![AllowanceType::Amount, AllowanceType::Amount], - vec![Cycle::Constant, Cycle::Constant], - vec![Uint128::zero(), Uint128::zero()], - vec![Uint128::new(21_000_000), Uint128::new(18_000_000)], - vec![vec![Uint128::new(5 * 10u128.pow(17)), Uint128::new(4_000_000), Uint128::new(4_000_000)], vec![Uint128::new(5 * 10u128.pow(17)), Uint128::new(4_000_000)]], - vec![vec![AllocationType::Portion, AllocationType::Amount, AllocationType::Amount],vec![AllocationType::Portion, AllocationType::Amount]], - vec![vec![Uint128::zero(), Uint128::zero(), Uint128::zero()],vec![Uint128::zero(), Uint128::zero()]], - true, - Uint128::new(49_000_000), - vec![Uint128::new(0), Uint128::new(0)], - vec![vec![Uint128::new(21_000_000), Uint128::new(4_000_000), Uint128::new(4_000_000)],vec![Uint128::new(18_000_000), Uint128::new(4_000_000)]], - ), - dao_test_2:( - Uint128::new(100), - "SSCRT", - vec![Uint128::new(5 * 10u128.pow(17))], - vec![AllowanceType::Portion], - vec![Cycle::Constant], - vec![Uint128::zero()], - vec![Uint128::new(9)], - vec![vec![Uint128::new(1 * 10u128.pow(17)), Uint128::new(40)]], - vec![vec![AllocationType::Portion, AllocationType::Amount]], - vec![vec![Uint128::zero(), Uint128::zero()]], - true, - Uint128::new(59), - vec![Uint128::new(0)], - vec![vec![Uint128::new(1), Uint128::new(40)]], - ), - dao_test_3: ( - Uint128::new(1000), - "SSCRT", - vec![ - Uint128::new(50), // Amount - 50 - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - Uint128::new(100), // Amount - 100 - Uint128::new(4 * 10u128.pow(17)), // Portion - 40% - ], // Allowance amount - vec![AllowanceType::Amount, AllowanceType::Portion, AllowanceType::Amount, AllowanceType::Portion], - vec![Cycle::Constant; 4], - vec![Uint128::zero(); 4], - vec![Uint128::new(6), Uint128::new(98), Uint128::new(16), Uint128::new(64)], - vec![ - vec![Uint128::new(6 * 10u128.pow(17)), Uint128::new(5), Uint128::new(2 * 10u128.pow(17)), Uint128::new(15)];4 - ], - vec![ - vec![AllocationType::Portion, AllocationType::Amount, AllocationType::Portion, AllocationType::Amount];4 - ], - vec![ - vec![Uint128::zero(); 4]; 4 - ], - true, - Uint128::new(184), - vec![Uint128::zero(); 4], - vec![ - vec![Uint128::new(18), Uint128::new(5), Uint128::new(6), Uint128::new(15)], - vec![Uint128::new(294), Uint128::new(5), Uint128::new(98), Uint128::new(15)], - vec![Uint128::new(48), Uint128::new(5), Uint128::new(16), Uint128::new(15)], - vec![Uint128::new(192), Uint128::new(5), Uint128::new(64), Uint128::new(15)], - ] - ), - dao_test_adapter_unbonding: ( - Uint128::new(1000), - "SSCRT", - vec![ - Uint128::new(50), // Amount - 50 - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - Uint128::new(100), // Amount - 100 - Uint128::new(4 * 10u128.pow(17)), // Portion - 40% - ], // Allowance amount - vec![AllowanceType::Amount, AllowanceType::Portion, AllowanceType::Amount, AllowanceType::Portion], - vec![Cycle::Constant; 4], - vec![Uint128::zero(); 4], - vec![Uint128::new(6), Uint128::new(98), Uint128::new(16), Uint128::new(64)], - vec![ - vec![Uint128::new(6 * 10u128.pow(17)), Uint128::new(5), Uint128::new(2 * 10u128.pow(17)), Uint128::new(15)];4 - ], - vec![ - vec![AllocationType::Portion, AllocationType::Amount, AllocationType::Portion, AllocationType::Amount];4 - ], - vec![ - vec![Uint128::zero(); 4]; 4 - ], - false, - Uint128::new(184), - vec![Uint128::zero(); 4], - vec![ - vec![Uint128::new(18), Uint128::new(5), Uint128::new(6), Uint128::new(15)], - vec![Uint128::new(294), Uint128::new(5), Uint128::new(98), Uint128::new(15)], - vec![Uint128::new(48), Uint128::new(5), Uint128::new(16), Uint128::new(15)], - vec![Uint128::new(192), Uint128::new(5), Uint128::new(64), Uint128::new(15)], - ] - ), -} diff --git a/contracts/dao/treasury/tests/integration/allowance.rs b/contracts/dao/treasury/tests/integration/allowance.rs deleted file mode 100644 index cbf0602..0000000 --- a/contracts/dao/treasury/tests/integration/allowance.rs +++ /dev/null @@ -1,389 +0,0 @@ -use shade_multi_test::multi::{admin::init_admin_auth, snip20::Snip20, treasury::Treasury}; -use shade_protocol::{ - c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}, - contract_interfaces::{ - dao::{treasury, treasury::AllowanceType}, - snip20, - }, - multi_test::App, - utils::{ - cycle::{parse_utc_datetime, Cycle}, - ExecuteCallback, - InstantiateCallback, - MultiTestable, - Query, - }, -}; - -fn allowance_cycle( - deposit: Uint128, - removed: Uint128, - expected: Uint128, - allowance: Uint128, - allow_type: AllowanceType, - cycle: Cycle, - start: String, - not_refreshed: String, - refreshed: String, -) { - let mut app = App::default(); - - let start = parse_utc_datetime(&start).unwrap(); - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(start.timestamp() as u64), - chain_id: "chain_id".to_string(), - }); - - let admin = Addr::unchecked("admin"); - let spender = Addr::unchecked("spender"); - let _user = Addr::unchecked("user"); - //let validator = Addr::unchecked("validator"); - let admin_auth = init_admin_auth(&mut app, &admin); - - let viewing_key = "viewing_key".to_string(); - - let token = snip20::InstantiateMsg { - name: "token".into(), - admin: Some("admin".into()), - symbol: "TKN".into(), - decimals: 6, - initial_balances: Some(vec![snip20::InitialBalance { - address: admin.to_string().clone(), - amount: deposit, - }]), - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - query_auth: None, - } - .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) - .unwrap(); - - let treasury = treasury::InstantiateMsg { - admin_auth: admin_auth.clone().into(), - viewing_key: viewing_key.clone(), - multisig: admin.to_string().clone(), - } - .test_init(Treasury::default(), &mut app, admin.clone(), "treasury", &[ - ]) - .unwrap(); - - // Set admin viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Register treasury assets - treasury::ExecuteMsg::RegisterAsset { - contract: token.clone().into(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // treasury allowance to spender - treasury::ExecuteMsg::Allowance { - asset: token.address.to_string().clone(), - allowance: treasury::RawAllowance { - //nick: "Mid-Stakes-Manager".to_string(), - spender: spender.clone().to_string(), - allowance_type: allow_type, - cycle, - amount: allowance, - // 100% (adapter balance will 2x before unbond) - tolerance: Uint128::zero(), - }, - refresh_now: true, - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Deposit funds into treasury - snip20::ExecuteMsg::Send { - recipient: treasury.address.to_string().clone(), - recipient_code_hash: None, - amount: deposit, - msg: None, - memo: None, - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update treasury - treasury::ExecuteMsg::Update { - asset: token.address.to_string().clone(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Check treasury allowance - match (treasury::QueryMsg::Allowance { - asset: token.address.to_string().clone(), - spender: spender.to_string().clone(), - } - .test_query(&treasury, &app) - .unwrap()) - { - treasury::QueryAnswer::Allowance { amount } => { - assert_eq!(amount, expected, "Initial Allowance"); - } - _ => panic!("query failed"), - }; - - // Send out of treasury to reduce allowance (user using funds) - snip20::ExecuteMsg::SendFrom { - recipient: spender.to_string().clone(), //treasury.address.to_string().clone(), - recipient_code_hash: None, - owner: treasury.address.to_string(), - amount: removed, - memo: None, - msg: None, - padding: None, - } - .test_exec(&token, &mut app, spender.clone(), &[]) - .unwrap(); - - // Send back to treasury to maintain balance/expected - snip20::ExecuteMsg::Send { - recipient: treasury.address.to_string().clone(), - recipient_code_hash: None, - amount: removed, - memo: None, - msg: None, - padding: None, - } - .test_exec(&token, &mut app, spender.clone(), &[]) - .unwrap(); - - // Check treasury allowance - match (treasury::QueryMsg::Allowance { - asset: token.address.to_string().clone(), - spender: spender.to_string().clone(), - } - .test_query(&treasury, &app) - .unwrap()) - { - treasury::QueryAnswer::Allowance { amount } => { - assert_eq!(amount, expected - removed, "Allowance after use"); - } - _ => panic!("query failed"), - }; - - // Update treasury - treasury::ExecuteMsg::Update { - asset: token.address.to_string().clone(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - let not_refreshed = parse_utc_datetime(¬_refreshed).unwrap(); - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(not_refreshed.timestamp() as u64), - chain_id: "chain_id".to_string(), - }); - - // Update treasury - treasury::ExecuteMsg::Update { - asset: token.address.to_string().clone(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Check treasury allowance - match (treasury::QueryMsg::Allowance { - asset: token.address.to_string().clone(), - spender: spender.to_string().clone(), - } - .test_query(&treasury, &app) - .unwrap()) - { - treasury::QueryAnswer::Allowance { amount } => { - assert_eq!(amount, expected - removed, "Allowance not refreshed"); - } - _ => panic!("query failed"), - }; - - let refreshed = parse_utc_datetime(&refreshed).unwrap(); - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(refreshed.timestamp() as u64), - chain_id: "chain_id".to_string(), - }); - - // Update treasury - treasury::ExecuteMsg::Update { - asset: token.address.to_string().clone(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Check treasury allowance - match (treasury::QueryMsg::Allowance { - asset: token.address.to_string().clone(), - spender: spender.to_string().clone(), - } - .test_query(&treasury, &app) - .unwrap()) - { - treasury::QueryAnswer::Allowance { amount } => { - assert_eq!(amount, expected, "Allowance refreshed"); - } - _ => panic!("query failed"), - }; -} - -macro_rules! allowance_cycle_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - deposit, - removed, - expected, - allowance, - allow_type, - cycle, - start, - not_refreshed, - refreshed, - ) = $value; - allowance_cycle( - deposit, - removed, - expected, - allowance, - allow_type, - cycle, - start.to_string(), - not_refreshed.to_string(), - refreshed.to_string(), - ); - } - )* - } -} - -allowance_cycle_tests! { - portion_seconds_30: ( - Uint128::new(100), // deposit - Uint128::new(100), // removed - Uint128::new(100), // expected - Uint128::new(1 * 10u128.pow(18)), // allowance - AllowanceType::Portion, - Cycle::Seconds { seconds: Uint128::new(30) }, - "1995-11-13T00:00:00.00Z", - "1995-11-13T00:00:29.00Z", - "1995-11-13T00:00:30.00Z", - ), - amount_seconds_30: ( - Uint128::new(100), // deposit - Uint128::new(100), // removed - Uint128::new(100), // expected - Uint128::new(100), // allowance - AllowanceType::Amount, - Cycle::Seconds { seconds: Uint128::new(30) }, - "1995-11-13T00:00:00.00Z", - "1995-11-13T00:00:29.00Z", - "1995-11-13T00:00:30.00Z", - ), - portion_minutes_30: ( - Uint128::new(100), // deposit - Uint128::new(100), // removed - Uint128::new(100), // expected - Uint128::new(1 * 10u128.pow(18)), // allowance - AllowanceType::Portion, - Cycle::Minutes { minutes: Uint128::new(30) }, - "1995-11-13T00:00:00.00Z", - "1995-11-13T00:15:00.00Z", - "1995-11-13T00:30:00.00Z", - ), - amount_minutes_30: ( - Uint128::new(100), // deposit - Uint128::new(100), // removed - Uint128::new(100), // expected - Uint128::new(100), // allowance - AllowanceType::Amount, - Cycle::Minutes { minutes: Uint128::new(30) }, - "1995-11-13T00:00:00.00Z", - "1995-11-13T00:15:00.00Z", - "1995-11-13T00:30:00.00Z", - ), - portion_daily_1: ( - Uint128::new(100), // deposit - Uint128::new(100), // removed - Uint128::new(100), // expected - Uint128::new(1 * 10u128.pow(18)), // allowance - AllowanceType::Portion, - Cycle::Daily { days: Uint128::new(1) }, - "1995-11-13T00:00:00.00Z", - "1995-11-13T12:00:00.00Z", - "1995-11-14T00:00:00.00Z", - ), - amount_daily_1: ( - Uint128::new(100), // deposit - Uint128::new(100), // removed - Uint128::new(100), // expected - Uint128::new(100), // allowance - AllowanceType::Amount, - Cycle::Daily { days: Uint128::new(1) }, - "1995-11-13T00:00:00.00Z", - "1995-11-13T12:00:00.00Z", - "1995-11-14T00:00:00.00Z", - ), - portion_monthly_1: ( - Uint128::new(100), // deposit - Uint128::new(100), // removed - Uint128::new(100), // expected - Uint128::new(1 * 10u128.pow(18)), // allowance - AllowanceType::Portion, - Cycle::Monthly { months: Uint128::new(1) }, - "1995-11-13T00:00:00.00Z", - "1995-11-13T12:00:00.00Z", - "1995-12-13T00:00:00.00Z", - ), - amount_monthly_1: ( - Uint128::new(100), // deposit - Uint128::new(100), // removed - Uint128::new(100), // expected - Uint128::new(100), // allowance - AllowanceType::Amount, - Cycle::Monthly { months: Uint128::new(1) }, - "1995-11-13T00:00:00.00Z", - "1995-11-20T00:00:00.00Z", - "1995-12-13T00:00:00.00Z", - ), - portion_yearly_1: ( - Uint128::new(100), // deposit - Uint128::new(100), // removed - Uint128::new(100), // expected - Uint128::new(1 * 10u128.pow(18)), // allowance - AllowanceType::Portion, - Cycle::Yearly { years: Uint128::new(1) }, - "1995-11-13T00:00:00.00Z", - "1995-12-29T12:00:00.00Z", - "1996-01-01T00:00:00.00Z", - ), - amount_yearly_1: ( - Uint128::new(100), // deposit - Uint128::new(100), // removed - Uint128::new(100), // expected - Uint128::new(100), // allowance - AllowanceType::Amount, - Cycle::Yearly { years: Uint128::new(1) }, - "1995-11-13T00:00:00.00Z", - "1995-12-29T12:00:00.00Z", - "1996-01-01T00:00:00.00Z", - ), -} diff --git a/contracts/dao/treasury/tests/integration/allowance_delay_refresh.rs b/contracts/dao/treasury/tests/integration/allowance_delay_refresh.rs deleted file mode 100644 index ac1d272..0000000 --- a/contracts/dao/treasury/tests/integration/allowance_delay_refresh.rs +++ /dev/null @@ -1,335 +0,0 @@ -use shade_multi_test::multi::{admin::init_admin_auth, snip20::Snip20, treasury::Treasury}; -use shade_protocol::{ - c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}, - contract_interfaces::{ - dao::{treasury, treasury::AllowanceType}, - snip20, - }, - multi_test::App, - utils::{ - cycle::{parse_utc_datetime, Cycle}, - ExecuteCallback, - InstantiateCallback, - MultiTestable, - Query, - }, -}; - -fn allowance_cycle( - deposit: Uint128, - removed: Uint128, - start_expected: Uint128, - start_allowance: Uint128, - start_allow_type: AllowanceType, - start_cycle: Cycle, - updated_expected: Uint128, - updated_allowance: Uint128, - updated_allow_type: AllowanceType, - updated_cycle: Cycle, - start: String, - refreshed: String, -) { - let mut app = App::default(); - - let start = parse_utc_datetime(&start).unwrap(); - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(start.timestamp() as u64), - chain_id: "chain_id".to_string(), - }); - - let admin = Addr::unchecked("admin"); - let spender = Addr::unchecked("spender"); - let _user = Addr::unchecked("user"); - //let validator = Addr::unchecked("validator"); - let admin_auth = init_admin_auth(&mut app, &admin); - - let viewing_key = "viewing_key".to_string(); - - let token = snip20::InstantiateMsg { - name: "token".into(), - admin: Some("admin".into()), - symbol: "TKN".into(), - decimals: 6, - initial_balances: Some(vec![snip20::InitialBalance { - address: admin.to_string().clone(), - amount: deposit + deposit, - }]), - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - query_auth: None, - } - .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) - .unwrap(); - - let treasury = treasury::InstantiateMsg { - admin_auth: admin_auth.clone().into(), - viewing_key: viewing_key.clone(), - multisig: admin.to_string().clone(), - } - .test_init(Treasury::default(), &mut app, admin.clone(), "treasury", &[ - ]) - .unwrap(); - - // Set admin viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Register treasury assets - treasury::ExecuteMsg::RegisterAsset { - contract: token.clone().into(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // treasury starting allowance to spender - treasury::ExecuteMsg::Allowance { - asset: token.address.to_string().clone(), - allowance: treasury::RawAllowance { - //nick: "Mid-Stakes-Manager".to_string(), - spender: spender.clone().to_string(), - allowance_type: start_allow_type, - cycle: start_cycle, - amount: start_allowance, - // 100% (adapter balance will 2x before unbond) - tolerance: Uint128::zero(), - }, - refresh_now: false, - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Deposit funds into treasury - snip20::ExecuteMsg::Send { - recipient: treasury.address.to_string().clone(), - recipient_code_hash: None, - amount: deposit, - msg: None, - memo: None, - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update treasury - treasury::ExecuteMsg::Update { - asset: token.address.to_string().clone(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Check treasury allowance - match (treasury::QueryMsg::Allowance { - asset: token.address.to_string().clone(), - spender: spender.to_string().clone(), - } - .test_query(&treasury, &app) - .unwrap()) - { - treasury::QueryAnswer::Allowance { amount } => { - assert_eq!(amount, start_expected, "Initial Allowance"); - } - _ => panic!("query failed"), - }; - - // Send out of treasury to reduce allowance (user using funds) - snip20::ExecuteMsg::SendFrom { - recipient: spender.to_string().clone(), //treasury.address.to_string().clone(), - recipient_code_hash: None, - owner: treasury.address.to_string(), - amount: removed, - memo: None, - msg: None, - padding: None, - } - .test_exec(&token, &mut app, spender.clone(), &[]) - .unwrap(); - - // Refill treasury balance - snip20::ExecuteMsg::Send { - recipient: treasury.address.to_string().clone(), - recipient_code_hash: None, - amount: removed, - memo: None, - msg: None, - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update treasury - treasury::ExecuteMsg::Update { - asset: token.address.to_string().clone(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Check treasury allowance reflects used funds - match (treasury::QueryMsg::Allowance { - asset: token.address.to_string().clone(), - spender: spender.to_string().clone(), - } - .test_query(&treasury, &app) - .unwrap()) - { - treasury::QueryAnswer::Allowance { amount } => { - assert_eq!(amount, start_expected - removed, "Allowance after use"); - } - _ => panic!("query failed"), - }; - - // Update allowance to spender - treasury::ExecuteMsg::Allowance { - asset: token.address.to_string().clone(), - allowance: treasury::RawAllowance { - //nick: "Mid-Stakes-Manager".to_string(), - spender: spender.clone().to_string(), - allowance_type: updated_allow_type, - cycle: updated_cycle, - amount: updated_allowance, - // 100% (adapter balance will 2x before unbond) - tolerance: Uint128::zero(), - }, - refresh_now: false, - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update treasury - treasury::ExecuteMsg::Update { - asset: token.address.to_string().clone(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Check allowance hasn't changed - match (treasury::QueryMsg::Allowance { - asset: token.address.to_string().clone(), - spender: spender.to_string().clone(), - } - .test_query(&treasury, &app) - .unwrap()) - { - treasury::QueryAnswer::Allowance { amount } => { - assert_eq!( - amount, - start_expected - removed, - "Allowance after update, not refreshed" - ); - } - _ => panic!("query failed"), - }; - - let refreshed = parse_utc_datetime(&refreshed).unwrap(); - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds(refreshed.timestamp() as u64), - chain_id: "chain_id".to_string(), - }); - - // Update treasury - treasury::ExecuteMsg::Update { - asset: token.address.to_string().clone(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Check treasury updated to new allowance settings - match (treasury::QueryMsg::Allowance { - asset: token.address.to_string().clone(), - spender: spender.to_string().clone(), - } - .test_query(&treasury, &app) - .unwrap()) - { - treasury::QueryAnswer::Allowance { amount } => { - assert_eq!( - amount, updated_expected, - "Allowance refreshed to updated amount" - ); - } - _ => panic!("query failed"), - }; -} - -macro_rules! allowance_delay_update_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - deposit, - removed, - start_expected, - start_allowance, - start_allow_type, - start_cycle, - updated_expected, - updated_allowance, - updated_allow_type, - updated_cycle, - start, - refreshed, - ) = $value; - allowance_cycle( - deposit, - removed, - start_expected, - start_allowance, - start_allow_type, - start_cycle, - updated_expected, - updated_allowance, - updated_allow_type, - updated_cycle, - start.to_string(), - refreshed.to_string(), - ); - } - )* - } -} - -allowance_delay_update_tests! { - portion_seconds_30: ( - Uint128::new(100), // deposit - Uint128::new(100), // used - Uint128::new(100), // start expected - Uint128::new(1 * 10u128.pow(18)), // start allowance - AllowanceType::Portion, - Cycle::Seconds { seconds: Uint128::new(30) }, - - Uint128::new(90), // updated expected - Uint128::new(9 * 10u128.pow(17)), // updated allowance - AllowanceType::Portion, - Cycle::Seconds { seconds: Uint128::new(30) }, - "1995-11-13T00:00:00.00Z", - "1995-11-13T00:00:30.00Z", - ), - amount_seconds_30: ( - Uint128::new(100), // deposit - Uint128::new(100), // used - Uint128::new(100), // start expected - Uint128::new(100), // start allowance - AllowanceType::Amount, - Cycle::Seconds { seconds: Uint128::new(30) }, - - Uint128::new(90), // updated expected - Uint128::new(9 * 10u128.pow(17)), // updated allowance - AllowanceType::Portion, - Cycle::Seconds { seconds: Uint128::new(30) }, - "1995-11-13T00:00:00.00Z", - "1995-11-13T00:00:30.00Z", - ), -} diff --git a/contracts/dao/treasury/tests/integration/batch.rs b/contracts/dao/treasury/tests/integration/batch.rs deleted file mode 100644 index 4ef729a..0000000 --- a/contracts/dao/treasury/tests/integration/batch.rs +++ /dev/null @@ -1,95 +0,0 @@ -use shade_multi_test::multi::{admin::init_admin_auth, snip20::Snip20, treasury::Treasury}; -use shade_protocol::{ - c_std::{to_binary, Addr, Uint128}, - contract_interfaces::{dao::treasury, snip20}, - multi_test::App, - utils::{ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -//TODO test with manager -// Add other adapters here as they come -fn batch_balance_test(amounts: Vec) { - let mut app = App::default(); - - let admin = Addr::unchecked("admin"); - let _user = Addr::unchecked("user"); - let admin_auth = init_admin_auth(&mut app, &admin); - let viewing_key = "veiwing_key".to_string(); - - let mut tokens = vec![]; - - let treasury = treasury::InstantiateMsg { - admin_auth: admin_auth.clone().into(), - viewing_key: viewing_key.clone(), - multisig: admin.to_string().clone(), - } - .test_init(Treasury::default(), &mut app, admin.clone(), "treasury", &[ - ]) - .unwrap(); - - for amount in amounts.clone() { - let token = snip20::InstantiateMsg { - name: "token".into(), - admin: Some("admin".into()), - symbol: "TKN".into(), - decimals: 6, - initial_balances: Some(vec![snip20::InitialBalance { - address: treasury.address.to_string().clone(), - amount, - }]), - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - query_auth: None, - } - .test_init( - Snip20::default(), - &mut app, - admin.clone(), - &amount.to_string(), - &[], - ) - .unwrap(); - - treasury::ExecuteMsg::RegisterAsset { - contract: token.clone().into(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - tokens.push(token); - } - - // Treasury Balances - let balances: Vec = treasury::QueryMsg::BatchBalance { - assets: tokens - .iter() - .map(|t| t.address.to_string().clone()) - .collect(), - } - .test_query(&treasury, &app) - .unwrap(); - - assert!(balances == amounts, "Reported balances match inputs"); -} - -macro_rules! batch_balance_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - batch_balance_test($value.into_iter().map(|a| Uint128::new(a as u128)).collect()); - } - )* - } -} - -batch_balance_tests! { - batch_balances_0: vec![10, 23840, 8402840, 123456, 0], -} diff --git a/contracts/dao/treasury/tests/integration/config.rs b/contracts/dao/treasury/tests/integration/config.rs deleted file mode 100644 index 7b00f1f..0000000 --- a/contracts/dao/treasury/tests/integration/config.rs +++ /dev/null @@ -1,80 +0,0 @@ -use shade_multi_test::interfaces::{dao::init_dao, treasury, utils::DeployedContracts}; -use shade_protocol::{ - c_std::{Addr, Uint128}, - contract_interfaces::dao::{self, treasury::AllowanceType, treasury_manager::AllocationType}, - multi_test::App, - utils::{ - asset::{Contract, RawContract}, - cycle::Cycle, - }, -}; - -#[test] -pub fn update_config() { - let mut app = App::default(); - let mut contracts = DeployedContracts::new(); - init_dao( - &mut app, - "admin", - &mut contracts, - Uint128::new(1500), - "SSCRT", - vec![ - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - ], - vec![Cycle::Constant; 4], - vec![ - Uint128::new(200), // Amount - 50 - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - Uint128::new(300), // Amount - 100 - Uint128::new(3 * 10u128.pow(17)), // Portion - 40% - ], // Allowance amount - vec![Uint128::zero(); 4], - vec![ - vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount - ]; - 4 - ], - vec![ - vec![ - Uint128::new(6 * 10u128.pow(17)), - Uint128::new(50), - Uint128::new(2 * 10u128.pow(17)), - Uint128::new(75), - ]; - 4 - ], - vec![vec![Uint128::zero(); 4]; 4], - true, - true, - ) - .unwrap(); - treasury::set_config( - &mut app, - "admin", - &contracts, - Some(RawContract { - address: "rando2".to_string(), - code_hash: "rando3".to_string(), - }), - Some(Addr::unchecked("rando").into()), - ) - .unwrap(); - assert_eq!( - treasury::config_query(&app, &contracts).unwrap(), - dao::treasury::Config { - admin_auth: Contract { - address: Addr::unchecked("rando2"), - code_hash: "rando3".to_string(), - }, - multisig: Addr::unchecked("rando"), - } - ); -} diff --git a/contracts/dao/treasury/tests/integration/execute_errors.rs b/contracts/dao/treasury/tests/integration/execute_errors.rs deleted file mode 100644 index 494c551..0000000 --- a/contracts/dao/treasury/tests/integration/execute_errors.rs +++ /dev/null @@ -1,244 +0,0 @@ -use shade_multi_test::interfaces::{ - dao::init_dao, - snip20, - treasury, - utils::{DeployedContracts, SupportedContracts}, -}; -use shade_protocol::{ - c_std::Uint128, - contract_interfaces::dao::{self, treasury::AllowanceType, treasury_manager::AllocationType}, - multi_test::App, - utils::{asset::RawContract, cycle::Cycle}, -}; - -#[test] -pub fn execute_error() { - let mut app = App::default(); - let mut contracts = DeployedContracts::new(); - init_dao( - &mut app, - "admin", - &mut contracts, - Uint128::new(1500), - "SSCRT", - vec![ - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - ], - vec![Cycle::Constant; 4], - vec![ - Uint128::new(200), // Amount - 50 - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - Uint128::new(300), // Amount - 100 - Uint128::new(3 * 10u128.pow(17)), // Portion - 40% - ], // Allowance amount - vec![Uint128::zero(); 4], - vec![ - vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount - ]; - 4 - ], - vec![ - vec![ - Uint128::new(6 * 10u128.pow(17)), - Uint128::new(50), - Uint128::new(2 * 10u128.pow(17)), - Uint128::new(75), - ]; - 4 - ], - vec![vec![Uint128::zero(); 4]; 4], - true, - true, - ) - .unwrap(); - match treasury::allowance_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - 0, - AllowanceType::Portion, - Cycle::Constant, - Uint128::new(1), - Uint128::new(10u128.pow(18u32)), - true, - ) { - Ok(_) => assert!(false), - Err(_) => assert!(true), - } - match treasury::allowance_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - 0, - AllowanceType::Portion, - Cycle::Constant, - Uint128::new(101 * 10u128.pow(16u32)), - Uint128::zero(), - true, - ) { - Ok(_) => assert!(false), - Err(_) => assert!(true), - } - snip20::init(&mut app, "admin", &mut contracts, "Shade", "SHD", 8, None).unwrap(); - match treasury::allowance_exec( - &mut app, - "admin", - &contracts, - "SHD", - 0, - AllowanceType::Portion, - Cycle::Constant, - Uint128::new(1), - Uint128::zero(), - true, - ) { - Ok(_) => assert!(false), - Err(_) => assert!(true), - } - match treasury::register_manager_exec(&mut app, "admin", &contracts, 0) { - Ok(_) => assert!(false), - Err(_) => assert!(true), - } - match treasury::register_wrap_exec( - &mut app, - "admin", - &contracts, - "SHD".to_string(), - RawContract { - address: "rando".to_string(), - code_hash: "code_hash".to_string(), - }, - ) { - Ok(_) => assert!(false), - Err(_) => assert!(true), - } - match treasury::update_exec(&mut app, "admin", &contracts, "SHD") { - Ok(_) => assert!(false), - Err(_) => assert!(true), - } - treasury::set_run_level_exec( - &mut app, - "admin", - &contracts, - dao::treasury::RunLevel::Deactivated, - ) - .unwrap(); - match treasury::update_exec(&mut app, "admin", &contracts, "SSCRT") { - Ok(_) => assert!(false), - Err(_) => assert!(true), - } - treasury::register_asset_exec(&mut app, "admin", &contracts, "SHD").unwrap(); - match treasury::register_wrap_exec( - &mut app, - "admin", - &contracts, - "SHD".to_string(), - contracts[&SupportedContracts::Snip20("SHD".to_string())] - .clone() - .into(), - ) { - Ok(_) => assert!(false), - Err(_) => assert!(true), - } -} - -#[test] -pub fn admin_errors() { - const NOT_ADMIN: &str = "not_admin"; - let mut app = App::default(); - let mut contracts = DeployedContracts::new(); - init_dao( - &mut app, - "admin", - &mut contracts, - Uint128::new(1500), - "SSCRT", - vec![ - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - ], - vec![Cycle::Constant; 4], - vec![ - Uint128::new(200), // Amount - 50 - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - Uint128::new(300), // Amount - 100 - Uint128::new(3 * 10u128.pow(17)), // Portion - 40% - ], // Allowance amount - vec![Uint128::zero(); 4], - vec![ - vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount - ]; - 4 - ], - vec![ - vec![ - Uint128::new(6 * 10u128.pow(17)), - Uint128::new(50), - Uint128::new(2 * 10u128.pow(17)), - Uint128::new(75), - ]; - 4 - ], - vec![vec![Uint128::zero(); 4]; 4], - true, - true, - ) - .unwrap(); - assert!(!treasury::set_config(&mut app, NOT_ADMIN, &contracts, None, None).is_ok()); - assert!( - !treasury::set_run_level_exec( - &mut app, - NOT_ADMIN, - &contracts, - dao::treasury::RunLevel::Migrating - ) - .is_ok() - ); - assert!(!treasury::register_asset_exec(&mut app, NOT_ADMIN, &contracts, "SSCRT").is_ok()); - assert!( - !treasury::register_wrap_exec( - &mut app, - NOT_ADMIN, - &contracts, - "SSCRT".to_string(), - RawContract { - address: "nana".to_string(), - code_hash: "nana".to_string() - } - ) - .is_ok() - ); - assert!(!treasury::register_manager_exec(&mut app, NOT_ADMIN, &contracts, 0).is_ok()); - assert!( - !treasury::allowance_exec( - &mut app, - NOT_ADMIN, - &contracts, - "SSCRT", - 0, - AllowanceType::Amount, - Cycle::Daily { - days: Uint128::new(1) - }, - Uint128::zero(), - Uint128::zero(), - true, - ) - .is_ok() - ); -} diff --git a/contracts/dao/treasury/tests/integration/migration.rs b/contracts/dao/treasury/tests/integration/migration.rs deleted file mode 100644 index 80fc50b..0000000 --- a/contracts/dao/treasury/tests/integration/migration.rs +++ /dev/null @@ -1,151 +0,0 @@ -use shade_multi_test::interfaces::{ - dao::{ - init_dao, - mock_adapter_complete_unbonding, - system_balance_reserves, - system_balance_unbondable, - update_dao, - }, - snip20, - treasury, - utils::{DeployedContracts, SupportedContracts}, -}; -use shade_protocol::{ - c_std::{Addr, Uint128}, - contract_interfaces::dao::{self, treasury::AllowanceType, treasury_manager::AllocationType}, - multi_test::App, - utils::cycle::Cycle, -}; - -pub fn migration_test(is_instant_unbond: bool) { - const MULTISIG: &str = "multisig"; - let mut app = App::default(); - let mut contracts = DeployedContracts::new(); - init_dao( - &mut app, - "admin", - &mut contracts, - Uint128::new(1500), - "SSCRT", - vec![ - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - ], - vec![Cycle::Constant; 4], - vec![ - Uint128::new(200), // Amount - 50 - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - Uint128::new(300), // Amount - 100 - Uint128::new(3 * 10u128.pow(17)), // Portion - 40% - ], // Allowance amount - vec![Uint128::zero(); 4], - vec![ - vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount - ]; - 4 - ], - vec![ - vec![ - Uint128::new(6 * 10u128.pow(17)), - Uint128::new(50), - Uint128::new(2 * 10u128.pow(17)), - Uint128::new(75), - ]; - 4 - ], - vec![vec![Uint128::zero(); 4]; 4], - is_instant_unbond, - true, - ) - .unwrap(); - snip20::set_viewing_key_exec( - &mut app, - MULTISIG, - &contracts, - "SSCRT", - MULTISIG.to_string(), - ) - .unwrap(); - treasury::set_config( - &mut app, - "admin", - &contracts, - Some( - contracts - .get(&SupportedContracts::AdminAuth) - .unwrap() - .clone() - .into(), - ), - Some(Addr::unchecked(MULTISIG).to_string()), - ) - .unwrap(); - treasury::set_run_level_exec( - &mut app, - "admin", - &contracts, - dao::treasury::RunLevel::Migrating, - ) - .unwrap(); - update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); - if is_instant_unbond { - update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); - } else { - let mut k = 0; - for _i in 0..4 { - for _j in 0..4 { - mock_adapter_complete_unbonding( - &mut app, - "admin", - &contracts, - SupportedContracts::MockAdapter(k), - ) - .unwrap(); - k += 1; - } - k += 1; - } - update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); - update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); - } - println!( - "{:?}\n{:?}", - system_balance_reserves(&app, &contracts, "SSCRT"), - system_balance_unbondable(&app, &contracts, "SSCRT") - ); - assert_eq!( - snip20::balance_query(&app, MULTISIG, &contracts, "SSCRT", MULTISIG.to_string()).unwrap(), - Uint128::new(1500) - ); -} - -macro_rules! dao_tests_migration { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - is_instant_unbond, - ) = $value; - migration_test( - is_instant_unbond, - ); - } - )* - } -} - -dao_tests_migration! ( - dao_test_migration_instant_unbond: ( - true, - ), - dao_test_migration_non_instant_unbond: ( - false, - ), -); diff --git a/contracts/dao/treasury/tests/integration/mod.rs b/contracts/dao/treasury/tests/integration/mod.rs deleted file mode 100644 index 8040158..0000000 --- a/contracts/dao/treasury/tests/integration/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub mod allowance; -pub mod allowance_delay_refresh; -pub mod batch; -pub mod config; -pub mod execute_errors; -pub mod migration; -pub mod non_manager_allowances; -pub mod query; -pub mod scrt_staking; -pub mod tolerance; -pub mod treasury; -pub mod wrap; diff --git a/contracts/dao/treasury/tests/integration/non_manager_allowances.rs b/contracts/dao/treasury/tests/integration/non_manager_allowances.rs deleted file mode 100644 index ca5160e..0000000 --- a/contracts/dao/treasury/tests/integration/non_manager_allowances.rs +++ /dev/null @@ -1,192 +0,0 @@ -use shade_multi_test::interfaces::{ - dao::{init_dao, system_balance_reserves, update_dao}, - snip20, - treasury, - utils::{DeployedContracts, SupportedContracts}, -}; -use shade_protocol::{ - c_std::{Addr, Uint128}, - contract_interfaces::dao::{treasury::AllowanceType, treasury_manager::AllocationType}, - multi_test::App, - utils::{asset::Contract, cycle::Cycle}, -}; - -#[test] -pub fn non_manager_allowances() { - let mut app = App::default(); - let mut contracts = DeployedContracts::new(); - const NOT_A_MANAGER: &str = "no_manager"; - init_dao( - &mut app, - "admin", - &mut contracts, - Uint128::new(1500), - "SSCRT", - vec![ - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - ], - vec![Cycle::Constant; 4], - vec![ - Uint128::new(200), // Amount - 50 - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - Uint128::new(300), // Amount - 100 - Uint128::new(3 * 10u128.pow(17)), // Portion - 40% - ], // Allowance amount - vec![Uint128::zero(); 4], - vec![ - vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount - ]; - 4 - ], - vec![ - vec![ - Uint128::new(6 * 10u128.pow(17)), - Uint128::new(50), - Uint128::new(2 * 10u128.pow(17)), - Uint128::new(75), - ]; - 4 - ], - vec![vec![Uint128::zero(); 4]; 4], - true, - true, - ) - .unwrap(); - contracts.insert(SupportedContracts::TreasuryManager(5), Contract { - address: Addr::unchecked(NOT_A_MANAGER), - code_hash: "".to_string(), - }); - treasury::allowance_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - 5, - AllowanceType::Amount, - Cycle::Once, - Uint128::new(100), - Uint128::zero(), - true, - ) - .unwrap(); - update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); - update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); - snip20::send_from_exec( - &mut app, - NOT_A_MANAGER, - &contracts, - "SSCRT", - contracts[&SupportedContracts::Treasury] - .clone() - .address - .into(), - NOT_A_MANAGER.to_string(), - Uint128::new(100), - None, - ) - .unwrap(); - update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); - assert_eq!( - Uint128::zero(), - treasury::allowance_query( - &app, - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(5) - ) - .unwrap() - ); - match snip20::send_from_exec( - &mut app, - NOT_A_MANAGER, - &contracts, - "SSCRT", - contracts[&SupportedContracts::Treasury] - .clone() - .address - .into(), - NOT_A_MANAGER.to_string(), - Uint128::new(100), - None, - ) { - Ok(_) => assert!(false, "cycle is set to once"), - Err(_) => assert!(true), - } - println!("{:?}", system_balance_reserves(&app, &contracts, "SSCRT"),); - snip20::set_viewing_key_exec( - &mut app, - NOT_A_MANAGER, - &contracts, - "SSCRT", - NOT_A_MANAGER.to_string(), - ) - .unwrap(); - assert_eq!( - snip20::balance_query( - &app, - NOT_A_MANAGER, - &contracts, - "SSCRT", - NOT_A_MANAGER.to_string() - ) - .unwrap(), - Uint128::new(100) - ); - treasury::allowance_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - 5, - AllowanceType::Amount, - Cycle::Constant, - Uint128::new(50), - Uint128::zero(), - true, - ) - .unwrap(); - update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); - snip20::send_from_exec( - &mut app, - NOT_A_MANAGER, - &contracts, - "SSCRT", - contracts[&SupportedContracts::Treasury] - .clone() - .address - .into(), - NOT_A_MANAGER.to_string(), - Uint128::new(25), - None, - ) - .unwrap(); - assert_eq!( - snip20::balance_query( - &app, - NOT_A_MANAGER, - &contracts, - "SSCRT", - NOT_A_MANAGER.to_string() - ) - .unwrap(), - Uint128::new(125) - ); - update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); - assert_eq!( - Uint128::new(50), - treasury::allowance_query( - &app, - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(5) - ) - .unwrap() - ); -} diff --git a/contracts/dao/treasury/tests/integration/query.rs b/contracts/dao/treasury/tests/integration/query.rs deleted file mode 100644 index 7ad095e..0000000 --- a/contracts/dao/treasury/tests/integration/query.rs +++ /dev/null @@ -1,240 +0,0 @@ -use shade_multi_test::interfaces::{ - dao::{init_dao, mock_adapter_sub_tokens, update_dao}, - snip20, - treasury, - utils::{DeployedContracts, SupportedContracts}, -}; -use shade_protocol::{ - c_std::{BlockInfo, Timestamp, Uint128}, - contract_interfaces::dao::{self, treasury::AllowanceType, treasury_manager::AllocationType}, - multi_test::App, - utils::{ - cycle::{parse_utc_datetime, Cycle}, - storage::plus::period_storage::Period, - }, -}; - -#[test] -pub fn query() { - let mut app = App::default(); - let mut contracts = DeployedContracts::new(); - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds( - parse_utc_datetime(&"1995-11-13T00:00:00.00Z".to_string()) - .unwrap() - .timestamp() as u64, - ), - chain_id: "chain_id".to_string(), - }); - init_dao( - &mut app, - "admin", - &mut contracts, - Uint128::new(1500), - "SSCRT", - vec![ - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - ], - vec![Cycle::Constant; 4], - vec![ - Uint128::new(200), // Amount - 50 - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - Uint128::new(300), // Amount - 100 - Uint128::new(3 * 10u128.pow(17)), // Portion - 40% - ], // Allowance amount - vec![Uint128::zero(); 4], - vec![ - vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount - ]; - 4 - ], - vec![ - vec![ - Uint128::new(6 * 10u128.pow(17)), - Uint128::new(50), - Uint128::new(2 * 10u128.pow(17)), - Uint128::new(75), - ]; - 4 - ], - vec![vec![Uint128::zero(); 4]; 4], - true, - true, - ) - .unwrap(); - assert_eq!( - treasury::batch_balance_query(&app, &contracts, vec!["SSCRT"]).unwrap(), - vec![Uint128::new(1500)] - ); - snip20::init(&mut app, "admin", &mut contracts, "Shade", "SHD", 8, None).unwrap(); - assert!(!treasury::batch_balance_query(&app, &contracts, vec!["SSCRT", "SHD"]).is_ok()); - assert!(!treasury::balance_query(&app, &contracts, "SHD",).is_ok()); - assert!(!treasury::reserves_query(&app, &contracts, "SHD",).is_ok()); - assert!( - !treasury::allowance_query( - &app, - &contracts, - "SHD", - SupportedContracts::TreasuryManager(0) - ) - .is_ok() - ); - treasury::register_asset_exec(&mut app, "admin", &contracts, "SHD").unwrap(); - assert_eq!( - treasury::batch_balance_query(&app, &contracts, vec!["SSCRT", "SHD"]).unwrap(), - vec![Uint128::new(1500), Uint128::zero()] - ); - assert_eq!( - treasury::run_level_query(&app, &contracts,).unwrap(), - dao::treasury::RunLevel::Normal - ); - assert!( - !treasury::metrics_query( - &app, - &contracts, - Some("1995-11-13T00:00:00.00Z".to_string()), - None, - Period::Hour, - ) - .unwrap() - .is_empty() - ); - assert!( - !treasury::metrics_query( - &app, - &contracts, - Some("1995-11-13T00:00:00.00Z".to_string()), - None, - Period::Day, - ) - .unwrap() - .is_empty() - ); - assert!( - !treasury::metrics_query( - &app, - &contracts, - Some("1995-11-13T00:00:00.00Z".to_string()), - None, - Period::Month, - ) - .unwrap() - .is_empty() - ); - assert!( - !treasury::metrics_query( - &app, - &contracts, - None, - Some(Uint128::new(816220800)), - Period::Hour, - ) - .unwrap() - .is_empty() - ); - assert!( - !treasury::metrics_query( - &app, - &contracts, - None, - Some(Uint128::new(816220800)), - Period::Day, - ) - .unwrap() - .is_empty() - ); - assert!( - !treasury::metrics_query( - &app, - &contracts, - None, - Some(Uint128::new(816220800)), - Period::Month, - ) - .unwrap() - .is_empty() - ); - assert!( - !treasury::metrics_query(&app, &contracts, None, None, Period::Month,) - .unwrap() - .is_empty() - ); - assert!( - !treasury::metrics_query( - &app, - &contracts, - Some("1995-11-13T00:00:00.00Z".to_string()), - Some(Uint128::new(816220800)), - Period::Month, - ) - .is_ok() - ); - assert!( - treasury::metrics_query( - &app, - &contracts, - None, - Some(Uint128::new( - parse_utc_datetime(&"1995-12-13T00:00:00.00Z".to_string()) - .unwrap() - .timestamp() as u128 - )), - Period::Month, - ) - .unwrap() - .is_empty() - ); - mock_adapter_sub_tokens( - &mut app, - "admin", - &contracts, - Uint128::new(10), - SupportedContracts::MockAdapter(7), - ) - .unwrap(); - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds( - parse_utc_datetime(&"1995-12-13T00:00:00.00Z".to_string()) - .unwrap() - .timestamp() as u64, - ), - chain_id: "chain_id".to_string(), - }); - update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); - update_dao(&mut app, "admin", &contracts, "SSCRT", 4).unwrap(); - assert!( - !treasury::metrics_query( - &app, - &contracts, - None, - Some(Uint128::new( - parse_utc_datetime(&"1995-12-13T00:00:00.00Z".to_string()) - .unwrap() - .timestamp() as u128 - )), - Period::Month, - ) - .unwrap() - .is_empty() - ); - assert!( - !treasury::metrics_query( - &app, - &contracts, - Some("1995-12-13T00:00:00.00Z".to_string()), - None, - Period::Month, - ) - .unwrap() - .is_empty() - ); -} diff --git a/contracts/dao/treasury/tests/integration/scrt_staking.rs b/contracts/dao/treasury/tests/integration/scrt_staking.rs deleted file mode 100644 index fc53617..0000000 --- a/contracts/dao/treasury/tests/integration/scrt_staking.rs +++ /dev/null @@ -1,861 +0,0 @@ -/* -use shade_protocol::c_std::{ - coins, - from_binary, - to_binary, - Addr, - Binary, - Coin, - Decimal, - Env, - StdError, - StdResult, - Uint128, - Validator, -}; - -use shade_protocol::{ - contract_interfaces::{ - dao::{ - adapter, - manager, - scrt_staking, - treasury, - treasury::{Allowance, AllowanceType, RunLevel}, - treasury_manager::{self, Allocation, AllocationType}, - }, - snip20, - }, - utils::{ - asset::Contract, - cycle::{utc_from_timestamp, Cycle}, - storage::plus::period_storage::Period, - ExecuteCallback, - InstantiateCallback, - MultiTestable, - Query, - }, -}; - -use shade_multi_test::multi::{ - admin::init_admin_auth, - scrt_staking::ScrtStaking, - snip20::Snip20, - treasury::Treasury, - treasury_manager::TreasuryManager, -}; -use shade_protocol::multi_test::{App, BankSudo, StakingSudo, SudoMsg}; - -use serde_json; - -// Add other adapters here as they come -fn single_asset_manager_scrt_staking_integration( - deposit: Uint128, - allowance: Uint128, - expected_allowance: Uint128, - alloc_type: AllocationType, - alloc_amount: Uint128, - rewards: Uint128, - // expected balances - pre_rewards: (Uint128, Uint128, Uint128), - post_rewards: (Uint128, Uint128, Uint128), -) { - let mut app = App::default(); - - let admin = Addr::unchecked("admin"); - let user = Addr::unchecked("user"); - let validator = Addr::unchecked("validator"); - let admin_auth = init_admin_auth(&mut app, &admin); - - let viewing_key = "viewing_key".to_string(); - - let token = snip20::InstantiateMsg { - name: "secretSCRT".into(), - admin: Some("admin".into()), - symbol: "SSCRT".into(), - decimals: 6, - initial_balances: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - query_auth: None, - } - .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) - .unwrap(); - - let treasury = treasury::InstantiateMsg { - admin_auth: admin_auth.clone().into(), - viewing_key: viewing_key.clone(), - multisig: admin.to_string().clone(), - } - .test_init(Treasury::default(), &mut app, admin.clone(), "treasury", &[ - ]) - .unwrap(); - - let manager = treasury_manager::InstantiateMsg { - admin_auth: admin_auth.clone().into(), - treasury: treasury.address.to_string(), - viewing_key: viewing_key.clone(), - } - .test_init( - TreasuryManager::default(), - &mut app, - admin.clone(), - "manager", - &[], - ) - .unwrap(); - - let scrt_staking = scrt_staking::InstantiateMsg { - admin_auth: admin_auth.clone().into(), - owner: manager.address.clone().to_string(), - sscrt: token.clone().into(), - validator_bounds: None, - viewing_key: viewing_key.clone(), - } - .test_init( - ScrtStaking::default(), - &mut app, - admin.clone(), - "scrt_staking", - &[], - ) - .unwrap(); - - app.sudo(SudoMsg::Staking(StakingSudo::AddValidator { - validator: validator.to_string().clone(), - })) - .unwrap(); - - // Set admin viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Register treasury assets - treasury::ExecuteMsg::RegisterAsset { - contract: token.clone().into(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Register manager assets - treasury_manager::ExecuteMsg::RegisterAsset { - contract: token.clone().into(), - } - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // Register manager w/ treasury - treasury::ExecuteMsg::RegisterManager { - contract: manager.clone().into(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // treasury allowance to manager - treasury::ExecuteMsg::Allowance { - asset: token.address.to_string().clone(), - allowance: treasury::Allowance { - //nick: "Mid-Stakes-Manager".to_string(), - spender: manager.address.clone(), - allowance_type: AllowanceType::Portion, - cycle: Cycle::Constant, - amount: allowance, - // 100% (adapter balance will 2x before unbond) - tolerance: Uint128::zero(), - }, - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Allocate to scrt_staking from manager - treasury_manager::ExecuteMsg::Allocate { - asset: token.address.to_string().clone(), - allocation: Allocation { - nick: Some("scrt_staking".to_string()), - contract: Contract { - address: scrt_staking.address.clone(), - code_hash: scrt_staking.code_hash.clone(), - }, - alloc_type, - amount: alloc_amount, - tolerance: Uint128::zero(), - }, - } - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - let deposit_coin = Coin { - denom: "uscrt".into(), - amount: deposit, - }; - app.init_modules(|router, _, storage| { - router - .bank - .init_balance(storage, &admin.clone(), vec![deposit_coin.clone()]) - .unwrap(); - }); - - assert!(deposit_coin.amount > Uint128::zero()); - - // Wrap L1 - snip20::ExecuteMsg::Deposit { padding: None } - .test_exec(&token, &mut app, admin.clone(), &vec![deposit_coin]) - .unwrap(); - - // Deposit funds into treasury - snip20::ExecuteMsg::Send { - recipient: treasury.address.to_string().clone(), - recipient_code_hash: None, - amount: Uint128::new(deposit.u128()), - msg: None, - memo: None, - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update treasury - println!("UPDATE TREASURY"); - adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { - asset: token.address.to_string().clone(), - }) - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Check treasury allowance to manager - match (treasury::QueryMsg::Allowance { - asset: token.address.to_string().clone(), - spender: manager.address.to_string().clone(), - } - .test_query(&treasury, &app) - .unwrap()) - { - treasury::QueryAnswer::Allowance { amount } => { - assert_eq!(amount, expected_allowance, "Treasury->Manager Allowance"); - } - _ => panic!("query failed"), - }; - - // Update manager - manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { - asset: token.address.to_string().clone(), - }) - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update SCRT Staking - adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { - asset: token.address.to_string().clone(), - }) - .test_exec(&scrt_staking, &mut app, admin.clone(), &[]) - .unwrap(); - - // Treasury reserves check - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { - asset: token.address.to_string().clone(), - }) - .test_query(&treasury, &app) - .unwrap()) - { - adapter::QueryAnswer::Reserves { amount } => { - assert_eq!(amount, pre_rewards.0, "Treasury Reserves"); - } - _ => panic!("Query Failed"), - }; - - // Manager reserves - match (manager::QueryMsg::Manager(manager::SubQueryMsg::Reserves { - asset: token.address.to_string().clone(), - holder: treasury.address.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap()) - { - adapter::QueryAnswer::Reserves { amount } => { - assert_eq!(amount, pre_rewards.1, "Manager Reserves"); - } - _ => panic!("Query Failed"), - }; - - // Scrt Staking reserves should be 0 (all staked) - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap()) - { - adapter::QueryAnswer::Reserves { amount } => { - assert_eq!(amount, Uint128::zero(), "SCRT Staking Reserves"); - } - _ => panic!("Query Failed"), - }; - - // Scrt Staking balance check - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap()) - { - adapter::QueryAnswer::Balance { amount } => { - assert_eq!(amount, pre_rewards.2, "SCRT Staking Balance"); - } - _ => panic!("Query Failed"), - }; - - // Add Rewards - app.sudo(SudoMsg::Staking(StakingSudo::AddRewards { - amount: Coin { - denom: "uscrt".to_string(), - amount: rewards, - }, - })) - .unwrap(); - - // Scrt Staking Balance - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap()) - { - adapter::QueryAnswer::Balance { amount } => { - println!("L352 scrt bal {}", amount); - assert_eq!( - amount, post_rewards.2, - "SCRT Staking Balance Post-Rewards Pre-update" - ); - } - _ => panic!("Query Failed"), - }; - - // Update SCRT Staking - adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { - asset: token.address.to_string().clone(), - }) - .test_exec(&scrt_staking, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update manager - manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { - asset: token.address.to_string().clone(), - }) - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update treasury - adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { - asset: token.address.to_string().clone(), - }) - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - app.sudo(SudoMsg::Staking(StakingSudo::FastForwardUndelegate {})) - .unwrap(); - - // Scrt Staking Balance - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap()) - { - adapter::QueryAnswer::Balance { amount } => { - println!("IN THE MIDDLE scrt bal {}", amount); - assert_eq!( - amount, post_rewards.2, - "SCRT Staking Balance Post-Rewards Pre-update" - ); - } - _ => panic!("Query Failed"), - }; - - // Update SCRT Staking - adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { - asset: token.address.to_string().clone(), - }) - .test_exec(&scrt_staking, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update manager - manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { - asset: token.address.to_string().clone(), - }) - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update treasury - adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { - asset: token.address.to_string().clone(), - }) - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Scrt Staking Balance - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap()) - { - adapter::QueryAnswer::Balance { amount } => { - println!("L397 scrt bal {}", amount); - assert_eq!( - amount, post_rewards.2, - "SCRT Staking Balance Post-Rewards Post-Update" - ); - } - _ => panic!("Query Failed"), - }; - - // Scrt Staking unbondable check - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbondable { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &mut app) - .unwrap()) - { - adapter::QueryAnswer::Unbondable { amount } => { - assert_eq!(amount, post_rewards.2, "Scrt Staking Unbondable"); - } - _ => panic!("Query Failed"), - }; - - // Manager unbondable check - match (manager::QueryMsg::Manager(manager::SubQueryMsg::Unbondable { - asset: token.address.to_string().clone(), - holder: treasury.address.to_string().clone(), - }) - .test_query(&manager, &mut app) - .unwrap()) - { - manager::QueryAnswer::Unbondable { amount } => { - assert_eq!( - amount, - post_rewards.1 + post_rewards.2, - "Manager Unbondable" - ); - } - _ => panic!("Query Failed"), - }; - - // Treasury unbondable check - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbondable { - asset: token.address.to_string().clone(), - }) - .test_query(&treasury, &mut app) - .unwrap()) - { - adapter::QueryAnswer::Unbondable { amount } => { - assert_eq!( - amount, - post_rewards.1 + post_rewards.2, - "Treasury Unbondable" - ); - } - _ => panic!("Query Failed"), - }; - - // Unbond all w/ treasury - adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Unbond { - amount: post_rewards.1 + post_rewards.2, - asset: token.address.to_string().clone(), - }) - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // scrt staking balance - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &mut app) - .unwrap()) - { - adapter::QueryAnswer::Balance { amount } => { - assert_eq!( - amount, - Uint128::zero(), - "Scrt Staking Balance Pre-fastforward" - ); - } - _ => panic!("Query Failed"), - }; - - // scrt staking unbonding - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbonding { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &mut app) - .unwrap()) - { - adapter::QueryAnswer::Unbonding { amount } => { - assert_eq!( - amount, post_rewards.2, - "Scrt Staking Unbonding Pre-fastforward" - ); - } - _ => panic!("Query Failed"), - }; - - // scrt staking claimable - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &mut app) - .unwrap()) - { - adapter::QueryAnswer::Claimable { amount } => { - assert_eq!( - amount, - Uint128::zero(), - "Scrt Staking Claimable Pre-fastforward" - ); - } - _ => panic!("Query Failed"), - }; - - // Manager Claimable - match (manager::QueryMsg::Manager(manager::SubQueryMsg::Claimable { - asset: token.address.to_string().clone(), - holder: treasury.address.to_string().clone(), - }) - .test_query(&manager, &mut app) - .unwrap()) - { - manager::QueryAnswer::Claimable { amount } => { - assert_eq!(amount, Uint128::zero(), "Manager Claimable Pre-fastforward"); - } - _ => panic!("Query Failed"), - }; - - // Manager Unbonding - match (manager::QueryMsg::Manager(manager::SubQueryMsg::Unbonding { - asset: token.address.to_string().clone(), - holder: treasury.address.to_string().clone(), - }) - .test_query(&manager, &mut app) - .unwrap()) - { - manager::QueryAnswer::Unbonding { amount } => { - assert_eq!(amount, post_rewards.2, "Manager Unbonding Pre-fastforward"); - } - _ => panic!("Query Failed"), - }; - - app.sudo(SudoMsg::Staking(StakingSudo::FastForwardUndelegate {})) - .unwrap(); - - // scrt staking unbonding - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbonding { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &mut app) - .unwrap()) - { - adapter::QueryAnswer::Unbonding { amount } => { - assert_eq!( - amount, - Uint128::zero(), - "Scrt Staking Unbonding Post-fastforward" - ); - } - _ => panic!("Query Failed"), - }; - - // scrt staking claimable - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &mut app) - .unwrap()) - { - adapter::QueryAnswer::Claimable { amount } => { - assert_eq!( - amount, post_rewards.2, - "Scrt Staking Claimable Post-fastforward" - ); - } - _ => panic!("Query Failed"), - }; - - /* - // Claim Treasury Manager - manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Claim { - asset: token.address.to_string().clone(), - }) - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - */ - - // Claim Treasury - adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Claim { - asset: token.address.to_string().clone(), - }) - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - /* - // Treasury reserves check - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { - asset: token.address.to_string().clone(), - }) - .test_query(&treasury, &mut app)) - .unwrap() - { - adapter::QueryAnswer::Reserves { amount } => { - assert_eq!(amount, deposit + rewards, "Treasury Reserves Post-Claim"); - } - _ => panic!("Bad Reserves Query Response"), - }; - */ - - /* - // Manager balance check - match (manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - holder: treasury.address.to_string().clone(), - }) - .test_query(&manager, &mut app) - .unwrap()) - { - manager::QueryAnswer::Balance { amount } => { - assert_eq!(amount, deposit + rewards, "Manager Balance Post Claim"); - } - _ => panic!("Query Failed"), - }; - */ - - // Scrt Staking balance - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &mut app) - .unwrap()) - { - adapter::QueryAnswer::Balance { amount } => { - assert_eq!(amount, Uint128::zero(), "SCRT Staking Balance Post Claim "); - } - _ => panic!("Query Failed"), - }; - - // Treasury balance check - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&treasury, &mut app) - .unwrap()) - { - adapter::QueryAnswer::Balance { amount } => { - assert_eq!(amount, deposit + rewards, "Treasury Balance Post Claim"); - } - _ => panic!("Query Failed"), - }; - - // Scrt Staking reserves - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &mut app) - .unwrap()) - { - adapter::QueryAnswer::Reserves { amount } => { - assert_eq!(amount, Uint128::zero(), "SCRT Staking Reserves Post Unbond"); - } - _ => panic!("Query Failed"), - }; - - // Manager unbonding check - match (manager::QueryMsg::Manager(manager::SubQueryMsg::Unbonding { - asset: token.address.to_string().clone(), - holder: treasury.address.to_string().clone(), - }) - .test_query(&manager, &mut app) - .unwrap()) - { - manager::QueryAnswer::Unbonding { amount } => { - assert_eq!(amount, Uint128::zero(), "Manager Unbonding Post-Claim"); - } - _ => panic!("Query Failed"), - }; - - // Manager balance check - match (manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - holder: treasury.address.to_string().clone(), - }) - .test_query(&manager, &mut app) - .unwrap()) - { - manager::QueryAnswer::Balance { amount } => { - assert_eq!(amount, Uint128::zero(), "Manager Balance Post-Claim"); - } - _ => panic!("Query Failed"), - }; - - // Manager reserves check - match (manager::QueryMsg::Manager(manager::SubQueryMsg::Reserves { - asset: token.address.to_string().clone(), - holder: treasury.address.to_string().clone(), - }) - .test_query(&manager, &mut app) - .unwrap()) - { - manager::QueryAnswer::Reserves { amount } => { - assert_eq!(amount, Uint128::zero(), "Manager Reserves Post-Unbond"); - } - _ => panic!("Query Failed"), - }; - - // Treasury reserves check - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&treasury, &mut app) - .unwrap()) - { - adapter::QueryAnswer::Balance { amount } => { - assert_eq!(amount, deposit + rewards, "Treasury Balance Post-Unbond"); - } - _ => panic!("Query Failed"), - }; - - // Treasury balance check - match (adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&treasury, &mut app) - .unwrap()) - { - adapter::QueryAnswer::Balance { amount } => { - assert_eq!(amount, deposit + rewards, "Treasury Balance Post-Unbond"); - } - _ => panic!("Query Failed"), - }; - - // Migration - println!("Setting migration runlevel"); - treasury::ExecuteMsg::SetRunLevel { - run_level: RunLevel::Migrating, - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - //Update - adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { - asset: token.address.to_string().clone(), - }) - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Check Metrics - match (treasury::QueryMsg::Metrics { - date: None, //Some(utc_from_timestamp(app.block_info().time).to_rfc3339()), - period: Period::Hour, - } - .test_query(&treasury, &app) - .unwrap()) - { - treasury::QueryAnswer::Metrics { metrics } => { - for m in metrics.clone() { - println!("{}", serde_json::to_string(&m).unwrap()); - } - } - _ => panic!("query failed"), - }; - - match (snip20::QueryMsg::Balance { - address: admin.to_string().clone(), - key: viewing_key.clone(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, deposit + rewards, "post-migration full unbond"); - } - _ => {} - }; -} - -macro_rules! single_asset_manager_scrt_staking_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - deposit, - allowance, - expected_allowance, - alloc_type, - alloc_amount, - rewards, - pre_rewards, - post_rewards, - ) = $value; - single_asset_manager_scrt_staking_integration( - deposit, - allowance, - expected_allowance, - alloc_type, - alloc_amount, - rewards, - pre_rewards, - post_rewards, - ); - } - )* - } -} - -single_asset_manager_scrt_staking_tests! { - single_asset_portion_manager_0: ( - Uint128::new(100), // deposit - Uint128::new(1 * 10u128.pow(18)), // manager allowance 100% - Uint128::new(100), // expected manager allowance - AllocationType::Portion, - Uint128::new(1 * 10u128.pow(18)), // allocate 100% - Uint128::new(100), // rewards - // pre-rewards - ( - Uint128::new(0), // treasury 10 - Uint128::new(0), // manager 0 - Uint128::new(100), // scrt_staking 90 - ), - //post-rewards - ( - Uint128::new(0), // treasury 10 - Uint128::new(0), // manager 0 - Uint128::new(200), // scrt_staking 90 - ), - ), - single_asset_portion_manager_1: ( - Uint128::new(1000), // deposit - Uint128::new(5 * 10u128.pow(17)), // %50 manager allowance - Uint128::new(500), // expected manager allowance - AllocationType::Portion, - Uint128::new(1 * 10u128.pow(18)), // 100% allocate - Uint128::new(10), // rewards - ( - Uint128::new(500), // treasury 55 (manager won't pull unused allowance - Uint128::new(0), // manager 0 - Uint128::new(500), // scrt_staking - ), - ( - Uint128::new(500), - Uint128::new(0), - Uint128::new(510), - ), - ), -} -*/ diff --git a/contracts/dao/treasury/tests/integration/tolerance.rs b/contracts/dao/treasury/tests/integration/tolerance.rs deleted file mode 100644 index 94cbc06..0000000 --- a/contracts/dao/treasury/tests/integration/tolerance.rs +++ /dev/null @@ -1,436 +0,0 @@ -use shade_multi_test::multi::{admin::init_admin_auth, snip20::Snip20, treasury::Treasury}; -use shade_protocol::{ - c_std::{to_binary, Addr, Uint128}, - contract_interfaces::{ - dao::{treasury, treasury::AllowanceType}, - snip20, - }, - multi_test::App, - utils::{cycle::Cycle, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -fn underfunded_tolerance( - deposit: Uint128, - added: Uint128, - tolerance: Uint128, - - allowance: Uint128, - allow_type: AllowanceType, - - expected: Uint128, -) { - let mut app = App::default(); - - let admin = Addr::unchecked("admin"); - let spender = Addr::unchecked("spender"); - let _user = Addr::unchecked("user"); - //let validator = Addr::unchecked("validator"); - let admin_auth = init_admin_auth(&mut app, &admin); - - let viewing_key = "viewing_key".to_string(); - - let token = snip20::InstantiateMsg { - name: "token".into(), - admin: Some("admin".into()), - symbol: "TKN".into(), - decimals: 6, - initial_balances: Some(vec![snip20::InitialBalance { - address: admin.to_string().clone(), - amount: deposit + added, - }]), - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - query_auth: None, - } - .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) - .unwrap(); - - let treasury = treasury::InstantiateMsg { - admin_auth: admin_auth.clone().into(), - viewing_key: viewing_key.clone(), - multisig: admin.to_string().clone(), - } - .test_init(Treasury::default(), &mut app, admin.clone(), "treasury", &[ - ]) - .unwrap(); - - // Set admin viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Register treasury assets - treasury::ExecuteMsg::RegisterAsset { - contract: token.clone().into(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // treasury allowance to spender - treasury::ExecuteMsg::Allowance { - asset: token.address.to_string().clone(), - allowance: treasury::RawAllowance { - //nick: "Mid-Stakes-Manager".to_string(), - spender: spender.clone().to_string(), - allowance_type: allow_type, - cycle: Cycle::Constant, - amount: allowance, - // 100% (adapter balance will 2x before unbond) - tolerance, - }, - refresh_now: true, - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Deposit funds into treasury - snip20::ExecuteMsg::Send { - recipient: treasury.address.to_string().clone(), - recipient_code_hash: None, - amount: deposit, - msg: None, - memo: None, - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update treasury - treasury::ExecuteMsg::Update { - asset: token.address.to_string().clone(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Check treasury allowance - match (treasury::QueryMsg::Allowance { - asset: token.address.to_string().clone(), - spender: spender.to_string().clone(), - } - .test_query(&treasury, &app) - .unwrap()) - { - treasury::QueryAnswer::Allowance { amount } => { - assert_eq!(amount, deposit, "Initial Treasury->Manager Allowance"); - } - _ => panic!("query failed"), - }; - - // Additional funds into treasury - snip20::ExecuteMsg::Send { - recipient: treasury.address.to_string().clone(), - recipient_code_hash: None, - amount: added, - msg: None, - memo: None, - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update treasury - treasury::ExecuteMsg::Update { - asset: token.address.to_string().clone(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Check treasury allowance - match (treasury::QueryMsg::Allowance { - asset: token.address.to_string().clone(), - spender: spender.to_string().clone(), - } - .test_query(&treasury, &app) - .unwrap()) - { - treasury::QueryAnswer::Allowance { amount } => { - assert_eq!(amount, expected, "Final Treasury->Manager Allowance"); - } - _ => panic!("query failed"), - }; -} - -macro_rules! underfunded_tolerance_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - deposit, - added, - tolerance, - allowance, - allow_type, - expected, - ) = $value; - underfunded_tolerance( - deposit, - added, - tolerance, - allowance, - allow_type, - expected, - ); - } - )* - } -} - -underfunded_tolerance_tests! { - portion_tolerance_90_no_increase: ( - Uint128::new(100), // deposit - Uint128::new(50), // added - Uint128::new(9 * 10u128.pow(17)), // tolerance - Uint128::new(1 * 10u128.pow(18)), // allowance - AllowanceType::Portion, - Uint128::new(100), // expected - ), - portion_tolerance_90_will_increase: ( - Uint128::new(100), // deposit - Uint128::new(1000), // added - Uint128::new(9 * 10u128.pow(17)), // tolerance - Uint128::new(1 * 10u128.pow(18)), // allowance - AllowanceType::Portion, - Uint128::new(1100), // expected - ), - amount_tolerance_10_no_increase: ( - Uint128::new(500), // deposit - Uint128::new(20), // added - Uint128::new(9 * 10u128.pow(17)), // tolerance - Uint128::new(520), //allowance - AllowanceType::Amount, - Uint128::new(500), // expected - ), - amount_tolerance_10_will_increase: ( - Uint128::new(500), // deposit - Uint128::new(1000), // added - Uint128::new(1 * 10u128.pow(17)), // tolerance - Uint128::new(1500), //allowance - AllowanceType::Amount, - Uint128::new(1500), // expected - ), -} - -fn overfunded_tolerance( - deposit: Uint128, - tolerance: Uint128, - allowance: Uint128, - reduced: Uint128, - allow_type: AllowanceType, - expected: Uint128, -) { - let mut app = App::default(); - - let admin = Addr::unchecked("admin"); - let spender = Addr::unchecked("spender"); - let _user = Addr::unchecked("user"); - //let validator = Addr::unchecked("validator"); - let admin_auth = init_admin_auth(&mut app, &admin); - - let viewing_key = "viewing_key".to_string(); - - let token = snip20::InstantiateMsg { - name: "token".into(), - admin: Some("admin".into()), - symbol: "TKN".into(), - decimals: 6, - initial_balances: Some(vec![snip20::InitialBalance { - address: admin.to_string().clone(), - amount: deposit, - }]), - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - query_auth: None, - } - .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) - .unwrap(); - - let treasury = treasury::InstantiateMsg { - admin_auth: admin_auth.clone().into(), - viewing_key: viewing_key.clone(), - multisig: admin.to_string().clone(), - } - .test_init(Treasury::default(), &mut app, admin.clone(), "treasury", &[ - ]) - .unwrap(); - - // Set admin viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Register treasury assets - treasury::ExecuteMsg::RegisterAsset { - contract: token.clone().into(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // treasury allowance to spender - treasury::ExecuteMsg::Allowance { - asset: token.address.to_string().clone(), - allowance: treasury::RawAllowance { - //nick: "Mid-Stakes-Manager".to_string(), - spender: spender.clone().to_string(), - allowance_type: allow_type.clone(), - cycle: Cycle::Constant, - amount: allowance, - // 100% (adapter balance will 2x before unbond) - tolerance, - }, - refresh_now: true, - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Deposit funds into treasury - snip20::ExecuteMsg::Send { - recipient: treasury.address.to_string().clone(), - recipient_code_hash: None, - amount: deposit, - msg: None, - memo: None, - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update treasury - treasury::ExecuteMsg::Update { - asset: token.address.to_string().clone(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Check treasury allowance - match (treasury::QueryMsg::Allowance { - asset: token.address.to_string().clone(), - spender: spender.to_string().clone(), - } - .test_query(&treasury, &app) - .unwrap()) - { - treasury::QueryAnswer::Allowance { amount } => { - println!("INITIAL {}", amount); - assert_eq!(amount, deposit, "Initial Treasury->Manager Allowance"); - } - _ => panic!("query failed"), - }; - - // Reduce allowance to simulate overfunding - treasury::ExecuteMsg::Allowance { - asset: token.address.to_string().clone(), - allowance: treasury::RawAllowance { - spender: spender.clone().to_string(), - allowance_type: allow_type, - cycle: Cycle::Constant, - amount: reduced, - tolerance, - }, - refresh_now: true, - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update treasury - treasury::ExecuteMsg::Update { - asset: token.address.to_string().clone(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - // Check treasury allowance - match (treasury::QueryMsg::Allowance { - asset: token.address.to_string().clone(), - spender: spender.to_string().clone(), - } - .test_query(&treasury, &app) - .unwrap()) - { - treasury::QueryAnswer::Allowance { amount } => { - assert_eq!(amount, expected, "Final Treasury->Manager Allowance"); - } - _ => panic!("query failed"), - }; -} - -macro_rules! overfunded_tolerance_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - deposit, - tolerance, - allowance, - reduced, - allow_type, - expected, - ) = $value; - overfunded_tolerance( - deposit, - tolerance, - allowance, - reduced, - allow_type, - expected, - ); - } - )* - } -} - -overfunded_tolerance_tests! { - portion_tolerance_10_no_decrease: ( - Uint128::new(1000), // deposit - Uint128::new(1 * 10u128.pow(17)), // tolerance - Uint128::new(1 * 10u128.pow(18)), // allowance - Uint128::new(99 * 10u128.pow(16)), // reduced_allowance - AllowanceType::Portion, - Uint128::new(1000), // expected - ), - portion_tolerance_10_will_decrease: ( - Uint128::new(1000), // deposit - Uint128::new(1 * 10u128.pow(17)), // tolerance - Uint128::new(1 * 10u128.pow(18)), // allowance - Uint128::new(5 * 10u128.pow(17)), // reduced_allowance - AllowanceType::Portion, - Uint128::new(500), // expected - ), - amount_tolerance_10_no_decrease: ( - Uint128::new(500), // deposit - Uint128::new(1 * 10u128.pow(17)), // tolerance - Uint128::new(500), //allowance - Uint128::new(460), // reduced allowance - AllowanceType::Amount, - Uint128::new(500), // expected - ), - amount_tolerance_10_will_decrease: ( - Uint128::new(500), // deposit - Uint128::new(1 * 10u128.pow(17)), // tolerance - Uint128::new(500), //allowance - Uint128::new(400), // reduced allowance - AllowanceType::Amount, - Uint128::new(400), // expected - ), -} diff --git a/contracts/dao/treasury/tests/integration/treasury.rs b/contracts/dao/treasury/tests/integration/treasury.rs deleted file mode 100644 index 7266862..0000000 --- a/contracts/dao/treasury/tests/integration/treasury.rs +++ /dev/null @@ -1,557 +0,0 @@ -use mock_adapter; -use shade_multi_test::{ - interfaces::{ - self, - utils::{DeployedContracts, SupportedContracts}, - }, - multi::{ - admin::init_admin_auth, - mock_adapter::MockAdapter, - snip20::Snip20, - treasury::Treasury, - treasury_manager::TreasuryManager, - }, -}; -use shade_protocol::{ - c_std::{to_binary, Addr, Coin, Uint128}, - contract_interfaces::{ - dao::{ - treasury, - treasury::{AllowanceType, RunLevel}, - treasury_manager::{self, Allocation, AllocationType}, - }, - snip20, - }, - multi_test::{App, BankSudo, StakingSudo, SudoMsg}, - utils::{ - asset::Contract, - cycle::Cycle, - ExecuteCallback, - InstantiateCallback, - MultiTestable, - Query, - }, -}; - -// Add other adapters here as they come -fn bonded_adapter_int( - deposit: Uint128, - allowance: Uint128, - expected_allowance: Uint128, - alloc_type: AllocationType, - alloc_amount: Uint128, - rewards: Uint128, - // expected balances - pre_rewards: (Uint128, Uint128, Uint128), - post_rewards: (Uint128, Uint128, Uint128), -) { - let mut app = App::default(); - let mut contracts = DeployedContracts::new(); - - let admin = Addr::unchecked("admin"); - let admin_auth = init_admin_auth(&mut app, &admin); - - let viewing_key = "viewing_key".to_string(); - let symbol = "TKN"; - - interfaces::dao::init_dao( - &mut app, - &admin.to_string(), - &mut contracts, - deposit, - symbol, - vec![AllowanceType::Portion], - vec![Cycle::Constant], - vec![allowance], - vec![Uint128::zero()], - vec![vec![alloc_type]], - vec![vec![alloc_amount]], - vec![vec![Uint128::zero()]], - false, - false, - ); - - // Update treasury - interfaces::treasury::update_exec(&mut app, &admin.to_string(), &contracts, symbol).unwrap(); - - // Check initial allowance - assert_eq!( - interfaces::treasury::allowance_query( - &app, - &contracts, - symbol, - SupportedContracts::TreasuryManager(0), - ) - .unwrap(), - expected_allowance, - "Treasury->Manager Allowance" - ); - - // Update manager - interfaces::treasury_manager::update_exec( - &mut app, - &admin.to_string(), - &contracts, - symbol, - SupportedContracts::TreasuryManager(0), - ) - .unwrap(); - - // Update Adapter - interfaces::dao::update_exec( - &mut app, - &admin.to_string(), - &contracts, - symbol, - SupportedContracts::MockAdapter(0), - ) - .unwrap(); - - // Treasury reserves check - assert_eq!( - interfaces::treasury::reserves_query(&app, &contracts, symbol).unwrap(), - pre_rewards.0, - "Treasury Reserves", - ); - - // Manager reserves - assert_eq!( - interfaces::treasury_manager::reserves_query( - &app, - &contracts, - symbol, - SupportedContracts::TreasuryManager(0), - SupportedContracts::Treasury, - ) - .unwrap(), - pre_rewards.1, - "Manager Reserves", - ); - - // Adapter reserves should be 0 (all staked) - assert_eq!( - interfaces::dao::reserves_query( - &app, - &contracts, - symbol, - SupportedContracts::MockAdapter(0) - ) - .unwrap(), - Uint128::zero(), - "Bonded Adapter Reserves", - ); - - // Adapter balance - assert_eq!( - interfaces::dao::balance_query( - &app, - &contracts, - symbol, - SupportedContracts::MockAdapter(0) - ) - .unwrap(), - pre_rewards.2, - "Adapter Balance", - ); - - // Add Rewards - interfaces::snip20::send_exec( - &mut app, - &admin.to_string(), - &contracts, - symbol, - contracts - .get(&SupportedContracts::MockAdapter(0)) - .unwrap() - .address - .to_string(), - rewards, - None, - ) - .unwrap(); - - // Adapter Balance - assert_eq!( - interfaces::dao::balance_query( - &app, - &contracts, - symbol, - SupportedContracts::MockAdapter(0) - ) - .unwrap(), - pre_rewards.2 + rewards, - "Adapter Balance Post-Rewards Pre-Update", - ); - - // Update manager - interfaces::treasury_manager::update_exec( - &mut app, - &admin.to_string(), - &contracts, - symbol, - SupportedContracts::TreasuryManager(0), - ) - .unwrap(); - - // Update treasury - interfaces::treasury::update_exec(&mut app, &admin.to_string(), &contracts, symbol).unwrap(); - - // Adapter Balance - assert_eq!( - interfaces::dao::balance_query( - &app, - &contracts, - symbol, - SupportedContracts::MockAdapter(0) - ) - .unwrap(), - pre_rewards.2 + rewards, - "Adapter Balance Post-Rewards Post-Update" - ); - - // Adapter Unbondable - assert_eq!( - interfaces::dao::unbondable_query( - &app, - &contracts, - symbol, - SupportedContracts::MockAdapter(0) - ) - .unwrap(), - post_rewards.2, - "Adapter Unbondable", - ); - - // Manager unbondable check - assert_eq!( - interfaces::treasury_manager::unbondable_query( - &app, - &contracts, - symbol, - SupportedContracts::TreasuryManager(0), - SupportedContracts::Treasury, - ) - .unwrap(), - post_rewards.1 + post_rewards.2, - "Manager Unbondable" - ); - - // Unbond all w/ manager - interfaces::treasury_manager::unbond_exec( - &mut app, - &admin.to_string(), - &contracts, - symbol, - SupportedContracts::TreasuryManager(0), - post_rewards.1 + post_rewards.2, - ) - .unwrap(); - - // Adapter Reserves - assert_eq!( - interfaces::dao::reserves_query( - &app, - &contracts, - symbol, - SupportedContracts::MockAdapter(0) - ) - .unwrap(), - Uint128::zero(), - "Adapter Reserves Pre-fastforward" - ); - - // Adapter Unbonding - assert_eq!( - interfaces::dao::unbonding_query( - &app, - &contracts, - symbol, - SupportedContracts::MockAdapter(0) - ) - .unwrap(), - pre_rewards.2 + rewards, - "Adapter Unbonding Pre-fastforward" - ); - - // Adapter Claimable - assert_eq!( - interfaces::dao::claimable_query( - &app, - &contracts, - symbol, - SupportedContracts::MockAdapter(0) - ) - .unwrap(), - Uint128::zero(), - "Adapter Claimable Pre-fastforward", - ); - - // Manager Claimable - assert_eq!( - interfaces::treasury_manager::claimable_query( - &app, - &contracts, - symbol, - SupportedContracts::TreasuryManager(0), - SupportedContracts::Treasury, - ) - .unwrap(), - Uint128::zero(), - "Manager Claimable Pre-fastforward" - ); - - // Manager Unbonding - assert_eq!( - interfaces::treasury_manager::unbonding_query( - &app, - &contracts, - symbol, - SupportedContracts::TreasuryManager(0), - SupportedContracts::Treasury, - ) - .unwrap(), - pre_rewards.2 + rewards, - "Manager Claimable Pre-fastforward" - ); - - // Complete unbondings - interfaces::dao::mock_adapter_complete_unbonding( - &mut app, - &admin.to_string(), - &contracts, - SupportedContracts::MockAdapter(0), - ) - .unwrap(); - - // adapter unbonding - assert_eq!( - interfaces::dao::unbonding_query( - &app, - &contracts, - symbol, - SupportedContracts::MockAdapter(0) - ) - .unwrap(), - Uint128::zero(), - "Adapter Unbonding Post-fastforward" - ); - - // adapter claimable - assert_eq!( - interfaces::dao::claimable_query( - &app, - &contracts, - symbol, - SupportedContracts::MockAdapter(0) - ) - .unwrap(), - pre_rewards.2 + rewards, - "Adapter Claimable Post-fastforward" - ); - - // Claim Treasury Manager - interfaces::treasury_manager::claim_exec( - &mut app, - &admin.to_string(), - &contracts, - symbol, - SupportedContracts::TreasuryManager(0), - ) - .unwrap(); - - // Adapter balance - assert_eq!( - interfaces::dao::balance_query( - &app, - &contracts, - symbol, - SupportedContracts::MockAdapter(0) - ) - .unwrap(), - Uint128::zero(), - "Adapter Balance Post-Claim" - ); - - // Treasury balance check - assert_eq!( - interfaces::treasury::balance_query(&app, &contracts, symbol,).unwrap(), - deposit + rewards, - "Treasury Balance Post Claim" - ); - - // Adapter reserves - assert_eq!( - interfaces::dao::reserves_query( - &app, - &contracts, - symbol, - SupportedContracts::MockAdapter(0) - ) - .unwrap(), - Uint128::zero(), - "Adapter Reserves Post-Claim" - ); - - // Manager unbonding check - assert_eq!( - interfaces::treasury_manager::reserves_query( - &app, - &contracts, - symbol, - SupportedContracts::TreasuryManager(0), - SupportedContracts::Treasury, - ) - .unwrap(), - Uint128::zero(), - "Manager Unbonding Post-Claim" - ); - - // Manager balance check - assert_eq!( - interfaces::treasury_manager::balance_query( - &app, - &contracts, - symbol, - SupportedContracts::TreasuryManager(0), - SupportedContracts::Treasury, - ) - .unwrap(), - Uint128::zero(), - "Manager Balance Post-Claim" - ); - - // Manager reserves check - assert_eq!( - interfaces::treasury_manager::reserves_query( - &app, - &contracts, - symbol, - SupportedContracts::TreasuryManager(0), - SupportedContracts::Treasury, - ) - .unwrap(), - Uint128::zero(), - "Manager Reserves Post-Unbond" - ); - - // Treasury reserves check - assert_eq!( - interfaces::treasury::reserves_query(&app, &contracts, symbol,).unwrap(), - deposit + rewards, - "Treasury Reserves Post-Unbond" - ); - assert_eq!( - interfaces::treasury::balance_query(&app, &contracts, symbol,).unwrap(), - deposit + rewards, - "Treasury Balance Post-Unbond" - ); - - // Migration - interfaces::treasury::set_run_level_exec( - &mut app, - &admin.to_string(), - &contracts, - RunLevel::Migrating, - ) - .unwrap(); - - interfaces::treasury::update_exec(&mut app, &admin.to_string(), &contracts, symbol).unwrap(); - - assert_eq!( - interfaces::treasury::balance_query(&app, &contracts, symbol,).unwrap(), - Uint128::zero(), - "post-migration full unbond" - ); -} - -macro_rules! bonded_adapter_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - deposit, - allowance, - expected_allowance, - alloc_type, - alloc_amount, - rewards, - pre_rewards, - post_rewards, - ) = $value; - bonded_adapter_int( - deposit, - allowance, - expected_allowance, - alloc_type, - alloc_amount, - rewards, - pre_rewards, - post_rewards, - ); - } - )* - } -} - -bonded_adapter_tests! { - portion_with_rewards_0: ( - Uint128::new(100), // deposit - Uint128::new(1 * 10u128.pow(18)), // manager allowance 100% - Uint128::new(100), // expected manager allowance - AllocationType::Portion, - Uint128::new(1 * 10u128.pow(18)), // allocate 100% - Uint128::new(100), // rewards - // pre-rewards - ( - Uint128::new(0), // treasury 10 - Uint128::new(0), // manager 0 - Uint128::new(100), // mock_adapter 90 - ), - //post-rewards - ( - Uint128::new(0), // treasury 10 - Uint128::new(0), // manager 0 - Uint128::new(200), // mock_adapter 90 - ), - ), - portion_with_rewards_1: ( - Uint128::new(1000), // deposit - Uint128::new(5 * 10u128.pow(17)), // %50 manager allowance - Uint128::new(500), // expected manager allowance - AllocationType::Portion, - Uint128::new(1 * 10u128.pow(18)), // 100% allocate - Uint128::new(10), // rewards - ( - Uint128::new(500), // treasury 55 (manager won't pull unused allowance - Uint128::new(0), // manager 0 - Uint128::new(500), // mock_adapter - ), - ( - Uint128::new(505), - Uint128::new(0), - Uint128::new(505), - ), - ), - /* - // TODO: this needs separate test logic bc of update - amount_with_rewards_0: ( - Uint128::new(1_000_000), // deposit - Uint128::new(5 * 10u128.pow(17)), // %50 manager allowance - Uint128::new(500_000), // expected manager allowance - AllocationType::Amount, - Uint128::new(500_000), // .5 tkn (all) allocate - Uint128::new(500), // rewards - ( - Uint128::new(500_000), // treasury - Uint128::new(0), // manager 0 - Uint128::new(500_000), // mock_adapter - ), - ( - Uint128::new(500_250), - Uint128::new(250), - Uint128::new(500_000), - ), - ), - */ -} diff --git a/contracts/dao/treasury/tests/integration/wrap.rs b/contracts/dao/treasury/tests/integration/wrap.rs deleted file mode 100644 index c591fab..0000000 --- a/contracts/dao/treasury/tests/integration/wrap.rs +++ /dev/null @@ -1,145 +0,0 @@ -use shade_multi_test::multi::{admin::init_admin_auth, snip20::Snip20, treasury::Treasury}; -use shade_protocol::{ - c_std::{from_binary, to_binary, Addr, Coin, Uint128}, - contract_interfaces::{dao::treasury, snip20}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -// Add other adapters here as they come -fn wrap_coins_test(coins: Vec) { - let mut app = App::default(); - - let admin = Addr::unchecked("admin"); - let _user = Addr::unchecked("user"); - //let validator = Addr::unchecked("validator"); - let admin_auth = init_admin_auth(&mut app, &admin); - - let viewing_key = "viewing_key".to_string(); - - let mut tokens = vec![]; - - let fail_coin = Coin { - denom: "fail".into(), - amount: Uint128::new(100), - }; - - for coin in coins.clone() { - let token = snip20::InstantiateMsg { - name: coin.denom.clone(), - admin: Some("admin".into()), - symbol: coin.denom.to_uppercase().clone(), - decimals: 6, - initial_balances: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - query_auth: None, - } - .test_init(Snip20::default(), &mut app, admin.clone(), &coin.denom, &[]) - .unwrap(); - - tokens.push(token); - } - - let treasury = treasury::InstantiateMsg { - admin_auth: admin_auth.clone().into(), - viewing_key: viewing_key.clone(), - multisig: admin.to_string().clone(), - } - .test_init(Treasury::default(), &mut app, admin.clone(), "treasury", &[ - ]) - .unwrap(); - - /* - // Set admin viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - */ - - // Register treasury assets - for (token, coin) in tokens.iter().zip(coins.clone().iter()) { - treasury::ExecuteMsg::RegisterAsset { - contract: token.clone().into(), - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - treasury::ExecuteMsg::RegisterWrap { - denom: coin.denom.clone(), - contract: RawContract { - address: token.address.clone().into(), - code_hash: token.code_hash.clone(), - }, - } - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - } - - let mut all_coins = coins.clone(); - all_coins.push(fail_coin.clone()); - - app.init_modules(|router, _, storage| { - router - .bank - .init_balance(storage, &treasury.address.clone(), all_coins.clone()) - .unwrap(); - }); - - // Wrap - let wrap_resp = treasury::ExecuteMsg::WrapCoins {} - .test_exec(&treasury, &mut app, admin.clone(), &[]) - .unwrap(); - - match from_binary(&wrap_resp.data.unwrap()).ok().unwrap() { - treasury::ExecuteAnswer::WrapCoins { success, failed } => { - assert!(success == coins, "All coins succeed"); - assert!(failed == vec![fail_coin], "Unconfigured coin fails"); - } - _ => { - panic!("WrapCoins bad response"); - } - } - - // Treasury Balances - for (token, coin) in tokens.iter().zip(coins.iter()) { - match (treasury::QueryMsg::Balance { - asset: token.address.to_string().clone(), - } - .test_query(&treasury, &app) - .unwrap()) - { - treasury::QueryAnswer::Balance { amount } => { - assert_eq!(amount, coin.amount, "Treasury Balance"); - } - _ => panic!("Balance query Failed"), - }; - } -} - -macro_rules! wrap_coins_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let coins = $value; - wrap_coins_test(coins); - } - )* - } -} - -wrap_coins_tests! { - wrap_sscrt: vec![Coin { denom: "uscrt".into(), amount: Uint128::new(100) }], - //wrap_other: vec![Coin { denom: "other".into(), amount: Uint128::new(100) }], -} diff --git a/contracts/dao/treasury/tests/mod.rs b/contracts/dao/treasury/tests/mod.rs deleted file mode 100644 index e6cb40c..0000000 --- a/contracts/dao/treasury/tests/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod dao; -pub mod integration; diff --git a/contracts/dao/treasury_manager/.cargo/config b/contracts/dao/treasury_manager/.cargo/config deleted file mode 100644 index 882fe08..0000000 --- a/contracts/dao/treasury_manager/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[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/dao/treasury_manager/.circleci/config.yml b/contracts/dao/treasury_manager/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/contracts/dao/treasury_manager/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/dao/treasury_manager/Cargo.toml b/contracts/dao/treasury_manager/Cargo.toml deleted file mode 100644 index 27640b1..0000000 --- a/contracts/dao/treasury_manager/Cargo.toml +++ /dev/null @@ -1,48 +0,0 @@ -[package] -name = "treasury_manager" -version = "0.1.0" -authors = ["Jack Swenson ", "Jack Sisson ./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 - -# 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/dao/treasury_manager/README.md b/contracts/dao/treasury_manager/README.md deleted file mode 100644 index 70ec594..0000000 --- a/contracts/dao/treasury_manager/README.md +++ /dev/null @@ -1,142 +0,0 @@ -# Treasury Contract -* [Introduction](#Introduction) -* [Sections](#Sections) - * [DAO Adapter](/packages/shade_protocol/src/DAO_ADAPTER.md) - * [Init](#Init) - * [Interface](#Interface) - * Messages - * [UpdateConfig](#UpdateConfig) - * [RegisterAsset](#RegisterAsset) - * [Allocate](#Allocate) - * Queries - * [Config](#Config) - * [Assets](#Assets) - * [PendingAllowance](#PendingAllowance) -# Introduction -The treasury contract holds network funds from things such as mint commission and pending airdrop funds - -# Sections - -## Init -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|admin | Addr| Admin address -|viewing_key | String | Key set on relevant SNIP-20's -|treasury | Addr | treasury that is owner of funds - -## Interface - -### Messages -#### UpdateConfig -Updates the given values -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|config | Config | New contract config -##### Response -```json -{ - "update_config": { - "status": "success" - } -} -``` - -#### RegisterAsset -Registers a supported asset. The asset must be SNIP-20 compliant since [RegisterReceive](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md#RegisterReceive) is called. - -Note: Will return an error if there's an asset with that address already registered. -##### Request -|Name |Type |Description | optional | -|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| -|contract | Contract | Type explained [here](#Contract) | no | -##### Response -```json -{ - "register_asset": { - "status": "success" - } -} -``` - -#### Allocate -Registers a supported asset. The asset must be SNIP-20 compliant since [RegisterReceive](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md#RegisterReceive) is called. - -Note: Will return an error if there's an asset with that address already registered. -##### Request -|Name |Type |Description | optional | -|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| -|asset | Addr | Desired SNIP-20 -|allocation | Allocation | Allocation data -##### Response -```json -{ - "allocate": { - "status": "success" - } -} -``` - -### Queries - -#### Config -Gets the contract's configuration variables -##### Response -```json -{ - "config": { - "config": { .. } - } -} -``` - -#### Assets -Get the list of registered assets -##### Response -```json -{ - "assets": { - "assets": ["asset address", ..], - } -} -``` - -#### Allocations -Get the allocations for a given asset - -##### Request -|Name |Type |Description | optional | -|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| -|asset | Addr | Address of desired SNIP-20 asset - -##### Response -```json -{ - "allocations": { - "allocations": [ - { - "allocation": {}, - }, - .. - ], - } -} -``` - -#### PendingAllowance -Get the pending allowance for a given asset - -##### Request -|Name |Type |Description | optional | -|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| -|asset | Addr | Address of desired SNIP-20 asset - -##### Response -```json -{ - "pending_allowance": { - "amount": "100000", - } -} -``` diff --git a/contracts/dao/treasury_manager/src/contract.rs b/contracts/dao/treasury_manager/src/contract.rs deleted file mode 100644 index f757a70..0000000 --- a/contracts/dao/treasury_manager/src/contract.rs +++ /dev/null @@ -1,160 +0,0 @@ -use crate::{execute, query, storage::*}; -use shade_protocol::{ - c_std::{ - shd_entry_point, - to_binary, - Binary, - Deps, - DepsMut, - Env, - MessageInfo, - Response, - StdResult, - }, - dao::{ - manager, - treasury_manager::{Config, ExecuteMsg, Holding, InstantiateMsg, QueryMsg, Status}, - }, -}; - -#[shd_entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - let treasury = deps.api.addr_validate(msg.treasury.as_str())?; - - CONFIG.save(deps.storage, &Config { - admin_auth: msg.admin_auth.into_valid(deps.api)?, - treasury: treasury.clone(), - })?; - - VIEWING_KEY.save(deps.storage, &msg.viewing_key)?; - ASSET_LIST.save(deps.storage, &Vec::new())?; - HOLDERS.save(deps.storage, &vec![treasury.clone()])?; - HOLDING.save(deps.storage, treasury, &Holding { - balances: vec![], - unbondings: vec![], - status: Status::Active, - })?; - - Ok(Response::new()) -} - -#[shd_entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - ExecuteMsg::Receive { - sender, - from, - amount, - msg, - .. - } => { - let sender = deps.api.addr_validate(&sender)?; - let from = deps.api.addr_validate(&from)?; - execute::receive(deps, env, info, sender, from, amount, msg) - } - ExecuteMsg::UpdateConfig { - admin_auth, - treasury, - } => execute::update_config(deps, env, info, admin_auth, treasury), - ExecuteMsg::RegisterAsset { contract } => { - let contract = contract.into_valid(deps.api)?; - execute::register_asset(deps, &env, info, &contract) - } - ExecuteMsg::Allocate { asset, allocation } => { - let asset = deps.api.addr_validate(&asset)?; - let allocation = allocation.valid(deps.api)?; - execute::allocate(deps, &env, info, asset, allocation) - } - ExecuteMsg::AddHolder { holder } => { - let holder = deps.api.addr_validate(&holder)?; - execute::add_holder(deps, &env, info, holder) - } - ExecuteMsg::RemoveHolder { holder } => { - let holder = deps.api.addr_validate(&holder)?; - execute::remove_holder(deps, &env, info, holder) - } - ExecuteMsg::Manager(a) => match a { - manager::SubExecuteMsg::Unbond { asset, amount } => { - let asset = deps.api.addr_validate(&asset)?; - execute::unbond(deps, &env, info, asset, amount) - } - manager::SubExecuteMsg::Claim { asset } => { - let asset = deps.api.addr_validate(&asset)?; - execute::claim(deps, &env, info, asset) - } - manager::SubExecuteMsg::Update { asset } => { - let asset = deps.api.addr_validate(&asset)?; - execute::update(deps, &env, info, asset) - } - }, - } -} - -#[shd_entry_point] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query::config(deps)?), - QueryMsg::Assets {} => to_binary(&query::assets(deps)?), - QueryMsg::Allocations { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::allocations(deps, asset)?) - } - QueryMsg::PendingAllowance { asset } => { - let asset = deps.api.addr_validate(&asset)?; - to_binary(&query::pending_allowance(deps, env, asset)?) - } - QueryMsg::Holders {} => to_binary(&query::holders(deps)?), - QueryMsg::Holding { holder } => { - let holder = deps.api.addr_validate(&holder)?; - to_binary(&query::holding(deps, holder)?) - } - QueryMsg::Metrics { - date, - epoch, - period, - } => to_binary(&query::metrics(deps, env, date, epoch, period)?), - - QueryMsg::Manager(a) => match a { - manager::SubQueryMsg::Balance { asset, holder } => { - let asset = deps.api.addr_validate(&asset)?; - let holder = deps.api.addr_validate(&holder)?; - to_binary(&query::balance(deps, asset, holder)?) - } - manager::SubQueryMsg::BatchBalance { assets, holder } => { - let mut val_assets = vec![]; - - for a in assets { - val_assets.push(deps.api.addr_validate(&a)?); - } - let holder = deps.api.addr_validate(&holder)?; - - to_binary(&query::batch_balance(deps, val_assets, holder)?) - } - manager::SubQueryMsg::Unbonding { asset, holder } => { - let asset = deps.api.addr_validate(&asset)?; - let holder = deps.api.addr_validate(&holder)?; - to_binary(&query::unbonding(deps, asset, holder)?) - } - manager::SubQueryMsg::Unbondable { asset, holder } => { - let asset = deps.api.addr_validate(&asset)?; - let holder = deps.api.addr_validate(&holder)?; - to_binary(&query::unbondable(deps, env, asset, holder)?) - } - manager::SubQueryMsg::Claimable { asset, holder } => { - let asset = deps.api.addr_validate(&asset)?; - let holder = deps.api.addr_validate(&holder)?; - to_binary(&query::claimable(deps, env, asset, holder)?) - } - manager::SubQueryMsg::Reserves { asset, holder } => { - let asset = deps.api.addr_validate(&asset)?; - let holder = deps.api.addr_validate(&holder)?; - to_binary(&query::reserves(deps, env, asset, holder)?) - } - }, - } -} diff --git a/contracts/dao/treasury_manager/src/execute.rs b/contracts/dao/treasury_manager/src/execute.rs deleted file mode 100644 index ecc9399..0000000 --- a/contracts/dao/treasury_manager/src/execute.rs +++ /dev/null @@ -1,1446 +0,0 @@ -use crate::storage::*; -use itertools::{Either, Itertools}; -use shade_protocol::{ - admin::helpers::{validate_admin, AdminPermissions}, - c_std::{ - to_binary, - Addr, - Binary, - DepsMut, - Env, - MessageInfo, - Response, - StdError, - StdResult, - Uint128, - }, - dao::{ - adapter, - treasury_manager::{ - Action, - Allocation, - AllocationMeta, - AllocationTempData, - AllocationType, - Balance, - Context, - ExecuteAnswer, - Holding, - Metric, - Status, - }, - }, - snip20, - snip20::{ - batch::{SendAction, SendFromAction}, - helpers::{ - allowance_query, - balance_query, - batch_send_from_msg, - batch_send_msg, - register_receive, - send_msg, - set_viewing_key_msg, - }, - }, - utils::{ - asset::{Contract, RawContract}, - generic_response::ResponseStatus, - }, -}; - -static ONE_HUNDRED_PERCENT: Uint128 = Uint128::new(10u128.pow(18)); - -pub fn receive( - deps: DepsMut, - env: Env, - info: MessageInfo, - _sender: Addr, - from: Addr, - amount: Uint128, - _msg: Option, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - let asset = match ASSETS.may_load(deps.storage, info.sender.clone())? { - Some(a) => a, - None => { - return Err(StdError::generic_err("Not a registered asset")); - } - }; - - METRICS.push(deps.storage, env.block.time, Metric { - action: Action::FundsReceived, - context: Context::Receive, - timestamp: env.block.time.seconds(), - token: info.sender.clone(), - amount, - user: from.clone(), - })?; - - // Do nothing if its an adapter (claimed funds) - if let Some(_) = ALLOCATIONS - .load(deps.storage, info.sender.clone())? - .iter() - .find(|a| a.contract.address == from) - { - return Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Receive { - status: ResponseStatus::Success, - })?)); - } - - // Default to treasury if not sent by a holder - let holder = match HOLDERS.load(deps.storage)?.contains(&from) { - true => from.clone(), - false => config.treasury, - }; - - let mut holding = HOLDING.load(deps.storage, holder.clone())?; - if holding.status == Status::Closed { - return Err(StdError::generic_err( - "Cannot add holdings when status is closed", - )); - } - if let Some(i) = holding - .balances - .iter() - .position(|b| b.token == asset.contract.address) - { - holding.balances[i].amount += amount; - } else { - holding.balances.push(Balance { - token: asset.contract.address, - amount, - }); - } - - HOLDING.save(deps.storage, holder, &holding)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Receive { - status: ResponseStatus::Success, - })?)) -} - -pub fn update_config( - deps: DepsMut, - _env: Env, - info: MessageInfo, - admin_auth: Option, - treasury: Option, -) -> StdResult { - let mut config = CONFIG.load(deps.storage)?; - - validate_admin( - &deps.querier, - AdminPermissions::TreasuryManager, - &info.sender, - &config.admin_auth, - )?; - - if let Some(admin_auth) = admin_auth { - config.admin_auth = admin_auth.into_valid(deps.api)?; - } - if let Some(treasury) = treasury { - config.treasury = deps.api.addr_validate(&treasury)?; - } - - CONFIG.save(deps.storage, &config)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { - config, - status: ResponseStatus::Success, - })?), - ) -} - -pub fn register_asset( - deps: DepsMut, - env: &Env, - info: MessageInfo, - contract: &Contract, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - validate_admin( - &deps.querier, - AdminPermissions::TreasuryManager, - &info.sender, - &config.admin_auth, - )?; - - let mut list = ASSET_LIST.load(deps.storage)?; - list.push(contract.address.clone()); - ASSET_LIST.save(deps.storage, &list)?; - - ASSETS.save( - deps.storage, - contract.address.clone(), - &snip20::helpers::fetch_snip20(&contract, &deps.querier)?, - )?; - - ALLOCATIONS.save(deps.storage, contract.address.clone(), &Vec::new())?; - - UNBONDINGS.save(deps.storage, contract.address.clone(), &Uint128::zero())?; - - Ok(Response::new() - .add_messages(vec![ - // Register contract in asset - register_receive(env.contract.code_hash.clone(), None, &contract)?, - // Set viewing key - set_viewing_key_msg(VIEWING_KEY.load(deps.storage)?, None, &contract)?, - ]) - .set_data(to_binary(&ExecuteAnswer::RegisterAsset { - status: ResponseStatus::Success, - })?)) -} - -pub fn allocate( - deps: DepsMut, - _env: &Env, - info: MessageInfo, - asset: Addr, - allocation: Allocation, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - validate_admin( - &deps.querier, - AdminPermissions::TreasuryManager, - &info.sender, - &config.admin_auth, - )?; - - if allocation.tolerance >= ONE_HUNDRED_PERCENT { - return Err(StdError::generic_err(format!( - "Tolerance {} >= 100%", - allocation.tolerance - ))); - } - - let mut allocations = ALLOCATIONS - .may_load(deps.storage, asset.clone())? - .unwrap_or_default(); - - // adapters can't have two allocations so remove the duplicate - let stale_alloc = allocations - .iter() - .position(|a| a.contract.address == allocation.contract.address); - - match stale_alloc { - Some(i) => { - allocations.swap_remove(i); - } - None => {} - }; - - allocations.push(AllocationMeta { - nick: allocation.nick, - contract: allocation.contract, - amount: allocation.amount, - alloc_type: allocation.alloc_type, - tolerance: allocation.tolerance, - }); - - // ensure that the portion allocations don't go above 100% - if allocations - .iter() - .map(|a| { - if a.alloc_type == AllocationType::Portion { - a.amount - } else { - Uint128::zero() - } - }) - .sum::() - > ONE_HUNDRED_PERCENT - { - return Err(StdError::generic_err( - "Invalid allocation total exceeding 100%", - )); - } - - // Sort the allocations Amount < Portion - allocations.sort_by(|a, b| match a.alloc_type { - AllocationType::Amount => match b.alloc_type { - AllocationType::Amount => std::cmp::Ordering::Equal, - AllocationType::Portion => std::cmp::Ordering::Less, - }, - AllocationType::Portion => match b.alloc_type { - AllocationType::Amount => std::cmp::Ordering::Greater, - AllocationType::Portion => std::cmp::Ordering::Equal, - }, - }); - - ALLOCATIONS.save(deps.storage, asset.clone(), &allocations)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::Allocate { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn claim(deps: DepsMut, env: &Env, info: MessageInfo, asset: Addr) -> StdResult { - let full_asset = match ASSETS.may_load(deps.storage, asset.clone())? { - Some(a) => a, - None => { - return Err(StdError::generic_err("Unrecognized asset")); - } - }; - - let config = CONFIG.load(deps.storage)?; - // if the claimer isn't a holder, it should default to the treasruy - let claimer = match HOLDERS.load(deps.storage)?.contains(&info.sender) { - true => info.sender, - false => config.treasury.clone(), - }; - - let mut total_claimed = Uint128::zero(); - let mut messages = vec![]; - - // claim from adapters that have claimable value - for alloc in ALLOCATIONS.load(deps.storage, asset.clone())? { - let claim = adapter::claimable_query(deps.querier, &asset, alloc.contract.clone())?; - if claim > Uint128::zero() { - messages.push(adapter::claim_msg(&asset, alloc.contract.clone())?); - METRICS.push(deps.storage, env.block.time, Metric { - action: Action::Claim, - context: Context::Claim, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: claim, - user: claimer.clone(), - })?; - total_claimed += claim; - } - } - - let mut holding = HOLDING.load(deps.storage, claimer.clone())?; - - // get the position of the holders unbondings - let unbonding_i = match holding - .unbondings - .iter_mut() - .position(|u| u.token == asset.clone()) - { - Some(i) => i, - None => { - return Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Claim { - status: ResponseStatus::Success, - amount: Uint128::zero(), - }, - )?)); - } - }; - - let reserves = balance_query( - &deps.querier, - env.contract.address.clone(), - VIEWING_KEY.load(deps.storage)?, - &full_asset.contract.clone(), - )?; - - let send_amount = { - // if reserves and total claimed is less than the unbondings of the holder, we need to send - // all of the reserves and all that will be claimed - if holding.unbondings[unbonding_i].amount > reserves + total_claimed { - reserves + total_claimed - } else { - // otherwise just send the unbonding amount - holding.unbondings[unbonding_i].amount - } - }; - - // Adjust unbonding amount - holding.unbondings[unbonding_i].amount = holding.unbondings[unbonding_i].amount - send_amount; - - if claimer != config.treasury && holding.status == Status::Closed { - if let Some(balance_i) = holding - .balances - .iter_mut() - .position(|u| u.token == asset.clone()) - { - if holding.unbondings[unbonding_i].amount == Uint128::zero() - && holding.balances[balance_i].amount == Uint128::zero() - { - holding.unbondings.swap_remove(unbonding_i); - holding.balances.swap_remove(balance_i); - } - } - } - - HOLDING.save(deps.storage, claimer.clone(), &holding)?; - - // Send claimed funds - messages.push(send_msg( - claimer.clone(), - send_amount, - None, - None, - None, - &full_asset.contract.clone(), - )?); - - METRICS.push(deps.storage, env.block.time, Metric { - action: Action::SendFunds, - context: Context::Claim, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: send_amount, - user: claimer.clone(), - })?; - - Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Claim { - status: ResponseStatus::Success, - amount: reserves + total_claimed, - }, - )?)) -} - -pub fn update(deps: DepsMut, env: &Env, _info: MessageInfo, asset: Addr) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - let full_asset = ASSETS.load(deps.storage, asset.clone())?; - - let mut allocations = ALLOCATIONS.load(deps.storage, asset.clone())?; - - // the sum of balances on 'amount' adapters - let mut amount_total = Uint128::zero(); - // the sum of balances on 'portion' adapters - let mut portion_total = Uint128::zero(); - // allocations marked for removal - let mut stale_allocs = vec![]; - let mut messages = vec![]; - let mut adapter_info = vec![]; - - /* this loop has 2 purposes - * - check for stale allocaitons that need to be removed - * - fill the amount_total and portion_total vars with data - */ - for (i, a) in allocations.clone().iter().enumerate() { - let bal = adapter::balance_query( - deps.querier, - &full_asset.contract.address, - a.contract.clone(), - )?; - let mut unbonding = adapter::unbonding_query( - deps.querier, - &full_asset.contract.address, - a.contract.clone(), - )?; - let unbondable = adapter::unbondable_query( - deps.querier, - &full_asset.contract.address, - a.contract.clone(), - )?; - let claimable = adapter::claimable_query( - deps.querier, - &full_asset.contract.address, - a.contract.clone(), - )?; - if !claimable.is_zero() { - messages.push(adapter::claim_msg( - &full_asset.contract.address.clone(), - a.contract.clone(), - )?); - unbonding += claimable; - } - // if all these values are zero we can safely drop the alloc - if bal.is_zero() - && a.amount.is_zero() - && unbonding.is_zero() - && unbondable.is_zero() - && claimable.is_zero() - { - stale_allocs.push(i); - } - - adapter_info.push(AllocationTempData { - contract: a.contract.clone(), - alloc_type: a.alloc_type.clone(), - amount: a.amount.clone(), - tolerance: a.tolerance.clone(), - balance: bal, - unbondable, - unbonding, - }); - - // fill totals with data - match a.alloc_type { - AllocationType::Amount => amount_total += bal, - AllocationType::Portion => portion_total += bal, - }; - } - - // actually drop the stale allocs - if !stale_allocs.is_empty() { - for index in stale_allocs.iter().rev() { - // remove used here to preserve sorted vec - allocations.remove(index.clone()); - } - ALLOCATIONS.save(deps.storage, asset.clone(), &allocations)?; - } - - // the holder is the entity that actually holds the tokens that the treasury manager can spend - // holder_unbonding represents how much the holder has currently asked to unbond - let mut holder_unbonding = Uint128::zero(); - // holder_principal represents how much of the asset has came form said holder - let mut holder_principal = Uint128::zero(); - - let mut holders = HOLDERS.load(deps.storage)?; - // Withold holder unbondings - for (i, h) in holders.clone().iter().enumerate() { - // for each holder, load the respective holdings - let holding = HOLDING.load(deps.storage, h.clone())?; - // sum the data - if let Some(u) = holding.unbondings.iter().find(|u| u.token == asset) { - holder_unbonding += u.amount; - } - if let Some(b) = holding.balances.iter().find(|u| u.token == asset) { - holder_principal += b.amount; - } - if holding.status == Status::Closed - && holding.balances.len() == 0 - && holding.unbondings.len() == 0 - { - HOLDING.remove(deps.storage, h.clone()); - holders.swap_remove(i); - HOLDERS.save(deps.storage, &holders)?; - } - } - - // Batch send_from actions - let mut send_from_actions = vec![]; - let mut send_actions = vec![]; - let mut metrics = vec![]; - - let key = VIEWING_KEY.load(deps.storage)?; - - // Available treasury allowance - let mut allowance = allowance_query( - &deps.querier, - config.treasury.clone(), - env.contract.address.clone(), - key.clone(), - 1, - &full_asset.contract.clone(), - )? - .allowance; - - // snip20 balance query to get the treasury managers current snip20 balance - let mut balance = balance_query( - &deps.querier, - env.contract.address.clone(), - key.clone(), - &full_asset.contract.clone(), - )?; - - // total amount allocated to adapters + current snip20 balance - // We subtract holder_unbonding to ensure that those tokens will be claimable - let out_total = (amount_total + portion_total + balance) - holder_unbonding; - // This gives us our total allowance from the treasury, used and unused - let total = out_total + allowance; - - balance = { - if balance > holder_unbonding { - balance - holder_unbonding - } else { - Uint128::zero() - } - }; - - // setting up vars - let mut allowance_used = Uint128::zero(); - let mut balance_used = Uint128::zero(); - let mut reserved_for_amount_adapters = Uint128::zero(); - - // loop through adapters with allocations - for adapter in adapter_info { - // calculate the target balance for each - let desired_amount = match adapter.alloc_type { - AllocationType::Amount => { - reserved_for_amount_adapters += adapter.amount; - // since amount adapters' allocations are static - adapter.amount - } - AllocationType::Portion => { - // Since the list of allocations is sorted, we can ensure that type::amount - // adapters will be processed first, so we can calculate the amount available for - // allocation with total - reserved_for_amount_adapters - // If statement to prevent overflow - if total > reserved_for_amount_adapters { - adapter - .amount - .multiply_ratio(total - reserved_for_amount_adapters, ONE_HUNDRED_PERCENT) - } else { - Uint128::zero() - } - } - }; - // threshold is the desired_amount * a percentage held in adapter.tolerance, - // the treasury manager will only attempt to rebalance if the adapter crosses the threshold - // in either direction - let threshold = desired_amount.multiply_ratio(adapter.tolerance, ONE_HUNDRED_PERCENT); - - // effective balance is the adapters' actual unbondable amount - let effective_balance = { - if adapter.balance > adapter.unbonding { - adapter.balance - adapter.unbonding - } else { - // adapter balance should never be less than unbonding so if it's equal to then we - // just set effective bal to zero - Uint128::zero() - } - }; - - match desired_amount.cmp(&effective_balance) { - // Under Funded -- prioritize tm snip20 balance over allowance from treasury - std::cmp::Ordering::Greater => { - // target send amount to adapter - let mut desired_input = desired_amount - effective_balance; - // check if threshold is crossed - if desired_input <= threshold { - continue; - } - - // Fully covered by balance - if desired_input < balance { - send_actions.push(SendAction { - recipient: adapter.contract.address.clone().to_string(), - recipient_code_hash: Some(adapter.contract.code_hash.clone()), - amount: desired_input, - msg: None, - memo: None, - }); - metrics.push(Metric { - action: Action::SendFunds, - context: Context::Update, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: desired_input, - user: adapter.contract.address.clone(), - }); - - // reduce snip20 balance for future loops - balance = balance - desired_input; - balance_used += desired_input; - // at this point we know we have fufilled what this adapter needs - continue; - } - // Send all snip20 balance since the adapter needs more that the balance can fufill, - // but balance is not 0 - else if !balance.is_zero() { - send_actions.push(SendAction { - recipient: adapter.contract.address.clone().to_string(), - recipient_code_hash: Some(adapter.contract.code_hash.clone()), - amount: balance, - msg: None, - memo: None, - }); - metrics.push(Metric { - action: Action::SendFunds, - context: Context::Update, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: balance, - user: adapter.contract.address.clone(), - }); - - // reduce the desired_input to reflect the balance being sent, we know this will - // not overflow because if balance was > desired_input, we would have hit a - // continue statement - desired_input = desired_input - balance; - // reset balance since we have effectively sent everything out - balance = Uint128::zero(); - } - - if !allowance.is_zero() { - // This will only execute after snip20 balance has been used up - // Fully covered by allowance - if desired_input < allowance { - send_from_actions.push(SendFromAction { - owner: config.treasury.clone().to_string(), - recipient: adapter.contract.address.clone().to_string(), - recipient_code_hash: Some(adapter.contract.code_hash.clone()), - amount: desired_input, - msg: None, - memo: None, - }); - metrics.push(Metric { - action: Action::SendFundsFrom, - context: Context::Update, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: desired_input, - user: adapter.contract.address.clone(), - }); - - allowance_used += desired_input; - // this will not overflow due to check in if statement - allowance = allowance - desired_input; - // similarily, we know that we have fufilled what this adapter needs at this - // point but we don't want to continue since we need to account for the - // allowance used in the holder's information - } - // Send all allowance - else if !allowance.is_zero() { - send_from_actions.push(SendFromAction { - owner: config.treasury.clone().to_string(), - recipient: adapter.contract.address.clone().to_string(), - recipient_code_hash: Some(adapter.contract.code_hash.clone()), - amount: allowance, - msg: None, - memo: None, - }); - metrics.push(Metric { - action: Action::SendFundsFrom, - context: Context::Update, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: allowance, - user: adapter.contract.address.clone(), - }); - - // account for allowance being sent out - allowance_used += allowance; - allowance = Uint128::zero(); - } - } - } - // Over funded -- unbond - std::cmp::Ordering::Less => { - // balance - target balance will give the amount we need to unbond - let desired_output = effective_balance - desired_amount; - - // check to see that the threshold has been crossed - if desired_output <= threshold { - continue; - } - - if !desired_output.is_zero() { - messages.push(adapter::unbond_msg( - &asset.clone(), - desired_output.clone(), - adapter.contract.clone(), - )?); - metrics.push(Metric { - action: Action::Unbond, - context: Context::Update, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: desired_output, - user: adapter.contract.address.clone(), - }); - } - let unbondings = UNBONDINGS - .load(deps.storage, full_asset.contract.address.clone())? - + desired_output; - UNBONDINGS.save( - deps.storage, - full_asset.contract.address.clone(), - &unbondings, - )?; - } - _ => {} - } - } - - // Credit treasury balance with allowance used by adding allowance_used to the existing balance - // or creating a new balance struct with allowance_used as the balance - let mut holding = HOLDING.load(deps.storage, config.treasury.clone())?; - if let Some(i) = holding - .balances - .iter() - .position(|u| u.token == asset.clone()) - { - holding.balances[i].amount = holding.balances[i].amount + allowance_used; - } else { - holding.balances.push(Balance { - token: asset.clone(), - amount: allowance_used, - }); - } - HOLDING.save(deps.storage, config.treasury.clone(), &holding)?; - - // Determine Gainz & Losses & credit to treasury - holder_principal += allowance_used; - - // this will never overflow because total is a sum of allowance - match (total - allowance).cmp(&holder_principal) { - std::cmp::Ordering::Greater => { - let gains = (total - allowance) - holder_principal; - // debit gains to treasury - let mut holding = HOLDING.load(deps.storage, config.treasury.clone())?; - if let Some(i) = holding.balances.iter().position(|u| u.token == asset) { - holding.balances[i].amount += gains; - } - HOLDING.save(deps.storage, config.treasury.clone(), &holding)?; - metrics.push(Metric { - action: Action::RealizeGains, - context: Context::Update, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: gains, - user: config.treasury.clone(), - }); - } - std::cmp::Ordering::Less => { - let losses = holder_principal - (total - allowance); - // credit losses to treasury - let mut holding = HOLDING.load(deps.storage, config.treasury.clone())?; - if let Some(i) = holding.balances.iter().position(|u| u.token == asset) { - holding.balances[i].amount -= losses; - } - HOLDING.save(deps.storage, config.treasury.clone(), &holding)?; - metrics.push(Metric { - action: Action::RealizeLosses, - context: Context::Update, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: losses, - user: config.treasury.clone(), - }); - } - _ => {} - } - - // exec batch balance send messages - if !send_actions.is_empty() { - messages.push(batch_send_msg( - send_actions, - None, - &full_asset.contract.clone(), - )?); - } - - // exec batch allowance send messages - if !send_from_actions.is_empty() { - messages.push(batch_send_from_msg( - send_from_actions, - None, - &full_asset.contract.clone(), - )?); - } - - METRICS.append(deps.storage, env.block.time, &mut metrics)?; - - Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Update { - status: ResponseStatus::Success, - }, - )?)) -} - -pub fn unbond( - deps: DepsMut, - env: &Env, - info: MessageInfo, - asset: Addr, - amount: Uint128, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - let holders = HOLDERS.load(deps.storage)?; - - // if the claimer isn't a holder, it should be an admin and default to the treasruy - let unbonder = match holders.contains(&info.sender) { - true => info.sender, - false => { - validate_admin( - &deps.querier, - AdminPermissions::TreasuryManager, - &info.sender, - &config.admin_auth, - )?; - config.treasury - } - }; - - let full_asset = ASSETS.load(deps.storage, asset.clone())?; - - // Adjust holder balance - let mut holding = HOLDING.load(deps.storage, unbonder.clone())?; - - // get the position of the balance for the asset - let balance_i = match holding - .balances - .iter() - .position(|h| h.token == asset.clone()) - { - Some(i) => i, - None => { - return Err(StdError::generic_err(format!( - "Cannot unbond, holder has no holdings of {}", - asset.clone() - ))); - } - }; - - let mut unbond_amount = amount; - // Check balance exceeds unbond amount - if holding.balances[balance_i].amount < amount { - return Err(StdError::generic_err("Not enough funds to unbond")); - } else { - if holding.status == Status::Active { - holding.balances[balance_i].amount = holding.balances[balance_i].amount - amount; - } else { - unbond_amount = holding.balances[balance_i].amount; - holding.balances[balance_i].amount = Uint128::zero(); - } - } - - // Add unbonding - if let Some(u) = holding - .unbondings - .iter() - .position(|h| h.token == asset.clone()) - { - holding.unbondings[u].amount += unbond_amount; - } else { - holding.unbondings.push(Balance { - token: asset.clone(), - amount: unbond_amount, - }); - } - - HOLDING.save(deps.storage, unbonder.clone(), &holding)?; - let allocations = ALLOCATIONS.load(deps.storage, asset.clone())?; - - // get the total amount that the adapters are currently unbonding - let mut unbonding_tot = Uint128::zero(); - for a in allocations.clone() { - unbonding_tot += - adapter::unbonding_query(deps.querier, &asset.clone(), a.contract.clone())?; - } - - // find the unbond_amount based off of amounts that the TM has unbonded independent of a holder - unbond_amount = { - let u = UNBONDINGS.load(deps.storage, full_asset.contract.address.clone())?; - // if the independent unbondings is less than what the adapters are acutally unbonding, we - // know another holder has asked to do some unbonding and the adapters are unbonding for - // that holder - if u <= unbonding_tot { - if u <= unbond_amount { - // if amount > independent unbonding, we reduce independent unbondings to - // zero and return the amount we actually want to unbond from the adapters - UNBONDINGS.save( - deps.storage, - full_asset.contract.address.clone(), - &Uint128::zero(), - )?; - unbond_amount - u - } else { - // independent unbondings covers the amount - UNBONDINGS.save( - deps.storage, - full_asset.contract.address.clone(), - &(u - unbond_amount), - )?; - Uint128::zero() - } - } else { - // We error out since this case is completely unexpected - // Independent unbonding should never be greater than what the adapters are curretnly - // unbonding - /*return Err(StdError::generic_err( - "Independent TM unbonding is greater than what the adapters are unbonding", - ));*/ - // TODO figure out why we can't throw an error here - // NOTE it has something to do with gains/losses - unbond_amount - } - }; - - // get other holders unbonding amount to hold - let mut other_unbondings = Uint128::zero(); - - for h in holders { - if h == unbonder.clone() { - continue; - } - let other_holding = HOLDING.load(deps.storage, h)?; - if let Some(u) = other_holding - .unbondings - .iter() - .find(|u| u.token == asset.clone()) - { - other_unbondings += u.amount; - } - } - - // Reserves to be sent immediately - let mut reserves = balance_query( - &deps.querier, - env.contract.address.clone(), - VIEWING_KEY.load(deps.storage)?, - &full_asset.contract.clone(), - )?; - - // Remove pending unbondings from reserves - if reserves > other_unbondings { - reserves = reserves - other_unbondings; - } else { - reserves = Uint128::zero(); - } - - let mut messages = vec![]; - let mut metrics = vec![]; - - // Send available reserves to unbonder - if reserves > Uint128::zero() { - if reserves < unbond_amount { - // reserves can't cover unbond - // Don't need batch send bc there's only one send msg - messages.push(send_msg( - unbonder.clone(), - reserves, - None, - None, - None, - &full_asset.contract.clone(), - )?); - metrics.push(Metric { - action: Action::SendFunds, - context: Context::Unbond, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: reserves, - user: unbonder.clone(), - }); - unbond_amount = unbond_amount - reserves; - - // Reflect sent funds in unbondings - let mut holding = HOLDING.load(deps.storage, unbonder.clone())?; - if let Some(i) = holding.unbondings.iter().position(|u| u.token == asset) { - holding.unbondings[i].amount = holding.unbondings[i].amount - reserves; - } - HOLDING.save(deps.storage, unbonder, &holding)?; - } else { - // reserves can cover unbond - messages.push(send_msg( - unbonder.clone(), - amount, - None, - None, - None, - &full_asset.contract.clone(), - )?); - metrics.push(Metric { - action: Action::SendFunds, - context: Context::Unbond, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount, - user: unbonder.clone(), - }); - - // Reflect sent funds in unbondings - let mut holding = HOLDING.load(deps.storage, unbonder.clone())?; - if let Some(i) = holding.unbondings.iter().position(|u| u.token == asset) { - holding.unbondings[i].amount = holding.unbondings[i].amount - amount; - } - HOLDING.save(deps.storage, unbonder, &holding)?; - - METRICS.append(deps.storage, env.block.time, &mut metrics)?; - return Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Unbond { - status: ResponseStatus::Success, - amount, - }, - )?)); - } - } - - // let full_asset = ASSETS.load(deps.storage, asset.clone())?; - - // Build metadata - let mut alloc_meta = vec![]; - let mut amount_total = Uint128::zero(); - let mut portion_total = Uint128::zero(); - let mut tot_unbond_available = Uint128::zero(); - - // Gather adapter outstanding amounts - for a in allocations { - let bal = adapter::balance_query(deps.querier, &asset, a.contract.clone())?; - let unbondable = adapter::unbondable_query(deps.querier, &asset, a.contract.clone())?; - - alloc_meta.push(AllocationTempData { - contract: a.contract.clone(), - alloc_type: a.alloc_type.clone(), - amount: a.amount.clone(), - tolerance: a.tolerance.clone(), - balance: bal, - unbondable, - unbonding: Uint128::zero(), - }); - - tot_unbond_available += unbondable; - - match a.alloc_type { - AllocationType::Amount => amount_total += bal, - AllocationType::Portion => portion_total += bal, - }; - } - - // if unbond_amount == tot_amount_unbonding, unbond all unbondable amounts and return - if unbond_amount == tot_unbond_available { - for a in alloc_meta.clone() { - messages.push(adapter::unbond_msg( - &full_asset.contract.address.clone(), - a.unbondable.clone(), - a.contract.clone(), - )?); - metrics.push(Metric { - action: Action::Unbond, - context: Context::Unbond, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: a.balance.clone(), - user: a.contract.address.clone(), - }); - } - METRICS.append(deps.storage, env.block.time, &mut metrics)?; - return Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Unbond { - status: ResponseStatus::Success, - amount, - }, - )?)); - } - - let mut total_amount_unbonding = Uint128::zero(); - let mut unbond_amounts = vec![]; - - let (amounts, portions): (Vec, Vec) = alloc_meta - .clone() - .into_iter() - .partition_map(|a| match a.alloc_type { - AllocationType::Amount => Either::Left(a), - AllocationType::Portion => Either::Right(a), - }); - - // unbond the extra tokens from the amount adapters - for meta in amounts.clone() { - if meta.unbondable > meta.amount { - total_amount_unbonding += meta.unbondable - meta.amount; - unbond_amounts.push(meta.unbondable - meta.amount); - } else { - unbond_amounts.push(Uint128::zero()) - } - } - - // if the extra tokens from the amount adapters covers the unbond request, push the messages - // and return - if unbond_amount == total_amount_unbonding { - for (i, meta) in amounts.clone().iter().enumerate() { - messages.push(adapter::unbond_msg( - &full_asset.contract.address.clone(), - unbond_amounts[i], - meta.contract.clone(), - )?); - metrics.push(Metric { - action: Action::Unbond, - context: Context::Unbond, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: unbond_amounts[i], - user: meta.contract.address.clone(), - }); - } - METRICS.append(deps.storage, env.block.time, &mut metrics)?; - return Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Unbond { - status: ResponseStatus::Success, - amount, - }, - )?)); - } else if unbond_amount < total_amount_unbonding { - // if the extra tokens are greater than the unbond request, unbond proportionally to the - // extra tokens available and return - let mut modified_total_amount_unbonding = Uint128::zero(); - for (i, meta) in amounts.clone().iter().enumerate() { - unbond_amounts[i] = - unbond_amount.multiply_ratio(unbond_amounts[i], total_amount_unbonding); - modified_total_amount_unbonding += unbond_amounts[i]; - // avoid off by one error - if i == amounts.len() - 1 - && modified_total_amount_unbonding < unbond_amount - && unbond_amounts[i] + Uint128::new(1) <= meta.unbondable - { - unbond_amounts[i] += Uint128::new(1); - } - messages.push(adapter::unbond_msg( - &full_asset.contract.address.clone(), - unbond_amounts[i], - meta.contract.clone(), - )?); - metrics.push(Metric { - action: Action::Unbond, - context: Context::Unbond, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: unbond_amounts[i], - user: meta.contract.address.clone(), - }); - } - METRICS.append(deps.storage, env.block.time, &mut metrics)?; - return Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Unbond { - status: ResponseStatus::Success, - amount, - }, - )?)); - } - - // if portion total > unbond - tot, we know the portion adapters can cover the rest - if unbond_amount - total_amount_unbonding < portion_total { - // unbond the tokens slotted for unbonding from the amount adapters - for (i, meta) in amounts.clone().iter().enumerate() { - if !unbond_amounts[i].is_zero() { - messages.push(adapter::unbond_msg( - &full_asset.contract.address.clone(), - unbond_amounts[i], - meta.contract.clone(), - )?); - metrics.push(Metric { - action: Action::Unbond, - context: Context::Unbond, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: unbond_amounts[i], - user: meta.contract.address.clone(), - }); - } - } - let amount_adapt_tot_unbonding = total_amount_unbonding; - /* For each portion adapter, unbond the amount proportional to its portion of the total - * balance - */ - for (i, meta) in portions.clone().iter().enumerate() { - let unbond_from_portion = (unbond_amount - amount_adapt_tot_unbonding) - .multiply_ratio(meta.unbondable, portion_total); - unbond_amounts.push(unbond_from_portion); - total_amount_unbonding += unbond_from_portion; - // Avoid off by 1 error - if i == portions.len() - 1 - && total_amount_unbonding < unbond_amount - && unbond_from_portion + Uint128::new(1) <= meta.unbondable - { - messages.push(adapter::unbond_msg( - &full_asset.contract.address.clone(), - unbond_from_portion + Uint128::new(1), - meta.contract.clone(), - )?); - metrics.push(Metric { - action: Action::Unbond, - context: Context::Unbond, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: unbond_from_portion + Uint128::new(1), - user: meta.contract.address.clone(), - }); - } else if !unbond_from_portion.is_zero() { - messages.push(adapter::unbond_msg( - &full_asset.contract.address.clone(), - unbond_from_portion, - meta.contract.clone(), - )?); - metrics.push(Metric { - action: Action::Unbond, - context: Context::Unbond, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: unbond_from_portion, - user: meta.contract.address.clone(), - }); - } - } - METRICS.append(deps.storage, env.block.time, &mut metrics)?; - return Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Unbond { - status: ResponseStatus::Success, - amount, - }, - )?)); - } else { - // Otherwise we need to unbond everything from the portion adapters and go back to the - // amount adapters - for meta in portions { - unbond_amounts.push(meta.unbondable); - if !meta.unbondable.is_zero() { - messages.push(adapter::unbond_msg( - &full_asset.contract.address, - meta.unbondable, - meta.contract.clone(), - )?); - metrics.push(Metric { - action: Action::Unbond, - context: Context::Unbond, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: meta.unbondable, - user: meta.contract.address.clone(), - }); - } - total_amount_unbonding += meta.unbondable; - } - // tot_amount_unbonding is equal to unbond_amount, unbonding everything from the portion - // adapters covers our requested unbonding, so we push msgs and return - if total_amount_unbonding == unbond_amount { - for (i, meta) in amounts.clone().iter().enumerate() { - if !unbond_amounts[i].is_zero() { - messages.push(adapter::unbond_msg( - &full_asset.contract.address, - unbond_amounts[i].clone(), - meta.contract.clone(), - )?); - metrics.push(Metric { - action: Action::Unbond, - context: Context::Unbond, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: unbond_amounts[i].clone(), - user: meta.contract.address.clone(), - }); - } - } - METRICS.append(deps.storage, env.block.time, &mut metrics)?; - return Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Unbond { - status: ResponseStatus::Success, - amount, - }, - )?)); - } else { - // unbond token amounts proportional to the ratio of the allocation of the adapter and - // the sum of the amount allocaitons - let mut amount_alloc = Uint128::zero(); - for meta in amounts.clone() { - amount_alloc += meta.amount; - } - let mut modified_total_amount_unbonding = total_amount_unbonding; - for (i, meta) in amounts.iter().enumerate() { - unbond_amounts[i] += (unbond_amount - total_amount_unbonding) - .multiply_ratio(meta.amount, amount_alloc); - - modified_total_amount_unbonding += meta.unbondable; - // this makes sure that the entire unbond request is fuffiled by the end of this - // block - if i == amounts.len() - 1 - && modified_total_amount_unbonding < unbond_amount - && unbond_amount - modified_total_amount_unbonding - < meta.unbondable - unbond_amounts[i] - { - unbond_amounts[i] += unbond_amount - total_amount_unbonding; - } - if !unbond_amounts[i].is_zero() { - messages.push(adapter::unbond_msg( - &full_asset.contract.address, - unbond_amounts[i], - meta.contract.clone(), - )?); - metrics.push(Metric { - action: Action::Unbond, - context: Context::Unbond, - timestamp: env.block.time.seconds(), - token: asset.clone(), - amount: unbond_amounts[i].clone(), - user: meta.contract.address.clone(), - }); - } - } - METRICS.append(deps.storage, env.block.time, &mut metrics)?; - return Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Unbond { - status: ResponseStatus::Success, - amount, - }, - )?)); - } - } -} - -pub fn add_holder( - deps: DepsMut, - env: &Env, - info: MessageInfo, - holder: Addr, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - validate_admin( - &deps.querier, - AdminPermissions::TreasuryManager, - &info.sender, - &config.admin_auth, - )?; - - let mut holders = HOLDERS.load(deps.storage)?; - if holders.contains(&holder.clone()) { - return Err(StdError::generic_err("Holder already exists")); - } - holders.push(holder.clone()); - HOLDERS.save(deps.storage, &holders)?; - - HOLDING.save(deps.storage, holder.clone(), &Holding { - balances: Vec::new(), - unbondings: Vec::new(), - status: Status::Active, - })?; - - METRICS.push(deps.storage, env.block.time, Metric { - action: Action::AddHolder, - context: Context::Holders, - timestamp: env.block.time.seconds(), - token: Addr::unchecked(""), - amount: Uint128::zero(), - user: holder, - })?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::AddHolder { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn remove_holder( - deps: DepsMut, - env: &Env, - info: MessageInfo, - holder: Addr, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - validate_admin( - &deps.querier, - AdminPermissions::TreasuryManager, - &info.sender, - &config.admin_auth, - )?; - - if holder == config.treasury { - return Err(StdError::generic_err("Cannot remove treasury as a holder")); - } - - if let Some(mut holding) = HOLDING.may_load(deps.storage, holder.clone())? { - holding.status = Status::Closed; - HOLDING.save(deps.storage, holder.clone(), &holding)?; - } else { - return Err(StdError::generic_err("Not an authorized holder")); - } - - METRICS.push(deps.storage, env.block.time, Metric { - action: Action::RemoveHolder, - context: Context::Holders, - timestamp: env.block.time.seconds(), - token: Addr::unchecked(""), - amount: Uint128::zero(), - user: holder, - })?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::RemoveHolder { - status: ResponseStatus::Success, - })?), - ) -} diff --git a/contracts/dao/treasury_manager/src/lib.rs b/contracts/dao/treasury_manager/src/lib.rs deleted file mode 100644 index 25ee0d9..0000000 --- a/contracts/dao/treasury_manager/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod contract; -pub mod execute; -pub mod query; -pub mod storage; diff --git a/contracts/dao/treasury_manager/src/query.rs b/contracts/dao/treasury_manager/src/query.rs deleted file mode 100644 index e0af3f8..0000000 --- a/contracts/dao/treasury_manager/src/query.rs +++ /dev/null @@ -1,292 +0,0 @@ -use crate::storage::*; -use shade_protocol::{ - c_std::{Addr, Deps, Env, StdError, StdResult, Uint128}, - dao::{adapter, manager, treasury_manager}, - snip20::helpers::{allowance_query, balance_query}, - utils::{cycle::parse_utc_datetime, storage::plus::period_storage::Period}, -}; - -pub fn config(deps: Deps) -> StdResult { - Ok(treasury_manager::QueryAnswer::Config { - config: CONFIG.load(deps.storage)?, - }) -} - -pub fn metrics( - deps: Deps, - env: Env, - date: Option, - epoch: Option, - period: Period, -) -> StdResult { - if date.is_some() && epoch.is_some() { - return Err(StdError::generic_err("cannot pass both epoch and date")); - } - let key = { - if let Some(d) = date { - parse_utc_datetime(&d)?.timestamp() as u64 - } else if let Some(e) = epoch { - e.u128() as u64 - } else { - env.block.time.seconds() - } - }; - Ok(treasury_manager::QueryAnswer::Metrics { - metrics: METRICS.load_period(deps.storage, key, period)?, - }) -} - -pub fn pending_allowance( - deps: Deps, - env: Env, - asset: Addr, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - let full_asset = match ASSETS.may_load(deps.storage, asset)? { - Some(a) => a, - None => { - return Err(StdError::generic_err("Not a registered asset")); - } - }; - - let allowance = allowance_query( - &deps.querier, - config.treasury, - env.contract.address, - VIEWING_KEY.load(deps.storage)?, - 1, - &full_asset.contract.clone(), - )? - .allowance; - - Ok(treasury_manager::QueryAnswer::PendingAllowance { amount: allowance }) -} - -pub fn reserves( - deps: Deps, - env: Env, - asset: Addr, - _holder: Addr, -) -> StdResult { - if let Some(full_asset) = ASSETS.may_load(deps.storage, asset)? { - let reserves = balance_query( - &deps.querier, - env.contract.address, - VIEWING_KEY.load(deps.storage)?, - &full_asset.contract.clone(), - )?; - - return Ok(manager::QueryAnswer::Reserves { amount: reserves }); - } - - Err(StdError::generic_err("Not a registered asset")) -} - -pub fn assets(deps: Deps) -> StdResult { - Ok(treasury_manager::QueryAnswer::Assets { - assets: ASSET_LIST.load(deps.storage)?, - }) -} - -pub fn allocations(deps: Deps, asset: Addr) -> StdResult { - Ok(treasury_manager::QueryAnswer::Allocations { - allocations: match ALLOCATIONS.may_load(deps.storage, asset)? { - None => vec![], - Some(a) => a, - }, - }) -} - -pub fn unbonding(deps: Deps, asset: Addr, holder: Addr) -> StdResult { - if ASSETS.may_load(deps.storage, asset.clone())?.is_none() { - return Err(StdError::generic_err("Not a registered asset")); - } - - let _config = CONFIG.load(deps.storage)?; - - match HOLDING.may_load(deps.storage, holder)? { - Some(holder) => Ok(manager::QueryAnswer::Unbonding { - amount: match holder.unbondings.iter().find(|u| u.token == asset.clone()) { - Some(u) => u.amount, - None => Uint128::zero(), - }, - }), - None => { - return Err(StdError::generic_err("Invalid holder")); - } - } -} - -pub fn claimable( - deps: Deps, - env: Env, - asset: Addr, - holder: Addr, -) -> StdResult { - let full_asset = match ASSETS.may_load(deps.storage, asset.clone())? { - Some(a) => a, - None => { - return Err(StdError::generic_err("Not a registered asset")); - } - }; - let allocations = match ALLOCATIONS.may_load(deps.storage, asset.clone())? { - Some(a) => a, - None => vec![], - }; - //TODO claiming needs ordered unbondings so other holders don't get bumped - - let mut claimable = balance_query( - &deps.querier, - env.contract.address, - VIEWING_KEY.load(deps.storage)?, - &full_asset.contract.clone(), - )?; - - for alloc in allocations { - claimable += adapter::claimable_query(deps.querier, &asset, alloc.contract.clone())?; - } - - match HOLDING.may_load(deps.storage, holder)? { - Some(holder) => { - let unbonding = match holder.unbondings.iter().find(|u| u.token == asset) { - Some(u) => u.amount, - None => Uint128::zero(), - }; - - if claimable > unbonding { - Ok(manager::QueryAnswer::Claimable { amount: unbonding }) - } else { - Ok(manager::QueryAnswer::Claimable { amount: claimable }) - } - } - None => Err(StdError::generic_err("Invalid holder")), - } -} - -pub fn unbondable( - deps: Deps, - env: Env, - asset: Addr, - holder: Addr, -) -> StdResult { - let full_asset = match ASSETS.may_load(deps.storage, asset.clone())? { - Some(a) => a, - None => { - return Err(StdError::generic_err("Not a registered asset")); - } - }; - let mut holder_balance = Uint128::zero(); - - match HOLDING.may_load(deps.storage, holder.clone())? { - Some(h) => { - if let Some(b) = h.balances.iter().find(|b| b.token == asset.clone()) { - holder_balance += b.amount; - } - } - None => { - return Err(StdError::generic_err("Invalid holder")); - } - } - - if holder_balance.is_zero() { - return Ok(manager::QueryAnswer::Unbondable { - amount: holder_balance, - }); - } - - let mut unbondable = balance_query( - &deps.querier, - env.contract.address, - VIEWING_KEY.load(deps.storage)?, - &full_asset.contract.clone(), - )?; - - let allocations = ALLOCATIONS - .may_load(deps.storage, asset.clone())? - .unwrap_or(vec![]); - - for alloc in allocations { - unbondable += adapter::unbondable_query(deps.querier, &asset, alloc.contract)?; - if unbondable > holder_balance { - break; - } - } - - if unbondable > holder_balance { - unbondable = holder_balance; - } - - return Ok(manager::QueryAnswer::Unbondable { amount: unbondable }); -} - -pub fn batch_balance( - deps: Deps, - assets: Vec, - holder: Addr, -) -> StdResult { - let holding = match HOLDING.may_load(deps.storage, holder.clone())? { - Some(h) => h, - None => { - return Err(StdError::generic_err("Invalid Holder")); - } - }; - - let mut balances = vec![]; - - for asset in assets { - if let Some(asset) = ASSETS.may_load(deps.storage, asset)? { - balances.push( - match holding - .balances - .iter() - .find(|b| b.token == asset.contract.address) - { - Some(b) => b.amount, - None => Uint128::zero(), - }, - ); - } else { - balances.push(Uint128::zero()); - } - } - - Ok(manager::QueryAnswer::BatchBalance { amounts: balances }) -} - -pub fn balance(deps: Deps, asset: Addr, holder: Addr) -> StdResult { - if let Some(asset) = ASSETS.may_load(deps.storage, asset)? { - let holding = match HOLDING.may_load(deps.storage, holder.clone())? { - Some(h) => h, - None => { - return Err(StdError::generic_err("Invalid Holder")); - } - }; - // TODO include unbonding so balance is more 'stable' - // likely requires treasury rebalance changes - let balance = match holding - .balances - .iter() - .find(|b| b.token == asset.contract.address) - { - Some(b) => b.amount, - None => Uint128::zero(), - }; - - Ok(manager::QueryAnswer::Balance { amount: balance }) - } else { - Err(StdError::generic_err("Not a registered asset")) - } -} - -pub fn holders(deps: Deps) -> StdResult { - Ok(treasury_manager::QueryAnswer::Holders { - holders: HOLDERS.load(deps.storage)?, - }) -} - -pub fn holding(deps: Deps, holder: Addr) -> StdResult { - match HOLDING.may_load(deps.storage, holder)? { - Some(h) => Ok(treasury_manager::QueryAnswer::Holding { holding: h }), - None => Err(StdError::generic_err("Not a holder")), - } -} diff --git a/contracts/dao/treasury_manager/src/storage.rs b/contracts/dao/treasury_manager/src/storage.rs deleted file mode 100644 index 16989e3..0000000 --- a/contracts/dao/treasury_manager/src/storage.rs +++ /dev/null @@ -1,21 +0,0 @@ -use shade_protocol::{ - c_std::{Addr, Uint128}, - dao::treasury_manager::{AllocationMeta, Config, Holding, Metric}, - secret_storage_plus::{Item, Map}, - snip20::helpers::Snip20Asset, - utils::storage::plus::period_storage::PeriodStorage, -}; - -pub const CONFIG: Item = Item::new("config"); -pub const VIEWING_KEY: Item = Item::new("viewing_key"); - -pub const ASSET_LIST: Item> = Item::new("asset_list"); -pub const ASSETS: Map = Map::new("assets"); - -pub const ALLOCATIONS: Map> = Map::new("allocations"); -pub const HOLDERS: Item> = Item::new("holders"); -pub const HOLDING: Map = Map::new("holding"); -pub const UNBONDINGS: Map = Map::new("unbondings"); - -pub const METRICS: PeriodStorage = - PeriodStorage::new("metrics-all", "metrics-recent", "metrics-timed"); diff --git a/contracts/dao/treasury_manager/tests/integration/batch.rs b/contracts/dao/treasury_manager/tests/integration/batch.rs deleted file mode 100644 index ae68f30..0000000 --- a/contracts/dao/treasury_manager/tests/integration/batch.rs +++ /dev/null @@ -1,138 +0,0 @@ -use shade_multi_test::{ - multi::{admin::init_admin_auth, snip20::Snip20, treasury_manager::TreasuryManager}, -}; -use shade_protocol::{ - c_std::{ - to_binary, - Addr, - Uint128, - }, - multi_test::{App}, -}; -use shade_protocol::{ - contract_interfaces::{ - dao::{ - manager, - treasury_manager::{self}, - }, - snip20, - }, - utils::{ - ExecuteCallback, - InstantiateCallback, - MultiTestable, - Query, - }, -}; - -// Add other adapters here as they come -fn batch_balance_test(balances: Vec) { - let mut app = App::default(); - - let admin = Addr::unchecked("admin"); - let _user = Addr::unchecked("user"); - let admin_auth = init_admin_auth(&mut app, &admin); - - let viewing_key = "viewing_key".to_string(); - - let manager = treasury_manager::InstantiateMsg { - admin_auth: admin_auth.clone().into(), - viewing_key: viewing_key.clone(), - treasury: admin.to_string().clone(), - } - .test_init( - TreasuryManager::default(), - &mut app, - admin.clone(), - "treasury", - &[], - ) - .unwrap(); - - let mut tokens = vec![]; - - for bal in balances.clone() { - let token = snip20::InstantiateMsg { - name: "token".into(), - admin: Some("admin".into()), - symbol: "TKN".into(), - decimals: 6, - initial_balances: Some(vec![snip20::InitialBalance { - address: admin.to_string().clone(), - amount: bal.clone(), - }]), - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - query_auth: None, - } - .test_init( - Snip20::default(), - &mut app, - admin.clone(), - &bal.to_string(), - &[], - ) - .unwrap(); - - treasury_manager::ExecuteMsg::RegisterAsset { - contract: token.clone().into(), - } - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // Deposit funds as treasury - snip20::ExecuteMsg::Send { - recipient: manager.address.to_string().clone(), - recipient_code_hash: None, - amount: bal, - msg: None, - memo: None, - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - tokens.push(token); - } - - // Treasury Balances - match manager::QueryMsg::Manager(manager::SubQueryMsg::BatchBalance { - assets: tokens - .iter() - .map(|t| t.address.to_string().clone()) - .collect(), - holder: admin.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::BatchBalance { amounts } => { - assert!(amounts == balances, "Reported balances match inputs"); - } - _ => { - panic!("Failed to query batch balances"); - } - } -} - -macro_rules! batch_balance_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - batch_balance_test($value.into_iter().map(|a| Uint128::new(a as u128)).collect()); - } - )* - } -} - -batch_balance_tests! { - batch_balances_0: vec![10, 23840, 8402840, 123456, 0], -} diff --git a/contracts/dao/treasury_manager/tests/integration/config.rs b/contracts/dao/treasury_manager/tests/integration/config.rs deleted file mode 100644 index 5d7c69b..0000000 --- a/contracts/dao/treasury_manager/tests/integration/config.rs +++ /dev/null @@ -1,86 +0,0 @@ -use shade_multi_test::interfaces::{ - dao::init_dao, - treasury_manager, - utils::{DeployedContracts, SupportedContracts}, -}; -use shade_protocol::{ - c_std::{Addr, Uint128}, - contract_interfaces::dao::{self, treasury::AllowanceType, treasury_manager::AllocationType}, - multi_test::App, - utils::{ - asset::{Contract, RawContract}, - cycle::Cycle, - }, -}; - -#[test] -pub fn update_config() { - let mut app = App::default(); - let mut contracts = DeployedContracts::new(); - init_dao( - &mut app, - "admin", - &mut contracts, - Uint128::new(1500), - "SSCRT", - vec![ - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - ], - vec![Cycle::Constant; 4], - vec![ - Uint128::new(200), // Amount - 50 - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - Uint128::new(300), // Amount - 100 - Uint128::new(3 * 10u128.pow(17)), // Portion - 40% - ], // Allowance amount - vec![Uint128::zero(); 4], - vec![ - vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount - ]; - 4 - ], - vec![ - vec![ - Uint128::new(6 * 10u128.pow(17)), - Uint128::new(50), - Uint128::new(2 * 10u128.pow(17)), - Uint128::new(75), - ]; - 4 - ], - vec![vec![Uint128::zero(); 4]; 4], - true, - true, - ) - .unwrap(); - treasury_manager::update_config_exec( - &mut app, - "admin", - &contracts, - SupportedContracts::TreasuryManager(0), - Some(RawContract { - address: "rando2".to_string(), - code_hash: "rando3".to_string(), - }), - Some(Addr::unchecked("rando").into()), - ) - .unwrap(); - assert_eq!( - treasury_manager::config_query(&app, &contracts, SupportedContracts::TreasuryManager(0)) - .unwrap(), - dao::treasury_manager::Config { - admin_auth: Contract { - address: Addr::unchecked("rando2"), - code_hash: "rando3".to_string(), - }, - treasury: Addr::unchecked("rando"), - } - ); -} diff --git a/contracts/dao/treasury_manager/tests/integration/execute_error.rs b/contracts/dao/treasury_manager/tests/integration/execute_error.rs deleted file mode 100644 index c464713..0000000 --- a/contracts/dao/treasury_manager/tests/integration/execute_error.rs +++ /dev/null @@ -1,183 +0,0 @@ -use shade_multi_test::interfaces::{ - dao::init_dao, - snip20, - treasury_manager, - utils::{DeployedContracts, SupportedContracts}, -}; -use shade_protocol::{ - c_std::Uint128, - contract_interfaces::dao::{treasury::AllowanceType, treasury_manager::AllocationType}, - multi_test::App, - utils::cycle::Cycle, -}; - -#[test] -pub fn execute_error() { - let mut app = App::default(); - let mut contracts = DeployedContracts::new(); - init_dao( - &mut app, - "admin", - &mut contracts, - Uint128::new(1500), - "SSCRT", - vec![ - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - ], - vec![Cycle::Constant; 4], - vec![ - Uint128::new(200), // Amount - 50 - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - Uint128::new(300), // Amount - 100 - Uint128::new(3 * 10u128.pow(17)), // Portion - 40% - ], // Allowance amount - vec![Uint128::zero(); 4], - vec![ - vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount - ]; - 4 - ], - vec![ - vec![ - Uint128::new(6 * 10u128.pow(17)), - Uint128::new(50), - Uint128::new(2 * 10u128.pow(17)), - Uint128::new(75), - ]; - 4 - ], - vec![vec![Uint128::zero(); 4]; 4], - true, - true, - ) - .unwrap(); - assert!( - !treasury_manager::allocate_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - None, - &SupportedContracts::MockAdapter(0), - AllocationType::Amount, - Uint128::new(1), - Uint128::new(10u128.pow(18u32)), - 0, - ) - .is_ok() - ); - assert!( - !treasury_manager::allocate_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - None, - &SupportedContracts::MockAdapter(0), - AllocationType::Portion, - Uint128::new(10u128.pow(18u32)), - Uint128::new(1), - 0, - ) - .is_ok() - ); - snip20::init(&mut app, "admin", &mut contracts, "Shade", "SHD", 8, None).unwrap(); - assert!( - !treasury_manager::claim_exec( - &mut app, - "admin", - &contracts, - "SHD", - SupportedContracts::TreasuryManager(0) - ) - .is_ok() - ); - treasury_manager::register_holder_exec( - &mut app, - "admin", - &contracts, - SupportedContracts::TreasuryManager(0), - "holder", - ) - .unwrap(); - assert!( - !treasury_manager::unbond_exec( - &mut app, - "holder", - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(0), - Uint128::new(1) - ) - .is_ok() - ); - snip20::send_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - "holder".to_string(), - Uint128::new(2), - None, - ) - .unwrap(); - snip20::send_exec( - &mut app, - "holder", - &contracts, - "SSCRT", - contracts[&SupportedContracts::TreasuryManager(0)] - .address - .clone() - .into(), - Uint128::new(1), - None, - ) - .unwrap(); - assert!( - !treasury_manager::unbond_exec( - &mut app, - "holder", - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(0), - Uint128::new(2) - ) - .is_ok() - ); - treasury_manager::register_asset_exec( - &mut app, - "admin", - &contracts, - "SHD", - SupportedContracts::TreasuryManager(0), - ) - .unwrap(); - assert!( - !treasury_manager::register_holder_exec( - &mut app, - "admin", - &contracts, - SupportedContracts::TreasuryManager(0), - "holder", - ) - .is_ok() - ); - assert!( - !treasury_manager::remove_holder_exec( - &mut app, - "admin", - &contracts, - SupportedContracts::TreasuryManager(0), - "not_a_holdler" - ) - .is_ok() - ); -} diff --git a/contracts/dao/treasury_manager/tests/integration/holder_integration.rs b/contracts/dao/treasury_manager/tests/integration/holder_integration.rs deleted file mode 100644 index 966de76..0000000 --- a/contracts/dao/treasury_manager/tests/integration/holder_integration.rs +++ /dev/null @@ -1,345 +0,0 @@ -use shade_multi_test::multi::admin::init_admin_auth; -use shade_protocol::c_std::{ - to_binary, - Addr, - Uint128, -}; - -//use shade_protocol::secret_toolkit::snip20; - -use shade_multi_test::multi::{snip20::Snip20, treasury_manager::TreasuryManager}; -use shade_protocol::{ - dao::{manager, treasury_manager}, - multi_test::App, - snip20, - utils::{ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -/* No adapters configured - * All assets will sit on manager unused as "reserves" - * No need to "claim" as "unbond" will send up to "reserves" - */ -fn single_asset_holder_no_adapters(initial: Uint128, deposit: Uint128) { - let mut app = App::default(); - - let viewing_key = "unguessable".to_string(); - - let admin = Addr::unchecked("admin"); - let holder = Addr::unchecked("holder"); - let treasury = Addr::unchecked("treasury"); - let admin_auth = init_admin_auth(&mut app, &admin); - - let token = snip20::InstantiateMsg { - name: "token".into(), - admin: Some("admin".into()), - symbol: "TKN".into(), - decimals: 6, - initial_balances: Some(vec![snip20::InitialBalance { - address: holder.to_string().clone(), - amount: initial, - }]), - prng_seed: to_binary("").ok().unwrap(), - config: None, - query_auth: None, - } - .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) - .unwrap(); - - let manager = treasury_manager::InstantiateMsg { - admin_auth: admin_auth.into(), - treasury: treasury.clone().into(), - viewing_key: viewing_key.clone(), - } - .test_init( - TreasuryManager::default(), - &mut app, - admin.clone(), - "manager", - &[], - ) - .unwrap(); - - // set holder viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, holder.clone(), &[]) - .unwrap(); - - // Register manager assets - treasury_manager::ExecuteMsg::RegisterAsset { - contract: token.clone().into(), - } - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // Add 'holder' as holder - treasury_manager::ExecuteMsg::AddHolder { - holder: holder.to_string().clone(), - } - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // Deposit funds into manager - snip20::ExecuteMsg::Send { - recipient: manager.address.to_string().clone(), - recipient_code_hash: None, - amount: deposit, - msg: None, - memo: None, - padding: None, - } - .test_exec(&token, &mut app, holder.clone(), &[]) - .unwrap(); - - // Balance Checks - - // manager reported holder balance - match manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Balance { amount } => { - assert_eq!(amount, deposit, "Pre-unbond Manager Holder Balance"); - } - _ => panic!("Query failed"), - }; - - // manager reported treasury balance - match manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - holder: treasury.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Balance { amount } => { - assert_eq!( - amount, - Uint128::zero(), - "Pre-unbond Manager Treasury Balance" - ); - } - _ => panic!("Query failed"), - }; - - // Manager reported total asset balance - match manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Balance { amount } => { - assert_eq!(amount, deposit, "Pre-unbond Manager Total Balance"); - } - _ => panic!("Query failed"), - }; - - // holder snip20 bal - match (snip20::QueryMsg::Balance { - address: holder.to_string().clone(), - key: viewing_key.clone(), - } - .test_query(&token, &app) - .unwrap()) - { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!( - amount.u128(), - initial.u128() - deposit.u128(), - "Pre-unbond Holder Snip20 balance" - ); - } - _ => { - panic!("Query failed"); - } - }; - - // Unbondable - match manager::QueryMsg::Manager(manager::SubQueryMsg::Unbondable { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Unbondable { amount } => { - assert_eq!(amount, deposit, "Pre-unbond unbondable"); - } - _ => panic!("Query failed"), - }; - - // Reserves - match manager::QueryMsg::Manager(manager::SubQueryMsg::Reserves { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Reserves { amount } => { - assert_eq!(amount, deposit, "Pre-unbond reserves"); - } - _ => panic!("Query failed"), - }; - - let unbond_amount = Uint128::new(deposit.u128() / 2); - - // unbond from manager - manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Unbond { - asset: token.address.to_string().clone().to_string(), - amount: unbond_amount, - }) - .test_exec(&manager, &mut app, holder.clone(), &[]) - .unwrap(); - - // Unbondable - match manager::QueryMsg::Manager(manager::SubQueryMsg::Unbondable { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Unbondable { amount } => { - assert_eq!( - amount, - Uint128::new(deposit.u128() - unbond_amount.u128()), - "Post-unbond total unbondable" - ); - } - _ => panic!("Query failed"), - }; - - match manager::QueryMsg::Manager(manager::SubQueryMsg::Unbondable { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Unbondable { amount } => { - assert_eq!( - amount, - Uint128::new(deposit.u128() - unbond_amount.u128()), - "Post-unbond holder unbondable" - ); - } - _ => panic!("Query failed"), - }; - - // Unbonding - match manager::QueryMsg::Manager(manager::SubQueryMsg::Unbonding { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Unbonding { amount } => { - assert_eq!(amount, Uint128::zero(), "Post-unbond total unbonding"); - } - _ => panic!("Query failed"), - }; - - match manager::QueryMsg::Manager(manager::SubQueryMsg::Unbonding { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Unbonding { amount } => { - assert_eq!(amount, Uint128::zero(), "Post-unbond Holder Unbonding"); - } - _ => panic!("Query failed"), - }; - - // Claimable (zero as its immediately claimed) - match manager::QueryMsg::Manager(manager::SubQueryMsg::Claimable { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Claimable { amount } => { - assert_eq!(amount, Uint128::zero(), "Post-unbond total claimable"); - } - _ => panic!("Query failed"), - }; - - match manager::QueryMsg::Manager(manager::SubQueryMsg::Claimable { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Claimable { amount } => { - assert_eq!(amount, Uint128::zero(), "Post-unbond holder claimable"); - } - _ => panic!("Query failed"), - }; - - // Manager reflects unbonded - match manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Balance { amount } => { - assert_eq!(amount.u128(), deposit.u128() - unbond_amount.u128()); - } - _ => { - panic!("Query failed"); - } - }; - - // user received unbonded - match (snip20::QueryMsg::Balance { - address: holder.to_string().clone(), - key: viewing_key.clone(), - } - .test_query(&token, &app) - .unwrap()) - { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!( - amount.u128(), - (initial.u128() - deposit.u128()) + unbond_amount.u128(), - "Post-claim holder snip20 balance" - ); - } - _ => { - panic!("Query failed"); - } - }; -} - -macro_rules! single_asset_holder_no_adapters_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let (initial, deposit) = $value; - single_asset_holder_no_adapters(initial, deposit); - } - )* - } -} - -single_asset_holder_no_adapters_tests! { - single_asset_holder_no_adapters_0: ( - Uint128::new(100_000_000), - Uint128::new(50_000_000), - ), -} diff --git a/contracts/dao/treasury_manager/tests/integration/mod.rs b/contracts/dao/treasury_manager/tests/integration/mod.rs deleted file mode 100644 index 89deaae..0000000 --- a/contracts/dao/treasury_manager/tests/integration/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod batch; -pub mod config; -pub mod execute_error; -pub mod holder_integration; -pub mod multiple_holders; -pub mod query; -pub mod scrt_staking_integration; -pub mod tm_unbond; -pub mod tolerance; diff --git a/contracts/dao/treasury_manager/tests/integration/multiple_holders.rs b/contracts/dao/treasury_manager/tests/integration/multiple_holders.rs deleted file mode 100644 index 5be17c2..0000000 --- a/contracts/dao/treasury_manager/tests/integration/multiple_holders.rs +++ /dev/null @@ -1,416 +0,0 @@ -use shade_multi_test::interfaces::{ - dao::{ - init_dao, - mock_adapter_complete_unbonding, - system_balance_reserves, - system_balance_unbondable, - update_dao, - }, - snip20, - treasury_manager, - utils::{DeployedContracts, SupportedContracts}, -}; -use shade_protocol::{ - c_std::Uint128, - contract_interfaces::dao::{treasury::AllowanceType, treasury_manager::AllocationType}, - multi_test::App, - utils::cycle::Cycle, -}; - -pub fn multiple_holders( - is_instant_unbond: bool, - after_holder_adds_tokens: (Uint128, Vec<(Uint128, Vec)>), - after_holder_removed: (Uint128, Vec<(Uint128, Vec)>), -) { - let num_managers = 4; - const HOLDER: &str = "holder"; - let mut app = App::default(); - let mut contracts = DeployedContracts::new(); - init_dao( - &mut app, - "admin", - &mut contracts, - Uint128::new(1500), - "SSCRT", - vec![ - AllowanceType::Amount, - AllowanceType::Portion, - AllowanceType::Amount, - AllowanceType::Portion, - ], - vec![Cycle::Constant; 4], - vec![ - Uint128::new(200), // Amount - 50 - Uint128::new(6 * 10u128.pow(17)), // Poriton - 60% - Uint128::new(300), // Amount - 100 - Uint128::new(3 * 10u128.pow(17)), // Portion - 40% - ], // Allowance amount - vec![Uint128::zero(); 4], - vec![ - vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount - ]; - 4 - ], - vec![ - vec![ - Uint128::new(6 * 10u128.pow(17)), - Uint128::new(50), - Uint128::new(2 * 10u128.pow(17)), - Uint128::new(75), - ]; - 4 - ], - vec![vec![Uint128::zero(); 4]; 4], - is_instant_unbond, - true, - ) - .unwrap(); - let bals = { - if is_instant_unbond { - system_balance_reserves(&app, &contracts, "SSCRT") - } else { - system_balance_unbondable(&app, &contracts, "SSCRT") - } - }; - assert_eq!(bals, after_holder_removed); - snip20::set_viewing_key_exec(&mut app, HOLDER, &contracts, "SSCRT", HOLDER.to_string()) - .unwrap(); - snip20::send_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - HOLDER.to_string(), - Uint128::new(1000), - None, - ) - .unwrap(); - treasury_manager::register_holder_exec( - &mut app, - "admin", - &contracts, - SupportedContracts::TreasuryManager(0), - HOLDER, - ) - .unwrap(); - snip20::send_exec( - &mut app, - HOLDER, - &contracts, - "SSCRT", - contracts[&SupportedContracts::TreasuryManager(0)] - .address - .to_string(), - Uint128::new(200), - None, - ) - .unwrap(); - snip20::send_exec( - &mut app, - HOLDER, - &contracts, - "SSCRT", - contracts[&SupportedContracts::TreasuryManager(0)] - .address - .to_string(), - Uint128::new(300), - None, - ) - .unwrap(); - assert_eq!( - Uint128::new(500), - treasury_manager::holding_query( - &app, - &contracts, - SupportedContracts::TreasuryManager(0), - HOLDER.to_string(), - ) - .unwrap() - .balances[0] - .amount - ); - update_dao(&mut app, "admin", &contracts, "SSCRT", num_managers).unwrap(); - let bals = { - if is_instant_unbond { - system_balance_reserves(&app, &contracts, "SSCRT") - } else { - system_balance_unbondable(&app, &contracts, "SSCRT") - } - }; - assert_eq!(bals, after_holder_adds_tokens); - treasury_manager::unbond_exec( - &mut app, - HOLDER, - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(0), - Uint128::new(300), - ) - .unwrap(); - if !is_instant_unbond { - update_dao(&mut app, "admin", &contracts, "SSCRT", num_managers).unwrap(); - let mut k = 0; - for _i in 0..num_managers { - for _j in 0..4 { - mock_adapter_complete_unbonding( - &mut app, - "admin", - &contracts, - SupportedContracts::MockAdapter(k), - ) - .unwrap(); - k += 1; - } - k += 1; - } - } - update_dao(&mut app, "admin", &contracts, "SSCRT", num_managers).unwrap(); - treasury_manager::claim_exec( - &mut app, - HOLDER, - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(0), - ) - .unwrap(); - match treasury_manager::remove_holder_exec( - &mut app, - "rando", - &contracts, - SupportedContracts::TreasuryManager(0), - HOLDER.clone(), - ) { - Ok(_) => assert!(false, "unauthorized removing of HOLDER"), - Err(_) => assert!(true), - } - treasury_manager::remove_holder_exec( - &mut app, - "admin", - &contracts, - SupportedContracts::TreasuryManager(0), - HOLDER.clone(), - ) - .unwrap(); - match treasury_manager::remove_holder_exec( - &mut app, - "admin", - &contracts, - SupportedContracts::TreasuryManager(0), - &contracts[&SupportedContracts::Treasury].address.to_string(), - ) { - Ok(_) => assert!(false, "removed treasury as a HOLDER"), - Err(_) => assert!(true), - } - match snip20::send_exec( - &mut app, - HOLDER, - &contracts, - "SSCRT", - contracts[&SupportedContracts::TreasuryManager(0)] - .address - .to_string(), - Uint128::new(300), - None, - ) { - Ok(_) => assert!(false, "closed HOLDERs shouldn't be able to send to TM"), - Err(_) => assert!(true), - } - treasury_manager::unbond_exec( - &mut app, - HOLDER, - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(0), - Uint128::zero(), - ) - .unwrap(); - if !is_instant_unbond { - let mut k = 0; - for _i in 0..num_managers { - for _j in 0..4 { - mock_adapter_complete_unbonding( - &mut app, - "admin", - &contracts, - SupportedContracts::MockAdapter(k), - ) - .unwrap(); - k += 1; - } - k += 1; - } - } - treasury_manager::claim_exec( - &mut app, - HOLDER, - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(0), - ) - .unwrap(); - update_dao(&mut app, "admin", &contracts, "SSCRT", num_managers).unwrap(); - if !is_instant_unbond { - let mut k = 0; - for _i in 0..num_managers { - for _j in 0..4 { - mock_adapter_complete_unbonding( - &mut app, - "admin", - &contracts, - SupportedContracts::MockAdapter(k), - ) - .unwrap(); - k += 1; - } - k += 1; - } - treasury_manager::claim_exec( - &mut app, - HOLDER, - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(0), - ) - .unwrap(); - } - update_dao(&mut app, "admin", &contracts, "SSCRT", num_managers).unwrap(); - match treasury_manager::holding_query( - &app, - &contracts, - SupportedContracts::TreasuryManager(0), - HOLDER.to_string(), - ) { - Ok(_) => assert!(false, "HOLDER was not removed"), - Err(_) => assert!(true), - } - let bals = { - if is_instant_unbond { - system_balance_reserves(&app, &contracts, "SSCRT") - } else { - system_balance_unbondable(&app, &contracts, "SSCRT") - } - }; - assert_eq!(bals, after_holder_removed); -} - -#[test] -pub fn mul_holders() { - multiple_holders( - true, - (Uint128::new(280), vec![ - (Uint128::new(100), vec![ - Uint128::new(345), - Uint128::new(50), - Uint128::new(115), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(285), - Uint128::new(50), - Uint128::new(95), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(105), - Uint128::new(50), - Uint128::new(35), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(105), - Uint128::new(50), - Uint128::new(35), - Uint128::new(75), - ]), - ]), - (Uint128::new(280), vec![ - (Uint128::new(0), vec![ - Uint128::new(45), - Uint128::new(50), - Uint128::new(15), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(285), - Uint128::new(50), - Uint128::new(95), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(105), - Uint128::new(50), - Uint128::new(35), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(105), - Uint128::new(50), - Uint128::new(35), - Uint128::new(75), - ]), - ]), - ); -} - -#[test] -pub fn mul_holders_unbond() { - multiple_holders( - false, - (Uint128::new(280), vec![ - (Uint128::new(100), vec![ - Uint128::new(345), - Uint128::new(50), - Uint128::new(115), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(285), - Uint128::new(50), - Uint128::new(95), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(105), - Uint128::new(50), - Uint128::new(35), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(105), - Uint128::new(50), - Uint128::new(35), - Uint128::new(75), - ]), - ]), - (Uint128::new(280), vec![ - (Uint128::new(0), vec![ - Uint128::new(45), - Uint128::new(50), - Uint128::new(15), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(285), - Uint128::new(50), - Uint128::new(95), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(105), - Uint128::new(50), - Uint128::new(35), - Uint128::new(75), - ]), - (Uint128::new(0), vec![ - Uint128::new(105), - Uint128::new(50), - Uint128::new(35), - Uint128::new(75), - ]), - ]), - ); -} diff --git a/contracts/dao/treasury_manager/tests/integration/query.rs b/contracts/dao/treasury_manager/tests/integration/query.rs deleted file mode 100644 index 0531c58..0000000 --- a/contracts/dao/treasury_manager/tests/integration/query.rs +++ /dev/null @@ -1,400 +0,0 @@ -use shade_multi_test::interfaces::{ - dao::{init_dao, mock_adapter_sub_tokens, update_dao}, - snip20, - treasury_manager, - utils::{DeployedContracts, SupportedContracts}, -}; -use shade_protocol::{ - c_std::{BlockInfo, Timestamp, Uint128}, - contract_interfaces::dao::{treasury::AllowanceType, treasury_manager::AllocationType}, - multi_test::App, - utils::{ - cycle::{parse_utc_datetime, Cycle}, - storage::plus::period_storage::Period, - }, -}; - -#[test] -pub fn query() { - let mut app = App::default(); - let mut contracts = DeployedContracts::new(); - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds( - parse_utc_datetime(&"1995-11-13T00:00:00.00Z".to_string()) - .unwrap() - .timestamp() as u64, - ), - chain_id: "chain_id".to_string(), - }); - init_dao( - &mut app, - "admin", - &mut contracts, - Uint128::new(1500), - "SSCRT", - vec![AllowanceType::Amount], - vec![Cycle::Constant], - vec![ - Uint128::new(1500), // Amount - 50 - ], // Allowance amount - vec![Uint128::zero(); 4], - vec![ - vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount - ]; - 4 - ], - vec![ - vec![ - Uint128::new(6 * 10u128.pow(17)), - Uint128::new(50), - Uint128::new(2 * 10u128.pow(17)), - Uint128::new(75), - ]; - 4 - ], - vec![vec![Uint128::zero(); 4]; 4], - true, - true, - ) - .unwrap(); - assert_eq!( - treasury_manager::batch_balance_query( - &app, - &contracts, - vec!["SSCRT"], - SupportedContracts::TreasuryManager(0), - SupportedContracts::Treasury - ) - .unwrap()[0] - + treasury_manager::pending_allowance_query( - &app, - &contracts, - SupportedContracts::TreasuryManager(0), - "SSCRT" - ) - .unwrap(), - Uint128::new(1500) - ); - snip20::init(&mut app, "admin", &mut contracts, "Shade", "SHD", 8, None).unwrap(); - assert!( - !treasury_manager::pending_allowance_query( - &app, - &contracts, - SupportedContracts::TreasuryManager(0), - "SHD" - ) - .is_ok() - ); - assert!( - !treasury_manager::assets_query(&app, &contracts, SupportedContracts::TreasuryManager(0),) - .unwrap() - .is_empty() - ); - assert_eq!( - treasury_manager::allocations_query( - &app, - &contracts, - SupportedContracts::TreasuryManager(0), - "SHD" - ) - .unwrap(), - vec![] - ); - assert!( - !treasury_manager::allocations_query( - &app, - &contracts, - SupportedContracts::TreasuryManager(0), - "SSCRT" - ) - .unwrap() - .is_empty(), - ); - assert!( - !treasury_manager::holders_query(&app, &contracts, SupportedContracts::TreasuryManager(0),) - .unwrap() - .is_empty(), - ); - assert_eq!( - treasury_manager::batch_balance_query( - &app, - &contracts, - vec!["SSCRT", "SHD"], - SupportedContracts::TreasuryManager(0), - SupportedContracts::Treasury - ) - .unwrap(), - vec![Uint128::new(1225), Uint128::zero()] - ); - assert!( - !treasury_manager::batch_balance_query( - &app, - &contracts, - vec!["SSCRT", "SHD"], - SupportedContracts::TreasuryManager(0), - SupportedContracts::AdminAuth - ) - .is_ok() - ); - assert!( - !treasury_manager::balance_query( - &app, - &contracts, - "SHD", - SupportedContracts::TreasuryManager(0), - SupportedContracts::Treasury - ) - .is_ok() - ); - assert!( - !treasury_manager::balance_query( - &app, - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(0), - SupportedContracts::AdminAuth - ) - .is_ok() - ); - assert!( - !treasury_manager::unbonding_query( - &app, - &contracts, - "SHD", - SupportedContracts::TreasuryManager(0), - SupportedContracts::Treasury - ) - .is_ok() - ); - assert!( - !treasury_manager::unbonding_query( - &app, - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(0), - SupportedContracts::AdminAuth - ) - .is_ok() - ); - assert!( - !treasury_manager::unbondable_query( - &app, - &contracts, - "SHD", - SupportedContracts::TreasuryManager(0), - SupportedContracts::Treasury - ) - .is_ok() - ); - assert!( - !treasury_manager::unbondable_query( - &app, - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(0), - SupportedContracts::AdminAuth - ) - .is_ok() - ); - assert!( - !treasury_manager::reserves_query( - &app, - &contracts, - "SHD", - SupportedContracts::TreasuryManager(0), - SupportedContracts::Treasury - ) - .is_ok() - ); - assert!( - !treasury_manager::claimable_query( - &app, - &contracts, - "SHD", - SupportedContracts::TreasuryManager(0), - SupportedContracts::Treasury - ) - .is_ok() - ); - assert!( - !treasury_manager::claimable_query( - &app, - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(0), - SupportedContracts::AdminAuth - ) - .is_ok() - ); - treasury_manager::register_asset_exec( - &mut app, - "admin", - &contracts, - "SHD", - SupportedContracts::TreasuryManager(0), - ) - .unwrap(); - assert!( - !treasury_manager::metrics_query( - &app, - &contracts, - SupportedContracts::TreasuryManager(0), - Some("1995-11-13T00:00:00.00Z".to_string()), - None, - Period::Hour, - ) - .unwrap() - .is_empty() - ); - assert!( - !treasury_manager::metrics_query( - &app, - &contracts, - SupportedContracts::TreasuryManager(0), - Some("1995-11-13T00:00:00.00Z".to_string()), - None, - Period::Day, - ) - .unwrap() - .is_empty() - ); - assert!( - !treasury_manager::metrics_query( - &app, - &contracts, - SupportedContracts::TreasuryManager(0), - Some("1995-11-13T00:00:00.00Z".to_string()), - None, - Period::Month, - ) - .unwrap() - .is_empty() - ); - assert!( - !treasury_manager::metrics_query( - &app, - &contracts, - SupportedContracts::TreasuryManager(0), - None, - Some(Uint128::new(816220800)), - Period::Hour, - ) - .unwrap() - .is_empty() - ); - assert!( - !treasury_manager::metrics_query( - &app, - &contracts, - SupportedContracts::TreasuryManager(0), - None, - Some(Uint128::new(816220800)), - Period::Day, - ) - .unwrap() - .is_empty() - ); - assert!( - !treasury_manager::metrics_query( - &app, - &contracts, - SupportedContracts::TreasuryManager(0), - None, - Some(Uint128::new(816220800)), - Period::Month, - ) - .unwrap() - .is_empty() - ); - assert!( - !treasury_manager::metrics_query( - &app, - &contracts, - SupportedContracts::TreasuryManager(0), - None, - None, - Period::Month, - ) - .unwrap() - .is_empty() - ); - assert!( - !treasury_manager::metrics_query( - &app, - &contracts, - SupportedContracts::TreasuryManager(0), - Some("1995-11-13T00:00:00.00Z".to_string()), - Some(Uint128::new(816220800)), - Period::Month, - ) - .is_ok() - ); - assert!( - treasury_manager::metrics_query( - &app, - &contracts, - SupportedContracts::TreasuryManager(0), - None, - Some(Uint128::new( - parse_utc_datetime(&"1995-12-13T00:00:00.00Z".to_string()) - .unwrap() - .timestamp() as u128 - )), - Period::Month, - ) - .unwrap() - .is_empty() - ); - mock_adapter_sub_tokens( - &mut app, - "admin", - &contracts, - Uint128::new(10), - SupportedContracts::MockAdapter(3), - ) - .unwrap(); - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds( - parse_utc_datetime(&"1995-12-13T00:00:00.00Z".to_string()) - .unwrap() - .timestamp() as u64, - ), - chain_id: "chain_id".to_string(), - }); - update_dao(&mut app, "admin", &contracts, "SSCRT", 1).unwrap(); - update_dao(&mut app, "admin", &contracts, "SSCRT", 1).unwrap(); - assert!( - !treasury_manager::metrics_query( - &app, - &contracts, - SupportedContracts::TreasuryManager(0), - None, - Some(Uint128::new( - parse_utc_datetime(&"1995-12-13T00:00:00.00Z".to_string()) - .unwrap() - .timestamp() as u128 - )), - Period::Month, - ) - .unwrap() - .is_empty() - ); - assert!( - !treasury_manager::metrics_query( - &app, - &contracts, - SupportedContracts::TreasuryManager(0), - Some("1995-12-13T00:00:00.00Z".to_string()), - None, - Period::Month, - ) - .unwrap() - .is_empty() - ); -} diff --git a/contracts/dao/treasury_manager/tests/integration/scrt_staking_integration.rs b/contracts/dao/treasury_manager/tests/integration/scrt_staking_integration.rs deleted file mode 100644 index 033fd95..0000000 --- a/contracts/dao/treasury_manager/tests/integration/scrt_staking_integration.rs +++ /dev/null @@ -1,548 +0,0 @@ -/* -use shade_protocol::{ - c_std::{to_binary, Addr, Coin, Decimal, Uint128, Validator}, - contract_interfaces::{ - dao::{ - adapter, - manager, - scrt_staking, - treasury_manager::{self, Allocation, AllocationType}, - }, - snip20, - }, - utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -use shade_multi_test::multi::{ - admin::init_admin_auth, - scrt_staking::ScrtStaking, - snip20::Snip20, - treasury_manager::TreasuryManager, -}; -use shade_protocol::multi_test::{App, BankSudo, StakingSudo, SudoMsg}; - -/* No adapters configured - * All assets will sit on manager unused as "reserves" - * No need to "claim" as "unbond" will send up to "reserves" - */ -fn single_holder_scrt_staking_adapter( - deposit: Uint128, - alloc_type: AllocationType, - alloc_amount: Uint128, - rewards: Uint128, - expected_scrt_staking: Uint128, - expected_manager_holder: Uint128, - expected_manager_treasury: Uint128, - unbond_amount: Uint128, -) { - let mut app = App::default(); - let viewing_key = "unguessable".to_string(); - - let admin = Addr::unchecked("admin"); - let holder = Addr::unchecked("holder"); - let treasury = Addr::unchecked("treasury"); - let validator = Addr::unchecked("validator"); - let admin_auth = init_admin_auth(&mut app, &admin); - - app.sudo(SudoMsg::Staking(StakingSudo::AddValidator { - validator: validator.to_string().clone(), - })) - .unwrap(); - - let token = snip20::InstantiateMsg { - name: "secretSCRT".into(), - admin: Some("admin".into()), - symbol: "SSCRT".into(), - decimals: 6, - initial_balances: None, - prng_seed: to_binary("").ok().unwrap(), - query_auth: None, - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) - .unwrap(); - - let manager = treasury_manager::InstantiateMsg { - admin_auth: admin_auth.clone().into(), - treasury: treasury.clone().into(), - viewing_key: viewing_key.clone(), - } - .test_init( - TreasuryManager::default(), - &mut app, - admin.clone(), - "manager", - &[], - ) - .unwrap(); - - let scrt_staking = scrt_staking::InstantiateMsg { - admin_auth: admin_auth.into(), - owner: manager.address.to_string().clone().into(), - sscrt: token.clone().into(), - validator_bounds: None, - viewing_key: viewing_key.clone(), - } - .test_init( - ScrtStaking::default(), - &mut app, - admin.clone(), - "scrt_staking", - &[], - ) - .unwrap(); - println!("scrt staking {}", scrt_staking.address.clone()); - - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, holder.clone(), &[]) - .unwrap(); - - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, treasury.clone(), &[]) - .unwrap(); - - // Register manager assets - treasury_manager::ExecuteMsg::RegisterAsset { - contract: token.clone().into(), - } - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // Add 'holder' as holder - treasury_manager::ExecuteMsg::AddHolder { - holder: holder.to_string().clone().into(), - } - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // Allocate to scrt_staking from manager - treasury_manager::ExecuteMsg::Allocate { - asset: token.address.to_string().clone(), - allocation: Allocation { - nick: Some("scrt_staking".to_string()), - contract: Contract { - address: scrt_staking.address.clone(), - code_hash: scrt_staking.code_hash.clone(), - }, - alloc_type, - amount: alloc_amount, - tolerance: Uint128::zero(), - }, - } - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - let deposit_coin = Coin { - denom: "uscrt".into(), - amount: deposit, - }; - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: holder.to_string().clone(), - amount: vec![deposit_coin.clone()], - })) - .unwrap(); - - assert!(deposit_coin.amount > Uint128::zero()); - - // Wrap L1 - &snip20::ExecuteMsg::Deposit { padding: None } - .test_exec(&token, &mut app, holder.clone(), &[deposit_coin]) - .unwrap(); - - // Deposit funds into manager - println!("deposit to manager"); - snip20::ExecuteMsg::Send { - recipient: manager.address.to_string().clone(), - recipient_code_hash: None, - amount: deposit, - msg: None, - memo: None, - padding: None, - } - .test_exec(&token, &mut app, holder.clone(), &[]) - .unwrap(); - - // Update manager - manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { - asset: token.address.to_string().clone(), - }) - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // Balance Checks - - // manager reported holder balance - match manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Balance { amount } => { - assert_eq!(amount, deposit, "Pre-unbond Manager Holder Balance"); - } - _ => assert!(false), - }; - - // manager reported treasury balance - match manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - holder: treasury.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Balance { amount } => { - assert_eq!( - amount, - Uint128::zero(), - "Pre-unbond Manager Treasury Balance" - ); - } - _ => assert!(false), - }; - - // scrt staking balance - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap() - { - manager::QueryAnswer::Balance { amount } => { - assert_eq!( - amount, expected_scrt_staking, - "Pre-unbond scrt staking balance" - ); - } - _ => assert!(false), - }; - - // manager unbondable - match manager::QueryMsg::Manager(manager::SubQueryMsg::Unbondable { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Unbondable { amount } => { - assert_eq!(amount, deposit, "Pre-unbond unbondable"); - } - _ => assert!(false), - }; - - let mut reserves = Uint128::zero(); - - // Reserves - match manager::QueryMsg::Manager(manager::SubQueryMsg::Reserves { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Reserves { amount } => { - reserves = amount; - assert_eq!(amount, expected_manager_holder, "Pre-unbond reserves"); - } - _ => assert!(false), - }; - - // Claimable - match manager::QueryMsg::Manager(manager::SubQueryMsg::Claimable { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Claimable { amount } => { - assert_eq!(amount, Uint128::zero(), "Pre-unbond claimable"); - } - _ => assert!(false), - }; - - // Add Rewards - app.sudo(SudoMsg::Staking(StakingSudo::AddRewards { - amount: Coin { - amount: rewards, - denom: "uscrt".into(), - }, - })) - .unwrap(); - - // Update scrt staking to claim & restake rewards - adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { - asset: token.address.to_string().clone(), - }) - .test_exec(&scrt_staking, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update manager to detect & rebalance after gainz - manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { - asset: token.address.to_string().clone(), - }) - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // holder unbond from manager - println!("manager unbond {}", unbond_amount); - manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Unbond { - asset: token.address.to_string().clone(), - amount: unbond_amount, - }) - .test_exec(&manager, &mut app, holder.clone(), &[]) - .unwrap(); - - // scrt staking Unbondable - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbondable { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap() - { - adapter::QueryAnswer::Unbondable { amount } => { - assert_eq!( - amount, - deposit - unbond_amount, - "Post-unbond scrt staking unbondable" - ); - } - _ => assert!(false), - }; - - // manager Unbondable - match manager::QueryMsg::Manager(manager::SubQueryMsg::Unbondable { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Unbondable { amount } => { - assert_eq!( - amount, - deposit - unbond_amount, - "Post-unbond manager holder unbondable" - ); - } - _ => assert!(false), - }; - - // Unbonding - match manager::QueryMsg::Manager(manager::SubQueryMsg::Unbonding { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Unbonding { amount } => { - assert_eq!( - amount, - unbond_amount - reserves, - "Post-unbond manager unbonding" - ); - } - _ => assert!(false), - }; - - // Manager Claimable - match manager::QueryMsg::Manager(manager::SubQueryMsg::Claimable { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Claimable { amount } => { - assert_eq!(amount, Uint128::zero(), "Pre-fastforward manager claimable"); - } - _ => assert!(false), - }; - - app.sudo(SudoMsg::Staking(StakingSudo::FastForwardUndelegate {})) - .unwrap(); - - // Scrt Staking Claimable - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { - asset: token.address.to_string().clone(), - }) - .test_query(&scrt_staking, &app) - .unwrap() - { - adapter::QueryAnswer::Claimable { amount } => { - assert_eq!( - amount, - unbond_amount - reserves + rewards, - "Post-fastforward scrt staking claimable" - ); - } - _ => assert!(false), - }; - - // Manager Claimable - match manager::QueryMsg::Manager(manager::SubQueryMsg::Claimable { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Claimable { amount } => { - assert_eq!( - amount, - unbond_amount - reserves, - "Post-fastforward manager claimable" - ); - } - _ => assert!(false), - }; - - // Claim - manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Claim { - asset: token.address.to_string().clone(), - }) - .test_exec(&manager, &mut app, holder.clone(), &[]) - .unwrap(); - - // Manager Holder Balance - match manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - holder: holder.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Balance { amount } => { - assert_eq!(amount, deposit - unbond_amount); - } - _ => { - assert!(false); - } - }; - - // Manager Treasury Balance - match manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - holder: treasury.to_string().clone(), - }) - .test_query(&manager, &app) - .unwrap() - { - manager::QueryAnswer::Balance { amount } => { - assert_eq!(amount, expected_manager_treasury); - } - _ => assert!(false), - }; - - // user received unbonded - match (snip20::QueryMsg::Balance { - address: holder.to_string().clone(), - key: viewing_key.clone(), - }) - .test_query(&token, &app) - .unwrap() - { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!( - amount.u128(), - unbond_amount.u128(), - "Post-claim holder snip20 balance" - ); - } - _ => { - assert!(false); - } - }; - - /* - // treasury received gainz - match (snip20::QueryMsg::Balance { - address: treasury.to_string().clone(), - key: viewing_key.clone(), - }).test_query(&token, &app).unwrap() { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, expected_manager_treasury, "treasury snip20 balance"); - }, - _ => assert!(false), - }; - */ -} - -macro_rules! single_holder_scrt_staking_adapter_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let (deposit, alloc_type, alloc_amount, rewards, - expected_scrt_staking, expected_manager_holder, expected_manager_treasury, unbond_amount) = $value; - single_holder_scrt_staking_adapter(deposit, alloc_type, alloc_amount, rewards, expected_scrt_staking, expected_manager_holder, expected_manager_treasury, unbond_amount); - } - )* - } -} - -single_holder_scrt_staking_adapter_tests! { - single_holder_scrt_staking_portion: ( - // 100 - Uint128::new(100_000_000), - // % 50 alloc - AllocationType::Portion, - Uint128::new(5u128 * 10u128.pow(17)), - // 0 rewards - Uint128::zero(), - // 50/50 - Uint128::new(50_000_000), - Uint128::new(50_000_000), - Uint128::zero(), - // unbond 75 - Uint128::new(75_000_000), - ), - single_holder_scrt_staking_amount: ( - // 100 - Uint128::new(100_000_000), - // 50 alloc - AllocationType::Amount, - Uint128::new(50_000_000), - // 0 rewards - Uint128::zero(), - // 50/50 - Uint128::new(50_000_000), - Uint128::new(50_000_000), - Uint128::zero(), - // unbond 75 - Uint128::new(75_000_000), - ), - single_holder_scrt_staking_amount_rewards: ( - // 100 - Uint128::new(100_000_000), - // 50 alloc - AllocationType::Amount, - Uint128::new(50_000_000), - // 0 rewards - Uint128::new(100_000_000), - // 50/50 - Uint128::new(50_000_000), - Uint128::new(50_000_000), - Uint128::new(100_000_000), - // unbond 75 - Uint128::new(75_000_000), - ), -} -*/ diff --git a/contracts/dao/treasury_manager/tests/integration/tm_unbond.rs b/contracts/dao/treasury_manager/tests/integration/tm_unbond.rs deleted file mode 100644 index 5d0fe7e..0000000 --- a/contracts/dao/treasury_manager/tests/integration/tm_unbond.rs +++ /dev/null @@ -1,222 +0,0 @@ -use shade_multi_test::interfaces::{ - dao::{init_dao, system_balance_reserves}, - snip20, - treasury_manager, - utils::{DeployedContracts, SupportedContracts}, -}; -use shade_protocol::{ - c_std::Uint128, - contract_interfaces::dao::{treasury::AllowanceType, treasury_manager::AllocationType}, - multi_test::App, - utils::cycle::Cycle, -}; - -pub fn test_tm_unbond( - unbond_amount: Uint128, - adapter_gain_amount: Option, - amount_adapter_bal: (Uint128, Uint128), - expected_before_unbond: (Uint128, Vec<(Uint128, Vec)>), - expected_after_unbond: (Uint128, Vec<(Uint128, Vec)>), -) { - let mut app = App::default(); - let mut contracts = DeployedContracts::new(); - init_dao( - &mut app, - "admin", - &mut contracts, - Uint128::new(1000), - "SSCRT", - vec![AllowanceType::Amount], - vec![Cycle::Constant], - vec![ - Uint128::new(500), // Amount - 500 - ], // Allowance amount - vec![Uint128::zero()], - vec![ - vec![ - AllocationType::Portion, - AllocationType::Amount, - AllocationType::Portion, - AllocationType::Amount - ]; - 4 - ], - vec![ - vec![ - Uint128::new(6 * 10u128.pow(17)), - amount_adapter_bal.0, - Uint128::new(2 * 10u128.pow(17)), - amount_adapter_bal.1, - ]; - 4 - ], - vec![vec![Uint128::zero(); 4]; 4], - true, - true, - ) - .unwrap(); - let bals = system_balance_reserves(&app, &contracts, "SSCRT"); - assert_eq!(bals, expected_before_unbond); - match adapter_gain_amount { - Some(x) => { - for i in vec![1, 3] { - snip20::send_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - contracts - .get(&SupportedContracts::MockAdapter(i)) - .unwrap() - .address - .to_string(), - x, - None, - ) - .unwrap(); - } - } - None => {} - } - treasury_manager::unbond_exec( - &mut app, - "admin", - &contracts, - "SSCRT", - SupportedContracts::TreasuryManager(0), - unbond_amount, - ) - .unwrap(); - - let bals = system_balance_reserves(&app, &contracts, "SSCRT"); - assert_eq!(bals, expected_after_unbond); -} - -macro_rules! dao_tests_tm_unbond { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - unbond_amount, - adapter_gain_amount, - amount_adapter_bal, - expected_before_unbond, - expected_after_unbond, - ) = $value; - test_tm_unbond( - unbond_amount, - adapter_gain_amount, - amount_adapter_bal, - expected_before_unbond, - expected_after_unbond, - ); - } - )* - } -} - -dao_tests_tm_unbond! { - unbond_only_from_amount_adapters:( - Uint128::new(7), - Some(Uint128::new(10)), - (Uint128::new(10), Uint128::new(20)), - (Uint128::new(594), vec![(Uint128::new(0), vec![ - Uint128::new(282), - Uint128::new(10), - Uint128::new(94), - Uint128::new(20), - ])]), - (Uint128::new(594), vec![(Uint128::new(7), vec![ - Uint128::new(282), - Uint128::new(17), - Uint128::new(94), - Uint128::new(26) - ])]) - ), - unbond_only_extra_from_amount_adapters:( - Uint128::new(20), - Some(Uint128::new(10)), - (Uint128::new(10), Uint128::new(20)), - (Uint128::new(594), vec![(Uint128::new(0), vec![ - Uint128::new(282), - Uint128::new(10), - Uint128::new(94), - Uint128::new(20), - ])]), - (Uint128::new(594), vec![(Uint128::new(20), vec![ - Uint128::new(282), - Uint128::new(10), - Uint128::new(94), - Uint128::new(20) - ])]) - ), - unbond_case_extra_from_amount_adapters_and_some_from_portion_adapters:( - Uint128::new(21), - Some(Uint128::new(10)), - (Uint128::new(10), Uint128::new(20)), - (Uint128::new(594), vec![(Uint128::new(0), vec![ - Uint128::new(282), - Uint128::new(10), - Uint128::new(94), - Uint128::new(20), - ])]), - (Uint128::new(594), vec![(Uint128::new(21), vec![ - Uint128::new(282), - Uint128::new(10), - Uint128::new(93), - Uint128::new(20) - ])]) - ), - unbond_case_extra_from_amount_adapters_and_all_from_portion_adapters:( - Uint128::new(396), - Some(Uint128::new(10)), - (Uint128::new(10), Uint128::new(20)), - (Uint128::new(594), vec![(Uint128::new(0), vec![ - Uint128::new(282), - Uint128::new(10), - Uint128::new(94), - Uint128::new(20), - ])]), - (Uint128::new(594), vec![(Uint128::new(396), vec![ - Uint128::new(0), - Uint128::new(10), - Uint128::new(0), - Uint128::new(20) - ])]) - ), - unbond_case_extra_and_some_from_amount_adapters_and_all_from_portion_adapters:( - Uint128::new(391), - None, - (Uint128::new(100), Uint128::new(200)), - (Uint128::new(540), vec![(Uint128::new(0), vec![ - Uint128::new(120), - Uint128::new(100), - Uint128::new(40), - Uint128::new(200), - ])]), - (Uint128::new(540), vec![(Uint128::new(391), vec![ - Uint128::new(0), - Uint128::new(23), - Uint128::new(0), - Uint128::new(46) - ])]) - ), - unbond_all:( - Uint128::new(459), - None, - (Uint128::new(101), Uint128::new(200)), - (Uint128::new(541), vec![(Uint128::new(0), vec![ - Uint128::new(119), - Uint128::new(101), - Uint128::new(39), - Uint128::new(200), - ])]), - (Uint128::new(541), vec![(Uint128::new(459), vec![ - Uint128::new(0), - Uint128::new(0), - Uint128::new(0), - Uint128::new(0) - ])]) - ), -} diff --git a/contracts/dao/treasury_manager/tests/integration/tolerance.rs b/contracts/dao/treasury_manager/tests/integration/tolerance.rs deleted file mode 100644 index 820e97a..0000000 --- a/contracts/dao/treasury_manager/tests/integration/tolerance.rs +++ /dev/null @@ -1,467 +0,0 @@ -use mock_adapter; -use shade_multi_test::multi::{ - admin::init_admin_auth, - mock_adapter::MockAdapter, - snip20::Snip20, - treasury_manager::TreasuryManager, -}; -use shade_protocol::{ - c_std::{to_binary, Addr, Uint128}, - contract_interfaces::{ - dao::{ - adapter, - manager, - treasury_manager::{self, AllocationType, RawAllocation}, - }, - snip20, - }, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -fn underfunded_tolerance( - deposit: Uint128, - added: Uint128, - tolerance: Uint128, - allocation: Uint128, - alloc_type: AllocationType, - expected: Uint128, -) { - let mut app = App::default(); - - let admin = Addr::unchecked("admin"); - let _spender = Addr::unchecked("spender"); - let treasury = Addr::unchecked("treasury"); - let _user = Addr::unchecked("user"); - //let validator = Addr::unchecked("validator"); - let admin_auth = init_admin_auth(&mut app, &admin); - - let viewing_key = "viewing_key".to_string(); - - let token = snip20::InstantiateMsg { - name: "token".into(), - admin: Some("admin".into()), - symbol: "TKN".into(), - decimals: 6, - initial_balances: Some(vec![snip20::InitialBalance { - address: admin.to_string().clone(), - amount: deposit + added, - }]), - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - query_auth: None, - } - .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) - .unwrap(); - - let manager = treasury_manager::InstantiateMsg { - admin_auth: admin_auth.clone().into(), - viewing_key: viewing_key.clone(), - treasury: treasury.to_string().clone(), - } - .test_init( - TreasuryManager::default(), - &mut app, - admin.clone(), - "manager", - &[], - ) - .unwrap(); - - let adapter = mock_adapter::contract::Config { - owner: manager.address.clone(), - instant: true, - token: token.clone().into(), - } - .test_init( - MockAdapter::default(), - &mut app, - admin.clone(), - "adapter", - &[], - ) - .unwrap(); - - // Set admin viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Register treasury assets - treasury_manager::ExecuteMsg::RegisterAsset { - contract: token.clone().into(), - } - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // treasury allocation to spender - treasury_manager::ExecuteMsg::Allocate { - asset: token.address.to_string().clone(), - allocation: RawAllocation { - nick: Some("Manager".to_string()), - contract: RawContract::from(adapter.clone()), - alloc_type, - amount: allocation, - // 100% (adapter balance will 2x before unbond) - tolerance, - }, - } - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // Deposit funds into treasury - snip20::ExecuteMsg::Send { - recipient: manager.address.to_string().clone(), - recipient_code_hash: None, - amount: deposit, - msg: None, - memo: None, - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update manager - manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { - asset: token.address.to_string().clone(), - }) - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // Check adapter balance - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&adapter, &app) - .unwrap() - { - manager::QueryAnswer::Balance { amount } => { - assert_eq!(amount, deposit, "Adapter Balance"); - } - _ => panic!("query failed"), - }; - - // Additional funds into manager - snip20::ExecuteMsg::Send { - recipient: manager.address.to_string().clone(), - recipient_code_hash: None, - amount: added, - msg: None, - memo: None, - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update manager - manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { - asset: token.address.to_string().clone(), - }) - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // Check adapter balance - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&adapter, &app) - .unwrap() - { - manager::QueryAnswer::Balance { amount } => { - assert_eq!(amount, expected, "Final Adapter Balance"); - } - _ => panic!("query failed"), - }; -} - -macro_rules! underfunded_tolerance_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - deposit, - added, - tolerance, - allocation, - alloc_type, - expected, - ) = $value; - underfunded_tolerance( - deposit, - added, - tolerance, - allocation, - alloc_type, - expected, - ); - } - )* - } -} - -underfunded_tolerance_tests! { - tolerance_portion_90_no_increase: ( - Uint128::new(100), // deposit - Uint128::new(50), // added - Uint128::new(9 * 10u128.pow(17)), // tolerance - Uint128::new(1 * 10u128.pow(18)), // allocation - AllocationType::Portion, - Uint128::new(100), // expected - ), - tolerance_portion_90_will_increase: ( - Uint128::new(100), // deposit - Uint128::new(1000), // added - Uint128::new(9 * 10u128.pow(17)), // tolerance - Uint128::new(1 * 10u128.pow(18)), // allowance - AllocationType::Portion, - Uint128::new(1100), // expected - ), - tolerance_amount_10_no_increase: ( - Uint128::new(100), // deposit - Uint128::new(5), // added - Uint128::new(1 * 10u128.pow(17)), // tolerance - Uint128::new(105), //allowance - AllocationType::Amount, - Uint128::new(100), // expected - ), -} - -fn overfunded_tolerance( - deposit: Uint128, - tolerance: Uint128, - allocation: Uint128, - reduced: Uint128, - alloc_type: AllocationType, - expected: Uint128, -) { - let mut app = App::default(); - - let admin = Addr::unchecked("admin"); - let _spender = Addr::unchecked("spender"); - let treasury = Addr::unchecked("treasury"); - let _user = Addr::unchecked("user"); - //let validator = Addr::unchecked("validator"); - let admin_auth = init_admin_auth(&mut app, &admin); - - let viewing_key = "viewing_key".to_string(); - - let token = snip20::InstantiateMsg { - name: "token".into(), - admin: Some("admin".into()), - symbol: "TKN".into(), - decimals: 6, - initial_balances: Some(vec![snip20::InitialBalance { - address: admin.to_string().clone(), - amount: deposit, - }]), - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: Some(false), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - query_auth: None, - } - .test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]) - .unwrap(); - - let manager = treasury_manager::InstantiateMsg { - admin_auth: admin_auth.clone().into(), - viewing_key: viewing_key.clone(), - treasury: treasury.to_string().clone(), - } - .test_init( - TreasuryManager::default(), - &mut app, - admin.clone(), - "manager", - &[], - ) - .unwrap(); - - let adapter = mock_adapter::contract::Config { - owner: manager.address.clone(), - instant: true, - token: token.clone().into(), - } - .test_init( - MockAdapter::default(), - &mut app, - admin.clone(), - "adapter", - &[], - ) - .unwrap(); - - // Set admin viewing key - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Register treasury assets - treasury_manager::ExecuteMsg::RegisterAsset { - contract: token.clone().into(), - } - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // treasury allocation to spender - treasury_manager::ExecuteMsg::Allocate { - asset: token.address.to_string().clone(), - allocation: RawAllocation { - nick: Some("Manager".to_string()), - contract: RawContract::from(adapter.clone()), - alloc_type: alloc_type.clone(), - amount: allocation, - // 100% (adapter balance will 2x before unbond) - tolerance, - }, - } - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // Deposit funds into treasury - snip20::ExecuteMsg::Send { - recipient: manager.address.to_string().clone(), - recipient_code_hash: None, - amount: deposit, - msg: None, - memo: None, - padding: None, - } - .test_exec(&token, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update manager - manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { - asset: token.address.to_string().clone(), - }) - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // Check adapter balance - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&adapter, &app) - .unwrap() - { - manager::QueryAnswer::Balance { amount } => { - assert_eq!(amount, deposit, "Adapter Balance"); - } - _ => panic!("query failed"), - }; - - // reduce allocation - treasury_manager::ExecuteMsg::Allocate { - asset: token.address.to_string().clone(), - allocation: RawAllocation { - nick: Some("Manager".to_string()), - contract: RawContract::from(adapter.clone()), - alloc_type, - amount: reduced, - // 100% (adapter balance will 2x before unbond) - tolerance, - }, - } - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // Update manager - manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { - asset: token.address.to_string().clone(), - }) - .test_exec(&manager, &mut app, admin.clone(), &[]) - .unwrap(); - - // Check adapter balance - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: token.address.to_string().clone(), - }) - .test_query(&adapter, &app) - .unwrap() - { - manager::QueryAnswer::Balance { amount } => { - assert_eq!(amount, expected, "Final Adapter Balance"); - } - _ => panic!("query failed"), - }; -} - -macro_rules! overfunded_tolerance_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let ( - deposit, - tolerance, - allocation, - reduced, - alloc_type, - expected, - ) = $value; - overfunded_tolerance( - deposit, - tolerance, - allocation, - reduced, - alloc_type, - expected, - ); - } - )* - } -} - -overfunded_tolerance_tests! { - over_portion_tolerance_10_no_decrease: ( - Uint128::new(100), // deposit - Uint128::new(1 * 10u128.pow(17)), // tolerance - Uint128::new(1 * 10u128.pow(18)), // allocation - Uint128::new(95 * 10u128.pow(16)), // reduced allocation - AllocationType::Portion, - Uint128::new(100), // expected - ), - over_portion_tolerance_10_will_decrease: ( - Uint128::new(100), // deposit - Uint128::new(1 * 10u128.pow(17)), // tolerance - Uint128::new(1 * 10u128.pow(18)), // allocation - Uint128::new(1 * 10u128.pow(17)), // reduced allocation - AllocationType::Portion, - Uint128::new(10), // expected - ), - over_amount_tolerance_10_no_decrease: ( - Uint128::new(100), // deposit - Uint128::new(1 * 10u128.pow(17)), // tolerance - Uint128::new(100), // allocation - Uint128::new(95), // reduced allocation - AllocationType::Amount, - Uint128::new(100), // expected - ), - over_amount_tolerance_10_will_decrease: ( - Uint128::new(100), // deposit - Uint128::new(1 * 10u128.pow(17)), // tolerance - Uint128::new(100), // allocation - Uint128::new(80), // reduced allocation - AllocationType::Amount, - Uint128::new(80), // expected - ), -} diff --git a/contracts/dao/treasury_manager/tests/mod.rs b/contracts/dao/treasury_manager/tests/mod.rs deleted file mode 100644 index 5155b77..0000000 --- a/contracts/dao/treasury_manager/tests/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod integration; diff --git a/contracts/mock/mock_adapter/.cargo/config b/contracts/mock/mock_adapter/.cargo/config deleted file mode 100644 index 882fe08..0000000 --- a/contracts/mock/mock_adapter/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[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/mock/mock_adapter/.circleci/config.yml b/contracts/mock/mock_adapter/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/contracts/mock/mock_adapter/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/mock/mock_adapter/Cargo.toml b/contracts/mock/mock_adapter/Cargo.toml deleted file mode 100644 index 64d7190..0000000 --- a/contracts/mock/mock_adapter/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -name = "mock_adapter" -version = "0.1.0" -authors = [ - "Jack Swenson ", -] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] -debug-print = ["shade-protocol/debug-print"] - -[dependencies] -shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ - "adapter", - "storage_plus", - "dao", - "snip20", -] } -cosmwasm-schema = { git = "https://github.com/CosmWasm/cosmwasm", commit = "1e05e7e" } -serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] } -schemars = "0.8.9" - -[dev-dependencies] -shade-multi-test = { path = "../../../packages/multi_test", features = [ - "dao", -] } diff --git a/contracts/mock/mock_adapter/Makefile b/contracts/mock/mock_adapter/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/contracts/mock/mock_adapter/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/mock/mock_adapter/README.md b/contracts/mock/mock_adapter/README.md deleted file mode 100644 index 8fad675..0000000 --- a/contracts/mock/mock_adapter/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Mock Band Contract -* [Introduction](#Introduction) -* [Sections](#Sections) - * [User](#User) - * Queries - * [GetReferenceData](#GetReferenceData) -# Introduction -The Mocked Band contract is used to test locally when there is no official band contract available - -### Queries - -#### GetReferenceData -Get a hardcoded sample from an ETH query for testing locally -##### Response -```json -{ - "rate": "3119154999999000000000", - "last_updated_base": 1628548483, - "last_updated_quote": 3377610 -} -``` diff --git a/contracts/mock/mock_adapter/src/contract.rs b/contracts/mock/mock_adapter/src/contract.rs deleted file mode 100644 index b174975..0000000 --- a/contracts/mock/mock_adapter/src/contract.rs +++ /dev/null @@ -1,326 +0,0 @@ -use cosmwasm_schema::cw_serde; -use shade_protocol::{ - c_std::{ - shd_entry_point, - to_binary, - Addr, - Binary, - Deps, - DepsMut, - Env, - MessageInfo, - Response, - StdError, - StdResult, - Uint128, - }, - contract_interfaces::dao::adapter, - snip20::helpers::{balance_query, register_receive, send_msg, set_viewing_key_msg}, - utils::{ - asset::Contract, - generic_response::ResponseStatus, - storage::plus::Item, - ExecuteCallback, - InstantiateCallback, - Query, - }, -}; - -#[cw_serde] -pub struct Config { - pub owner: Addr, - pub instant: bool, - pub token: Contract, -} - -impl InstantiateCallback for Config { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteMsg { - Receive { - sender: Addr, - from: Addr, - amount: Uint128, - memo: Option, - msg: Option, - }, - GiveMeMoney { - amount: Uint128, - }, - CompleteUnbonding {}, - Adapter(adapter::SubExecuteMsg), -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryMsg { - Config, - Adapter(adapter::SubQueryMsg), -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryAnswer { - Config { config: Config }, - Adapter(adapter::SubQueryMsg), -} - -const VIEWING_KEY: &str = "jUsTfOrTeStInG"; - -const CONFIG: Item = Item::new("config"); -const ADDRESS: Item = Item::new("address"); -const REWARDS: Item = Item::new("rewards"); - -const UNBONDING: Item = Item::new("unbonding"); -const CLAIMABLE: Item = Item::new("claimable"); - -#[shd_entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: Config, -) -> StdResult { - CONFIG.save(deps.storage, &msg)?; - ADDRESS.save(deps.storage, &env.contract.address)?; - //BLOCK.save(deps.storage, &Uint128::new(env.block.height as u128))?; - - UNBONDING.save(deps.storage, &Uint128::zero())?; - CLAIMABLE.save(deps.storage, &Uint128::zero())?; - REWARDS.save(deps.storage, &Uint128::zero())?; - - Ok(Response::new().add_messages(vec![ - set_viewing_key_msg(VIEWING_KEY.to_string(), None, &msg.token.clone())?, - register_receive(env.contract.code_hash.clone(), None, &msg.token.clone())?, - ])) -} - -#[shd_entry_point] -pub fn execute( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - //BLOCK.save(deps.storage, &Uint128::new(env.block.height as u128))?; - - match msg { - ExecuteMsg::Receive { - sender: _, - from, - amount, - memo: _, - msg: _, - } => { - if info.sender != config.token.address { - return Err(StdError::generic_err("Unrecognized Asset")); - } - - // If sender is not manager, consider rewards - if from != config.owner { - let rew = REWARDS.load(deps.storage)?; - REWARDS.save(deps.storage, &(rew + amount))?; - } - - Ok(Response::new()) - } - ExecuteMsg::GiveMeMoney { amount } => Ok(Response::new().add_message(send_msg( - info.sender, - amount, - None, - None, - None, - &config.token, - )?)), - ExecuteMsg::CompleteUnbonding {} => { - let unbonding = UNBONDING.load(deps.storage)?; - let claimable = CLAIMABLE.load(deps.storage)?; - - UNBONDING.save(deps.storage, &Uint128::zero())?; - CLAIMABLE.save(deps.storage, &(claimable + unbonding))?; - Ok(Response::new()) - } - ExecuteMsg::Adapter(adapter) => match adapter { - adapter::SubExecuteMsg::Unbond { asset, amount } => { - if asset != config.token.address { - return Err(StdError::generic_err("Unrecognized Asset")); - } - - let balance = balance_query( - &deps.querier, - ADDRESS.load(deps.storage)?, - VIEWING_KEY.to_string(), - &config.token.clone(), - )?; - - //let rewards = REWARDS.load(deps.storage)?; - - let unbonding = UNBONDING.load(deps.storage)?; - let claimable = CLAIMABLE.load(deps.storage)?; - let rewards = REWARDS.load(deps.storage)?; - - let available = (balance + rewards) - (unbonding + claimable); - - if available < amount { - return Err(StdError::generic_err(format!( - "Cannot unbond {}, {} available", - amount, available - ))); - } - - let mut messages = vec![]; - - if config.instant { - messages.push(send_msg( - config.owner.clone(), - amount, - None, - None, - None, - &config.token.clone(), - )?); - } else { - UNBONDING.save(deps.storage, &(unbonding + amount))?; - } - - Ok(Response::new().add_messages(messages).set_data(to_binary( - &adapter::ExecuteAnswer::Unbond { - status: ResponseStatus::Success, - amount, - }, - )?)) - } - adapter::SubExecuteMsg::Claim { asset } => { - if asset != config.token.address { - return Err(StdError::generic_err("Unrecognized Asset")); - } - let claimable = CLAIMABLE.load(deps.storage)?; - CLAIMABLE.save(deps.storage, &Uint128::zero())?; - REWARDS.save(deps.storage, &Uint128::zero())?; - - Ok(Response::new() - .add_message(send_msg( - config.owner.clone(), - claimable, - None, - None, - None, - &config.token.clone(), - )?) - .set_data(to_binary(&adapter::ExecuteAnswer::Claim { - status: ResponseStatus::Success, - amount: claimable, - })?)) - } - adapter::SubExecuteMsg::Update { asset } => { - if asset != config.token.address { - return Err(StdError::generic_err("Unrecognized Asset")); - } - // 'claim & restake' rewards - REWARDS.save(deps.storage, &Uint128::zero())?; - - Ok( - Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Update { - status: ResponseStatus::Success, - })?), - ) - } - }, - } -} - -#[shd_entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - match msg { - QueryMsg::Config => to_binary(&QueryAnswer::Config { config }), - QueryMsg::Adapter(adapter) => to_binary(&match adapter { - adapter::SubQueryMsg::Balance { asset } => { - if asset != config.token.address { - return Err(StdError::generic_err("Unrecognized Asset")); - } - let balance = balance_query( - &deps.querier, - ADDRESS.load(deps.storage)?, - VIEWING_KEY.to_string(), - &config.token.clone(), - )?; - - adapter::QueryAnswer::Balance { amount: balance } - } - adapter::SubQueryMsg::Unbonding { asset } => { - if asset != config.token.address { - return Err(StdError::generic_err("Unrecognized Asset")); - } - adapter::QueryAnswer::Unbonding { - amount: UNBONDING.load(deps.storage)?, - } - } - adapter::SubQueryMsg::Claimable { asset } => { - if asset != config.token.address { - return Err(StdError::generic_err("Unrecognized Asset")); - } - - let _c = CLAIMABLE.load(deps.storage)?; - - adapter::QueryAnswer::Claimable { - amount: CLAIMABLE.load(deps.storage)?, - } - } - adapter::SubQueryMsg::Unbondable { asset } => { - if asset != config.token.address { - return Err(StdError::generic_err("Unrecognized Asset")); - } - let unbonding = UNBONDING.load(deps.storage)?; - let claimable = CLAIMABLE.load(deps.storage)?; - let balance = balance_query( - &deps.querier, - ADDRESS.load(deps.storage)?, - VIEWING_KEY.to_string(), - &config.token.clone(), - )?; - - adapter::QueryAnswer::Unbondable { - amount: balance - (unbonding + claimable), - } - } - adapter::SubQueryMsg::Reserves { asset } => { - if asset != config.token.address { - return Err(StdError::generic_err("Unrecognized Asset")); - } - - let reserves = match config.instant { - true => { - balance_query( - &deps.querier, - ADDRESS.load(deps.storage)?, - VIEWING_KEY.to_string(), - &config.token.clone(), - )? - (UNBONDING.load(deps.storage)? + CLAIMABLE.load(deps.storage)?) - } - false => { - let rewards = REWARDS.load(deps.storage)?; - let unbonding = UNBONDING.load(deps.storage)?; - if rewards > unbonding { - rewards - unbonding - } else { - Uint128::zero() - } - } - }; - - adapter::QueryAnswer::Reserves { amount: reserves } - } - }), - } -} diff --git a/contracts/mock/mock_adapter/src/execute.rs b/contracts/mock/mock_adapter/src/execute.rs deleted file mode 100644 index f41fc38..0000000 --- a/contracts/mock/mock_adapter/src/execute.rs +++ /dev/null @@ -1,200 +0,0 @@ -use shade_protocol::c_std::{ - to_binary, - MessageInfo, - Api, - BalanceResponse, - BankQuery, - Binary, - Coin, - CosmosMsg, - Env, - DepsMut, - Response, - Addr, - Querier, - StakingMsg, - StdError, - StdResult, - Storage, - Uint128, - Validator, -}; - -use shade_protocol::snip20::helpers::{ - deposit_msg, - redeem_msg, - register_receive, - send_from_msg, - set_viewing_key_msg, -}; - -use shade_protocol::{ - contract_interfaces::{ - dao::{ - adapter, - rewards_emission::{Config, ExecuteAnswer, Reward}, - }, - snip20::helpers::{fetch_snip20, Snip20Asset}, - }, - utils::{ - asset::{scrt_balance, Contract}, - generic_response::ResponseStatus, - cycle::{Cycle, exceeds_cycle, utc_now, parse_utc_datetime}, - }, -}; - -use crate::{ - query, - storage::*, -}; - -pub fn receive( - deps: DepsMut, - env: Env, - info: MessageInfo, - _sender: Addr, - _from: Addr, - amount: Uint128, - _msg: Option, -) -> StdResult { - //TODO: forward to distributor (quick fix mechanism) - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Receive { - status: ResponseStatus::Success, - })?)) -} - -pub fn try_update_config( - deps: DepsMut, - env: Env, - info: MessageInfo, - config: Config, -) -> StdResult { - let cur_config = CONFIG.load(deps.storage)?; - - if !cur_config.admins.contains(&info.sender) { - return Err(StdError::generic_err("unauthorized")); - } - - CONFIG.save(deps.storage, &config)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { - status: ResponseStatus::Success, - })?)) -} - -pub fn refill_rewards( - deps: DepsMut, - env: Env, - info: MessageInfo, -) -> StdResult { - - let config = CONFIG.load(deps.storage)?; - let mut messages = vec![]; - - if let Some(mut reward) = REWARD.may_load(deps.storage, info.sender.clone())? { - - let token = TOKEN.load(deps.storage)?; - let now = utc_now(&env); - - // Check expiration - if let Some(expiry) = reward.expiration.clone() { - if now > parse_utc_datetime(&expiry)? { - return Err(StdError::generic_err(format!("Rewards expired on {}", expiry))); - } - } - - if exceeds_cycle(&now, &parse_utc_datetime(&reward.last_refresh.clone())?, reward.cycle.clone()) { - reward.last_refresh = now.to_rfc3339(); - REWARD.save(deps.storage, info.sender, &reward)?; - // Send from treasury - messages.push(send_from_msg( - config.treasury.clone(), - reward.distributor.address.clone(), - reward.amount, - None, - None, - None, - &token.contract.clone(), - )?); - } - else { - return Err(StdError::generic_err(format!("Last rewards were requested on {}", reward.last_refresh))); - } - } - else { - return Err(StdError::generic_err("No rewards for you")); - } - - Ok(Response::new() - .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::RefillRewards { - status: ResponseStatus::Success, - })?) - ) -} - -pub fn register_rewards( - deps: DepsMut, - env: Env, - info: MessageInfo, - token: Addr, - distributor: Contract, - amount: Uint128, - cycle: Cycle, - expiration: Option, -) -> StdResult { - - if token != TOKEN.load(deps.storage)?.contract.address { - return Err(StdError::generic_err("Invalid token")); - } - - REWARD.save(deps.storage, info.sender, &Reward { - distributor, - amount, - cycle, - //TODO change to null/zero for first refresh - last_refresh: utc_now(&env).to_rfc3339(), - expiration, - })?; - - Ok(Response::new() - .set_data(to_binary(&ExecuteAnswer::RegisterReward{ - status: ResponseStatus::Success, - })?) - ) -} - -/* -pub fn update( - deps: DepsMut, - env: Env, - info: MessageInfo, - asset: Addr, -) -> StdResult { - Ok(Response::new().set_data(to_binary(&adapter::ExecuteAnswer::Update { - status: ResponseStatus::Success, - })?)) -} - -pub fn claim( - deps: DepsMut, - _env: Env, - asset: Addr, -) -> StdResult { - match asset_r(deps.storage).may_load(&asset.as_str().as_bytes())? { - Some(_) => Ok(Response { - messages: vec![], - log: vec![], - data: Some(to_binary(&adapter::ExecuteAnswer::Claim { - status: ResponseStatus::Success, - amount: Uint128::zero(), - })?), - }), - None => Err(StdError::generic_err(format!( - "Unrecognized Asset {}", - asset - ))), - } -} -*/ diff --git a/contracts/mock/mock_adapter/src/lib.rs b/contracts/mock/mock_adapter/src/lib.rs deleted file mode 100644 index 2943dbb..0000000 --- a/contracts/mock/mock_adapter/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod contract; diff --git a/contracts/mock/mock_adapter/src/storage.rs b/contracts/mock/mock_adapter/src/storage.rs deleted file mode 100644 index a6847bb..0000000 --- a/contracts/mock/mock_adapter/src/storage.rs +++ /dev/null @@ -1,23 +0,0 @@ -use shade_protocol::c_std::{Addr, Storage, Uint128}; -use shade_protocol::storage::{ - bucket, - bucket_read, - singleton, - singleton_read, - Bucket, - ReadonlyBucket, - ReadonlySingleton, - Singleton, -}; -use shade_protocol::contract_interfaces::{dao::rewards_emission, snip20::helpers::Snip20Asset}; - -use shade_protocol::{ - secret_storage_plus::{Map, Item}, -}; - -pub const CONFIG: Item = Item::new("config"); -pub const SELF_ADDRESS: Item = Item::new("self_address"); -pub const VIEWING_KEY: Item = Item::new("viewing_key"); -pub const TOKEN: Item = Item::new("token"); -pub const REWARD: Map = Map::new("rewards"); - diff --git a/contracts/mock/mock_sienna_pair/.cargo/config b/contracts/mock/mock_sienna_pair/.cargo/config deleted file mode 100644 index 882fe08..0000000 --- a/contracts/mock/mock_sienna_pair/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[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/mock/mock_sienna_pair/.circleci/config.yml b/contracts/mock/mock_sienna_pair/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/contracts/mock/mock_sienna_pair/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/mock/mock_sienna_pair/Cargo.toml b/contracts/mock/mock_sienna_pair/Cargo.toml deleted file mode 100644 index 5508629..0000000 --- a/contracts/mock/mock_sienna_pair/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "mock_sienna_pair" -version = "0.1.0" -authors = ["Jack Swenson "] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] -debug-print = ["shade-protocol/debug-print"] - -[dependencies] -cosmwasm-schema = "1.1.5" -shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ - "dex", -] } diff --git a/contracts/mock/mock_sienna_pair/Makefile b/contracts/mock/mock_sienna_pair/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/contracts/mock/mock_sienna_pair/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/mock/mock_sienna_pair/README.md b/contracts/mock/mock_sienna_pair/README.md deleted file mode 100644 index 8fad675..0000000 --- a/contracts/mock/mock_sienna_pair/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Mock Band Contract -* [Introduction](#Introduction) -* [Sections](#Sections) - * [User](#User) - * Queries - * [GetReferenceData](#GetReferenceData) -# Introduction -The Mocked Band contract is used to test locally when there is no official band contract available - -### Queries - -#### GetReferenceData -Get a hardcoded sample from an ETH query for testing locally -##### Response -```json -{ - "rate": "3119154999999000000000", - "last_updated_base": 1628548483, - "last_updated_quote": 3377610 -} -``` diff --git a/contracts/mock/mock_sienna_pair/src/contract.rs b/contracts/mock/mock_sienna_pair/src/contract.rs deleted file mode 100644 index 6344be6..0000000 --- a/contracts/mock/mock_sienna_pair/src/contract.rs +++ /dev/null @@ -1,303 +0,0 @@ -use shade_protocol::{ - c_std::{ - shd_entry_point, from_binary, to_binary, - Addr, Binary, Decimal, Deps, DepsMut, - Env, MessageInfo, Response, StdError, - StdResult, QuerierWrapper, Uint128, - }, - contract_interfaces::{ - dex::{ - dex::pool_take_amount, - sienna::{ - self, - Pair, - TokenType, - }, - }, - snip20::helpers::{balance_query, send_msg, set_viewing_key_msg}, - }, - cosmwasm_schema::cw_serde, - utils::{ - asset::Contract, ExecuteCallback, InstantiateCallback, - storage::plus::{Item, ItemStorage}, - }, -}; -pub use shade_protocol::dex::sienna::{ - PairQuery as QueryMsg, - PairInfoResponse, - SimulationResponse, - ReceiverCallbackMsg, -}; - -#[cw_serde] -pub struct Config { - pub address: Addr, - pub viewing_key: String, - pub commission: Decimal, -} - -impl ItemStorage for Config { - const ITEM: Item<'static, Self> = Item::new("item-config"); -} - -#[cw_serde] -pub struct PairInfo { - pub token_0: Contract, - pub token_1: Contract, -} - -impl ItemStorage for PairInfo { - const ITEM: Item<'static, Self> = Item::new("item-pair"); -} - -#[cw_serde] -pub struct InstantiateMsg { - pub token_0: Contract, - pub token_1: Contract, - pub viewing_key: String, - pub commission: Decimal, -} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[shd_entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg -) -> StdResult { - let pair_info = PairInfo { - token_0: msg.token_0.clone(), - token_1: msg.token_1.clone(), - }; - pair_info.save(deps.storage)?; - - let config = Config { - address: env.contract.address, - viewing_key: msg.viewing_key.clone(), - commission: msg.commission, - }; - config.save(deps.storage)?; - - let messages = vec![ - set_viewing_key_msg( - msg.viewing_key.clone(), - None, - &msg.token_0, - )?, - set_viewing_key_msg( - msg.viewing_key, - None, - &msg.token_1, - )?, - ]; - Ok(Response::default() - .add_messages(messages)) -} - -#[cw_serde] -pub enum ExecuteMsg { - MockPool { - token_a: Contract, - token_b: Contract, - }, - // SNIP20 receiver interface - Receive { - sender: Addr, - from: Addr, - msg: Option, - amount: Uint128, - }, -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[shd_entry_point] -pub fn execute( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: ExecuteMsg -) -> StdResult { - match msg { - ExecuteMsg::MockPool { - token_a, - token_b, - } => { - let pair_info = PairInfo { - token_0: token_a, - token_1: token_b, - }; - - pair_info.save(deps.storage)?; - Ok(Response::default()) - } - - // Swap - ExecuteMsg::Receive { - from, - msg, - amount, - .. - } => { - let msg = msg.ok_or_else(|| { - StdError::generic_err("Receiver callback \"msg\" parameter cannot be empty.") - })?; - - match from_binary(&msg)? { - ReceiverCallbackMsg::Swap { expected_return, to } => { - let config = Config::load(deps.storage)?; - let pair = PairInfo::load(deps.storage)?; - - let (in_token, out_token) = if info.sender == pair.token_0.address { - (pair.token_0, pair.token_1) - } else if info.sender == pair.token_1.address { - (pair.token_1, pair.token_0) - } else { - return Err(StdError::generic_err("unauthorized")); - }; - - let (in_pool, out_pool) = query_pool_amounts( - &deps.querier, - &config, - in_token.clone(), - out_token.clone(), - )?; - - // Sienna takes commission before swap - let swap_amount = amount - (amount * config.commission); - let return_amount = pool_take_amount( - swap_amount, - in_pool - amount, // amount has already been added to this pool - out_pool, - ); - - if return_amount < expected_return.unwrap_or(Uint128::zero()) { - return Err(StdError::generic_err( - "Operation fell short of expected_return" - )); - } - - // send tokens - let return_addr = to.unwrap_or(from); - return Ok(Response::default() - .add_message(send_msg( - return_addr, - return_amount, - None, - None, - None, - &out_token, - )?)) - }, - } - } - } - -} - -#[shd_entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::PairInfo => { - let config = Config::load(deps.storage)?; - let pair_info = PairInfo::load(deps.storage)?; - let (amount_0, amount_1) = query_pool_amounts( - &deps.querier, - &config, - pair_info.token_0.clone(), - pair_info.token_1.clone(), - )?; - - to_binary(&PairInfoResponse { - pair_info: sienna::PairInfo { - liquidity_token: Contract { - address: Addr::unchecked("lp_token"), - code_hash: "hash".to_string(), - }, - factory: Contract { - address: Addr::unchecked("factory"), - code_hash: "hash".to_string(), - }, - pair: Pair { - token_0: TokenType::CustomToken { - contract_addr: pair_info.token_0.address, - token_code_hash: pair_info.token_0.code_hash, - }, - token_1: TokenType::CustomToken { - contract_addr: pair_info.token_1.address, - token_code_hash: pair_info.token_1.code_hash, - } - }, - amount_0, - amount_1, - total_liquidity: Uint128::zero(), - contract_version: 0, - }, - }) - }, - QueryMsg::SwapSimulation { offer } => { - let config = Config::load(deps.storage)?; - let pair = PairInfo::load(deps.storage)?; - let token_0 = pair.token_0; - let token_1 = pair.token_1; - - let (in_token, out_token) = match offer.token { - TokenType::CustomToken { contract_addr, .. } => { - if contract_addr == token_0.address { - (token_0, token_1) - } else if contract_addr == token_1.address { - (token_1, token_0) - } else { - return Err(StdError::generic_err(format!( - "The supplied token {}, is not managed by this contract", - contract_addr - ))) - } - }, - _ => { - return Err(StdError::generic_err("Only CustomToken supported")); - } - }; - - let (amount_0, amount_1) = query_pool_amounts( - &deps.querier, - &config, - in_token.clone(), - out_token.clone(), - )?; - - // Sienna takes commission before swap - let commission = offer.amount * config.commission; - let swap_amount = offer.amount - commission; - - return to_binary(&SimulationResponse { - return_amount: pool_take_amount( - swap_amount, - amount_0, - amount_1, - ), - spread_amount: Uint128::zero(), - commission_amount: commission, - }); - - } - } -} - -fn query_pool_amounts( - querier: &QuerierWrapper, - config: &Config, - token_0: Contract, - token_1: Contract, -) -> StdResult<(Uint128, Uint128)> { - Ok(( - balance_query(querier, config.address.clone(), config.viewing_key.clone(), &token_0)?, - balance_query(querier, config.address.clone(), config.viewing_key.clone(), &token_1)?, - )) -} diff --git a/contracts/mock/mock_sienna_pair/src/lib.rs b/contracts/mock/mock_sienna_pair/src/lib.rs deleted file mode 100644 index 2943dbb..0000000 --- a/contracts/mock/mock_sienna_pair/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod contract; diff --git a/contracts/mock/mock_stkd_derivative/Cargo.toml b/contracts/mock/mock_stkd_derivative/Cargo.toml deleted file mode 100644 index e14004e..0000000 --- a/contracts/mock/mock_stkd_derivative/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -name = "mock_stkd_derivative" -version = "0.1.0" -authors = ["Aidan St. George "] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] -debug-print = ["shade-protocol/debug-print"] - -[dependencies] -shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ - "dex", - "stkd", -] } -cosmwasm-schema = "1.1.5" - -[dev-dependencies] -mock_sienna_pair = { version = "0.1.0", path = "../mock_sienna_pair" } -shade-multi-test = { version = "0.1.0", path = "../../../packages/multi_test", features = [ - "mock_sienna", - "mock_stkd", - "snip20", -] } - diff --git a/contracts/mock/mock_stkd_derivative/Makefile b/contracts/mock/mock_stkd_derivative/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/contracts/mock/mock_stkd_derivative/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/mock/mock_stkd_derivative/README.md b/contracts/mock/mock_stkd_derivative/README.md deleted file mode 100644 index 2c1f6e7..0000000 --- a/contracts/mock/mock_stkd_derivative/README.md +++ /dev/null @@ -1 +0,0 @@ -# Mock stkd-SCRT Derivative Contract diff --git a/contracts/mock/mock_stkd_derivative/src/contract.rs b/contracts/mock/mock_stkd_derivative/src/contract.rs deleted file mode 100644 index 446a3df..0000000 --- a/contracts/mock/mock_stkd_derivative/src/contract.rs +++ /dev/null @@ -1,386 +0,0 @@ -use shade_protocol::{ - Contract, - c_std::{ - shd_entry_point, - to_binary, - Addr, - BankMsg, - Binary, - Coin, - Decimal, - Deps, - DepsMut, - Env, - MessageInfo, - Response, - StdResult, - StdError, - Uint128, - }, - cosmwasm_schema::cw_serde, - contract_interfaces::snip20::ReceiverHandleMsg, - utils::{ - ExecuteCallback, - InstantiateCallback, - storage::plus::{ - Item, - ItemStorage, - Map, - MapStorage, - } - }, -}; - -pub use shade_protocol::contract_interfaces::stkd::{ - HandleAnswer as ExecuteAnswer, - HandleMsg as ExecuteMsg, - QueryAnswer, - QueryMsg, - Unbond, -}; - -#[cw_serde] -struct Unbonding { - amount: Uint128, - // Time for maturity, when bonding is claimable - maturity: u32, -} - -// Keep track of a user's balance -#[cw_serde] -#[derive(Default)] -struct Balance (pub Uint128); - -impl MapStorage<'static, Addr> for Balance { - const MAP: Map<'static, Addr, Self> = Map::new("balance-"); -} - -// Keep track of a user's unbondings -#[cw_serde] -#[derive(Default)] -struct Unbondings(pub Vec); - -impl MapStorage<'static, Addr> for Unbondings { - const MAP: Map<'static, Addr, Self> = Map::new("unbondings-"); -} - -#[cw_serde] -struct ViewingKey(pub String); - -impl MapStorage<'static, Addr> for ViewingKey { - const MAP: Map<'static, Addr, Self> = Map::new("vk-"); -} - -#[cw_serde] -struct Price(pub Uint128); - -impl ItemStorage for Price { - const ITEM: Item<'static, Self> = Item::new("item-price"); -} - -// Global time tracker -#[cw_serde] -struct Time(pub u32); - -impl ItemStorage for Time { - const ITEM: Item<'static, Self> = Item::new("item-time"); -} - -#[cw_serde] -pub struct Config { - name: String, - symbol: String, - decimals: u8, - admin: Addr, - unbonding_time: u32, - unbonding_batch_interval: u32, - staking_commission: Decimal, - unbond_commission: Decimal, -} - -impl ItemStorage for Config { - const ITEM: Item<'static, Self> = Item::new("item-config"); -} - -// INSTANTIATE - -#[cw_serde] -pub struct InstantiateMsg { - pub name: String, - pub symbol: String, - pub decimals: u8, - pub price: Uint128, - pub unbonding_time: u32, - pub unbonding_batch_interval: u32, - pub staking_commission: Decimal, - pub unbond_commission: Decimal, -} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[shd_entry_point] -pub fn instantiate( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - let config = Config { - name: msg.name, - symbol: msg.symbol, - decimals: msg.decimals, - admin: info.sender.clone(), - unbonding_time: msg.unbonding_time, - unbonding_batch_interval: msg.unbonding_batch_interval, - staking_commission: msg.staking_commission, - unbond_commission: msg.unbond_commission, - }; - config.save(deps.storage)?; - - // Adjust price relative to uscrt for the off chance that msg.decimals isn't 6 - let mut price = msg.price; - if msg.decimals != 6 { - if msg.decimals > 6 { - price = price / Uint128::new(10).pow(msg.decimals as u32 - 6); - } else { - price = price * Uint128::new(10).pow(6 - msg.decimals as u32); - } - } - Price(price).save(deps.storage)?; - - Time(0).save(deps.storage)?; - - Ok(Response::new()) -} - -// EXECUTE - -pub fn execute( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: ExecuteMsg -) -> StdResult { - match msg { - ExecuteMsg::Send { recipient, amount, recipient_code_hash, msg, .. } => { - let my_balance = Balance::load(deps.storage, info.sender.clone()) - .map_err(|_| StdError::generic_err("Insufficient funds"))?.0; - let their_balance = Balance::load(deps.storage, recipient.clone()) - .unwrap_or_default().0; - - Balance(my_balance.checked_sub(amount) - .map_err(|_| StdError::generic_err("Insufficient funds"))?) - .save(deps.storage, info.sender.clone())?; - Balance(their_balance + amount).save(deps.storage, recipient.clone())?; - - let mut messages = vec![]; - if let Some(receiver_hash) = recipient_code_hash { - let recipient_addr = Addr::unchecked(recipient); - messages.push( - ReceiverHandleMsg::new( - info.sender.to_string(), - info.sender.to_string(), - amount, - None, - msg - ).to_cosmos_msg( - &Contract { - address: recipient_addr, - code_hash: receiver_hash, - }, - vec![], - )? - ); - } - Ok(Response::default() - .add_messages(messages)) - } - // TODO: fees - ExecuteMsg::Stake {} => { - let mut amount = Uint128::zero(); - for coin in info.funds { - if coin.denom == "uscrt".to_string() { - amount += coin.amount; - } - } - if amount.is_zero() { - return Err(StdError::generic_err("No SCRT was sent for staking")); - } - - let config = Config::load(deps.storage)?; - let amount = amount - (amount * config.staking_commission); - let deriv_amount = amount.multiply_ratio(Uint128::from(1_000_000u32), Price::load(deps.storage)?.0); - - let balance = Balance::load(deps.storage, info.sender.clone()) - .unwrap_or_default().0; - Balance(balance + deriv_amount).save(deps.storage, info.sender)?; - - Ok(Response::default() - .set_data(to_binary(&ExecuteAnswer::Stake { - scrt_staked: amount, - tokens_returned: deriv_amount, - })?) - ) - }, - ExecuteMsg::Unbond { redeem_amount } => { - let balance = Balance::load(deps.storage, info.sender.clone()) - .unwrap_or_default().0; - if balance < redeem_amount { - return Err(StdError::generic_err(format!( - "insufficient funds to burn: balance={}, required={}", balance, redeem_amount - ))); - } - - let config = Config::load(deps.storage)?; - let time = Time::load(deps.storage)?.0; - let maturity = time + config.unbonding_time - + config.unbonding_batch_interval - (time % config.unbonding_batch_interval); - let unbond_amount = redeem_amount - (redeem_amount * config.unbond_commission); - let unbonding = Unbonding { - amount: unbond_amount, - maturity, - }; - - let mut unbondings = Unbondings::load(deps.storage, info.sender.clone()) - .unwrap_or_default().0; - unbondings.push(unbonding); - Unbondings(unbondings).save(deps.storage, info.sender.clone())?; - - Balance(balance - redeem_amount).save(deps.storage, info.sender)?; - let scrt_amount = redeem_amount - .multiply_ratio(Price::load(deps.storage)?.0, Uint128::from(1_000_000u32)); - Ok(Response::default() - .set_data(to_binary(&ExecuteAnswer::Unbond { - tokens_redeemed: redeem_amount, - scrt_to_be_received: scrt_amount, - estimated_time_of_maturity: maturity as u64, - })?) - ) - - }, - ExecuteMsg::Claim {} => { - let mut claimable = Uint128::zero(); - let unbondings = Unbondings::load(deps.storage, info.sender.clone())?.0; - let time = Time::load(deps.storage)?.0; - let mut new_unbondings = vec![]; - for unbonding in unbondings { - if unbonding.maturity <= time { - claimable += unbonding.amount; - } else { - new_unbondings.push(unbonding); - } - } - let returned = claimable.multiply_ratio(Price::load(deps.storage)?.0, Uint128::new(1_000_000)); - Unbondings(new_unbondings).save(deps.storage, info.sender.clone())?; - - Ok(Response::default() - .set_data(to_binary(&ExecuteAnswer::Claim { - withdrawn: claimable, - fees: Uint128::zero(), // no fees - })?) - .add_message(BankMsg::Send { - to_address: info.sender.to_string(), - amount: vec![Coin { - amount: returned, - denom: "uscrt".to_string(), - }] - })) - }, - ExecuteMsg::SetViewingKey { key, .. } => { - ViewingKey(key).save(deps.storage, info.sender)?; - Ok(Response::default()) - }, - ExecuteMsg::MockFastForward { steps } => { - let time = Time::load(deps.storage)?.0; - Time(time + steps).save(deps.storage)?; - Ok(Response::default()) - } - } -} - -// QUERY - -pub fn query( - deps: Deps, - _env: Env, - msg: QueryMsg, -) -> StdResult { - match msg { - QueryMsg::Balance { address, key } => { - if key != ViewingKey::load(deps.storage, address.clone())?.0 { - return Err(StdError::generic_err("unauthorized")); - } - - to_binary(&QueryAnswer::Balance { - amount: Balance::load(deps.storage, address).unwrap_or_default().0, - }) - }, - QueryMsg::StakingInfo { .. } => { - let time = Time::load(deps.storage)?.0; - let config = Config::load(deps.storage)?; - let next_unbonding_batch_time = time + config.unbonding_batch_interval - - (time % config.unbonding_batch_interval); - - // Convert back to basis of 6 decimals - let mut price = Price::load(deps.storage)?.0; - if config.decimals != 6 { - if config.decimals > 6 { - price = price * Uint128::new(10).pow(config.decimals as u32 - 6); - } else { - price = price / Uint128::new(10).pow(6 - config.decimals as u32); - } - } - - to_binary(&QueryAnswer::StakingInfo { - validators: vec![], - unbonding_time: config.unbonding_time, - unbonding_batch_interval: config.unbonding_batch_interval, - next_unbonding_batch_time: next_unbonding_batch_time as u64, - // Not supported by mock stkd - unbond_amount_of_next_batch: Uint128::zero(), - batch_unbond_in_progress: false, - bonded_scrt: Uint128::zero(), - reserved_scrt: Uint128::zero(), - available_scrt: Uint128::zero(), - rewards: Uint128::zero(), - total_derivative_token_supply: Uint128::zero(), - price, - }) - }, - QueryMsg::Unbonding { address, key, .. } => { - if key != ViewingKey::load(deps.storage, address.clone())?.0 { - return Err(StdError::generic_err("unauthorized")); - } - - let mut count: u64 = 0; - let mut unbonds = vec![]; - let mut amount_in_next_batch = Uint128::zero(); - let time = Time::load(deps.storage)?.0; - let config = Config::load(deps.storage)?; - let unbondings = Unbondings::load(deps.storage, address).unwrap_or_default().0; - for unbonding in unbondings { - if unbonding.maturity <= time + config.unbonding_time { - count += 1; - unbonds.push(Unbond { - amount: unbonding.amount, - unbonds_at: unbonding.maturity as u64, - is_mature: None, - }); - } else { - amount_in_next_batch += unbonding.amount; - } - } - - to_binary(&QueryAnswer::Unbonding { - count, - claimable_scrt: None, - unbondings: unbonds, - unbond_amount_in_next_batch: amount_in_next_batch, - estimated_time_of_maturity_for_next_batch: None, - }) - }, - } -} - diff --git a/contracts/mock/mock_stkd_derivative/src/lib.rs b/contracts/mock/mock_stkd_derivative/src/lib.rs deleted file mode 100644 index de375de..0000000 --- a/contracts/mock/mock_stkd_derivative/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod contract; - -#[cfg(test)] -mod tests; - diff --git a/contracts/mock/mock_stkd_derivative/src/tests.rs b/contracts/mock/mock_stkd_derivative/src/tests.rs deleted file mode 100644 index afcbb22..0000000 --- a/contracts/mock/mock_stkd_derivative/src/tests.rs +++ /dev/null @@ -1,510 +0,0 @@ -use shade_protocol::{ - c_std::{ - coins, from_binary, to_binary, - Addr, Coin, StdError, - Binary, StdResult, Env, - Uint128, QueryRequest, BankQuery, - BalanceResponse, Decimal - }, - contract_interfaces::dex::sienna::{ - Pair, PairInfo, TokenType, - }, - utils::{ - asset::Contract, - MultiTestable, - InstantiateCallback, - ExecuteCallback, - Query, - }, - snip20, -}; -use shade_protocol::multi_test::App; -use shade_multi_test::multi::{ - mock_stkd::MockStkd, - mock_sienna::MockSienna, - snip20::Snip20, -}; -use crate::contract as stkd; - -use mock_sienna_pair::contract as mock_sienna; - - -#[test] -fn test() { - let mut chain = App::default(); - - let admin = Addr::unchecked("admin"); - let user = Addr::unchecked("user"); - let other = Addr::unchecked("other-user"); - - let init_scrt = Coin { - denom: "uscrt".to_string(), - amount: Uint128::new(1000), - }; - - let some_scrt = Coin { - denom: "uscrt".to_string(), - amount: Uint128::new(100), - }; - - // Init balances - chain.init_modules(|router, _, storage| { - router.bank.init_balance(storage, &user, vec![init_scrt.clone()]).unwrap(); - router.bank.init_balance(storage, &admin, vec![init_scrt.clone()]).unwrap(); - }); - - let stkd = stkd::InstantiateMsg { - name: "Staking Derivative".to_string(), - symbol: "stkd-SCRT".to_string(), - decimals: 6, - price: Uint128::from(2_000_000u64), - unbonding_time: 21, - unbonding_batch_interval: 3, - staking_commission: Decimal::permille(2), - unbond_commission: Decimal::from_ratio(5u32, 10_000u32), - }.test_init(MockStkd::default(), &mut chain, admin.clone(), "stkd", &[]).unwrap(); - - // Test Staking - stkd::ExecuteMsg::Stake {} - .test_exec(&stkd, &mut chain, user.clone(), &[some_scrt]).unwrap(); - - stkd::ExecuteMsg::SetViewingKey { - key: "password".to_string(), - padding: None, - }.test_exec(&stkd, &mut chain, user.clone(), &[]).unwrap(); - - assert_eq!( - stkd::QueryMsg::StakingInfo { - time: 0u64, - }.test_query::(&stkd, &chain).unwrap(), - stkd::QueryAnswer::StakingInfo { - validators: vec![], - unbonding_time: 21u32, - unbonding_batch_interval: 3u32, - next_unbonding_batch_time: 3u64, - unbond_amount_of_next_batch: Uint128::zero(), - batch_unbond_in_progress: false, - bonded_scrt: Uint128::zero(), - reserved_scrt: Uint128::zero(), - available_scrt: Uint128::zero(), - rewards: Uint128::zero(), - total_derivative_token_supply: Uint128::zero(), - price: Uint128::from(2_000_000u64), - }, - ); - - assert_eq!( - stkd::QueryMsg::Balance { - address: user.clone(), - key: "password".to_string(), - }.test_query::(&stkd, &chain).unwrap(), - stkd::QueryAnswer::Balance { - amount: Uint128::new(50), - }, - ); - - assert_eq!( // right amount of scrt left - chain.wrap().query::(&QueryRequest::Bank(BankQuery::Balance { - address: user.to_string(), - denom: "uscrt".to_string(), - })).unwrap(), - BalanceResponse { - amount: Coin { - amount: Uint128::new(900), - denom: "uscrt".to_string(), - }, - }, - ); - - // Test Unbonding - stkd::ExecuteMsg::Unbond { - redeem_amount: Uint128::new(25), - }.test_exec(&stkd, &mut chain, user.clone(), &[]).unwrap(); - - stkd::ExecuteMsg::MockFastForward { - steps: 1, - }.test_exec(&stkd, &mut chain, admin.clone(), &[]).unwrap(); - - assert_eq!( - stkd::QueryMsg::Unbonding { - address: user.clone(), - key: "password".to_string(), - page: None, - page_size: None, - time: None, - }.test_query::(&stkd, &chain).unwrap(), - stkd::QueryAnswer::Unbonding { - count: 0, - claimable_scrt: None, - unbondings: vec![], - unbond_amount_in_next_batch: Uint128::new(25), - estimated_time_of_maturity_for_next_batch: None, - }, - ); - - stkd::ExecuteMsg::MockFastForward { - steps: 2, - }.test_exec(&stkd, &mut chain, admin.clone(), &[]).unwrap(); - - assert_eq!( - stkd::QueryMsg::Unbonding { - address: user.clone(), - key: "password".to_string(), - page: None, - page_size: None, - time: None, - }.test_query::(&stkd, &chain).unwrap(), - stkd::QueryAnswer::Unbonding { - count: 1, - claimable_scrt: None, - unbondings: vec![stkd::Unbond { - amount: Uint128::new(25), - unbonds_at: 24u64, - is_mature: None, - }], - unbond_amount_in_next_batch: Uint128::zero(), - estimated_time_of_maturity_for_next_batch: None, - }, - ); - - stkd::ExecuteMsg::MockFastForward { - steps: 1 - }.test_exec(&stkd, &mut chain, admin.clone(), &[]).unwrap(); - - assert_eq!( - stkd::QueryMsg::Unbonding { - address: user.clone(), - key: "password".to_string(), - page: None, - page_size: None, - time: None, - }.test_query::(&stkd, &chain).unwrap(), - stkd::QueryAnswer::Unbonding { - count: 1, - claimable_scrt: None, - unbondings: vec![stkd::Unbond { - amount: Uint128::new(25), - unbonds_at: 24u64, - is_mature: None, - }], - unbond_amount_in_next_batch: Uint128::zero(), - estimated_time_of_maturity_for_next_batch: None, - }, - ); - - stkd::ExecuteMsg::MockFastForward { - steps: 21 - }.test_exec(&stkd, &mut chain, admin.clone(), &[]).unwrap(); - - assert_eq!( - stkd::QueryMsg::Unbonding { - address: user.clone(), - key: "password".to_string(), - page: None, - page_size: None, - time: None, - }.test_query::(&stkd, &chain).unwrap(), - stkd::QueryAnswer::Unbonding { - count: 1, - claimable_scrt: None, - unbondings: vec![stkd::Unbond { - amount: Uint128::new(25), - unbonds_at: 24u64, - is_mature: None, - }], - unbond_amount_in_next_batch: Uint128::zero(), - estimated_time_of_maturity_for_next_batch: None, - }, - ); - - // Test Claiming - stkd::ExecuteMsg::Claim {} - .test_exec(&stkd, &mut chain, user.clone(), &[]).unwrap(); - - assert_eq!( - stkd::QueryMsg::Balance { - address: user.clone(), - key: "password".to_string(), - }.test_query::(&stkd, &chain).unwrap(), - stkd::QueryAnswer::Balance { - amount: Uint128::new(25), - }, - ); - - assert_eq!( // right amount of scrt returned - chain.wrap().query::(&QueryRequest::Bank(BankQuery::Balance { - address: user.to_string(), - denom: "uscrt".to_string(), - })).unwrap(), - BalanceResponse { - amount: Coin { - amount: Uint128::new(950), - denom: "uscrt".to_string(), - }, - }, - ); - - // Test wrong viewing key - assert_eq!( - stkd::QueryMsg::Balance { - address: user.clone(), - key: "not password".to_string(), - }.test_query::(&stkd, &chain), - Err(StdError::generic_err("Querier contract error: Generic error: unauthorized")), - ); - - assert_eq!( - stkd::QueryMsg::Unbonding { - address: other.clone(), - key: "password".to_string(), - page: None, - page_size: None, - time: None, - }.test_query::(&stkd, &chain), - Err(StdError::generic_err("Querier contract error: mock_stkd_derivative::contract::ViewingKey not found")), - ); - - // Test Sending - stkd::ExecuteMsg::Send { - recipient: other.clone(), - recipient_code_hash: None, - amount: Uint128::new(25), - msg: None, - memo: None, - padding: None, - }.test_exec(&stkd, &mut chain, user.clone(), &[]).unwrap(); - - assert_eq!( - stkd::QueryMsg::Balance { - address: user.clone(), - key: "password".to_string(), - }.test_query::(&stkd, &chain).unwrap(), - stkd::QueryAnswer::Balance { - amount: Uint128::new(0), - }, - ); - - stkd::ExecuteMsg::SetViewingKey { - key: "other password".to_string(), - padding: None, - }.test_exec(&stkd, &mut chain, other.clone(), &[]).unwrap(); - - assert_eq!( - stkd::QueryMsg::Balance { - address: other.clone(), - key: "other password".to_string(), - }.test_query::(&stkd, &chain).unwrap(), - stkd::QueryAnswer::Balance { - amount: Uint128::new(25), - }, - ); - - // Test swap - let other_snip = snip20::InstantiateMsg { - name: "other_token".into(), - admin: None, - symbol: "OTHER".into(), - decimals: 6, - initial_balances: Some(vec![ - snip20::InitialBalance { - address: user.to_string(), - amount: Uint128::new(1000), - }, - snip20::InitialBalance { - address: admin.to_string(), - amount: Uint128::new(1000), - }, - ]), - prng_seed: Binary::from("random".as_bytes()), - config: None, - query_auth: None, - } - .test_init( - Snip20::default(), - &mut chain, - Addr::unchecked("admin"), - "snip20", - &[], - ).unwrap(); - - let sienna_pair = mock_sienna::InstantiateMsg { - token_0: stkd.clone().into(), - token_1: other_snip.clone().into(), - viewing_key: "key".into(), - commission: Decimal::permille(3), - }.test_init( - MockSienna::default(), - &mut chain, - Addr::unchecked("admin"), - "stkd pair", - &[], - ).unwrap(); - - stkd::ExecuteMsg::Stake {} // get stkd to seed pair - .test_exec(&stkd, &mut chain, admin.clone(), &[init_scrt]).unwrap(); - - stkd::ExecuteMsg::Send { // seed pair - recipient: sienna_pair.address.clone(), - recipient_code_hash: None, - amount: Uint128::new(499), - msg: None, - memo: None, - padding: None, - }.test_exec(&stkd, &mut chain, admin.clone(), &[]).unwrap(); - - snip20::ExecuteMsg::Send { // seed pair - recipient: sienna_pair.address.to_string(), - recipient_code_hash: None, - amount: Uint128::new(998), - msg: None, - memo: None, - padding: None, - }.test_exec(&other_snip, &mut chain, admin.clone(), &[]).unwrap(); - - mock_sienna::ExecuteMsg::MockPool { - token_a: Contract { - address: stkd.address.clone(), - code_hash: stkd.code_hash.clone(), - }, - token_b: Contract { - address: other_snip.address.clone(), - code_hash: other_snip.code_hash.clone(), - }, - }.test_exec(&sienna_pair, &mut chain, user.clone(), &[]).unwrap(); - - assert_eq!( - mock_sienna::QueryMsg::PairInfo {} - .test_query::(&sienna_pair, &chain).unwrap(), - mock_sienna::PairInfoResponse { - pair_info: PairInfo { - liquidity_token: Contract { - address: Addr::unchecked("lp_token"), - code_hash: "hash".to_string(), - }, - factory: Contract { - address: Addr::unchecked("factory"), - code_hash: "hash".to_string(), - }, - pair: Pair { - token_0: TokenType::CustomToken { - contract_addr: stkd.address.clone(), - token_code_hash: stkd.code_hash.clone(), - }, - token_1: TokenType::CustomToken { - contract_addr: other_snip.address.clone(), - token_code_hash: other_snip.code_hash.clone(), - }, - }, - amount_0: Uint128::new(499), - amount_1: Uint128::new(998), - total_liquidity: Uint128::new(0), - contract_version: 0, - }, - }, - ); - - snip20::ExecuteMsg::SetViewingKey { - key: "password".to_string(), - padding: None, - }.test_exec(&other_snip, &mut chain, user.clone(), &[]).unwrap(); - - assert_eq!( - snip20::QueryMsg::Balance { - address: user.to_string(), - key: "password".to_string(), - }.test_query::(&other_snip, &chain).unwrap(), - snip20::QueryAnswer::Balance { - amount: Uint128::new(1000), - }, - ); - - assert_eq!( - stkd::QueryMsg::Balance { - address: user.clone(), - key: "password".to_string(), - }.test_query::(&stkd, &chain).unwrap(), - stkd::QueryAnswer::Balance { - amount: Uint128::zero(), - }, - ); - - snip20::ExecuteMsg::Send { - recipient: sienna_pair.address.to_string(), - recipient_code_hash: Some(sienna_pair.clone().code_hash), - amount: Uint128::new(10), - msg: Some(to_binary(&mock_sienna::ReceiverCallbackMsg::Swap { - expected_return: None, - to: None, - }).unwrap()), - memo: None, - padding: None, - }.test_exec(&other_snip, &mut chain, user.clone(), &[]).unwrap(); - - assert_eq!( - snip20::QueryMsg::Balance { - address: user.to_string(), - key: "password".to_string(), - }.test_query::(&other_snip, &chain).unwrap(), - snip20::QueryAnswer::Balance { - amount: Uint128::new(990), - }, - ); - - assert_eq!( - stkd::QueryMsg::Balance { - address: user.clone(), - key: "password".to_string(), - }.test_query::(&stkd, &chain).unwrap(), - stkd::QueryAnswer::Balance { - amount: Uint128::new(5), - }, - ); - - assert_eq!( - mock_sienna::QueryMsg::PairInfo {} - .test_query::(&sienna_pair, &chain).unwrap(), - mock_sienna::PairInfoResponse { - pair_info: PairInfo { - liquidity_token: Contract { - address: Addr::unchecked("lp_token"), - code_hash: "hash".to_string(), - }, - factory: Contract { - address: Addr::unchecked("factory"), - code_hash: "hash".to_string(), - }, - pair: Pair { - token_0: TokenType::CustomToken { - contract_addr: stkd.address.clone(), - token_code_hash: stkd.code_hash.clone(), - }, - token_1: TokenType::CustomToken { - contract_addr: other_snip.address.clone(), - token_code_hash: other_snip.code_hash.clone(), - }, - }, - amount_0: Uint128::new(494), - amount_1: Uint128::new(1_008), - total_liquidity: Uint128::new(0), - contract_version: 0, - }, - }, - ); - - // test No balance - stkd::ExecuteMsg::SetViewingKey { - key: "key".into(), - padding: None, - }.test_exec(&stkd, &mut chain, Addr::unchecked("new"), &[]).unwrap(); - - assert_eq!( - stkd::QueryMsg::Balance { - address: Addr::unchecked("new"), - key: "key".to_string(), - }.test_query::(&stkd, &chain).unwrap(), - stkd::QueryAnswer::Balance { - amount: Uint128::zero(), - }, - ); - -} diff --git a/contracts/query_auth/.cargo/config b/contracts/query_auth/.cargo/config deleted file mode 100644 index c1e7c50..0000000 --- a/contracts/query_auth/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" -schema = "run --example schema" \ No newline at end of file diff --git a/contracts/query_auth/.circleci/config.yml b/contracts/query_auth/.circleci/config.yml deleted file mode 100644 index a6f10d6..0000000 --- a/contracts/query_auth/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.46 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/query_auth/Cargo.toml b/contracts/query_auth/Cargo.toml deleted file mode 100644 index 7cc8fcd..0000000 --- a/contracts/query_auth/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "query_auth" -version = "0.1.0" -authors = ["Guy Garcia "] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] -debug-print = ["shade-protocol/debug-print"] - -[dependencies] -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ - "query_auth_impl", - "admin" -] } -cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" } -schemars = "0.7" - -[dev-dependencies] -shade-multi-test = { version = "0.1.0", path = "../../packages/multi_test", features = [ "query_auth", "admin" ] } diff --git a/contracts/query_auth/README.md b/contracts/query_auth/README.md deleted file mode 100644 index d8f5fca..0000000 --- a/contracts/query_auth/README.md +++ /dev/null @@ -1,202 +0,0 @@ -# Query Authentication -* [Introduction](#Introduction) -* [Sections](#Sections) - * [Init](#Init) - * [Admin](#Admin) - * Messages - * [SetAdmin](#SetAdmin) - * [SetRunState](#SetRunState) - * [User](#User) - * Messages - * [SetViewingKey](#SetViewingKey) - * [CreateViewingKey](#CreateViewingKey) - * [BlockPermitKey](#BlockPermitKey) - * Queries - * [Config](#Config) - * [ValidateViewingKey](#ValidateViewingKey) - * [ValidatePermit](#ValidatePermit) - -# Introduction -User authentication manager that allows for validation for permits and viewing keys, making all smart contracts -share one viewing key. -# Sections - -## Init -##### Request -| Name | Type | Description | optional | -|-----------|-----------|------------------------------------------------|----------| -| admin | Addr | Contract admin | yes | -| prng_seed | Binary | Randomness seed for the viewing key generation | no | - -## Admin - -### Messages - -#### SetAdmin -Changes the current admin -##### Request -| Name | Type | Description | optional | -|---------|-----------|------------------------------------------------------|----------| -| admin | Addr | New contract admin; SHOULD be a valid bech32 address | no | -| padding | String | Randomly generated data to pad the message | yes | - - -##### Response -``` json -{ - "update_config": { - "status": "success" - } -} -``` - -#### SetRunState -Limits the smart contract's run state -##### Request -| Name | Type | Description | optional | -|---------|----------------|-------------------------------------------------------------------|----------| -| state | ContractStatus | Limits what queries / handlemsgs can be triggered in the contract | no | -| padding | String | Randomly generated data to pad the message | yes | - -#### ContractStatus -* Default -* DisablePermit -* DisableVK -* DisableAll - -##### Response -``` json -{ - "update_config": { - "status": "success" - } -} -``` - -## User - -### Messages - -#### SetViewingKey -Sets the signers viewing key -##### Request -| Name | Type | Description | optional | -|---------|--------|--------------------------------------------|----------| -| key | String | The new viewing key | no | -| padding | String | Randomly generated data to pad the message | yes | - -##### Response -``` json -{ - "update_config": { - "status": "success" - } -} -``` - -#### CreateViewingKey -Generated the signers viewing key with the given entropy -##### Request -| Name | Type | Description | optional | -|---------|--------|--------------------------------------------|----------| -| entropy | String | The entropy used for VK generation | no | -| padding | String | Randomly generated data to pad the message | yes | - -##### Response -``` json -{ - "update_config": { - "key": "new VK" - } -} -``` - -#### BlockPermitKey -Blocks a permit key, whenever a permit with that key is queried then it will return that its not valid -##### Request -| Name | Type | Description | optional | -|---------|--------|--------------------------------------------|----------| -| key | String | Permit key to block | no | -| padding | String | Randomly generated data to pad the message | yes | - -##### Response -``` json -{ - "update_config": { - "status": "success" - } -} -``` - -### Queries - -#### Config -Get the contracts config - -##### Response -```json -{ - "config": { - "admin": "address", - "state": "contract state" - } -} -``` - -#### ValidateViewingKey -Validates the users viewing key - -##### Request -| Name | Type | Description | optional | -|------|-----------|--------------------|----------| -| user | Addr | User to verify | no | -| key | String | User's viewing key | no | - -##### Response -```json -{ - "validate_viewing_key": { - "is_valid": true - } -} -``` - -#### ValidatePermit -Validates the users permit - -##### Request -| Name | Type | Description | optional | -|--------------|------------|-----------------------------|----------| -| permit | Permit | User's signed permit | no | - -#### Permit -```json -{ - "params": { - "data": "base64 data specific to the contract", - "key": "permit key" - }, - "signature": { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "Secp256k1 PubKey" - }, - "signature": "base64 signature of permit" - }, - "account_number": "optional account number", - "chain_id": "optional chain id", - "sequence": "optional sequence", - "memo": "Optional memo" -} -``` - -##### Response -NOTE: is revoked refers to if the permit's key has been blocked -```json -{ - "validate_permit": { - "user": "Signer's address", - "is_revoked": false - } -} -``` \ No newline at end of file diff --git a/contracts/query_auth/src/contract.rs b/contracts/query_auth/src/contract.rs deleted file mode 100644 index 856b1a5..0000000 --- a/contracts/query_auth/src/contract.rs +++ /dev/null @@ -1,130 +0,0 @@ -use crate::{handle, query}; -use shade_protocol::{ - c_std::{ - shd_entry_point, - to_binary, - Binary, - Deps, - DepsMut, - Env, - MessageInfo, - Response, - StdError, - StdResult, - }, - contract_interfaces::query_auth::{ - Admin, - ContractStatus, - ExecuteMsg, - InstantiateMsg, - QueryMsg, - RngSeed, - }, - utils::{pad_handle_result, pad_query_result, storage::plus::ItemStorage}, -}; - -// Used to pad up responses for better privacy. -pub const RESPONSE_BLOCK_SIZE: usize = 256; - -#[shd_entry_point] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - Admin(msg.admin_auth).save(deps.storage)?; - - RngSeed::new(msg.prng_seed).save(deps.storage)?; - - ContractStatus::Default.save(deps.storage)?; - - Ok(Response::new()) -} - -#[shd_entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - // Check what msgs are allowed - let status = ContractStatus::load(deps.storage)?; - match status { - // Do nothing - ContractStatus::Default => {} - // No permit interactions - ContractStatus::DisablePermit => match msg { - ExecuteMsg::BlockPermitKey { .. } => return Err(StdError::generic_err("unauthorized")), - _ => {} - }, - // No VK interactions - ContractStatus::DisableVK => match msg { - ExecuteMsg::CreateViewingKey { .. } | ExecuteMsg::SetViewingKey { .. } => { - return Err(StdError::generic_err("unauthorized")); - } - _ => {} - }, - // Nothing - ContractStatus::DisableAll => match msg { - ExecuteMsg::CreateViewingKey { .. } - | ExecuteMsg::SetViewingKey { .. } - | ExecuteMsg::BlockPermitKey { .. } => { - return Err(StdError::generic_err("unauthorized")); - } - _ => {} - }, - } - - pad_handle_result( - match msg { - ExecuteMsg::SetAdminAuth { admin, .. } => handle::try_set_admin(deps, env, info, admin), - ExecuteMsg::SetRunState { state, .. } => { - handle::try_set_run_state(deps, env, info, state) - } - ExecuteMsg::SetViewingKey { key, .. } => { - handle::try_set_viewing_key(deps, env, info, key) - } - ExecuteMsg::CreateViewingKey { entropy, .. } => { - handle::try_create_viewing_key(deps, env, info, entropy) - } - ExecuteMsg::BlockPermitKey { key, .. } => { - handle::try_block_permit_key(deps, env, info, key) - } - }, - RESPONSE_BLOCK_SIZE, - ) -} - -#[shd_entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - let status = ContractStatus::load(deps.storage)?; - match status { - // Do nothing - ContractStatus::Default => {} - // No permit interactions - ContractStatus::DisablePermit => { - if let QueryMsg::ValidatePermit { .. } = msg { - return Err(StdError::generic_err("unauthorized")); - } - } - // No VK interactions - ContractStatus::DisableVK => { - if let QueryMsg::ValidateViewingKey { .. } = msg { - return Err(StdError::generic_err("unauthorized")); - } - } - // Nothing - ContractStatus::DisableAll => { - if let QueryMsg::Config { .. } = msg { - } else { - return Err(StdError::generic_err("unauthorized")); - } - } - } - - pad_query_result( - to_binary(&match msg { - QueryMsg::Config { .. } => query::config(deps)?, - QueryMsg::ValidateViewingKey { user, key } => query::validate_vk(deps, user, key)?, - QueryMsg::ValidatePermit { permit } => query::validate_permit(deps, permit)?, - }), - RESPONSE_BLOCK_SIZE, - ) -} diff --git a/contracts/query_auth/src/handle.rs b/contracts/query_auth/src/handle.rs deleted file mode 100644 index bde98ee..0000000 --- a/contracts/query_auth/src/handle.rs +++ /dev/null @@ -1,95 +0,0 @@ -use shade_protocol::{ - admin::helpers::{validate_admin, AdminPermissions}, - c_std::{to_binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}, - contract_interfaces::query_auth::{ - auth::{HashedKey, Key, PermitKey}, - Admin, - ContractStatus, - ExecuteAnswer, - RngSeed, - }, - query_authentication::viewing_keys::ViewingKey, - utils::{ - generic_response::ResponseStatus::Success, - storage::plus::{ItemStorage, MapStorage}, - }, -}; - -use shade_protocol::utils::asset::Contract; - -fn user_authorized(deps: &Deps, _env: Env, info: &MessageInfo) -> StdResult<()> { - let contract = Admin::load(deps.storage)?.0; - - validate_admin( - &deps.querier, - AdminPermissions::QueryAuthAdmin, - info.sender.clone(), - &contract, - ) -} - -pub fn try_set_admin( - deps: DepsMut, - env: Env, - info: MessageInfo, - admin: Contract, -) -> StdResult { - user_authorized(&deps.as_ref(), env, &info)?; - - Admin(admin).save(deps.storage)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetAdminAuth { status: Success })?)) -} - -pub fn try_set_run_state( - deps: DepsMut, - env: Env, - info: MessageInfo, - state: ContractStatus, -) -> StdResult { - user_authorized(&deps.as_ref(), env, &info)?; - - state.save(deps.storage)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetRunState { status: Success })?)) -} - -pub fn try_create_viewing_key( - deps: DepsMut, - env: Env, - info: MessageInfo, - entropy: String, -) -> StdResult { - let seed = RngSeed::load(deps.storage)?.0; - - let key = Key::generate(&info, &env, seed.as_slice(), &entropy.as_ref()); - - HashedKey(key.hash()).save(deps.storage, info.sender)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::CreateViewingKey { key: key.0 })?)) -} - -pub fn try_set_viewing_key( - deps: DepsMut, - _env: Env, - info: MessageInfo, - key: String, -) -> StdResult { - HashedKey(Key(key).hash()).save(deps.storage, info.sender)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetViewingKey { status: Success })?)) -} - -pub fn try_block_permit_key( - deps: DepsMut, - _env: Env, - info: MessageInfo, - key: String, -) -> StdResult { - PermitKey::revoke(deps.storage, key, info.sender)?; - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::BlockPermitKey { - status: Success, - })?), - ) -} diff --git a/contracts/query_auth/src/lib.rs b/contracts/query_auth/src/lib.rs deleted file mode 100644 index ff47582..0000000 --- a/contracts/query_auth/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod contract; -pub mod handle; -pub mod query; - -#[cfg(test)] -mod tests; diff --git a/contracts/query_auth/src/query.rs b/contracts/query_auth/src/query.rs deleted file mode 100644 index b052201..0000000 --- a/contracts/query_auth/src/query.rs +++ /dev/null @@ -1,33 +0,0 @@ -use shade_protocol::{ - c_std::{Addr, Deps, StdResult}, - contract_interfaces::query_auth::{ - auth::{Key, PermitKey}, - Admin, - ContractStatus, - QueryAnswer, - QueryPermit, - }, - utils::storage::plus::{ItemStorage, MapStorage}, -}; - -pub fn config(deps: Deps) -> StdResult { - Ok(QueryAnswer::Config { - admin: Admin::load(deps.storage)?.0, - state: ContractStatus::load(deps.storage)?, - }) -} - -pub fn validate_vk(deps: Deps, user: Addr, key: String) -> StdResult { - Ok(QueryAnswer::ValidateViewingKey { - is_valid: Key::verify(deps.storage, user, key)?, - }) -} - -pub fn validate_permit(deps: Deps, permit: QueryPermit) -> StdResult { - let user = permit.validate(deps.api, None)?.as_addr(None)?; - - Ok(QueryAnswer::ValidatePermit { - user: user.clone(), - is_revoked: PermitKey::may_load(deps.storage, (user, permit.params.key))?.is_some(), - }) -} diff --git a/contracts/query_auth/src/tests/handle.rs b/contracts/query_auth/src/tests/handle.rs deleted file mode 100644 index 608db9f..0000000 --- a/contracts/query_auth/src/tests/handle.rs +++ /dev/null @@ -1,274 +0,0 @@ -use crate::tests::{get_config, init_contract, validate_permit, validate_vk}; -use shade_protocol::{ - c_std::{from_binary, Addr}, - contract_interfaces::{query_auth, query_auth::ContractStatus}, - utils::{asset::Contract, ExecuteCallback}, -}; - -#[test] -fn set_admin() { - let (mut chain, auth) = init_contract().unwrap(); - - let msg = query_auth::ExecuteMsg::SetAdminAuth { - admin: Contract { - address: Addr::unchecked("some_addr"), - code_hash: "some_hash".to_string(), - }, - padding: None, - }; - - assert!( - &msg.test_exec(&auth, &mut chain, Addr::unchecked("not_admin"), &[]) - .is_err() - ); - - assert!( - &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) - .is_ok() - ); - - match get_config(&chain, &auth) { - Ok((admin, _)) => assert_eq!(admin.address, Addr::unchecked("some_addr")), - Err(_) => assert!(false), - }; -} - -#[test] -fn set_runstate() { - let (mut chain, auth) = init_contract().unwrap(); - - let msg = query_auth::ExecuteMsg::SetRunState { - state: ContractStatus::DisableAll, - padding: None, - }; - - assert!( - &msg.test_exec(&auth, &mut chain, Addr::unchecked("not_admin"), &[]) - .is_err() - ); - - assert!( - &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) - .is_ok() - ); - - match get_config(&chain, &auth) { - Ok((_, state)) => assert_eq!(state, ContractStatus::DisableAll), - Err(_) => assert!(false), - }; -} - -#[test] -fn runstate_block_permits() { - let (mut chain, auth) = init_contract().unwrap(); - - // Validate permits - - let msg = query_auth::ExecuteMsg::SetRunState { - state: ContractStatus::DisablePermit, - padding: None, - }; - - assert!( - &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) - .is_ok() - ); - - let msg = query_auth::ExecuteMsg::BlockPermitKey { - key: "key".to_string(), - padding: None, - }; - - assert!( - &msg.test_exec(&auth, &mut chain, Addr::unchecked("user"), &[]) - .is_err() - ); - - let msg = query_auth::ExecuteMsg::SetViewingKey { - key: "key".to_string(), - padding: None, - }; - - assert!( - &msg.test_exec(&auth, &mut chain, Addr::unchecked("user"), &[]) - .is_ok() - ); - - let msg = query_auth::ExecuteMsg::CreateViewingKey { - entropy: "random".to_string(), - padding: None, - }; - - assert!( - &msg.test_exec(&auth, &mut chain, Addr::unchecked("user"), &[]) - .is_ok() - ); - - assert!(validate_permit(&chain, &auth).is_err()); - - assert!(validate_vk(&chain, &auth, "user", "key").is_ok()); -} - -#[test] -fn runstate_block_vks() { - let (mut chain, auth) = init_contract().unwrap(); - - // Validate permits - - let msg = query_auth::ExecuteMsg::SetRunState { - state: ContractStatus::DisableVK, - padding: None, - }; - - assert!( - &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) - .is_ok() - ); - - let msg = query_auth::ExecuteMsg::BlockPermitKey { - key: "key".to_string(), - padding: None, - }; - - assert!( - &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) - .is_ok() - ); - - let msg = query_auth::ExecuteMsg::SetViewingKey { - key: "key".to_string(), - padding: None, - }; - - assert!( - &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) - .is_err() - ); - - let msg = query_auth::ExecuteMsg::CreateViewingKey { - entropy: "random".to_string(), - padding: None, - }; - - assert!( - &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) - .is_err() - ); - - assert!(validate_permit(&chain, &auth).is_ok()); - - assert!(validate_vk(&chain, &auth, "user", "key").is_err()); -} - -#[test] -fn runstate_block_all() { - let (mut chain, auth) = init_contract().unwrap(); - - // Validate permits - - let msg = query_auth::ExecuteMsg::SetRunState { - state: ContractStatus::DisableAll, - padding: None, - }; - - assert!( - &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) - .is_ok() - ); - - let msg = query_auth::ExecuteMsg::BlockPermitKey { - key: "key".to_string(), - padding: None, - }; - - assert!( - &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) - .is_err() - ); - - let msg = query_auth::ExecuteMsg::SetViewingKey { - key: "key".to_string(), - padding: None, - }; - - assert!( - &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) - .is_err() - ); - - let msg = query_auth::ExecuteMsg::CreateViewingKey { - entropy: "random".to_string(), - padding: None, - }; - - assert!( - &msg.test_exec(&auth, &mut chain, Addr::unchecked("admin"), &[]) - .is_err() - ); - - assert!(validate_permit(&chain, &auth).is_err()); - - assert!(validate_vk(&chain, &auth, "user", "key").is_err()); -} - -#[test] -fn set_vk() { - let (mut chain, auth) = init_contract().unwrap(); - - assert!( - query_auth::ExecuteMsg::SetViewingKey { - key: "password".to_string(), - padding: None - } - .test_exec(&auth, &mut chain, Addr::unchecked("user"), &[]) - .is_ok() - ); -} - -#[test] -fn create_vk() { - let (mut chain, auth) = init_contract().unwrap(); - - let data = query_auth::ExecuteMsg::CreateViewingKey { - entropy: "blah".to_string(), - padding: None, - } - .test_exec(&auth, &mut chain, Addr::unchecked("user"), &[]) - .unwrap() - .data - .unwrap(); - - let msg: query_auth::ExecuteAnswer = from_binary(&data).unwrap(); - - let key = match msg { - query_auth::ExecuteAnswer::CreateViewingKey { key, .. } => key, - _ => { - assert!(false); - "doesnt_work".to_string() - } - }; - - assert!(validate_vk(&chain, &auth, "user", &key).unwrap()); -} - -#[test] -fn block_permit_key() { - let (mut chain, auth) = init_contract().unwrap(); - - let msg = query_auth::ExecuteMsg::BlockPermitKey { - key: "key".to_string(), - padding: None, - }; - - assert!( - msg.test_exec( - &auth, - &mut chain, - Addr::unchecked("secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq"), - &[] - ) - .is_ok() - ); - - assert!(validate_permit(&chain, &auth).unwrap().1); -} diff --git a/contracts/query_auth/src/tests/mod.rs b/contracts/query_auth/src/tests/mod.rs deleted file mode 100644 index 266ea2e..0000000 --- a/contracts/query_auth/src/tests/mod.rs +++ /dev/null @@ -1,119 +0,0 @@ -pub mod handle; -pub mod query; - -use shade_multi_test::multi::{admin::Admin, query_auth::QueryAuth}; -use shade_protocol::{ - admin::{self, helpers::AdminPermissions}, - c_std::{Addr, Binary, ContractInfo, StdError, StdResult}, - contract_interfaces::query_auth::{self, PermitData, QueryPermit}, - multi_test::{App}, - query_auth::{ContractStatus}, - query_authentication::transaction::{PermitSignature, PubKey}, - utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -pub fn init_contract() -> StdResult<(App, ContractInfo)> { - let mut chain = App::default(); - - let admin = admin::InstantiateMsg { - super_admin: Some("admin".into()), - } - .test_init( - Admin::default(), - &mut chain, - Addr::unchecked("admin"), - "admin_auth", - &[], - ) - .unwrap(); - - let auth = query_auth::InstantiateMsg { - admin_auth: Contract { - address: admin.address.clone(), - code_hash: admin.code_hash.clone(), - }, - prng_seed: Binary::from("random".as_bytes()), - } - .test_init( - QueryAuth::default(), - &mut chain, - Addr::unchecked("admin"), - "query_auth", - &[], - ) - .unwrap(); - - admin::ExecuteMsg::UpdateRegistryBulk { - actions: vec![ - admin::RegistryAction::RegisterAdmin { - user: "admin".to_string(), - }, - admin::RegistryAction::GrantAccess { - permissions: vec![AdminPermissions::QueryAuthAdmin.into_string()], - user: "admin".to_string(), - }, - ], - } - .test_exec(&admin, &mut chain, Addr::unchecked("admin"), &[]) - .unwrap(); - - Ok((chain, auth)) -} - -pub fn get_permit() -> QueryPermit { - QueryPermit { - params: PermitData { - key: "key".to_string(), - data: Binary::from_base64("c29tZSBzdHJpbmc=").unwrap() - }, - signature: PermitSignature { - pub_key: PubKey::new( - Binary::from_base64( - "A9NjbriiP7OXCpoTov9ox/35+h5k0y1K0qCY/B09YzAP" - ).unwrap() - ), - signature: Binary::from_base64( - "XRzykrPmMs0ZhksNXX+eU0TM21fYBZXZogr5wYZGGy11t2ntfySuQNQJEw6D4QKvPsiU9gYMsQ259dOzMZNAEg==" - ).unwrap() - }, - account_number: None, - chain_id: Some(String::from("chain")), - sequence: None, - memo: None - } -} - -pub fn get_config(chain: &App, auth: &ContractInfo) -> StdResult<(Contract, ContractStatus)> { - let query: query_auth::QueryAnswer = - query_auth::QueryMsg::Config {}.test_query(&auth, &chain)?; - - match query { - query_auth::QueryAnswer::Config { admin, state } => Ok((admin, state)), - _ => Err(StdError::generic_err("Config not found")), - } -} - -pub fn validate_vk(chain: &App, auth: &ContractInfo, user: &str, key: &str) -> StdResult { - let query: query_auth::QueryAnswer = query_auth::QueryMsg::ValidateViewingKey { - user: Addr::unchecked(user), - key: key.to_string(), - } - .test_query(&auth, &chain)?; - - match query { - query_auth::QueryAnswer::ValidateViewingKey { is_valid } => Ok(is_valid), - _ => Err(StdError::generic_err("VK not found")), - } -} - -pub fn validate_permit(chain: &App, auth: &ContractInfo) -> StdResult<(Addr, bool)> { - let query: query_auth::QueryAnswer = query_auth::QueryMsg::ValidatePermit { - permit: get_permit(), - } - .test_query(&auth, &chain)?; - - match query { - query_auth::QueryAnswer::ValidatePermit { user, is_revoked } => Ok((user, is_revoked)), - _ => Err(StdError::generic_err("VK not found")), - } -} diff --git a/contracts/query_auth/src/tests/query.rs b/contracts/query_auth/src/tests/query.rs deleted file mode 100644 index de86a25..0000000 --- a/contracts/query_auth/src/tests/query.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::tests::{get_config, get_permit, init_contract, validate_permit, validate_vk}; -use shade_protocol::{ - c_std::{Addr}, - contract_interfaces::{query_auth, query_auth::ContractStatus}, - utils::{ExecuteCallback}, -}; - -#[test] -fn config() { - let (chain, auth) = init_contract().unwrap(); - - let (_admin, state) = get_config(&chain, &auth).unwrap(); - - assert_eq!(state, ContractStatus::Default); -} - -#[test] -fn vk_validation() { - let (mut chain, auth) = init_contract().unwrap(); - - assert!(!validate_vk(&chain, &auth, "user", "password").unwrap()); - - assert!( - query_auth::ExecuteMsg::SetViewingKey { - key: "password".to_string(), - padding: None - } - .test_exec(&auth, &mut chain, Addr::unchecked("user"), &[]) - .is_ok() - ); - - assert!(!validate_vk(&chain, &auth, "user", "not_password").unwrap()); - - assert!(validate_vk(&chain, &auth, "user", "password").unwrap()); -} - -#[test] -fn permit_validation() { - let _permit = get_permit(); - - let (chain, auth) = init_contract().unwrap(); - - let (user, is_revoked) = validate_permit(&chain, &auth).unwrap(); - - assert!(!is_revoked); - assert_eq!( - user, - Addr::unchecked("secret19rla95xfp22je7hyxv7h0nhm6cwtwahu69zraq") - ); -} diff --git a/contracts/snip20/.cargo/config b/contracts/snip20/.cargo/config deleted file mode 100644 index c1e7c50..0000000 --- a/contracts/snip20/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" -schema = "run --example schema" \ No newline at end of file diff --git a/contracts/snip20/.circleci/config.yml b/contracts/snip20/.circleci/config.yml deleted file mode 100644 index a6f10d6..0000000 --- a/contracts/snip20/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.46 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/snip20/Cargo.toml b/contracts/snip20/Cargo.toml deleted file mode 100644 index 4694cba..0000000 --- a/contracts/snip20/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "snip20" -version = "0.1.0" -authors = ["Guy Garcia "] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] -debug-print = ["shade-protocol/debug-print"] - -[dependencies] -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ - "storage", - "math", - "storage_plus", - "snip20-impl", - "query_auth", -] } -cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" } - -[dev-dependencies] -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ "multi-test", "admin" ] } -shade-multi-test = { path = "../../packages/multi_test", features = [ "snip20", "query_auth", "admin" ] } diff --git a/contracts/snip20/Makefile b/contracts/snip20/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/contracts/snip20/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/snip20/src/batch.rs b/contracts/snip20/src/batch.rs deleted file mode 100644 index 84e0e17..0000000 --- a/contracts/snip20/src/batch.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Types used in batch operations - -use shade_protocol::cosmwasm_schema::cw_serde; - -use shade_protocol::c_std::{Addr, Binary, Uint128}; - -#[cw_serde] -pub struct TransferAction { - pub recipient: Addr, - pub amount: Uint128, - pub memo: Option, -} - -#[cw_serde] -pub struct SendAction { - pub recipient: Addr, - pub recipient_code_hash: Option, - pub amount: Uint128, - pub msg: Option, - pub memo: Option, -} - -#[cw_serde] -pub struct TransferFromAction { - pub owner: Addr, - pub recipient: Addr, - pub amount: Uint128, - pub memo: Option, -} - -#[cw_serde] -pub struct SendFromAction { - pub owner: Addr, - pub recipient: Addr, - pub recipient_code_hash: Option, - pub amount: Uint128, - pub msg: Option, - pub memo: Option, -} - -#[cw_serde] -pub struct MintAction { - pub recipient: Addr, - pub amount: Uint128, - pub memo: Option, -} - -#[cw_serde] -pub struct BurnFromAction { - pub owner: Addr, - pub amount: Uint128, - pub memo: Option, -} diff --git a/contracts/snip20/src/contract.rs b/contracts/snip20/src/contract.rs deleted file mode 100644 index 94656b0..0000000 --- a/contracts/snip20/src/contract.rs +++ /dev/null @@ -1,443 +0,0 @@ -use crate::{ - handle::{ - allowance::{ - try_batch_send_from, - try_batch_transfer_from, - try_decrease_allowance, - try_increase_allowance, - try_send_from, - try_transfer_from, - }, - burning::{try_batch_burn_from, try_burn, try_burn_from}, - minting::{try_add_minters, try_batch_mint, try_mint, try_remove_minters, try_set_minters}, - transfers::{try_batch_send, try_batch_transfer, try_send, try_transfer}, - try_change_admin, - try_create_viewing_key, - try_deposit, - try_redeem, - try_register_receive, - try_revoke_permit, - try_set_contract_status, - try_set_viewing_key, - try_update_query_auth, - }, - query, -}; -use shade_protocol::{ - c_std::{ - shd_entry_point, - to_binary, - Addr, - Binary, - Deps, - DepsMut, - Env, - MessageInfo, - Response, - StdResult, - }, - contract_interfaces::snip20::{ - errors::{ - action_disabled, - invalid_viewing_key, - not_authenticated_msg, - permit_revoked, - unauthorized_permit, - }, - manager::{ContractStatusLevel, Key, PermitKey}, - ExecuteMsg, - InstantiateMsg, - Permission, - QueryMsg, - QueryWithPermit, - }, - query_auth::helpers::{authenticate_permit, authenticate_vk, PermitAuthentication}, - snip20::{errors::permit_not_found, manager::QueryAuth, PermitParams}, - utils::{ - asset::validate_vec, - pad_handle_result, - pad_query_result, - storage::plus::{ItemStorage, MapStorage}, - }, -}; - -// Used to pad up responses for better privacy. -pub const RESPONSE_BLOCK_SIZE: usize = 256; -pub const PREFIX_REVOKED_PERMITS: &str = "revoked_permits"; - -#[shd_entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - msg.save(deps.storage, deps.api, env, info)?; - Ok(Response::new()) -} - -#[shd_entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - // Check if transfers are allowed - let status = ContractStatusLevel::load(deps.storage)?; - match status { - // Ignore if normal run - ContractStatusLevel::NormalRun => {} - // Allow only status level updates or redeeming - ContractStatusLevel::StopAllButRedeems | ContractStatusLevel::StopAll => match msg { - ExecuteMsg::Redeem { .. } => { - if status != ContractStatusLevel::StopAllButRedeems { - return Err(action_disabled()); - } - } - ExecuteMsg::SetContractStatus { .. } => {} - _ => return Err(action_disabled()), - }, - } - - pad_handle_result( - match msg { - ExecuteMsg::Redeem { - amount, denom: _, .. - } => try_redeem(deps, env, info, amount), - - ExecuteMsg::Deposit { .. } => try_deposit(deps, env, info), - - ExecuteMsg::Transfer { - recipient, - amount, - memo, - .. - } => { - let recipient = deps.api.addr_validate(recipient.as_str())?; - try_transfer(deps, env, info, recipient, amount, memo) - } - - ExecuteMsg::Send { - recipient, - recipient_code_hash, - amount, - msg, - memo, - .. - } => { - let recipient = deps.api.addr_validate(recipient.as_str())?; - try_send( - deps, - env, - info, - recipient, - recipient_code_hash, - amount, - memo, - msg, - ) - } - - ExecuteMsg::BatchTransfer { actions, .. } => { - try_batch_transfer(deps, env, info, actions) - } - - ExecuteMsg::BatchSend { actions, .. } => try_batch_send(deps, env, info, actions), - - ExecuteMsg::Burn { amount, memo, .. } => try_burn(deps, env, info, amount, memo), - - ExecuteMsg::RegisterReceive { code_hash, .. } => { - try_register_receive(deps, env, info, code_hash) - } - - ExecuteMsg::CreateViewingKey { entropy, .. } => { - try_create_viewing_key(deps, env, info, entropy) - } - - ExecuteMsg::SetViewingKey { key, .. } => try_set_viewing_key(deps, env, info, key), - - ExecuteMsg::IncreaseAllowance { - spender, - amount, - expiration, - .. - } => { - let spender = deps.api.addr_validate(spender.as_str())?; - try_increase_allowance(deps, env, info, spender, amount, expiration) - } - ExecuteMsg::DecreaseAllowance { - spender, - amount, - expiration, - .. - } => { - let spender = deps.api.addr_validate(spender.as_str())?; - try_decrease_allowance(deps, env, info, spender, amount, expiration) - } - ExecuteMsg::TransferFrom { - owner, - recipient, - amount, - memo, - .. - } => { - let owner = deps.api.addr_validate(owner.as_str())?; - let recipient = deps.api.addr_validate(recipient.as_str())?; - try_transfer_from(deps, env, info, owner, recipient, amount, memo) - } - ExecuteMsg::SendFrom { - owner, - recipient, - recipient_code_hash, - amount, - msg, - memo, - .. - } => { - let owner = deps.api.addr_validate(owner.as_str())?; - let recipient = deps.api.addr_validate(recipient.as_str())?; - try_send_from( - deps, - env, - info, - owner, - recipient, - recipient_code_hash, - amount, - msg, - memo, - ) - } - ExecuteMsg::BatchTransferFrom { actions, .. } => { - try_batch_transfer_from(deps, env, info, actions) - } - - ExecuteMsg::BatchSendFrom { actions, .. } => { - try_batch_send_from(deps, env, info, actions) - } - - ExecuteMsg::BurnFrom { - owner, - amount, - memo, - .. - } => { - let owner = deps.api.addr_validate(owner.as_str())?; - try_burn_from(deps, env, info, owner, amount, memo) - } - ExecuteMsg::BatchBurnFrom { actions, .. } => { - try_batch_burn_from(deps, env, info, actions) - } - ExecuteMsg::Mint { - recipient, - amount, - memo, - .. - } => { - let recipient = deps.api.addr_validate(recipient.as_str())?; - try_mint(deps, env, info, recipient, amount, memo) - } - ExecuteMsg::BatchMint { actions, .. } => try_batch_mint(deps, env, info, actions), - ExecuteMsg::AddMinters { minters, .. } => { - let minters = validate_vec(deps.api, minters)?; - try_add_minters(deps, env, info, minters) - } - ExecuteMsg::RemoveMinters { minters, .. } => { - let minters = validate_vec(deps.api, minters)?; - try_remove_minters(deps, env, info, minters) - } - ExecuteMsg::SetMinters { minters, .. } => { - let minters = validate_vec(deps.api, minters)?; - try_set_minters(deps, env, info, minters) - } - ExecuteMsg::ChangeAdmin { address, .. } => { - let address = deps.api.addr_validate(address.as_str())?; - try_change_admin(deps, env, info, address) - } - ExecuteMsg::UpdateQueryAuth { auth } => try_update_query_auth(deps, env, info, auth), - ExecuteMsg::SetContractStatus { level, .. } => { - try_set_contract_status(deps, env, info, level) - } - - ExecuteMsg::RevokePermit { permit_name, .. } => { - try_revoke_permit(deps, env, info, permit_name) - } - }, - RESPONSE_BLOCK_SIZE, - ) -} - -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - pad_query_result( - to_binary(&match msg { - QueryMsg::TokenInfo {} => query::token_info(deps)?, - QueryMsg::TokenConfig {} => query::token_config(deps)?, - QueryMsg::ContractStatus {} => query::contract_status(deps)?, - QueryMsg::ExchangeRate {} => query::exchange_rate(deps)?, - QueryMsg::Minters {} => query::minters(deps)?, - - QueryMsg::WithPermit { - permit, - auth_permit, - query, - } => { - // Verify which authentication setting is set - let account: Addr; - let params: PermitParams; - - match QueryAuth::may_load(deps.storage)? { - None => { - if let Some(permit) = permit { - // Validate permit and get account - account = permit.validate(deps.api, None)?.as_addr(None)?; - - // Check that permit is not revoked - if PermitKey::may_load( - deps.storage, - (account.clone(), permit.params.permit_name.clone()), - )? - .is_some() - { - return Err(permit_revoked(permit.params.permit_name)); - } - - params = permit.params; - } else { - return Err(permit_not_found()); - } - } - Some(authenticator) => { - if let Some(permit) = auth_permit { - let res: PermitAuthentication = - authenticate_permit(permit, &deps.querier, authenticator.0)?; - - if res.revoked { - return Err(permit_revoked(res.data.permit_name)); - } - - account = res.sender; - params = res.data; - } else { - return Err(permit_not_found()); - } - } - }; - - match query { - QueryWithPermit::Allowance { owner, spender, .. } => { - let owner = deps.api.addr_validate(&owner)?; - let spender = deps.api.addr_validate(&spender)?; - - if !params.contains(Permission::Allowance) { - return Err(unauthorized_permit(Permission::Allowance)); - } - - if owner != account && spender != account { - return Err(unauthorized_permit(Permission::Allowance)); - } - - query::allowance(deps, owner, spender)? - } - QueryWithPermit::Balance {} => { - if !params.contains(Permission::Balance) { - return Err(unauthorized_permit(Permission::Balance)); - } - - query::balance(deps, account.clone())? - } - QueryWithPermit::TransferHistory { page, page_size } => { - if !params.contains(Permission::History) { - return Err(unauthorized_permit(Permission::History)); - } - - query::transfer_history( - deps, - account.clone(), - page.unwrap_or(0), - page_size, - )? - } - QueryWithPermit::TransactionHistory { page, page_size } => { - if !params.contains(Permission::History) { - return Err(unauthorized_permit(Permission::History)); - } - - query::transaction_history( - deps, - account.clone(), - page.unwrap_or(0), - page_size, - )? - } - } - } - - _ => match msg { - QueryMsg::Allowance { - owner, - spender, - key, - } => { - let owner = deps.api.addr_validate(&owner)?; - let spender = deps.api.addr_validate(&spender)?; - if try_authenticate_vk(&deps, owner.clone(), key.clone())? - || try_authenticate_vk(&deps, spender.clone(), key)? - { - query::allowance(deps, owner, spender)? - } else { - return Err(invalid_viewing_key()); - } - } - QueryMsg::Balance { address, key } => { - let address = deps.api.addr_validate(&address)?; - if try_authenticate_vk(&deps, address.clone(), key.clone())? { - query::balance(deps, address.clone())? - } else { - return Err(invalid_viewing_key()); - } - } - QueryMsg::TransferHistory { - address, - key, - page, - page_size, - } => { - let address = deps.api.addr_validate(&address)?; - if try_authenticate_vk(&deps, address.clone(), key.clone())? { - query::transfer_history( - deps, - address.clone(), - page.unwrap_or(0), - page_size, - )? - } else { - return Err(invalid_viewing_key()); - } - } - QueryMsg::TransactionHistory { - address, - key, - page, - page_size, - } => { - let address = deps.api.addr_validate(&address)?; - if try_authenticate_vk(&deps, address.clone(), key.clone())? { - query::transaction_history( - deps, - address.clone(), - page.unwrap_or(0), - page_size, - )? - } else { - return Err(invalid_viewing_key()); - } - } - _ => return Err(not_authenticated_msg()), - }, - }), - RESPONSE_BLOCK_SIZE, - ) -} - -fn try_authenticate_vk(deps: &Deps, address: Addr, key: String) -> StdResult { - match QueryAuth::may_load(deps.storage)? { - None => Key::verify(deps.storage, address, key), - Some(authenticator) => authenticate_vk(address, key, &deps.querier, &authenticator.0), - } -} diff --git a/contracts/snip20/src/handle/allowance.rs b/contracts/snip20/src/handle/allowance.rs deleted file mode 100644 index 69458d7..0000000 --- a/contracts/snip20/src/handle/allowance.rs +++ /dev/null @@ -1,203 +0,0 @@ -use crate::handle::transfers::{try_send_impl, try_transfer_impl}; -use shade_protocol::{ - c_std::{to_binary, Addr, Binary, DepsMut, Env, MessageInfo, Response, StdResult, Uint128}, - contract_interfaces::snip20::{ - batch, - manager::{Allowance, CoinInfo}, - ExecuteAnswer, - }, - utils::{ - generic_response::ResponseStatus::Success, - storage::plus::{ItemStorage, MapStorage}, - }, -}; - -pub fn try_increase_allowance( - deps: DepsMut, - env: Env, - info: MessageInfo, - spender: Addr, - amount: Uint128, - expiration: Option, -) -> StdResult { - let owner = info.sender; - let mut allowance = Allowance::may_load(deps.storage, (owner.clone(), spender.clone()))? - .unwrap_or(Allowance::default()); - - // Reset allowance if its expired - if allowance.is_expired(&env.block) { - allowance.amount = amount; - allowance.expiration = None; - } else { - allowance.amount = match allowance.amount.checked_add(amount) { - Ok(amount) => amount, - Err(_) => Uint128::MAX, - } - } - - if expiration.is_some() { - allowance.expiration = expiration; - } - - allowance.save(deps.storage, (owner.clone(), spender.clone()))?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::IncreaseAllowance { - spender, - owner, - allowance: allowance.amount, - })?), - ) -} - -pub fn try_decrease_allowance( - deps: DepsMut, - env: Env, - info: MessageInfo, - spender: Addr, - amount: Uint128, - expiration: Option, -) -> StdResult { - let owner = info.sender; - - let mut allowance = Allowance::load(deps.storage, (owner.clone(), spender.clone()))?; - - // Reset allowance if its expired - if allowance.is_expired(&env.block) { - allowance = Allowance::default(); - } else { - allowance.amount = match allowance.amount.checked_sub(amount) { - Ok(amount) => amount, - Err(_) => Uint128::zero(), - } - } - - if expiration.is_some() { - allowance.expiration = expiration; - } - - allowance.save(deps.storage, (owner.clone(), spender.clone()))?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::IncreaseAllowance { - spender, - owner, - allowance: allowance.amount, - })?), - ) -} - -pub fn try_transfer_from( - deps: DepsMut, - env: Env, - info: MessageInfo, - owner: Addr, - recipient: Addr, - amount: Uint128, - memo: Option, -) -> StdResult { - let denom = CoinInfo::load(deps.storage)?.symbol; - try_transfer_impl( - deps.storage, - &info.sender, - Some(&owner), - &recipient, - amount, - memo, - denom, - &env.block, - )?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::TransferFrom { status: Success })?)) -} - -pub fn try_batch_transfer_from( - deps: DepsMut, - env: Env, - info: MessageInfo, - actions: Vec, -) -> StdResult { - let denom = CoinInfo::load(deps.storage)?.symbol; - let block = &env.block; - for action in actions { - try_transfer_impl( - deps.storage, - &info.sender, - Some(&deps.api.addr_validate(action.owner.as_str())?), - &deps.api.addr_validate(action.recipient.as_str())?, - action.amount, - action.memo, - denom.clone(), - block, - )?; - } - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::BatchTransferFrom { - status: Success, - })?), - ) -} - -pub fn try_send_from( - deps: DepsMut, - env: Env, - info: MessageInfo, - owner: Addr, - recipient: Addr, - recipient_code_hash: Option, - amount: Uint128, - msg: Option, - memo: Option, -) -> StdResult { - let mut messages = vec![]; - let denom = CoinInfo::load(deps.storage)?.symbol; - try_send_impl( - deps.storage, - &mut messages, - &info.sender, - Some(&owner), - &recipient, - recipient_code_hash, - amount, - memo, - msg, - denom, - &env.block, - )?; - - Ok(Response::new() - .set_data(to_binary(&ExecuteAnswer::SendFrom { status: Success })?) - .add_submessages(messages)) -} - -pub fn try_batch_send_from( - deps: DepsMut, - env: Env, - info: MessageInfo, - actions: Vec, -) -> StdResult { - let mut messages = vec![]; - let sender = info.sender; - let denom = CoinInfo::load(deps.storage)?.symbol; - - for action in actions { - try_send_impl( - deps.storage, - &mut messages, - &sender, - Some(&deps.api.addr_validate(action.owner.as_str())?), - &deps.api.addr_validate(action.recipient.as_str())?, - action.recipient_code_hash, - action.amount, - action.memo, - action.msg, - denom.clone(), - &env.block, - )?; - } - - Ok(Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchSendFrom { status: Success })?) - .add_submessages(messages)) -} diff --git a/contracts/snip20/src/handle/burning.rs b/contracts/snip20/src/handle/burning.rs deleted file mode 100644 index e0e817f..0000000 --- a/contracts/snip20/src/handle/burning.rs +++ /dev/null @@ -1,127 +0,0 @@ -use shade_protocol::{ - c_std::{to_binary, Addr, DepsMut, Env, MessageInfo, Response, StdResult, Uint128}, - contract_interfaces::snip20::{ - batch, - errors::burning_disabled, - manager::{Allowance, Balance, CoinInfo, Config, TotalSupply}, - transaction_history::store_burn, - ExecuteAnswer, - }, - utils::{generic_response::ResponseStatus::Success, storage::plus::ItemStorage}, -}; - -pub fn try_burn( - deps: DepsMut, - env: Env, - info: MessageInfo, - amount: Uint128, - memo: Option, -) -> StdResult { - let sender = &info.sender; - let denom = CoinInfo::load(deps.storage)?.symbol; - - // Burn enabled - if !Config::burn_enabled(deps.storage)? { - return Err(burning_disabled()); - } - - Balance::sub(deps.storage, amount, sender)?; - // Dec total supply - TotalSupply::sub(deps.storage, amount)?; - - store_burn( - deps.storage, - &sender, - &sender, - amount, - denom, - memo, - &env.block, - )?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Burn { status: Success })?)) -} - -pub fn try_burn_from( - deps: DepsMut, - env: Env, - info: MessageInfo, - owner: Addr, - amount: Uint128, - memo: Option, -) -> StdResult { - let sender = &info.sender; - let denom = CoinInfo::load(deps.storage)?.symbol; - - // Burn enabled - if !Config::burn_enabled(deps.storage)? { - return Err(burning_disabled()); - } - - Allowance::spend(deps.storage, &owner, &sender, amount, &env.block)?; - Balance::sub(deps.storage, amount, &owner)?; - // Dec total supply - TotalSupply::sub(deps.storage, amount)?; - - store_burn( - deps.storage, - &owner, - &sender, - amount, - denom, - memo, - &env.block, - )?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::BurnFrom { status: Success })?)) -} - -pub fn try_batch_burn_from( - deps: DepsMut, - env: Env, - info: MessageInfo, - actions: Vec, -) -> StdResult { - let sender = &info.sender; - let denom = CoinInfo::load(deps.storage)?.symbol; - - // Burn enabled - if !Config::burn_enabled(deps.storage)? { - return Err(burning_disabled()); - } - - let mut supply = TotalSupply::load(deps.storage)?; - - for action in actions { - Allowance::spend( - deps.storage, - &deps.api.addr_validate(action.owner.as_str())?, - &sender, - action.amount, - &env.block, - )?; - - Balance::sub( - deps.storage, - action.amount, - &deps.api.addr_validate(action.owner.as_str())?, - )?; - - // Dec total supply - supply.0 = supply.0.checked_sub(action.amount)?; - - store_burn( - deps.storage, - &deps.api.addr_validate(action.owner.as_str())?, - &sender, - action.amount, - denom.clone(), - action.memo, - &env.block, - )?; - } - - supply.save(deps.storage)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::BatchBurnFrom { status: Success })?)) -} diff --git a/contracts/snip20/src/handle/minting.rs b/contracts/snip20/src/handle/minting.rs deleted file mode 100644 index 422a863..0000000 --- a/contracts/snip20/src/handle/minting.rs +++ /dev/null @@ -1,158 +0,0 @@ -use shade_protocol::{ - c_std::{to_binary, Addr, DepsMut, Env, MessageInfo, Response, StdResult, Storage, Uint128}, - contract_interfaces::snip20::{ - batch, - errors::{minting_disabled, not_admin, not_minter}, - manager::{Admin, Balance, CoinInfo, Config, Minters, TotalSupply}, - transaction_history::store_mint, - ExecuteAnswer, - }, - utils::{generic_response::ResponseStatus::Success, storage::plus::ItemStorage}, -}; - -fn try_mint_impl( - storage: &mut dyn Storage, - minter: &Addr, - recipient: &Addr, - amount: Uint128, - denom: String, - memo: Option, - block: &shade_protocol::c_std::BlockInfo, -) -> StdResult<()> { - Balance::add(storage, amount, recipient)?; - store_mint(storage, minter, recipient, amount, denom, memo, block)?; - Ok(()) -} - -pub fn try_mint( - deps: DepsMut, - env: Env, - info: MessageInfo, - recipient: Addr, - amount: Uint128, - memo: Option, -) -> StdResult { - // Mint enabled - if !Config::mint_enabled(deps.storage)? { - return Err(minting_disabled()); - } - // User is minter - if !Minters::load(deps.storage)?.0.contains(&info.sender) { - return Err(not_minter(&info.sender)); - } - // Inc total supply - TotalSupply::add(deps.storage, amount)?; - let sender = info.sender; - let block = env.block; - let denom = CoinInfo::load(deps.storage)?.symbol; - try_mint_impl( - deps.storage, - &sender, - &recipient, - amount, - denom, - memo, - &block, - )?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?)) -} - -pub fn try_batch_mint( - deps: DepsMut, - env: Env, - info: MessageInfo, - actions: Vec, -) -> StdResult { - // Mint enabled - if !Config::mint_enabled(deps.storage)? { - return Err(minting_disabled()); - } - // User is minter - if !Minters::load(deps.storage)?.0.contains(&info.sender) { - return Err(not_minter(&info.sender)); - } - - let sender = info.sender; - let block = env.block; - let denom = CoinInfo::load(deps.storage)?.symbol; - let supply = TotalSupply::load(deps.storage)?; - for action in actions { - supply.0.checked_add(action.amount)?; - try_mint_impl( - deps.storage, - &sender, - &deps.api.addr_validate(action.recipient.as_str())?, - action.amount, - denom.clone(), - action.memo, - &block, - )?; - } - supply.save(deps.storage)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::BatchMint { status: Success })?)) -} - -pub fn try_add_minters( - deps: DepsMut, - _env: Env, - info: MessageInfo, - new_minters: Vec, -) -> StdResult { - // Mint enabled - if !Config::mint_enabled(deps.storage)? { - return Err(minting_disabled()); - } - if Admin::load(deps.storage)?.0 != info.sender { - return Err(not_admin()); - } - - let mut minters = Minters::load(deps.storage)?; - minters.0.extend(new_minters); - minters.save(deps.storage)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::AddMinters { status: Success })?)) -} - -pub fn try_remove_minters( - deps: DepsMut, - _env: Env, - info: MessageInfo, - minters_to_remove: Vec, -) -> StdResult { - // Mint enabled - if !Config::mint_enabled(deps.storage)? { - return Err(minting_disabled()); - } - if Admin::load(deps.storage)?.0 != info.sender { - return Err(not_admin()); - } - - let mut minters = Minters::load(deps.storage)?; - for minter in minters_to_remove { - minters.0.retain(|x| x != &minter); - } - minters.save(deps.storage)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RemoveMinters { status: Success })?)) -} - -pub fn try_set_minters( - deps: DepsMut, - _env: Env, - info: MessageInfo, - minters: Vec, -) -> StdResult { - // Mint enabled - if !Config::mint_enabled(deps.storage)? { - return Err(minting_disabled()); - } - if Admin::load(deps.storage)?.0 != info.sender { - return Err(not_admin()); - } - - Minters(minters).save(deps.storage)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetMinters { status: Success })?)) -} diff --git a/contracts/snip20/src/handle/mod.rs b/contracts/snip20/src/handle/mod.rs deleted file mode 100644 index fb1cbdd..0000000 --- a/contracts/snip20/src/handle/mod.rs +++ /dev/null @@ -1,238 +0,0 @@ -pub mod allowance; -pub mod burning; -pub mod minting; -pub mod transfers; - -use shade_protocol::{ - c_std::{ - to_binary, - Addr, - BankMsg, - Coin, - CosmosMsg, - DepsMut, - Env, - MessageInfo, - Response, - StdResult, - Uint128, - }, - contract_interfaces::snip20::{ - errors::{ - deposit_disabled, - no_tokens_received, - not_admin, - not_enough_tokens, - redeem_disabled, - unsupported_token, - }, - manager::{ - Admin, - Balance, - CoinInfo, - Config, - ContractStatusLevel, - HashedKey, - Key, - PermitKey, - RandSeed, - ReceiverHash, - TotalSupply, - }, - transaction_history::{store_deposit, store_redeem}, - ExecuteAnswer, - }, - query_authentication::viewing_keys::ViewingKey, - snip20::manager::QueryAuth, - utils::{ - generic_response::ResponseStatus::Success, - storage::plus::{ItemStorage, MapStorage}, - }, - Contract, -}; - -pub fn try_redeem( - deps: DepsMut, - env: Env, - info: MessageInfo, - amount: Uint128, -) -> StdResult { - let sender = info.sender; - - if !Config::redeem_enabled(deps.storage)? { - return Err(redeem_disabled()); - } - - Balance::sub(deps.storage, amount, &sender)?; - TotalSupply::sub(deps.storage, amount)?; - - let token_reserve = Uint128::from( - deps.querier - .query_balance(&env.contract.address, "uscrt")? - .amount, - ); - if amount > token_reserve { - return Err(not_enough_tokens(amount, token_reserve)); - } - - let withdrawal_coins: Vec = vec![Coin { - denom: "uscrt".to_string(), - amount: amount.into(), - }]; - - let denom = CoinInfo::load(deps.storage)?.symbol; - - store_redeem(deps.storage, &sender, amount, denom, &env.block)?; - - Ok(Response::new() - .add_message(CosmosMsg::Bank(BankMsg::Send { - to_address: sender.into(), - amount: withdrawal_coins, - })) - .set_data(to_binary(&ExecuteAnswer::Redeem { status: Success })?)) -} - -pub fn try_deposit(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { - let sender = info.sender; - let mut amount = Uint128::zero(); - for coin in &info.funds { - // TODO: implement IBC coins - if coin.denom == "uscrt" { - amount = Uint128::from(coin.amount) - } else { - return Err(unsupported_token()); - } - } - - if amount.is_zero() { - return Err(no_tokens_received()); - } - - if !Config::deposit_enabled(deps.storage)? { - return Err(deposit_disabled()); - } - - TotalSupply::add(deps.storage, amount)?; - Balance::add(deps.storage, amount, &sender)?; - - store_deposit( - deps.storage, - &sender, - amount, - "uscrt".to_string(), - &env.block, - )?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Deposit { status: Success })?)) -} - -pub fn try_change_admin( - deps: DepsMut, - _env: Env, - info: MessageInfo, - address: Addr, -) -> StdResult { - if info.sender != Admin::load(deps.storage)?.0 { - return Err(not_admin()); - } - - Admin(address).save(deps.storage)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::ChangeAdmin { status: Success })?)) -} - -pub fn try_update_query_auth( - deps: DepsMut, - _env: Env, - info: MessageInfo, - auth: Option, -) -> StdResult { - if info.sender != Admin::load(deps.storage)?.0 { - return Err(not_admin()); - } - - if let Some(auth) = auth { - QueryAuth(auth).save(deps.storage)?; - } else { - QueryAuth::remove(deps.storage); - } - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::UpdateQueryAuth { - status: Success, - })?), - ) -} - -pub fn try_set_contract_status( - deps: DepsMut, - _env: Env, - info: MessageInfo, - status_level: ContractStatusLevel, -) -> StdResult { - if info.sender != Admin::load(deps.storage)?.0 { - return Err(not_admin()); - } - - status_level.save(deps.storage)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::SetContractStatus { - status: Success, - })?), - ) -} - -pub fn try_register_receive( - deps: DepsMut, - _env: Env, - info: MessageInfo, - code_hash: String, -) -> StdResult { - ReceiverHash(code_hash).save(deps.storage, info.sender)?; - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::RegisterReceive { - status: Success, - })?), - ) -} - -pub fn try_create_viewing_key( - deps: DepsMut, - env: Env, - info: MessageInfo, - entropy: String, -) -> StdResult { - let seed = RandSeed::load(deps.storage)?.0; - - let key = Key::generate(&info, &env, seed.as_slice(), (&entropy).as_ref()); - - HashedKey(key.hash()).save(deps.storage, info.sender)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::CreateViewingKey { key: key.0 })?)) -} - -pub fn try_set_viewing_key( - deps: DepsMut, - _env: Env, - info: MessageInfo, - key: String, -) -> StdResult { - // TODO: review this - //let seed = RandSeed::load(deps.storage)?.0; - - HashedKey(Key(key).hash()).save(deps.storage, info.sender)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetViewingKey { status: Success })?)) -} - -pub fn try_revoke_permit( - deps: DepsMut, - _env: Env, - info: MessageInfo, - permit_name: String, -) -> StdResult { - PermitKey::revoke(deps.storage, permit_name, info.sender)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RevokePermit { status: Success })?)) -} diff --git a/contracts/snip20/src/handle/transfers.rs b/contracts/snip20/src/handle/transfers.rs deleted file mode 100644 index 5f9e0b1..0000000 --- a/contracts/snip20/src/handle/transfers.rs +++ /dev/null @@ -1,239 +0,0 @@ -use shade_protocol::{ - c_std::{ - to_binary, - Addr, - Binary, - DepsMut, - Env, - MessageInfo, - Response, - StdResult, - Storage, - SubMsg, - Uint128, - }, - contract_interfaces::snip20::{ - batch, - errors::transfer_disabled, - manager::{Allowance, Balance, CoinInfo, Config, ReceiverHash}, - transaction_history::store_transfer, - ExecuteAnswer, - ReceiverHandleMsg, - }, - utils::{ - generic_response::ResponseStatus::Success, - storage::plus::{ItemStorage, MapStorage}, - ExecuteCallback, - }, - Contract, -}; - -pub fn try_transfer_impl( - storage: &mut dyn Storage, - sender: &Addr, //spender when using from - owner: Option<&Addr>, - recipient: &Addr, - amount: Uint128, - memo: Option, - denom: String, - block: &shade_protocol::c_std::BlockInfo, -) -> StdResult<()> { - if !Config::transfer_enabled(storage)? { - return Err(transfer_disabled()); - } - - let some_owner = match owner { - None => sender, - Some(owner) => { - Allowance::spend(storage, owner, sender, amount, block)?; - owner - } - }; - - Balance::transfer(storage, amount, some_owner, recipient)?; - - store_transfer( - storage, some_owner, sender, recipient, amount, denom, memo, block, - )?; - Ok(()) -} - -pub fn try_transfer( - deps: DepsMut, - env: Env, - info: MessageInfo, - recipient: Addr, - amount: Uint128, - memo: Option, -) -> StdResult { - let denom = CoinInfo::load(deps.storage)?.symbol; - try_transfer_impl( - deps.storage, - &info.sender, - None, - &recipient, - amount, - memo, - denom, - &env.block, - )?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?)) -} - -pub fn try_batch_transfer( - deps: DepsMut, - env: Env, - info: MessageInfo, - actions: Vec, -) -> StdResult { - let sender = info.sender; - let block = env.block; - let denom = CoinInfo::load(deps.storage)?.symbol; - for action in actions { - try_transfer_impl( - deps.storage, - &sender, - None, - &deps.api.addr_validate(action.recipient.as_str())?, - action.amount, - action.memo, - denom.clone(), - &block, - )?; - } - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::BatchTransfer { status: Success })?)) -} - -#[allow(clippy::too_many_arguments)] -fn try_add_receiver_api_callback( - storage: &dyn Storage, - messages: &mut Vec, - recipient: Addr, - recipient_code_hash: Option, - msg: Option, - sender: Addr, - from: Addr, - amount: Uint128, - memo: Option, -) -> StdResult<()> { - let receiver_hash = match recipient_code_hash { - None => ReceiverHash::may_load(storage, recipient.clone())?, - Some(hash) => Some(ReceiverHash(hash)), - }; - - if let Some(hash) = receiver_hash { - messages.push(SubMsg::new( - ReceiverHandleMsg::new(sender.to_string(), from.to_string(), amount, memo, msg) - .to_cosmos_msg( - &Contract { - address: recipient, - code_hash: hash.0, - }, - vec![], - )?, - )); - } - Ok(()) -} - -pub fn try_send_impl( - storage: &mut dyn Storage, - messages: &mut Vec, - sender: &Addr, - owner: Option<&Addr>, - recipient: &Addr, - recipient_code_hash: Option, - amount: Uint128, - memo: Option, - msg: Option, - denom: String, - block: &shade_protocol::c_std::BlockInfo, -) -> StdResult<()> { - try_transfer_impl( - storage, - &sender, - owner, - &recipient, - amount, - memo.clone(), - denom, - block, - )?; - try_add_receiver_api_callback( - storage, - messages, - recipient.clone(), - recipient_code_hash, - msg, - sender.clone(), - sender.clone(), - amount, - memo, - )?; - - Ok(()) -} - -pub fn try_send( - deps: DepsMut, - env: Env, - info: MessageInfo, - recipient: Addr, - recipient_code_hash: Option, - amount: Uint128, - memo: Option, - msg: Option, -) -> StdResult { - let mut messages = vec![]; - let denom = CoinInfo::load(deps.storage)?.symbol; - - try_send_impl( - deps.storage, - &mut messages, - &info.sender, - None, - &recipient, - recipient_code_hash, - amount, - memo, - msg, - denom, - &env.block, - )?; - - Ok(Response::new() - .set_data(to_binary(&ExecuteAnswer::Send { status: Success })?) - .add_submessages(messages)) -} - -pub fn try_batch_send( - deps: DepsMut, - env: Env, - info: MessageInfo, - actions: Vec, -) -> StdResult { - let mut messages = vec![]; - let sender = info.sender; - let denom = CoinInfo::load(deps.storage)?.symbol; - - for action in actions { - try_send_impl( - deps.storage, - &mut messages, - &sender, - None, - &deps.api.addr_validate(action.recipient.as_str())?, - action.recipient_code_hash, - action.amount, - action.memo, - action.msg, - denom.clone(), - &env.block, - )?; - } - - Ok(Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchSend { status: Success })?) - .add_submessages(messages)) -} diff --git a/contracts/snip20/src/lib.rs b/contracts/snip20/src/lib.rs deleted file mode 100644 index dfefd9b..0000000 --- a/contracts/snip20/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod contract; -pub mod handle; -pub mod query; - -#[cfg(test)] -mod tests; \ No newline at end of file diff --git a/contracts/snip20/src/query.rs b/contracts/snip20/src/query.rs deleted file mode 100644 index 07b845e..0000000 --- a/contracts/snip20/src/query.rs +++ /dev/null @@ -1,143 +0,0 @@ -use shade_protocol::c_std::{Uint128, Deps}; -use shade_protocol::c_std::{Addr, StdResult}; -use shade_protocol::{ - contract_interfaces::snip20::{ - manager::{ - Allowance, - Balance, - CoinInfo, - Config, - ContractStatusLevel, - Minters, - TotalSupply, - }, - transaction_history::{RichTx, Tx}, - QueryAnswer, - }, - utils::storage::plus::{ItemStorage, MapStorage}, -}; - -pub fn token_info( - deps: Deps, -) -> StdResult { - let info = CoinInfo::load(deps.storage)?; - - let total_supply = match Config::public_total_supply(deps.storage)? { - true => Some(TotalSupply::load(deps.storage)?.0), - false => None, - }; - - Ok(QueryAnswer::TokenInfo { - name: info.name, - symbol: info.symbol, - decimals: info.decimals, - total_supply, - }) -} - -pub fn token_config( - deps: Deps, -) -> StdResult { - Ok(QueryAnswer::TokenConfig { - // TODO: show the other addrd config items - public_total_supply: Config::public_total_supply(deps.storage)?, - deposit_enabled: Config::deposit_enabled(deps.storage)?, - redeem_enabled: Config::redeem_enabled(deps.storage)?, - mint_enabled: Config::mint_enabled(deps.storage)?, - burn_enabled: Config::burn_enabled(deps.storage)?, - transfer_enabled: Config::transfer_enabled(deps.storage)?, - }) -} - -pub fn contract_status( - deps: Deps, -) -> StdResult { - Ok(QueryAnswer::ContractStatus { - status: ContractStatusLevel::load(deps.storage)?, - }) -} - -pub fn exchange_rate( - deps: Deps, -) -> StdResult { - let decimals = CoinInfo::load(deps.storage)?.decimals; - if Config::deposit_enabled(deps.storage)? || Config::redeem_enabled(deps.storage)? { - let rate: Uint128; - let denom: String; - // if token has more decimals than SCRT, you get magnitudes of SCRT per token - if decimals >= 6 { - rate = Uint128::new(10u128.pow(decimals as u32 - 6)); - denom = "SCRT".to_string(); - // if token has less decimals, you get magnitudes token for SCRT - } else { - rate = Uint128::new(10u128.pow(6 - decimals as u32)); - denom = CoinInfo::load(deps.storage)?.symbol; - } - return Ok(QueryAnswer::ExchangeRate { rate, denom }); - } - Ok(QueryAnswer::ExchangeRate { - rate: Uint128::new(0), - denom: String::new(), - }) -} - -pub fn minters(deps: Deps) -> StdResult { - Ok(QueryAnswer::Minters { - minters: Minters::load(deps.storage)?.0, - }) -} - -pub fn allowance( - deps: Deps, - owner: Addr, - spender: Addr, -) -> StdResult { - let allowance = Allowance::may_load( - deps.storage, - (owner.clone(), spender.clone()) - )?.unwrap_or_default(); - - //panic!("allowance {}", allowance.amount); - - Ok(QueryAnswer::Allowance { - spender, - owner, - allowance: allowance.amount, - expiration: allowance.expiration, - }) -} - -pub fn balance( - deps: Deps, - account: Addr, -) -> StdResult { - Ok(QueryAnswer::Balance { - amount: Balance::may_load(deps.storage, account)?.unwrap_or(Balance(Uint128::zero())).0, - }) -} - -pub fn transfer_history( - deps: Deps, - account: Addr, - page: u32, - page_size: u32, -) -> StdResult { - let transfer = Tx::get(deps.storage, &account, page, page_size)?; - Ok(QueryAnswer::TransferHistory { - txs: transfer.0, - total: Some(transfer.1), - }) -} - -pub fn transaction_history( - deps: Deps, - account: Addr, - page: u32, - page_size: u32, -) -> StdResult { - let transfer = RichTx::get(deps.storage, &account, page, page_size)?; - Ok(QueryAnswer::TransactionHistory { - txs: transfer.0, - total: Some(transfer.1), - }) -} diff --git a/contracts/snip20/src/tests/handle/allowance.rs b/contracts/snip20/src/tests/handle/allowance.rs deleted file mode 100644 index 23503ba..0000000 --- a/contracts/snip20/src/tests/handle/allowance.rs +++ /dev/null @@ -1,267 +0,0 @@ -use shade_protocol::c_std::{Addr, Timestamp}; -use shade_protocol::utils::{ExecuteCallback, InstantiateCallback, Query, MultiTestable}; -use shade_protocol::c_std::Uint128; -use shade_protocol::contract_interfaces::snip20::{ExecuteMsg, InitialBalance, QueryAnswer, QueryMsg}; -use crate::tests::init_snip20_with_config; - -#[test] -fn increase_allowance() { - let (mut chain, snip) = init_snip20_with_config(Some(vec![ - InitialBalance{ - address: "sam".into(), - amount: (Uint128::new(5000)) - }, - InitialBalance { - address: "esmail".into(), - amount: Uint128::new(1) - }, - ]), None).unwrap(); - - chain.update_block(|block| block.time = Timestamp::from_seconds(0)); - - assert!(ExecuteMsg::IncreaseAllowance { - spender: "esmail".into(), - amount: Uint128::new(1000), - expiration: Some(1_000_000_000), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); - - let answer: QueryAnswer = QueryMsg::Allowance { - owner: "sam".into(), - spender: "esmail".into(), - key: "password".into() - }.test_query(&snip, &chain).unwrap(); - - match answer { - QueryAnswer::Allowance { spender, owner, allowance, expiration} => { - assert_eq!(allowance, Uint128::new(1000)); - }, - _ => assert!(false) - } - - assert!(ExecuteMsg::IncreaseAllowance { - spender: "esmail".into(), - amount: Uint128::new(1000), - expiration: Some(1_000_000_000), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); - - let answer: QueryAnswer = QueryMsg::Allowance { - owner: "sam".into(), - spender: "esmail".into(), - key: "password".into() - }.test_query(&snip, &chain).unwrap(); - - match answer { - QueryAnswer::Allowance { spender, owner, allowance, expiration} => { - assert_eq!(allowance, Uint128::new(2000)); - }, - _ => assert!(false) - } -} - -#[test] -fn decrease_allowance() { - let (mut chain, snip) = init_snip20_with_config(Some(vec![ - InitialBalance{ - address: "sam".into(), - amount: (Uint128::new(5000)) - }, - InitialBalance { - address: "esmail".into(), - amount: Uint128::new(1) - }, - ]), None).unwrap(); - - chain.update_block(|block| block.time = Timestamp::from_seconds(10000)); - - assert!(ExecuteMsg::IncreaseAllowance { - spender: "esmail".into(), - amount: Uint128::new(1000), - expiration: Some(1_000_000_000), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); - - assert!(ExecuteMsg::DecreaseAllowance { - spender: "esmail".into(), - amount: Uint128::new(600), - expiration: Some(1_000_000_000), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); - - let answer: QueryAnswer = QueryMsg::Allowance { - owner: "sam".into(), - spender: "esmail".into(), - key: "password".into() - }.test_query(&snip, &chain).unwrap(); - - match answer { - QueryAnswer::Allowance { spender, owner, allowance, expiration} => { - assert_eq!(allowance, Uint128::new(400)); - }, - _ => assert!(false) - } -} - -#[test] -fn transfer_from() { - let (mut chain, snip) = init_snip20_with_config(Some(vec![ - InitialBalance{ - address: "sam".into(), - amount: (Uint128::new(5000)) - }, - InitialBalance { - address: "esmail".into(), - amount: Uint128::new(1) - }, - ]), None).unwrap(); - - chain.update_block(|block| block.time = Timestamp::from_seconds(0)); - - // Insufficient allowance - assert!(ExecuteMsg::TransferFrom { - owner: "sam".into(), - recipient: "eliot".into(), - amount: Uint128::new(100), - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); - - assert!(ExecuteMsg::IncreaseAllowance { - spender: "esmail".into(), - amount: Uint128::new(1000), - expiration: Some(1_000_000_000), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); - - // Transfer more than allowed amount - assert!(ExecuteMsg::TransferFrom { - owner: "sam".into(), - recipient: "eliot".into(), - amount: Uint128::new(1100), - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); - - chain.update_block(|block| block.time = Timestamp::from_seconds(1_000_000_010)); - - - // Transfer expired - assert!(ExecuteMsg::TransferFrom { - owner: "sam".into(), - recipient: "eliot".into(), - amount: Uint128::new(900), - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); - - assert!(ExecuteMsg::IncreaseAllowance { - spender: "esmail".into(), - amount: Uint128::new(1000), - expiration: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); - - assert!(ExecuteMsg::TransferFrom { - owner: "sam".into(), - recipient: "eliot".into(), - amount: Uint128::new(900), - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_ok()); - - // Check that allowance gets spent - assert!(ExecuteMsg::TransferFrom { - owner: "sam".into(), - recipient: "eliot".into(), - amount: Uint128::new(200), - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); -} - -#[test] -fn send_from() { - let (mut chain, snip) = init_snip20_with_config(Some(vec![ - InitialBalance{ - address: "sam".into(), - amount: (Uint128::new(5000)) - }, - InitialBalance { - address: "esmail".into(), - amount: Uint128::new(1) - }, - ]), None).unwrap(); - - chain.update_block(|block| block.time = Timestamp::from_seconds(0)); - - // Insufficient allowance - assert!(ExecuteMsg::SendFrom { - owner: "sam".into(), - recipient: "eliot".into(), - recipient_code_hash: None, - amount: Uint128::new(100), - msg: None, - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); - - assert!(ExecuteMsg::IncreaseAllowance { - spender: "esmail".into(), - amount: Uint128::new(1000), - expiration: Some(1_000_000_000), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); - - // Transfer more than allowed amount - assert!(ExecuteMsg::SendFrom { - owner: "sam".into(), - recipient: "eliot".into(), - recipient_code_hash: None, - amount: Uint128::new(1100), - msg: None, - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); - - chain.update_block(|block| block.time = Timestamp::from_seconds(1_000_000_010)); - - // Transfer expired - assert!(ExecuteMsg::SendFrom { - owner: "sam".into(), - recipient: "eliot".into(), - recipient_code_hash: None, - amount: Uint128::new(900), - msg: None, - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); - - assert!(ExecuteMsg::IncreaseAllowance { - spender: "esmail".into(), - amount: Uint128::new(1000), - expiration: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); - - assert!(ExecuteMsg::SendFrom { - owner: "sam".into(), - recipient: "eliot".into(), - recipient_code_hash: None, - amount: Uint128::new(900), - msg: None, - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_ok()); - - // Check that allowance gets spent - assert!(ExecuteMsg::SendFrom { - owner: "sam".into(), - recipient: "eliot".into(), - recipient_code_hash: None, - amount: Uint128::new(200), - msg: None, - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); -} diff --git a/contracts/snip20/src/tests/handle/burn.rs b/contracts/snip20/src/tests/handle/burn.rs deleted file mode 100644 index f411f0b..0000000 --- a/contracts/snip20/src/tests/handle/burn.rs +++ /dev/null @@ -1,219 +0,0 @@ -use shade_protocol::c_std::{Addr, Timestamp}; -use shade_protocol::utils::{ExecuteCallback, Query, MultiTestable}; -use shade_protocol::c_std::Uint128; -use shade_protocol::contract_interfaces::snip20::{ExecuteMsg, InitConfig, InitialBalance}; -use shade_protocol::contract_interfaces::snip20::batch::BurnFromAction; -use shade_protocol::contract_interfaces::snip20::manager::{Balance, TotalSupply}; -use shade_protocol::utils::storage::plus::{ItemStorage, MapStorage}; -use crate::tests::init_snip20_with_config; - -#[test] -fn burn() { - let (mut chain, snip) = init_snip20_with_config(Some(vec![ - InitialBalance{ - address: "finger".into(), - amount: (Uint128::new(5000)) - }, - ]), Some(InitConfig { - public_total_supply: None, - enable_deposit: None, - enable_redeem: None, - enable_mint: None, - enable_burn: Some(true), - enable_transfer: None - })).unwrap(); - - chain.update_block(|block| block.time = Timestamp::from_seconds(0)); - - // Insufficient tokens - assert!(ExecuteMsg::Burn { - amount: Uint128::new(8000), - padding: None, - memo: None - }.test_exec(&snip, &mut chain, Addr::unchecked("finger"), &[]).is_err()); - - // Burn some - assert!(ExecuteMsg::Burn { - amount: Uint128::new(4000), - padding: None, - memo: None - }.test_exec(&snip, &mut chain, Addr::unchecked("finger"), &[]).is_ok()); - - // Check that tokens were spend - chain.deps(&snip.address, |storage| { - assert_eq!(Balance::load( - storage, - Addr::unchecked("finger")).unwrap().0, Uint128::new(1000) - ); - assert_eq!(TotalSupply::load(storage).unwrap().0, Uint128::new(1000) - ); - }); - -} - -#[test] -fn burn_from() { - let (mut chain, snip) = init_snip20_with_config(Some(vec![ - InitialBalance{ - address: "sam".into(), - amount: (Uint128::new(5000)) - }, - InitialBalance { - address: "esmail".into(), - amount: Uint128::new(1) - }, - ]), Some(InitConfig { - public_total_supply: None, - enable_deposit: None, - enable_redeem: None, - enable_mint: None, - enable_burn: Some(true), - enable_transfer: None - })).unwrap(); - - chain.update_block(|block| block.time = Timestamp::from_seconds(0)); - - // Insufficient allowance - assert!(ExecuteMsg::BurnFrom { - owner: "sam".into(), - amount: Uint128::new(1000), - padding: None, - memo: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); - - assert!(ExecuteMsg::IncreaseAllowance { - spender: "esmail".into(), - amount: Uint128::new(700), - expiration: Some(1_000_000_000), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); - - // Transfer more than allowed amount - assert!(ExecuteMsg::BurnFrom { - owner: "sam".into(), - amount: Uint128::new(1000), - padding: None, - memo: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); - - chain.update_block(|block| block.time = Timestamp::from_seconds(1_000_000_010)); - // Transfer expired - assert!(ExecuteMsg::BurnFrom { - owner: "sam".into(), - amount: Uint128::new(1000), - padding: None, - memo: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); - - assert!(ExecuteMsg::IncreaseAllowance { - spender: "esmail".into(), - amount: Uint128::new(1000), - expiration: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); - - assert!(ExecuteMsg::BurnFrom { - owner: "sam".into(), - amount: Uint128::new(800), - padding: None, - memo: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_ok()); - - // Check that allowance gets spent - assert!(ExecuteMsg::BurnFrom { - owner: "sam".into(), - amount: Uint128::new(300), - padding: None, - memo: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); -} - -#[test] -fn batch_burn_from() { - let (mut chain, snip) = init_snip20_with_config(Some(vec![ - InitialBalance{ - address: "eliot".into(), - amount: (Uint128::new(5000)) - }, - InitialBalance{ - address: "alderson".into(), - amount: (Uint128::new(5000)) - }, - InitialBalance{ - address: "sam".into(), - amount: (Uint128::new(5000)) - }, - InitialBalance { - address: "esmail".into(), - amount: Uint128::new(1) - }, - ]), Some(InitConfig { - public_total_supply: None, - enable_deposit: None, - enable_redeem: None, - enable_mint: None, - enable_burn: Some(true), - enable_transfer: None - })).unwrap(); - - chain.update_block(|block| block.time = Timestamp::from_seconds(0)); - - let granters = vec!["eliot", "alderson", "sam"]; - - let batch: Vec<_> = granters.iter().map(|name| { - BurnFromAction { - owner: (*name).to_string(), - amount: Uint128::new(800), - memo: None - } - }).collect(); - - // Insufficient allowance - assert!(ExecuteMsg::BatchBurnFrom { - actions: batch.clone(), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); - - for granter in granters.iter() { - assert!(ExecuteMsg::IncreaseAllowance { - spender: "esmail".into(), - amount: Uint128::new(700), - expiration: Some(1_000_000_000), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked(*granter), &[]).is_ok()); - } - - // Transfer more than allowed amount - assert!(ExecuteMsg::BatchBurnFrom { - actions: batch.clone(), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); - - chain.update_block(|block| block.time = Timestamp::from_seconds(1_000_000_010)); - - // Transfer expired - assert!(ExecuteMsg::BatchBurnFrom { - actions: batch.clone(), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); - - for granter in granters.iter() { - assert!(ExecuteMsg::IncreaseAllowance { - spender: "esmail".into(), - amount: Uint128::new(1000), - expiration: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked(*granter), &[]).is_ok()); - } - - assert!(ExecuteMsg::BatchBurnFrom { - actions: batch.clone(), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_ok()); - - // Check that allowance gets spent - assert!(ExecuteMsg::BatchBurnFrom { - actions: batch.clone(), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("esmail"), &[]).is_err()); -} \ No newline at end of file diff --git a/contracts/snip20/src/tests/handle/mint.rs b/contracts/snip20/src/tests/handle/mint.rs deleted file mode 100644 index a282d32..0000000 --- a/contracts/snip20/src/tests/handle/mint.rs +++ /dev/null @@ -1,152 +0,0 @@ -use shade_protocol::c_std::Addr; -use shade_protocol::utils::{ExecuteCallback, Query, MultiTestable}; -use shade_protocol::c_std::Uint128; -use shade_protocol::contract_interfaces::snip20::{ExecuteMsg, InitConfig}; -use shade_protocol::contract_interfaces::snip20::manager::{Balance, Minters, TotalSupply}; -use shade_protocol::utils::storage::plus::{ItemStorage, MapStorage}; -use crate::tests::init_snip20_with_config; - -#[test] -fn mint() { - let (mut chain, snip) = init_snip20_with_config(None, Some(InitConfig { - public_total_supply: None, - enable_deposit: None, - enable_redeem: None, - enable_mint: Some(true), - enable_burn: None, - enable_transfer: None - })).unwrap(); - - assert!(ExecuteMsg::Mint { - recipient: "jimmy".into(), - amount: Uint128::new(1000), - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_err()); - - assert!(ExecuteMsg::AddMinters { - minters: vec!["admin".into()], - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); - - assert!(ExecuteMsg::Mint { - recipient: "jimmy".into(), - amount: Uint128::new(1500), - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); - - chain.deps(&snip.address, |storage| { - assert_eq!(Balance::load( - storage, - Addr::unchecked("jimmy")).unwrap().0, Uint128::new(1500) - ); - assert_eq!(TotalSupply::load(storage).unwrap().0, Uint128::new(1500) - ); - }).unwrap(); -} - -#[test] -fn set_minters() { - let (mut chain, snip) = init_snip20_with_config(None, Some(InitConfig { - public_total_supply: None, - enable_deposit: None, - enable_redeem: None, - enable_mint: Some(true), - enable_burn: None, - enable_transfer: None - })).unwrap(); - - assert!(ExecuteMsg::SetMinters { - minters: vec!["admin".into()], - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("notadmin"), &[]).is_err()); - - assert!(ExecuteMsg::SetMinters { - minters: vec!["admin".into()], - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); - - chain.deps(&snip.address.clone(), |storage| { - assert_eq!(Minters::load(storage).unwrap().0, vec![Addr::unchecked("admin")]); - }).unwrap(); - - assert!(ExecuteMsg::SetMinters { - minters: vec!["other_address".into(), "some_other".into()], - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); - - chain.deps(&snip.address, |storage| { - assert_eq!(Minters::load(storage).unwrap().0, - vec![Addr::unchecked("other_address"), Addr::unchecked("some_other")]); - }).unwrap(); -} - -#[test] -fn add_minters() { - let (mut chain, snip) = init_snip20_with_config(None, Some(InitConfig { - public_total_supply: None, - enable_deposit: None, - enable_redeem: None, - enable_mint: Some(true), - enable_burn: None, - enable_transfer: None - })).unwrap(); - - assert!(ExecuteMsg::AddMinters { - minters: vec!["admin".into()], - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("notadmin"), &[]).is_err()); - - assert!(ExecuteMsg::AddMinters { - minters: vec!["admin".into()], - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); - - chain.deps(&snip.address.clone(), |storage| { - assert_eq!(Minters::load(storage).unwrap().0, vec![Addr::unchecked("admin")]); - }).unwrap(); - - assert!(ExecuteMsg::AddMinters { - minters: vec!["other_address".into(), "some_other".into()], - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); - - chain.deps(&snip.address, |storage| { - assert_eq!(Minters::load(storage).unwrap().0, - vec![ - Addr::unchecked("admin"), - Addr::unchecked("other_address"), - Addr::unchecked("some_other") - ]); - }).unwrap(); -} - -#[test] -fn remove_minters() { - let (mut chain, snip) = init_snip20_with_config(None, Some(InitConfig { - public_total_supply: None, - enable_deposit: None, - enable_redeem: None, - enable_mint: Some(true), - enable_burn: None, - enable_transfer: None - })).unwrap(); - - assert!(ExecuteMsg::AddMinters { - minters: vec!["other_address".into(), "some_other".into()], - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); - - assert!(ExecuteMsg::RemoveMinters { - minters: vec!["other_address".into()], - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); - - chain.deps(&snip.address, |storage| { - assert_eq!(Minters::load(storage).unwrap().0, - vec![ - Addr::unchecked("some_other") - ]); - }).unwrap(); -} \ No newline at end of file diff --git a/contracts/snip20/src/tests/handle/mod.rs b/contracts/snip20/src/tests/handle/mod.rs deleted file mode 100644 index 9e9a259..0000000 --- a/contracts/snip20/src/tests/handle/mod.rs +++ /dev/null @@ -1,229 +0,0 @@ -use shade_protocol::c_std::{Coin, Addr}; -use shade_protocol::c_std::Uint128; -use shade_protocol::utils::{ExecuteCallback}; -use shade_protocol::contract_interfaces::snip20::{ExecuteMsg, InitConfig}; -use shade_protocol::contract_interfaces::snip20::manager::{ContractStatusLevel, HashedKey, Key, ReceiverHash}; -use shade_protocol::utils::storage::plus::MapStorage; -use crate::tests::init_snip20_with_config; - -pub mod transfer; -pub mod wrap; -pub mod mint; -pub mod burn; -pub mod allowance; - -#[test] -fn register_receive() { - let (mut chain, snip) = init_snip20_with_config(None, None).unwrap(); - - assert!(ExecuteMsg::RegisterReceive { - code_hash: "some_hash".into(), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("contract"), &[]).is_ok()); - - chain.deps(&snip.address, |borrowed_chain| { - let hash = ReceiverHash::load(borrowed_chain, Addr::unchecked("contract")).unwrap(); - assert_eq!(hash.0, "some_hash".to_string()); - }).unwrap(); -} - -#[test] -fn create_viewing_key() { - let (mut chain, snip) = init_snip20_with_config(None, None).unwrap(); - - assert!(ExecuteMsg::CreateViewingKey { - entropy: "some_entropy".into(), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); - - chain.deps(&snip.address, |borrowed_chain| { - assert!(HashedKey:: - may_load(borrowed_chain, Addr::unchecked("sam")) - .unwrap().is_some()); - }).unwrap(); -} - -#[test] -fn set_viewing_key() { - let (mut chain, snip) = init_snip20_with_config(None, None).unwrap(); - - assert!(ExecuteMsg::SetViewingKey { - key: "some_key".into(), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("sam"), &[]).is_ok()); - - chain.deps(&snip.address, |borrowed_chain| { - assert!(Key::verify( - borrowed_chain, - Addr::unchecked("sam"), - "some_key".into() - ).unwrap()); - }).unwrap(); -} - -#[test] -fn change_admin() { - let (mut chain, snip) = init_snip20_with_config(None, None).unwrap(); - - assert!(ExecuteMsg::ChangeAdmin { - address: "newadmin".into(), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("notadmin"), &[]).is_err()); - - assert!(ExecuteMsg::ChangeAdmin { - address: "newadmin".into(), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); - - assert!(ExecuteMsg::ChangeAdmin { - address: "otheradmin".into(), - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_err()); -} - -#[test] -fn set_contract_status() { - let (mut chain, snip) = init_snip20_with_config(None, None).unwrap(); - - assert!(ExecuteMsg::SetContractStatus { - level: ContractStatusLevel::StopAll, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("notadmin"), &[]).is_err()); - - chain.deps(&snip.address.clone(), |storage| { - assert_eq!(ContractStatusLevel::load(storage).unwrap(), ContractStatusLevel::NormalRun); - }).unwrap(); - - assert!(ExecuteMsg::SetContractStatus { - level: ContractStatusLevel::StopAll, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); - - chain.deps(&snip.address, |storage| { - assert_eq!(ContractStatusLevel::load(storage).unwrap(), ContractStatusLevel::StopAll); - }).unwrap(); -} - -#[test] -fn contract_status_stop_all() { - let (mut chain, snip) = init_snip20_with_config(None, Some(InitConfig { - public_total_supply: None, - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: None, - enable_burn: None, - enable_transfer: None - })).unwrap(); - - let scrt_coin = Coin { - denom: "uscrt".into(), - amount: Uint128::new(1000) - }; - - chain.init_modules(|router, _, storage| { - router.bank.init_balance(storage, &Addr::unchecked("bob"), vec![scrt_coin.clone()]).unwrap(); - }); - - // Deposit - assert!(ExecuteMsg::Deposit { - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[scrt_coin]).is_ok()); - - assert!(ExecuteMsg::SetContractStatus { - level: ContractStatusLevel::StopAll, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); - - assert!(ExecuteMsg::Transfer { - recipient: "dylan".into(), - amount: Uint128::new(100), - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_err()); - - assert!(ExecuteMsg::Redeem { - amount: Uint128::new(100), - denom: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_err()); - - assert!(ExecuteMsg::SetContractStatus { - level: ContractStatusLevel::NormalRun, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); - - assert!(ExecuteMsg::Transfer { - recipient: "dylan".into(), - amount: Uint128::new(100), - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_ok()); - - assert!(ExecuteMsg::Redeem { - amount: Uint128::new(100), - denom: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_ok()); -} - -#[test] -fn contract_status_stop_all_but_redeem() { - let (mut chain, snip) = init_snip20_with_config(None, Some(InitConfig { - public_total_supply: None, - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: None, - enable_burn: None, - enable_transfer: None - })).unwrap(); - - let scrt_coin = Coin { - denom: "uscrt".into(), - amount: Uint128::new(1000) - }; - - chain.init_modules(|router, _, storage| { - router.bank.init_balance(storage, &Addr::unchecked("bob"), vec![scrt_coin.clone()]).unwrap(); - }); - - // Deposit - assert!(ExecuteMsg::Deposit { - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[scrt_coin]).is_ok()); - - assert!(ExecuteMsg::SetContractStatus { - level: ContractStatusLevel::StopAllButRedeems, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); - - assert!(ExecuteMsg::Transfer { - recipient: "dylan".into(), - amount: Uint128::new(100), - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_err()); - - assert!(ExecuteMsg::Redeem { - amount: Uint128::new(100), - denom: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_ok()); - - assert!(ExecuteMsg::SetContractStatus { - level: ContractStatusLevel::NormalRun, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("admin"), &[]).is_ok()); - - assert!(ExecuteMsg::Transfer { - recipient: "dylan".into(), - amount: Uint128::new(100), - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_ok()); - - assert!(ExecuteMsg::Redeem { - amount: Uint128::new(100), - denom: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_ok()); -} \ No newline at end of file diff --git a/contracts/snip20/src/tests/handle/transfer.rs b/contracts/snip20/src/tests/handle/transfer.rs deleted file mode 100644 index 13891f8..0000000 --- a/contracts/snip20/src/tests/handle/transfer.rs +++ /dev/null @@ -1,134 +0,0 @@ -use shade_protocol::c_std::Addr; -use shade_protocol::utils::{ExecuteCallback, Query}; -use shade_protocol::c_std::Uint128; -use shade_protocol::contract_interfaces::snip20::{ExecuteMsg, InitialBalance, QueryMsg, QueryAnswer}; -use crate::tests::init_snip20_with_config; - -#[test] -fn total_supply_overflow() { - assert!(init_snip20_with_config(Some(vec![ - InitialBalance{ - address: "john".into(), - amount: Uint128::MAX - } - ]), None).is_ok()); - - assert!(init_snip20_with_config(Some(vec![ - InitialBalance{ - address: "john".into(), - amount: (Uint128::MAX - Uint128::new(1)) - }, - InitialBalance { - address: "salchi".into(), - amount: Uint128::new(1) - }, - InitialBalance { - address: "chonn".into(), - amount: Uint128::new(1) - } - ]), None).is_err()); -} - -#[test] -fn transfer() { - let (mut chain, snip) = init_snip20_with_config(Some(vec![ - InitialBalance{ - address: "bob".into(), - amount: (Uint128::new(1000)) - }, - InitialBalance { - address: "dylan".into(), - amount: Uint128::new(1000) - }, - ]), None).unwrap(); - - assert!(ExecuteMsg::Transfer { - recipient: "dylan".into(), - amount: Uint128::new(100), - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_ok()); - - { - let answer: QueryAnswer = QueryMsg::Balance { - address: "bob".into(), - key: "password".into() - }.test_query(&snip, &chain).unwrap(); - - match answer { - QueryAnswer::Balance {amount} => assert_eq!(amount, Uint128::new(900)), - _ => assert!(false) - } - - let answer: QueryAnswer = QueryMsg::Balance { - address: "dylan".into(), - key: "password".into() - }.test_query(&snip, &chain).unwrap(); - - match answer { - QueryAnswer::Balance {amount} => assert_eq!(amount, Uint128::new(1100)), - _ => assert!(false) - } - } - - assert!(ExecuteMsg::Transfer { - recipient: "dylan".into(), - amount: Uint128::new(1000), - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_err()); -} - -#[test] -fn send() { - let (mut chain, snip) = init_snip20_with_config(Some(vec![ - InitialBalance{ - address: "bob".into(), - amount: (Uint128::new(1000)) - }, - InitialBalance { - address: "dylan".into(), - amount: Uint128::new(1000) - }, - ]), None).unwrap(); - - assert!(ExecuteMsg::Send { - recipient: "dylan".into(), - amount: Uint128::new(100), - recipient_code_hash: None, - memo: None, - padding: None, - msg: None - }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_ok()); - - { - let answer: QueryAnswer = QueryMsg::Balance { - address: "bob".into(), - key: "password".into() - }.test_query(&snip, &chain).unwrap(); - - match answer { - QueryAnswer::Balance {amount} => assert_eq!(amount, Uint128::new(900)), - _ => assert!(false) - } - - let answer: QueryAnswer = QueryMsg::Balance { - address: "dylan".into(), - key: "password".into() - }.test_query(&snip, &chain).unwrap(); - - match answer { - QueryAnswer::Balance {amount} => assert_eq!(amount, Uint128::new(1100)), - _ => assert!(false) - } - } - - assert!(ExecuteMsg::Send { - recipient: "dylan".into(), - amount: Uint128::new(1000), - recipient_code_hash: None, - memo: None, - padding: None, - msg: None - }.test_exec(&snip, &mut chain, Addr::unchecked("bob"), &[]).is_err()); -} \ No newline at end of file diff --git a/contracts/snip20/src/tests/handle/wrap.rs b/contracts/snip20/src/tests/handle/wrap.rs deleted file mode 100644 index 2318909..0000000 --- a/contracts/snip20/src/tests/handle/wrap.rs +++ /dev/null @@ -1,107 +0,0 @@ -use shade_protocol::c_std::{Coin, Addr}; -use shade_protocol::utils::{ExecuteCallback, Query, MultiTestable}; -use shade_protocol::c_std::Uint128; -use shade_protocol::contract_interfaces::snip20::{ExecuteMsg, InitConfig}; -use shade_protocol::contract_interfaces::snip20::manager::{Balance, TotalSupply}; -use shade_protocol::utils::storage::plus::{ItemStorage, MapStorage}; -use crate::tests::init_snip20_with_config; - -#[test] -fn deposit() { - let (mut chain, snip20) = init_snip20_with_config(None, Some(InitConfig{ - public_total_supply: None, - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: None, - enable_burn: None, - enable_transfer: None - })).unwrap(); - - let scrt_coin = Coin { - denom: "uscrt".into(), - amount: Uint128::new(1000) - }; - - let not_coin = Coin { - denom: "token".into(), - amount: Uint128::new(1000) - }; - - // chain.add_funds(Addr::unchecked("marco"), vec![ - // scrt_coin.clone(), not_coin.clone()]); - chain.init_modules(|router, _, storage| { - router.bank.init_balance(storage, &Addr::unchecked("marco"), vec![scrt_coin.clone(), not_coin.clone()]).unwrap(); - }); - - // Deposit - assert!(ExecuteMsg::Deposit { - padding: None - }.test_exec(&snip20, &mut chain, Addr::unchecked("marco"), &vec![not_coin]).is_err()); - - assert!(ExecuteMsg::Deposit { - padding: None - }.test_exec(&snip20, &mut chain, Addr::unchecked("marco"), &vec![scrt_coin]).is_ok()); - - // Check that internal states were updated accordingly - chain.deps(&snip20.address, |storage| { - assert_eq!(Balance::load( - storage, - Addr::unchecked("marco")).unwrap().0, Uint128::new(1000) - ); - assert_eq!(TotalSupply::load(storage).unwrap().0, Uint128::new(1000) - ); - }).unwrap(); -} - -#[test] -fn redeem() { - let (mut chain, snip20) = init_snip20_with_config(None, Some(InitConfig{ - public_total_supply: None, - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: None, - enable_burn: None, - enable_transfer: None - })).unwrap(); - - let scrt_coin = Coin { - denom: "uscrt".into(), - amount: Uint128::new(1000) - }; - - chain.init_modules(|router, _, storage| { - router.bank.init_balance(storage, &Addr::unchecked("marco"), vec![scrt_coin.clone()]).unwrap(); - }); - - - // Deposit - assert!(ExecuteMsg::Deposit { - padding: None - }.test_exec(&snip20, &mut chain, Addr::unchecked("marco"), &vec![scrt_coin]).is_ok()); - - // Redeem - assert!(ExecuteMsg::Redeem { - amount: Uint128::new(10000), - denom: None, - padding: None - }.test_exec(&snip20, &mut chain, Addr::unchecked("marco"), &[]).is_err()); - - assert!(ExecuteMsg::Redeem { - amount: Uint128::new(500), - denom: None, - padding: None - }.test_exec(&snip20, &mut chain, Addr::unchecked("marco"), &[]).is_ok()); - - // Check that internal states were updated accordingly - chain.deps(&snip20.address, |storage| { - assert_eq!(Balance::load( - storage, - Addr::unchecked("marco")).unwrap().0, Uint128::new(500) - ); - assert_eq!(TotalSupply::load(storage).unwrap().0, Uint128::new(500) - ); - }).unwrap(); - - let balance = chain.wrap().query_balance("marco", "uscrt").unwrap(); - assert_eq!(balance.amount, Uint128::new(500)); -} \ No newline at end of file diff --git a/contracts/snip20/src/tests/mod.rs b/contracts/snip20/src/tests/mod.rs deleted file mode 100644 index cb02326..0000000 --- a/contracts/snip20/src/tests/mod.rs +++ /dev/null @@ -1,116 +0,0 @@ -pub mod handle; -pub mod query; - -use shade_multi_test::multi::{admin::Admin, query_auth::QueryAuth, snip20::Snip20}; -use shade_protocol::{ - admin, - c_std::{Addr, Binary, ContractInfo, StdResult}, - contract_interfaces::{ - query_auth, - snip20, - snip20::{InitConfig, InitialBalance}, - }, - multi_test::{App, AppResponse, Executor}, - utils::{ExecuteCallback, InstantiateCallback, MultiTestable, Query}, - AnyResult, - Contract, -}; - -pub fn init_snip20_with_auth( - initial_balances: Option>, - config: Option, - auth: bool, -) -> AnyResult<(App, ContractInfo, Option)> { - let mut chain = App::default(); - - let query_auth_addr: Option; - let query_auth_contract: Option; - - if auth { - let stored_code = chain.store_code(Admin::default().contract()); - let admin = chain - .instantiate_contract( - stored_code, - Addr::unchecked("admin"), - &admin::InstantiateMsg { super_admin: None }, - &[], - "admin", - None, - ) - .unwrap(); - - let auth = query_auth::InstantiateMsg { - admin_auth: Contract { - address: admin.address.clone(), - code_hash: admin.code_hash.clone(), - }, - prng_seed: Binary::from("random".as_bytes()), - } - .test_init( - QueryAuth::default(), - &mut chain, - Addr::unchecked("admin"), - "query_auth", - &[], - ) - .unwrap(); - - query_auth_contract = Some(auth.clone()); - - query_auth_addr = Some(Contract { - address: auth.address, - code_hash: auth.code_hash, - }) - } else { - query_auth_addr = None; - query_auth_contract = None; - } - - let snip = snip20::InstantiateMsg { - name: "Token".into(), - admin: None, - symbol: "TKN".into(), - decimals: 8, - initial_balances: initial_balances.clone(), - prng_seed: Binary::from("random".as_bytes()), - config, - query_auth: query_auth_addr, - } - .test_init( - Snip20::default(), - &mut chain, - Addr::unchecked("admin"), - "snip20", - &[], - )?; - - if let Some(balances) = initial_balances { - for balance in balances.iter() { - create_vk(&mut chain, &snip, balance.address.as_str(), None)?; - } - } - - Ok((chain, snip, query_auth_contract)) -} - -pub fn init_snip20_with_config( - initial_balances: Option>, - config: Option, -) -> AnyResult<(App, ContractInfo)> { - let (chain, snip20, _) = init_snip20_with_auth(initial_balances, config, false)?; - - Ok((chain, snip20)) -} - -pub fn create_vk( - chain: &mut App, - snip: &ContractInfo, - addr: &str, - key: Option, -) -> AnyResult { - snip20::ExecuteMsg::SetViewingKey { - key: key.unwrap_or("password".into()), - padding: None, - } - .test_exec(snip, chain, Addr::unchecked(addr), &[]) -} diff --git a/contracts/snip20/src/tests/query/mod.rs b/contracts/snip20/src/tests/query/mod.rs deleted file mode 100644 index c65978d..0000000 --- a/contracts/snip20/src/tests/query/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod user; -pub mod public; \ No newline at end of file diff --git a/contracts/snip20/src/tests/query/public.rs b/contracts/snip20/src/tests/query/public.rs deleted file mode 100644 index 14bd2ac..0000000 --- a/contracts/snip20/src/tests/query/public.rs +++ /dev/null @@ -1,73 +0,0 @@ -use shade_protocol::contract_interfaces::snip20::{InitConfig, QueryAnswer, QueryMsg}; -use shade_protocol::utils::{ExecuteCallback, InstantiateCallback, Query, MultiTestable}; -use crate::tests::init_snip20_with_config; - -#[test] -fn token_info() { - let (mut chain, snip) = init_snip20_with_config(None, None).unwrap(); - let answer: QueryAnswer = QueryMsg::TokenInfo { }.test_query(&snip, &chain).unwrap(); - - match answer { - QueryAnswer::TokenInfo { name, symbol, decimals, total_supply} => { - assert_eq!(name, "Token"); - assert_eq!(symbol, "TKN"); - assert_eq!(decimals, 8); - assert_eq!(total_supply, None); - }, - _ => assert!(false) - } -} - -#[test] -fn token_config() { - let (mut chain, snip) = init_snip20_with_config(None, None).unwrap(); - let answer: QueryAnswer = QueryMsg::TokenConfig { }.test_query(&snip, &chain).unwrap(); - - match answer { - QueryAnswer::TokenConfig { - public_total_supply, - deposit_enabled, - redeem_enabled, - mint_enabled, - burn_enabled, - transfer_enabled - } => { - assert_eq!(public_total_supply, false); - assert_eq!(deposit_enabled, false); - assert_eq!(redeem_enabled, false); - assert_eq!(mint_enabled, false); - assert_eq!(burn_enabled, false); - }, - _ => assert!(false) - } - - let (mut chain, snip) = init_snip20_with_config(None, Some(InitConfig{ - public_total_supply: Some(true), - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: None, - enable_burn: None, - enable_transfer: None - })).unwrap(); - let answer: QueryAnswer = QueryMsg::TokenConfig { }.test_query(&snip, &chain).unwrap(); - - match answer { - QueryAnswer::TokenConfig { - public_total_supply, - deposit_enabled, - redeem_enabled, - mint_enabled, - burn_enabled, - transfer_enabled - } => { - assert_eq!(public_total_supply, true); - assert_eq!(deposit_enabled, true); - assert_eq!(redeem_enabled, true); - assert_eq!(mint_enabled, false); - assert_eq!(burn_enabled, false); - }, - _ => assert!(false) - } -} - -// TODO: add exchange rate after IBC is added \ No newline at end of file diff --git a/contracts/snip20/src/tests/query/user.rs b/contracts/snip20/src/tests/query/user.rs deleted file mode 100644 index ffdf784..0000000 --- a/contracts/snip20/src/tests/query/user.rs +++ /dev/null @@ -1,208 +0,0 @@ -use shade_protocol::c_std::{Coin, Addr, Uint128}; -use shade_protocol::contract_interfaces::snip20::{ExecuteMsg, InitialBalance, QueryAnswer, QueryMsg}; -use shade_protocol::contract_interfaces::snip20::transaction_history::{RichTx, TxAction}; -use shade_protocol::query_auth; -use shade_protocol::utils::{ExecuteCallback, InstantiateCallback, Query, MultiTestable}; -use crate::tests::{create_vk, init_snip20_with_auth, init_snip20_with_config}; - -#[test] -fn allowance_vk() { - let (mut chain, snip) = init_snip20_with_config(None, None).unwrap(); - - let saul = Addr::unchecked("saul"); - let goodman = Addr::unchecked("goodman"); - - create_vk(&mut chain, &snip, "saul", None).unwrap(); - - ExecuteMsg::IncreaseAllowance { - spender: goodman.clone().into_string(), - amount: Uint128::new(100), - expiration: None, - padding: None - }.test_exec(&snip, &mut chain, saul.clone(), &[]).unwrap(); - - let answer: QueryAnswer = QueryMsg::Allowance { - owner: saul.clone().into(), - spender: goodman.clone().into(), - key: "password".into() - }.test_query(&snip, &chain).unwrap(); - - match answer { - QueryAnswer::Allowance { spender, owner, allowance, expiration} => { - assert_eq!(owner, saul); - assert_eq!(spender, goodman); - assert_eq!(allowance, Uint128::new(100)); - assert_eq!(expiration, None); - }, - _ => assert!(false) - } -} - -#[test] -fn allowance_auth_vk() { - let (mut chain, snip, auth) = init_snip20_with_auth(None, None, true).unwrap(); - - let saul = Addr::unchecked("saul"); - let goodman = Addr::unchecked("goodman"); - - query_auth::ExecuteMsg::SetViewingKey { - key: "password".into(), - padding: None, - }.test_exec(&auth.unwrap(), &mut chain, saul.clone(), &[]).unwrap(); - - ExecuteMsg::IncreaseAllowance { - spender: goodman.clone().into_string(), - amount: Uint128::new(100), - expiration: None, - padding: None - }.test_exec(&snip, &mut chain, saul.clone(), &[]).unwrap(); - - let answer: QueryAnswer = QueryMsg::Allowance { - owner: saul.clone().into(), - spender: goodman.clone().into(), - key: "password".into() - }.test_query(&snip, &chain).unwrap(); - - match answer { - QueryAnswer::Allowance { spender, owner, allowance, expiration} => { - assert_eq!(owner, saul); - assert_eq!(spender, goodman); - assert_eq!(allowance, Uint128::new(100)); - assert_eq!(expiration, None); - }, - _ => assert!(false) - } -} - -#[test] -fn balance_vk() { - let (mut chain, snip) = init_snip20_with_config(Some(vec![InitialBalance { - address: "robinson".into(), - amount: Uint128::new(1500) - }]), None).unwrap(); - - let answer: QueryAnswer = QueryMsg::Balance { - address: "robinson".into(), - key: "password".into() - }.test_query(&snip, &chain).unwrap(); - - match answer { - QueryAnswer::Balance { amount } => { - assert_eq!(amount, Uint128::new(1500)); - }, - _ => assert!(false) - } -} - -#[test] -fn transaction_history() { - let setsuna = Addr::unchecked("setsuna"); - let stratos = Addr::unchecked("stratos"); - let smirnoff = Addr::unchecked("smirnoff"); - let felt = Addr::unchecked("felt"); - let tieria = Addr::unchecked("tieria"); - - let (mut chain, snip) = init_snip20_with_config(Some(vec![InitialBalance { - address: setsuna.clone().into_string(), - amount: Uint128::new(1500) - }]), None).unwrap(); - - ExecuteMsg::Transfer { - recipient: stratos.clone().into_string(), - amount: Uint128::new(200), - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("setsuna"), &[]).unwrap(); - - ExecuteMsg::Send { - recipient: smirnoff.clone().into_string(), - recipient_code_hash: None, - amount: Uint128::new(140), - msg: None, - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("setsuna"), &[]).unwrap(); - - ExecuteMsg::Transfer { - recipient: felt.clone().into_string(), - amount: Uint128::new(300), - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("setsuna"), &[]).unwrap(); - - ExecuteMsg::Transfer { - recipient: tieria.clone().into_string(), - amount: Uint128::new(540), - memo: None, - padding: None - }.test_exec(&snip, &mut chain, Addr::unchecked("setsuna"), &[]).unwrap(); - - let answer: QueryAnswer = QueryMsg::TransactionHistory { - address: setsuna.clone().into(), - key: "password".into(), - page: None, - page_size: 10 - }.test_query(&snip, &chain).unwrap(); - - match answer { - QueryAnswer::TransactionHistory { txs, .. } => { - assert_eq!(txs.len(), 5); - - assert_eq!(txs[0].id, 1); - assert_eq!(txs[0].action, TxAction::Mint { - minter: Addr::unchecked("admin"), - recipient: setsuna.clone() - }); - assert_eq!(txs[0].coins, Coin { - denom: "TKN".into(), - amount: Uint128::new(1500) - }); - - assert_eq!(txs[1].id, 2); - assert_eq!(txs[1].action, TxAction::Transfer { - from: setsuna.clone(), - sender: setsuna.clone(), - recipient: stratos.clone() - }); - assert_eq!(txs[1].coins, Coin { - denom: "TKN".into(), - amount: Uint128::new(200) - }); - - assert_eq!(txs[2].id, 3); - assert_eq!(txs[2].action, TxAction::Transfer { - from: setsuna.clone(), - sender: setsuna.clone(), - recipient: smirnoff.clone() - }); - assert_eq!(txs[2].coins, Coin { - denom: "TKN".into(), - amount: Uint128::new(140) - }); - - assert_eq!(txs[3].id, 4); - assert_eq!(txs[3].action, TxAction::Transfer { - from: setsuna.clone(), - sender: setsuna.clone(), - recipient: felt.clone() - }); - assert_eq!(txs[3].coins, Coin { - denom: "TKN".into(), - amount: Uint128::new(300) - }); - - assert_eq!(txs[4].id, 5); - assert_eq!(txs[4].action, TxAction::Transfer { - from: setsuna.clone(), - sender: setsuna.clone(), - recipient: tieria.clone() - }); - assert_eq!(txs[4].coins, Coin { - denom: "TKN".into(), - amount: Uint128::new(540) - }); - - }, - _ => assert!(false) - } -} \ No newline at end of file diff --git a/contracts/snip20_derivative/.cargo/config b/contracts/snip20_derivative/.cargo/config deleted file mode 100644 index bbe1fc9..0000000 --- a/contracts/snip20_derivative/.cargo/config +++ /dev/null @@ -1,8 +0,0 @@ -[alias] -# Temporarily removed the backtraces feature from the unit-test run due to compilation errors in -# the cosmwasm-std package: -# cosmwasm-std = { git = "https://github.com/scrtlabs/cosmwasm", branch = "secret" } -# unit-test = "test --lib --features backtraces" -unit-test = "test --lib" -integration-test = "test --test integration" -schema = "run --example schema" diff --git a/contracts/snip20_derivative/.circleci/config.yml b/contracts/snip20_derivative/.circleci/config.yml deleted file mode 100644 index 161e927..0000000 --- a/contracts/snip20_derivative/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.46 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: make compile-optimized - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/snip20_derivative/.editorconfig b/contracts/snip20_derivative/.editorconfig deleted file mode 100644 index 3d36f20..0000000 --- a/contracts/snip20_derivative/.editorconfig +++ /dev/null @@ -1,11 +0,0 @@ -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/snip20_derivative/.github/workflows/cicd.yaml b/contracts/snip20_derivative/.github/workflows/cicd.yaml deleted file mode 100644 index a7907e6..0000000 --- a/contracts/snip20_derivative/.github/workflows/cicd.yaml +++ /dev/null @@ -1,26 +0,0 @@ -on: [pull_request] - -name: Basic CICD - -jobs: - TestAndCompileContract: - runs-on: ubuntu-22.04 - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - target: wasm32-unknown-unknown - override: true - - - name: Run unit test - run: cargo test --locked - shell: bash - - # - name: Build contracts - # run: make compile-optimized-reproducible - # shell: bash diff --git a/contracts/snip20_derivative/.images/engineering-diagram.png b/contracts/snip20_derivative/.images/engineering-diagram.png deleted file mode 100644 index 50f1b07eb4261b241f08f462517d7d90641cd119..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63431 zcmd?QcQl+|^fxL6NdzfG5Rs7RB_SfBr70mIdL5!gA3=C#)D$Hl2vH(h^e_z3nHhoz zF-T`$WT%3>wj=hTVZEs$HT+pSm}_XGlA zV`C#YIM~+Kc3@yYQBkqBwpLG1Z)RquqoZSUbMwcKA8TuCxw*Mdo;)!zG11r8kB^UE zTwLtw=}AgT8Xq6Gv$KnhjqUC2O-@dR!{Non#h#v?-@kvKnwm;WOLKE`d;R)#XlQ6z zSy@|KTWV^mqoZR*MMYj--piLS`}+E#qoYShN56jkIy*b-iCEiW%eMMXiO(8$P0OH0e<=4KcSW^Qg?SXh{!pKoSn z=I-wP=FOX;qN1FfoRpN5nwpx3h=^COUcGqn!otGB-rl~YrN!FXy1cyH%gc*QCfnH9 z`1<;O`SK+zE6c&b0gXn#efu^sF)=(m9DzVMJ3Bi$IhmT8y12MJefkuMMEd*t0{{>h z7>LK?3knJX0s_dZt7GF6pNcDb`v>RdNhuk*s@e}Ph{|2N_2Bu-@TQjbM~@ywyh}29 z8WB6sfGSy@>L2?>dd zi~IKNTYrE5r%#_S7>u#8ac5_ztE+2RSeUoB_q%uRCaf2W!728ct18{0dP&Wp4E}TY zv9f_H71imu-QRt8-Cjh34{4v=Ra2myJV?VrL)V&A=na|-)!jQcwPF2pTdz{b6@7*d zb3LX;9$Z+}`E=Qfr%xu^$nLC8UADPooE^d{&$e$SN<#QmNvHT+alQ*ByHAFWA}q1{D1l9X>83q z9B*9hJCsjebMLD#MsPFt4zyCXmNK6%hXft%o}-td^h}z^I|+0;Uh~8*mQK)uLli?U z@h}P}*-Hc@7oK-6y8j^#GwU~!A@C&x9Tzr4Kk&o>9Q5$s8^BR898a!!;MvNpC8A;! zKzFhB!IVMTLUhpM_Jyu{4vD8w37Q$&5ywOc zyn2WcZSrDNio$ogCG-2VtCqP-mgTf%;ilT+tuYiyG-A1aiAv@ett$Z;UyGumYKx)E zXW3y%R!RP>PZ{jShotiHZctlhURt|yG!$#MAcE(vJ`L%hv zH_HW;r~;g1PFNljEz&6<5delTYkaaDi zMnyp?(ZE9_N&`tKziyQYa4gNGM5aGTf1SdAq*U)Z46!NVUmuBJej|yt$8HvBoatZn z=3y45jXo?Au+5iI{Z{YxjyNsV>p`b(OhK@)kf+jm9*^B)OAh`h`HsJ<#d>Fx# zH7Mz&PIB^2`T;7%nvPbcy3!=-jhoM)jW6i&#b?+=6t)odqY$3kYOJJwmxY3vpj@LD zsw7SOPr?@W!sQs|>G8~6E-tu}aejn1Huh$d*?MI+qvYQr^~_7zF7j0PSo!d%0hL+E0eFFRyCHxOk(2O5$`0$&GpKG736|$ z!2(<*7e)7eZnm_G9kJMwCbKB`K~6h!8lv&><>f99{VT=FhFY*o$;o;Jo!aQ+>iZ#M z*aGD;G4=b)FT|Fz4>k3u{kwcZpB~sy`%MZW!M%5$X5n~%YXQh zFbV`BYnW_dr87xZxiHD7fk=mCy8tJDZcT#Sp~#cVnmNS)_7<7RL?Tc!vJglpN-ROJ zG-pqYs?=$O>^;KdilS;|r$K?bn!J61lWiH&q5Vm&pQj_QF|-j9T2 z#3cop;!EONDKM?kc2zT8J!0^eJiBD2dP*VTVsg#v6vJ4qMlT8cwH2(~K*57G6Q*>w zR~VtU>EDdcI)=Wl*IQGm7V(lzH0%_c)mLt}KZPnFMt-bHo#kCZ5o)yW!?`NM2nm#t zCB;P*nY{s0VA6gco2E%^EWpC+Plo+s!Xxx^=F3SaAQ z^QGB(2B69++$^T#1s@+Vfv?$ptm23DoIyevwgQj7)C<_YJw=ZmsGd*WN&GwwbNe7K z0P8tTJh~-$^h#;qw$>CK8dY=g{hQa1KKLRQko?BqO~2z}L_fdz7`-ihd*|}XK|JJH z3NEgcM85@4hlZ_}uJ@nw)dfeQg|-7s&Q_ebQL;L8y5fZFGeEEGe37)i=(%gcD^BEz zb4me#+6XaH`jp!9{taG_%m5&!oJX$7^S0jp_@LlqITKh9(=lSGf81>e6ON;3)X`!t zdI;^~>(HYsPRw|TgXiq8&XIdeyybp5rxO9Y~I;E=+MX-&e4 zz^k0=^&aBFOJW*A%_{JBI%hw)o%6(aVD4m^;m>A9x|}`hC3V2N-TUd^N#se2cjtRM z`=%hg=~ohUJoSD&R(m*oHAuMu!H&ju)V*)?w}mtiGntkwV3Pj8GiuU%22dF(Nx%XA z;&X2Tu#z5nZ14TL7erc7FLW+i$?NXRE$&0vOT=D{u#`@U@IFckID@A?g|@#AYV@TK zzFC#CUu{Px`a%&f-{@oKG?n3%5Z_=J&z78OJna<%2wPWoD-{aZtfszcPg7Y4=j4@n zg@s!1hEI`&bWsqKm;XTs?3y9D5>BylT5;SmTphn21FI{vLCc zC@WKD16<^>FcPh+!64u3_}W^0o6BGDA0;_ zUGxlOI{Shh36f(^1y%Ma|H4daj6kKRCnxNZew(O*DL?^@tGTu)-T0zcDT0$6F~m7*n0JKE;5d~ga%uLSW?Y;$%t z40;|uRtO2k_z&wPA37Tf&`+K)H98pZEzGRPIJS1S2 z0f97i^~-vkT^x)g>GkwgY3>>CvMSv)9wXN@EhezGJ>_XRh##Zcluunb=LX(98jahQ`3HT-G<_xVC@WYVKI`@ zX0w>Z_dKH$D`}qvmva`O`u$H{>2TKTSQ;NmTK<}!FM-!Nzco`fD?leW5F?Ap2&5+M zr{BACFDqwt;6BA)6fVAplA^ahlkwMY6vftvxcc9WYW@^Dj=PQ)@i%qbw_$`HXh%-2 zm#`HG6{c~7f~I+J?_PY_H!Ch`E}K|7_hx<-Dy5l?YcZ}GMKfP!YovEM>)`P8*e~qpfip(AMRDDWB50$1B#{GqE@Slb!hO z&nWCKM>9o$9IUAK_8|DdoT+yBn=~-zC5X$O|`6$;%#?!A|uS<`bKG20SrjU3- zoOL`s01bxY2rpCWFURQ`Y5A&AH}>y|172GDy99Ki;s60s-1xSqmwiDZ-0p&fg;Zo# z(TpLfF?y=VHOe~n!eO`b?iQ>mp2}KnNApj|ue5&Lnq^HFVJj?95$R34g583)_qDBFP zfWZ(;ydCbf6{$V;0v*<+#si-Fb)WXK+c=^|FgVy8S5$d$=@`@!J|d@nu5-@`t4&UN z!r@C}HJ}^+y!=w7-DTx*CSY~PI5b?qz@jP5z~N!;GLpw+`5-=ux+am##QJ$lhdHUr&`o3=c1l0O>&WG6k^fX&x)kjhMwR^=aQnjNp$^K>P3wy4q3txd-s`NuvCmUvLt0X$Zd4blDd z*xIsnzE=X@w499a%<$YaxqP|T?WJ~A$V!or@2H)3X-44XvHoegLB}M+P3RGAk!;+K zYe6ZOr*U`iZ&=FFj-om3g0={~%Wn;Cn@*JxAUps^mX(fcGUtJw2cY)ID(cZcn#D`t zY(c^CcDZ*+y8RbxG649f;r*OQ;YKxYLbcBHE9~IrL9WHMAICWqeJ+iP-*Xf_@w{2c zyH`GH7>Z%xVkL_)!A-;>J)AaFF?fG=%dr>H5pXGI92w%s#VOjdUn04SA zNrzhORENDDhni$`&wQ`N5>b9*(pTH}$AU;NGk648GjVo*UNuuR`H>Y!sMRjm*aftnx(3E zdhf6LCZ3Wbw#!DUSXAZxG_;XhJ(Mhxs_OI&(QHZY0(mQeS9>4JA$d$9XPcqF@Riai zPX78PDQ(>oZmzfd%b~u-Y)p=}&KZe9M{+ay`E%Dk5Ie2Vs&9Rl0suF4r#JEjpcU*zGd;cJ3e{pkNcA0X=qPp~=+fmpTktH6I zgUJa}!i?WmG7UnFO~!CvPrLFuG9(zX*2pazoNjUUX(g>9jBMCrxdbPIq}t8uOPbse|&S14F9Tz*&8kTws5~3S$%<}0|nx4 zTS!=sY*$&fW<~e#d=o*3-?JbNpZ*9gneEMkG>#}zOm`$hOKQuq?F=6m!~yK2seuS? z!E6jlOxyb?9t@bA#a*8E{9OdCyPsb0?JQX6Dfe%a26H>Ozhz1%`hSev;1U4JXR&SQtS3Wm{>qCW~<;p~8 zFtp8N;~#b=k#-j3w-*gbhTQjdbLCht+T3^a&hWd58-QmzKH}TN~fkJT>)%IeVRj6x0Xwc2AuQ4a) zjVf+jrx(nG*misLbk`B=yPH26CT|&%W*{*@93n=D!zWo#r7EFxTENlY&PR=IiEjwEpT!Z?g+Ztsp0i{4i0j?~_791HY^;{$8SSrGmBG0dCyzO>BH$ zIn!5{qwF;0iT4V}aRoodoIhQF*iXS?z8{6CvZ<_HwJ++bP752Ug?n*lY6ZU3)$4l|C5L(q`XbFAdquBx1Sq1*0OU+i@be2C9(t}OQ( zivS33w@2&VN8^>7*+i_LOk2!+4Ynf9xSfXw0tt8AXv#cF-y&SFPL^hPV@Ho;?%Q6# z3K1#y7v~(6OO$%z?fZRhkzq@L(C9BpxrQt<{1_osq-?>|-8S(+J^`PVs5zv6*Dgx= zOgdRakW+&+oeDG43APytj8du9eU>Q7`TSVyWur4WKKpvD~K9Aqk31{^W`N2rSzi?+9nW&)kss1@wt!L76&7;1`0R%Xo zs(`$3858fd{d3J2BS~en{rmSL5La-qM+6WD5k2q5oEU+$)otT3+%AJ>EBfCOO3FM$ z0n+oI#7Nxgq$io^S-=nONV=d3@Ru^-sOUa9LF~uU?t$zO5M)>FyeObMyf?z>zAj_x za7#ZO!`|nF$&D2)Nw(XP!;pOL4NpDGveUa0s2Eq-CwPKr5tKyHr*a5B0}R~e+2It; zUC2~e%!?xx&L$A5QQ`;ivXb_BoO{6iWhq3I+9q<^ew(4Np?94p95h5Yc^ znMSFL#8GHPkqe3%o6BDZ?xcBCT2vfION$TVTn~_1s6e7?WpZQv?cENP^a^R4&>A#K z?y_7UHa!Je>Cw^$KoTQMe#m!0x(XErBcj4}{{P)q@6(swGc<~`E(k&vu`vPb^2(p^L_z+<{ zrmVEU7DlQt)|Vv6gpC_<;yKzOp4HD?P>51Pk4E)mw~`e!10Y7P7m9!!pw*?Sw?&6Fy%S*Co$Ji0+C~}<=k=v z`(1AK$RdlW@qFY7JT+u)6j$OwOMy15rTADEMo!-QfOl>&H#1pNgi8+dyB$=Lntat8 z#UKn$wqd^5M5agO4e>#bLh4*y`Q-?DqJc>2YxFUBLDHkpySo=P6z}*Elnq4V^hQny z_eX)QpX#wu@6v$-j)qB-qowsQ83#hhGLg*5Tz8UvwQ9>t!^?U~B{9Kh)S_~+zwN-W z>ceI3N;ais7$b8SP+*L4zh!d5_ZGXmyvCLA84f&(YnWhbT!*G7d5qk0JUqn$?-4m* zjANg3A<`RpI&WS;N; z!a@{`AR5HM(@U+h^;mW&UWA5%B&Cq$XJYo)cCuQx^sX;-ra4%iunS?Cw$XvMX z6aV53T6&I}GXQ&Y2lu?Az?Bo6M9t>u*Jf1an1aT30B*=+-P4o6KsKhIhul;?@X>g! zU>c&wSlEtNgG;4JjWY*tg}L7wciS9e_%h+UQQtAf__j(v;6CANa1g}==Gi+d%hW?0 z4De_kCqgY^55s_nHZMQVK$gNp+yAmVskK&LVC+COexi;7bIbR~^Zo?=zi|AF-Hf{~!{zbgk8hr*WeHR+;L8TTJET_>^B7Jo?h{ zoV4!zN9%m)1tL?qE)#EvJFo(^-%o18lN-rmw{klJfG&~al~OrD-h>UuqC_$g&1PwsCIN7BB*uKF-=K5 z>bvmq4xwbeiTDm1foU9H-gQq1*}ko@wGP!?`lwVfs(SN9$`9Il!Ffyyj5MetP28K# zM0!mi&j{@~+1*$m^ukk5M5E5;PGJy4VKA=X4h;rr%+8%|YE}*ns3jm@o+?P0 z(DDVf?0M>WwTupvI!A8)Y^R_5)`H7wcE3jJL&jkozgQU{eHxE4dDC7to;H`Cp4=S& z9rcpu^ryBXa_cnl7WRqcKv+F2h?r z%02%0?%QS7`G4Q;80ELh9eYh%xfv@kZ>?m&Xj7lfK|Z>=|Ihk{Q&t&a%f|>g#`{Qo zzvmbIGe1uqMODjZ=XAQPLGq%i6{jJz`AD_yf zd;2@L=H`c@ZpP3Dy|d)yW)pd%u22t}jeVs1$6-A(paw4@?X*ldO!`S(>09{npHnmb znVNrbqqx{-XQ4f!qNtXAci+yf59R;JmGOnq&YlB*&vPwjIIu=xWn=gv!u_Xi)c=g( z`9j!P8VwfpAr0+p$RDfPju{go!i3;+`o$;XtBIdcS|Cx)2xGYyGKMFH8TN@));u<*d11Y=~8Yu6WuiGJ}LvW4sMl3?I+Z;o-P&iaDQuDR_| z$0$s)`F$$V+`~8;gZL$xUZFAwCNA$tP9`9GzV^bS7KXd?XNL|uMB9Khy7V~AK;|a? z?@LWfM5DT3+HrR)bbW`7Rtg7ZcEJ&ki@j$f*)MF1#|E2m+$(I>nbY$Dt*Y{;!6Y1i z1gB7ft&x=0gylgxg)i0}?7R=2IZbUuv(t+8AAQl=fv4Ykw}w6cagwIVbGcYN;@qnB`+2CxVSwU00uGV#!=2Q`bw@3Y#oy_altQJ$c`Mrv z`}3+w*Q7$y0UbR)Q?a1DQdI(X(`+O*Zo#p`x=eaHw)3O~sQ0#RbaV(6ju1?Lc?eVJ0sO0m9*=Ck#2Jpo zBYF8vZ$Yg^wy zz`$)gyv;tk;Vn@l>1e#z7Zk7j3*663Db0NpgdFS&;WBHQoIWn;&wF9#txl?{3pWJI z!9~&=M3R#gR^&3Nx{U0mm{Fq*N7924mBwLrqV5i8YKY}Wb>hjzq;W>z;**>pm4@X4 zI}?K{clzu~YxT;8G;@1%133#~`?d~AvEangCd&$UkU>7uEBs=*IdUtl$I7%-J$O{T zht#~f=|N4ZL5+cU_;>)kr(!!5G$9##xDS%2s|;<&x0)W=pcLwm;x_9(am}7nI%V(` zZWT%BeFb&V6>hP5Y3wQ4sWL9+V>}DV%VFXk>!R8Oc zU+s<|fh>upNDG>+NnCx-$^^!O=K%5g>r2h3X!6$38a~(?Hf!5B-@s#dT8m0P zPt&HLYgelyjkdzI~HSdR7I2~1Y-hm@!ZJ< z*a?N}akqfA$`)_3ZXUKWu}U~dvYUACi}3hbh+vJ-zTf+ufR{=lTlor35?A64vx-KG~J{SYeIuNT<|)kR(FCskv2L zNfMejB}2zHb!_6+g53y|c#h(P=F*L-O#=fQVR6S9*WFOPpVV>)R(h-9!eQGs zG48sHCgb80S1^Ks_r9H1WKeysESSafP$=zVM8IjCw@_QZh?to0^Ybk_Gdd*Ao&2~Q z3i7oW%WJDnOQ9L-!t}Z;GPo|4jj;v`mn(W9e7-j=G78hTIL5MPmVu8Qh9Mg>BEIKH zhEBeyA8Q>-g7Y7Bq`zIJ{EXqm*G~a^b6ggyiozoLwUa8TaZS!eMw~pJAq1H*q5l{z zm0YctEq=8%mMQ;@<*lpH0TIc0*wY=UPucP5<+bs-6yhx%0di;ZQr9=>(Mz^9U0G=% zR~`LRvy)0!>EOHtI(^0OjPq1B9TMf~&#PjiE}$AY8&RG!RCs@&9!wiS$M?R0H}6tk zO~tWCScQ9Fn-gIo%PEUr;&q-)bC6BM@m8HX%2wf}=y#c{YRy(2xT7PNTk`^yCD5wO z`Z=Vb^o;rV%ugNJroop)^z==>*jc*M#hMC#*O<2^AIn~1c8cQEID`zI5%5(e5x;}Q ztzQPd01f^KEek=1ZsCav1{$4weEnAG^?a}S9<*G&(5G3YNRP2`^>I=SVIxSs)inaH zr{3=MK>32~4kyU&{GNme`=IfKuKQi7xKjz9NLd2ggGixZ>Zihe+ zfje!k%wl&?YI0jHufi0{cpP*82SkbdPmw`u$K}cNKE$M4T#+*nc&X!n`%wqE3ilY z>LYc9C~HGG1~thoNB%Qfkz~RRN3SnY_r`gaLf^o8u1Ne7#!q8BmB!0tdzoXem)3Q8 zmV2`NRXzv?YR=>_xZliZ#TCae{ym4UQ@FUc+Vg`3p4B6Nv~2kr&&(=9gF4qS`J0FN zdjJw)#XbK_Fbr7+kFN9SuekRUH%k0Ite@bG{^)?3@N!=edbTcBWu)&D`>Ualts>!0yj#K3zTkAtRR3lN zrAJ3N1`5RDRPs;VJXeU4vu&Y3){lG0VZT5ZRkd%QvCIV7a&V6JEjEoc4qSnW z@hJsnrVDnS(_J1_{WVYS84-KP%Q?Dh1wOXtnr0hVTvw6G8>!;Djyp%rp8O4aqZg5v zj-5PlFTH3jj_?=XENcZW9iGq}^PzSGc^gU!$~*E(u!`kMBQPbcz+)v?yoCBY?K6ru3RBnaOiDJs?FVK zlhcj2rj)oE7ZpqzNh+Kks`!w`ZJqA#I|B3bVu~gVwH3T2D|VQw0o45|MFEXeFC$(x zVRfJKW2Tq4O*bHAW^1+@o6}~81$-`SZ<}5l ziJWY>AF?H8L$=fn>vuj`H4m{jMDMfzQv>{@c?#p&SmvucxokDyhK>d8Il}t%SE^|C*ICe zG6yrOtR@AW#(CADJV1(k>i!T7*mCq}=?!r5Yy>b?5Yy%48S^d19xaIGjLge2lQvj_ zTKJX%3Nnd>Prji>W#7VP(!LnTEq0r*K=rpv`0%~a!}Ta;^7w517P}u%{T<9eYtN8; zGWmS+DUc6y{`4{ES(VmlSbHi%djN3qguZ?YcTmBTEMLit?fwrdB3gJi+f>c#9b0t> zd`R?cOe3t9!qLe2pllEd;$_5kKYAX=mww*J4UK4I%sv3O;$3yv09ql%W`c+IvclJk zL@P}hUvH9e$%D|ij{M9LU`!bvzBm|cnF)fJ$X8dz1cO*z`DUKP`9}oS#59AotvEynrlU`idf*s-sX^(|INzwDl~A^o6Y7fb@~SY4M{10ILjbfwvip z`Chiv^l_`2arpxaA`0KK-n(4LTuGZQ0BrB8M-)V@XK{aHm51LW77;wO5A#>y^E@1_ zK)s4eYFZR_@X4oWbk1y!U%3FIccrA4VB529AYU#3#j6V`58e$y^pJ^8|t(t~lLu}iRUuvAc)kMvp4xp=Do`aip%KB>1n>^ z`X3F5o|2!Axx92;(vzLy*{zuJar1kNj-zwMvfHWIA`_U=DnJwtGqQGXR6`V>k^Pee z2j0Yc+fE5QUHmN@K=#|Jsd9hv{lmT!0y+2Mq z&M)0q`T`v(kpl)?{gq%2|0HG)<7JQj9mzWQn}jvS3+stIK(I-i@SK;RXCv7NQ^P2a zD|-Ok(M!J~iRD2&b5WP{Dbjkp)azxL;LZ$c;icK^?>csuZ}M0T+_#1o>0{_fXPb01 z&a8z)|D?Ou&;z<87&PG10(5dLvWq= zhJ47OdnbP7y&(Ym3`ZCswLGP91gxB{Pr9-F5ehNIY!u>1=!42yRwh zAc!{rdGOlFJ!NxYJjwls;dgBw))IMxcr-EGOCqvEb;(NaJ2IZG9&FBRE?Z&haj$qF z`*e`C8wIk0NlzA>f<|e5#g1=hCb85dF}hJ#gRK09m@28#cW3(rEu&o7&PW^cbnpT( zfxYhOCJbKNJixsK)yDAa%yt~;;;=GY$Ha?tsPl7nqZE%Fb=z0nxBI?43BNNnrmiBl zHMcx1>xJbaRVpA+p4Uy72u{6Q^XYN(f`CZ<7(};L? zk6?!Fk9^y;ruY2hPffm||1Fg`Ns&=*%kotqi4l^ul}zl?BXO9tc({{>n3IVKs`f|c zo4ynAZapwb)?O4uny0HOZmYS0L^b>pw1(IiK;HBd@(O9(+$0&JC#vq@2f-LI+MI1c zg|FC54_T2K7qFA%R&dq%CSg)&azfuzXPs^`dtGS&^1x3OPmJRzRc$F-KMFy;z!rwh z*0wuZ1CjlSn|TV(?KXfCsf1^NVG>tyGRDIp1~VXqY&24l1p6;}K=zYw3`6}i6(mrs z6!Dn6=eRKW>_9Ed4uRVWPkUPrDxcU3ERJLPh&cAr{`aTbP_HwgOjhNIoSmy8Y8;K4 ztjB3(oLac;(oUXtoijN90$`jps-wHDw=bR+G@Io`;-o;4cYfzbubtq-B@*&_o8(tv zV6g#!LWG^z+E84E1Y5)0!0)S2u9(IKteg0WMIJZZ_C9m_d#^@tx2xBi3isjXu@2~s zHA9RK|JZQgZN0~wG!!W90goP+6NWpN`FngL2UJLkE(yv}p(fY{_lpUy-CTPD0Y)&R zr3VdgVid=iZd^5(@1&yEyg)n>T4-iER$O{DLA;lnbnzE4_GlERoK1e==2_KS1o-U{ zg}XGB6%T&v^_L2t&ZUhsf_i309o-&-3DRdVa2Q3-mXb<;ST0^y6>JCmO|nglK;8?l z*jNP7Uci6LF$Q~{cRA19Hw<8u0}(S@&9-uh?e8A#5sJIlxv2(~5V$jCGq^^v!#u?5^jEeF1PnS(|ctn_I@f zn$<2(xa);n7l^6zso96@Le5rXgZ+Dd1d@Wqct8RDlNL&{Vf+4(ry7+scdDTVMgOx5 zxVIz^9%mKMrom~BT>#7Q1+4Z-0Ja`4^<;2e@u*&ue-hzFtSAW(F z_cS2|qm1GF))z$(h9L+3vv85ja*UA{rpiKJhUPDj7NaHk@cXUT-OnnKl`#|gM?(zw zD8}NUipO7BI#^qdlGqOY(zO&!Z*>r=H4~`iu^{{Ra2|o7>eluX)z2F?x0;(4kk5Yk z{8)b=RlQVhjI_<$VUT+SFLC^r?g|%>#zA&t+9^}P313Y@sQ(eR7ImDw;8S7dLQHiu zKg;q{DYYkDhTMW`8$UJjNH8oc+=GID$YQyUHfEAUQSLRP zoR~!u*IpWjpIs4!6#}JiINty@yQd!v=%_Abaz2?0_eLdoU5}Ew^99U+Qu&~ayIGE) z!lDW#F>8+rOrpGu=QaEKEiO@A1LOe{t=6`=8qFVd2=*+;iWZ+)zr37~zCZrlstW1) z%@6yn8%d3uDca9^nO;TM#LQ}$CAT^lMA&PU2?PF-npKBp&zO8QcH@I@)^aT`g7MVq zDqhEZYrN13^V2b^PLh6{P6eD^odIZ2Tw0T%Kg;Fs-vfC;qXUz&DGq&PDaI#l0ww`2 z_Duwl5kmZ^nYv=}mz1cXyLB!4k09d(%^qeF%ZPiCnsdxGA=g?78rL|quHJHu(sWE? zVVL~%gP@tEAyn%u223vU(t%g452go<`Ye5`%`h9<(KH0i`#I8qJRFk&bg2AluR`o4 zmX-8$eb?!X3CL_#3T!6lxDzW$#1J2qZN-$=PG{8c{h~sKPqc`{UEW(sHw6lh+^z`r z%hVP3WeRw;1m&SSF4iohKG((1@k#2??I3T=rOqwXEA1k_*3=be8?1`kS>J-)6A1^p z9JnIpY8i>XHkjSWCAt#b%Cp+~@HIZ_Jo{AZ`^SI>9^;eB7r|?KokhO9eG7%`T0$Ok z%w~(MlMCG6cB4_7&owJS4;B}os9`CPK5eHEvzBXT9L1a3`z~@O+$Jh;z`M2|<)W9? zLD#Ctj}Z#%=KBQW?hHASv%XrGxe)oqO&lkDdzHJh9C; z`Xu4&Jfc|7%>!SYL)vMfn8>g=$wF318Q~{B*tP4dUOZuY3%5^D`mxa{bF9Ps{cv70 zzkwzvFiYfuloj|!_s1Gk;!$ZfK~K_qvv5*lIWuCF~F;!yPq7V<&IcG@iUsU=~z))I{Cf;SW@KLr*!Yn z_cABu1;ALT)`5-oQeTRwuP#$w*yI)Wk?mUb?!~&8jv)Bk(ejg=$$3BXlhT_R!G5$> zCm$0vES#|@^b)c*;#>-{N0-s*);*zX=W z+4(7Fj6`j*{eDdS6zJG7^*WfX`O!)Bu!$=r%hjom29~(1S=5$&WMZwk^j$K(&IZO5 zfaDSQMWg|~4fO+A5AHuQ9>eivULAU38$}l0w7X{V6+=PUGJVPyvjd+GkJ;H29l-srhQB=FHjH`7>{S0m+%2M9v2OPt&yusKac_1$O$m8YCQti{IW6rK;A1v#MR|Vk5 zuP2ht6CdfOt%u&0y4~~L)nk?)ZJZlD(kG>Mv7np7HygY}QVl4?&7Nx*_9)>dPT%tW zD0c5{gK~iFEi7*v$%mIF-~8EZOZ4wSU_X8zVw>-$2%J>>Zg&{+?t4D^Am|JZOpxUC zF6g1s*yA^n+;iT2zmsP`{>IDLec^}X*vRCL#_2`UeV^u^)BGgh2vnrA1(jc=zGJOq z2CvwaUm>No+_~K2u`ja>oJF#Gm-tjSB+H^|jCiXY{uxmL zkjIXloqMzLqL@+nbkuY7&I)JnC}+Yt-#>%O0J8XY2{r2aVyf&3#+MY%%Zc#cgx}=> zlnM$Z|J|hcZkP#r^!CQcE5@Ha*n2GBBTqe={IJz``U~l=VM@cOj=|sXTLodaIp;_-%gefpfxL2i4QxNDtipF#BarJSqcjz~B`2w?1S210N8fOPsME`P;?`9!=jf0a0v?Sl_>(P(?QkIvy zQ5wFUDi*$%Rt`IWmDc0G_`BC+5w{MXeph2LHTiYe)X843t?M_j`ECYBVPxeX`wa2S zxbE8}&iA&P;XxU>d0vpPZ9b*dOZ9jB=^5Nq{!8*!xG2lvzoP>@D9Z;tZ8$?cmBs%v zR+Pbb;}#8i3?h8uuRKjF#F73@^GhY~n!w z=Q-zDvr8^lCM3YFs6TQjljN=|4hfNuGkSB)J_W45%we!n0|j-HGBQePr`0J<$6bg{ z>N?%>G9BRnFnX)L(EX3j^V5gei_*{y>>PEX@f z{jwK`ZPte`ER&%v^WE;AHl%glRpM@1Yn}}-lHP9}!c$v3^uc#IBUhZJ=!`vB)}>nO z*vntQS(gdg)k3SqrCz?r%P({5c|t#nsc)_plf8xtQ~aCv_xFEJ<#=*z(U1tpViLZ+ zFhnL91V7C>qO(UVu4*gjo#GOl-ALwpI>U>ZrOj?@uqw&4(;_O!vHNG%L;SyaQu=*Ge6A>yhjD7J-S+R=J}5=| z!^RhHzRdV1uOfL(E|@8sM~Iw&J`q+zFIf--vjh|uk$mTz6%I$Xf4Xxjyy*m6D+`xx zn~ZiX%3MBmt0c0VLx8oved9Xb%|KW8lK!}ANUbm_$ne{`|M=hyo9ijp$B_b6_@Nx% z(~qJ6?AGY@$I@>`PmxuhY<;ci+~pMmvXjE`-mnYvK9^JSqJ-X_eooSK%f#-Fe<$#PY&B71P6u>7j!pzhGs0xfVEIyVUvC zbc)}zf%zhOQc%hXrFnHefz`AW{S25HD?%%!F(K5mS?_sZ01 zudBNAp`9o_$-vc~$nsKR0Rs`&Q{^_Hx>#{&#?NEFW@OhqPP-u_slHL7SUdk7>Knf9|{l8d;YCW(xjZLAVF$T5+>Ornr{3JS^4A0jVK1? zEV574=GXjjoJ-ZZ$p|*-6LP1n%{P1E@!0gUZME2GNC#^=K)Q(ve7tPe?8ecLYJJss zQ8Y2UH1U)Pfor1k>+F(9;d^|6s;6C6ke-z4w-<7mtNmYioD`h1rPb^sr}t;w6Ybur zVloh`F_An6KQH}s;1(2Knq$g0;BR$4D>5s`4kOPOR<$d^zyj1@2T|IxX6rEO6g(Q@ zc-Tj#J^OH?mv_Fe_HK7U>i=v$rClWgAR98dEvcq!w9pQt$MNV|1g~oHbm(aahQEb? zQFAH2{?m_7Z=`RPT%o>eDUR)+`<3^qWnGOG=X3~?7$J`5=%uCw%U|IihMn6|OHu6P^ zooM*vnXppKtcLbf1d33`(-XB-&8liFz-@c>6^$S~)*(W7!?j;@Og-kic8Ul|@v?>H zr(ETCG!_cg*+4nVHxjkj6MC{up6(V$WjAV6@P-{C?;I-@yAI}9dyN*UKYM3)yFgI( zqC@$3?^3=>-_;k5?p=4VVNt1(g!uD{1@`b){U$M^IKQjZ{Pcwns#Z_(DMLA98^mFj?Z83Z?YbuX(fwzTx z$gB$1S*O4!RDx}BPK|U&yHL za%+83rT(U6*^9i<`J%f{gO0Ym)_Kj|K|yG>!zG>V&f}6nmxZ--{`LapV0>YqZ?O_os3dZM!4!d_3#ulA?^S;hjng|T|m zX+2X}ADJgsq5J=)+W#lbMpn`I5R)J4vn(rW#8H1qyjEV-`Z~uff~9iC?WH*Q{ny`& ziIfM$-&BqVpC)XIjR`Y-BU%L?TO7*^M9z!+!vO@~L(aWL2UtF>yEG7(=v z{#aDpGQrP^OqDJW^8%1Fq5oO2xDvkkLy~m2ZhHn4=wjsf?DSFhfS>Jey9GV040C;X;gIbJhuVCwk9-v%t=^JkRgvEc=1f$5x3QBt1zTdgEP7n7@HEX_AN&04|1(14f zeK`rX(f_4_komJ?raoKDmdMF_J-t3K=7Zf@!%zn{-sm?wzmBt&i_*##m!$!{%lk#c{DXyundHv ze_PlF%#4Tg2C(pJPpD&_0)dKN>aGFE+M?g}*V4Cq%Gbgm%;Oc6Nwp|iR~J$cNy#&P zXu&G`Yfq9>nbu6f+4#bf36{$KU>8lMbW7}>$%j{CJe{4aIu`J5C*Z&syw9)6y>dE~ z3habFHuR$X+CaR`1-x4w=l!NRNW{d3&i}#QSB6E^{q5Q)hzioB(v6g~C?(x6)PS_K zfG`Xpg3?HLO2Ys{=M12fz)+G>gLE_0fRvsM==1#FbFS-sKb{Zge4UxS*P6A~-oLu< z`_)^3s{%S6n?UCto(|1rMANoZ-cH6KVhR<+_(QgY?bSt74P&%9vO!mNQI7fv%M|K zLCL-$y;`~p0q43Ie-QBehpxMamR95=(jfDyl!wfa`m&}Iziv`XO%lo=)8ezMqv*p?DdSd@~ zynj$((CA3UkBvnuwf#=`>9G7{#nJtsZ8mYuYqx!eHkyZHpyXaDSJPg{XpsD8U0Sag z6h^|4ttpC$PL4ZK`R!*~;)dH&SM+ZT1r3g?;I|76>Y_4SKAwhk#8@ai5Eu=VSKrptuP) zNP%j6C`REUJ~U1}`9p-!ERz$GV8e)A09%b@3nmojS;_g_E0>YR+=-uEyn0GA#-hDJ zuABe}JMDxNIhGccs%o;S%_SC)WU?06K9iFKjg>}fnrZV6&2P75jHfo6)I8`Yaoacr zs+X%fq$bfNtPOf^KPA5+Y(=~%8ZSLx?OE}_i9NQG)UbG|!@r>QNrYm%>ITQr;;J7W zwz1xC1g+H3l^7U^Lu1McpGJ5eFj1c*Xa4+>e!IEpxFz!?7j}V^72RDSD^o2&UvTr4 zWI=W)5oLrZ^$K`~YEf6g88(5&@6;&UgHJA~KYB!*`s5&Bz`1jv7N-3+cm8|hiTQ09 zVI5Iv{f@^obzB`z7w6Sgt0{t_K-A)|Q9aU`H1KsCMwx z5%;Ygk+-{!?+k2&er+mu@C>#p3e*vbO=RC9ku!@M1a9%gwpNn{lG6^^`3;nX0wI90 zVmHhzL%^_ntP_sTW4%RQ`HHQDu8U>`C6xfz_gMZ{gsYZJ%oHB>m*SJ{8-?iM;}!tI zHV2Le;Ws!QI_2oCN~=F;vQUekL^rKY!A?{=gvSgT;cadG4`3OhyRC^mctG)Y2E4Y@ z%LY;T8ZsO1*s|%sbvo7Pv{X)30g1ij77y{lGE(_LerLW1>S4dPNL#l0e*b9ubt0Ck zHLo7cQ}vxE=tEu{*|fgUppi&0E4qc`BAVL2j4~@KQgC zSjAdoO6##Hs8a-RK?P#{j`(zXBQxxV_F*LxvBjJiy@Aa!Jpk^>u)ZK$1Bz?XDxJ7b)N5q*3qr$8^bgX1^=<4J zYB$hRtntk}ntG}h_WsifBBUcaD=9}pvi&3%;&)=p6>W&GLl{-ACmBORn~z=B!t6x& zzu?k*l#v1lW5NadQUgm4;HoJbxi$@Kncb}+p14lb^7Cy)VqJQ7a6;akcFhR@un5r? zVU#u)BP^VGIxPQ0b|3O02&QRyxV2eUI5NB8Kq@X?2qaHfcnHrcW}8z$1sjAPQjARQ zqO8An8R;QIf}N+L5Q`0w9ky~GjdTuC7gG*q3ffl+S|wJVK<7Mf)Ffh1S};7SoG^?{ zmZX@Js;DmLtSvJt9;`(ps$r}R3JZk`SUzTnKIaB;@EE4BWuo|05e|~xScj*y#_{M; zuz~W@3wSid`id(&-44D3CHKs;u;4w!Vba>hoP1PU=g_JOIrfbKioMYyK8x6a;9#X-aO)oz!mA@lEtPek3BVTB7W zaas?)j751*Tn$6^u2tlme0v!NY2)A*+9rGl_)vdY544#%H#$N$-HnNj^T`4;iz7(x z?W_g9M**->QW)DieGC1c*7Ua>3pfNxW&TXpY>?JZLt=s0!M zd3PrT?=2{Q#=VWkp3e+)Z%Cq<|K4jM7fW~bUS0mU73VYM4k`?tHnT)|q z$)*x9SbN0x?!AiKQEXsqY=3)P`{!DKNWdIC@g5wpvMaBK9B9Bn=e&H<_aWEz5WZu- z%sY`mnx@jvHhN$%8f+U(3jpAJyY*R7owF|qIwue#n@yqVMoC8 z64n4A!Pi2A27^6GtT6*`JsgqDdc201O|O@3q_*Zj_{rcjNxmMem?|>6+A!IGhjtO< zrxmfH>8JtxC31>^C&UZ`Pcq1RE2K{H5Wc8~by5sH9Ci9}5o+IP!N_&6BUP?&v=}2V zhhK{iD{Is=UYGq+z30l5qIM~-cW<4fp=(w(#EVTG>!3BBx2!`{+OZHxl6kbs%D(&( zN-m^7%WiP07t=f^#i(&S2jp<)u$#XDTxJ$80KG)8lutG~+m(j!7;`QwD3mP@d_fo^ z%ypY~Kd_PBQfWgm-;N$Y)b$%jgwUIKHwMNg*htksTXxN;q`EdB6BNcXVM0Hc6FLhn zw6s%7s@84&K z38vLpX#0@4_=@G_ZOR3ZSKTb-I|Y`jbsW*xOFBz@`{_fLe+CaA6pZ+o3(=o&324B9 ze3@N!H(z_9Zjnajb9!BWgn^OR%|KP+>6JOciXNB4sSn>$gS~5)(gl%t z)jaZ;f&Pd6$yIiH?>zvh9=@m5x-v#DBQ>VD5Fc|yd&k9$j=k#Y(wBjo3|t{D(WP7! z>Q7&|4{U6~c@jYGk-GItx{RS~J8RsUhV5_UhiBJ0mmGdD))(2KbFRVaCX1tkm4-BX zS|8C0bhs#Zq-s@N>ZozL2{L)Hla6I~`(@oH?sRteQqWbLh5+%msV2y2ZvW`GwIA)J zJj4Zce1<-<>|%)AEU>HOt?(R1m^7`Pgqn=0Pu_8Jwk`z&sgMR5K=;!RK_tXWTbhKn zi!t)3;S?{b_bxnPqb{O>4(V@BSCtRL7Y!P=%sqIs7?%t_4DLP1tF~}_SGfo8O&UR0 zHA`9jUMa@5xEM7R%ZD$7 z1dwb2R1AQgsOrFVQx0+qc8DBQvr)tKdWghTaAT;?eR5vN!hw1kCoRPak_dCdzL5tP z@oWEK*pl?qkI9*721x-i9UVq8y&<^}D`a&Zr(U;?Y=P}s?M0Y!1`YSeZX%?&PcurM z0aQZO1k@}0a4K_lq9mud2&L9`&9GTJcM!*^W;L;m8Hi1ZTs{dMyBjosMT1qyV)$`J$&84&iA3SB!Kx`M`EoD<|v08?oC;szOk!8 zF}~Kt7YpGKP+^%C#^|LfDF_R1WSSiou!@IbYZ>)#svkAd#Ot*VlrgE$X3D9H8gXJ@{H z+y(4w8cv8Taux~2>(Y-$Rv=1 z4OGBMJBrUa*e=MmXpotMvw@O7JRz!noT2#C{nMR^{Y@v6Ji_vdj8G077*})BeA-Rd zmRFHZjXK+K%!JUARlsQjzuK0QQo}d`J^q>eAC?C#adqq>1@q$Ll5=%i5O^~mG%fbaQ&73u<*l?zS6P|N zL<7l$M(zh_=jaZ^!K9%CA=5rjGfQfOGoP06#oo^reI+QkD71^BF7L(1$jnm8z}=&G z9VKQ(HIW6d_FX^G`{J@y+rw=)&B-&ESjqLsmo(o68pWQH^%ZJtugg4&EC#6b0$SF_ z$LL@Phn4~HWJtCnt2%;P6pZ*C`jK%Y^ z!j5QdfIgnc0NPP)yQ%PfZ(xLx*zYk*x~9wrsTD9Qe{AB-jVZrzGR=*7UGO6bw;pZ& z1+_s$K{uoF7|NxvWZym?mp-S^cVvtoql5uZCdjV10YoAKs)5=N5J>fT8FTCtL&syM55lH>6E zDLIoVZLbRpu9@bOEIzyyP6DL>T0WxO-Q|Ae?k+c_@$e_^YO3UlUP3qBlB9)H3b=(` z{DLdv0Bq-^qjWLtHf!WUJw9e&MTWb9FfTFaq0x|{?&FAC&gwA1`-_D-+M0qv2J#Qf zL+0rBi0N|IO7r=;@_pf=#A>9_s8y$*hN_4Wbt41YKGr6YWeiRZnfia$}D0d56H|#xf=Hn4^Jf z!lI~rk=o&gWxoM$o*>N}wygFdAQ`q7A(y9$DB$A%Vy4PW%)UXep&RMD zik>z7VrVt8KV?J81eD>@ zH{)jvr_2mkmGm=!6NH~{*j3BH#Wdm0GJ2W?vx@V7F_y!ET2vm}PaYq574^e+yI_4myw%W1au7F3Ea?{l&b zu>(=vMnL@2MK`R*QpLzMFpeNfDtHqjBalT|XxNuB3r>&gP_5303Bw)NbPe#g(&CtQ z(r14!Ua|ou&|NI2@9G_5bIE4q?2QQ?jDy?{hL~U9k%yPprwPM~D?uM$XWm#Z3Wc1u z3*67!@$h05{vLM&jD=GSa8L#zjvxj1Ct*mGCOA9QOC@G4M2WlajXre!2G&x;Yc9h{ z6!#SR_8V4LyozBm^`Gsq-Q8RW_t&-yvFwy;&Y+E&Rn8CKmX2#cBPz8u4aSvZ+zjc^ zJ#Br`EO`TnFgkuR)F;)Lm;7cqSt2jm5E+CK_AIdz1}lB_5A+JSG8EO#>uhu3jE_6t zkeg;O4XPPAw;&SPlgr26B+-~wKjqN1~$WCU3?y}?##!SE1<<1aQCR&|03 z*f_*J-aU+RE^z^JufSD^gL$ED9m-Ur9$#-ixxW@+ShM@WnBElq`(TynbbffGX=>#s zpck$Wu*119NS-~gchY373&v?fYibdkF-196kvIEsR^ z_tom^eH2z-MddRIav^NNcKixAVswQJ71lcnccaiQZ?4;tj|6ZFr8W}C(($^dSH^99 z`lSx#21EqS6&(1flv0CFDdGmzJW%}60EzNf#A$7MEc)m)oYL?bVPD;usQWDMtgSaP%o zF6l5emCO06-Ep?VE0wSdh%rLT(k zzAj|W1-wrJi#iU#b)Kbq3JK$;ScR{aa$?Kh%dE&l{V*s-{nY*)lf@V1k6k{nEAKtl z@3Z8o=#k}swRTumGk?wH58e2{Pm}ivVK*fRSDIaRN`1GoK-=;uF#v7B`o`c|_s1^w zFg+rnE}e%0>RRi5!Z?SrmsK>MFPat^x|t9RMp4b0yqWct)Wobha&aL*A*-7tn;tH@4VcH6l8it%WNVeg6Q)x)n4 z#vA!8=h5+$&7S4)Dk_fxWW#e{KlN}MZe@+0oyzPey6?MmDeXFf>Z#owmEk8I*30#w z@Z1Zq0w$2E2ImaZdm(`}zCLBc^otY+ljieIfXRrwd9A9)@IgIiR^i1MVB;_l&KZdk zP{XPjL*$+R^m4m~YZwzwT6}sekHQ@;RAo{#kRCoOyf2r_M=zZVV^Q?^++C++QHguv z;k29-TJmRjY5$Nj1&KPsQHbzESOT4o_sr-z(p~btbL(xfoulVqQG_mHkvj?c?mq_oiVfo9O>g-3vPH*(&3=9|%YSd)JvSv6YVp+jpHD>Y!|kV--}xYS zc7}VsuU(9o1t7%$6@Kg`<*&Ue4+Ad5K&}JAU7SYbdiXzLCehoNKe{eNfOOWUR|kt5 z9;BZI4daXo3@0}Je)k#S{KLP0bF<;yOLc=OqV!EbSm}?Ni^x@Ykjf#tKlW1ItjSV1 z>0k8`-X9AMzjb|B98E|40X$srdGWmW-Dt2051!n+Q>gjqnW)#rs+=ovt)CqJUG;Mj zu&WpfK-HV(ub>w{#aXHNT)7PRm=Dm{aLa(yH)lN-m0#f?HJr|k0Sr0l#AMy@&g9tL~`7{}j%GSS($0PTvju&{h@AD%3Kb?4`YyowQo z?@50%4t8@;dOIWwoO|cTlq6mG zcIPyI`bE$3&{)6a8sD9J#oL_s#U&jLAU4p*ioM27&V4H2 zjZtamR3wzcb{^z}NE5BG@w?M{%)+sp)bUzhmF(2gP|?C4Z#L8EuXov*pQx@I1kSoZ z?Ry7n_v}gXhF(wY1|RHg-j_B>jVR1?JB?ZB0}AnHYXP-8z!BRwBgIq?PmCfI1@>fk z)(uqCZ8$}l-fJ53bBe7i%q^tcs$f8^#5E|aw8Gz2*TpE$>td!97c+NE9P{a|{Y%PU z5)kkjk89d*3m!;OJL#q|g++BxthVs#YxY;Uf9pf2h--+BV%!khXX;%VapJsy44Dt) zqkkpjpAzdn)*g16*8wH$;xu9q9nHBEA1UcK;sm;u&MIo3tG^GQjAc>7y)F_*x{F0C znSFKYHMQoB8zHhTw!R-_1uoDTzRh{9o3~+Nu^>P4W_}LD)M_#!?=+VkyR%u*M|?tkEfKhtEJt25@dLQqu+HjyM^3_ zbIl$#97DVZzBB<@$rCw&-@0b?#{->iJBgV;cL#s-_uc%n3B|P@g=A3=xEyMsh$cwI zky1M}+j}iArj^_pBR|aJJ}6%^3s#Ax9Fa`h!OkUWjMFu*Mr^T&5yyY~xe99&>h{cS zF&!cMT|{hKXhw^96diTrRCLt$)j|HbEIh@>Ew~GGDfUbq%&~8(^msy{W(l-uuzz4L z8Pr&v6q84Fdl;zWnhz#x*Art_omzyc*LLi?bh>o2(VG2~6Y_4&bf9|IFGFDii08Tz z*-f`G1JJNn zCuZ}A-R`mb#eP|wmk!r+danN$H2XNm;`xSOJ1N@zI4>G}m7Tqw>vxh9H6PVv@Y)6~KKgT)%20 z*U%KHiJqB)J8IE_h4_RFvG6#TN>|Z}tbIYf3~Qg;y`}^*OF~(+>SLu;3#n*3fIDGU zQ!e|R3y>wh5E6uc3;lu+u&?6(Hi}T|g8qLR&1&LPerE`iKSQ)Vp0Ao3O z)OVO~fMD}5CzZCN6m}8j* zmjZI^e_mt|`{K~rl{K)82u%4VryH~4Zy@r&+Yg2A-9i!}f}GA3DKYCw^|j%Ha+o9D&| zo4c6=#@6_HF~2a8Q%|T!bd@UKg4SV;!-O#7Yni~xF=jJ^up-MmA!WaTjjNM2oNIm(=4qRwS0f%gd@ zrVnUy-MBdOJyZ>Ahd5%cFZcfHIgY+sTm}yUNuYDBNBFl33-!ljoLlSv`L6$+g*uZ; zyHJiCeRb|%{9hl)R1hO$OlGgpk^pXe<2fzftOjnU!kWsEVZbj=IG~as6G6ZR0Ds+- z8RO5*RGlB=oNFYFd^_c{hH;w!I9kKu{0k{tDL5^!F-LVL`s&j;WJ}hCM{`jggmKc6 zpq-e{h_fyDs){DA>C&8}g*I|)ZRo}C+De8d?G%?)m{dTrc5>}VEIb4vEOH*~!BL1k zNHtk~5)w-ACSo^4u*)F&nAN)0Osr#0lDN)+GA461r*=;37^{SEEX`XK0+8E;_= zqVZ=1EkEXGn=Rms$N-3||EL|N4Z4FSZ(iL-MJuBS2~Ngorg(LULcU8O_oRYF*&8Tf z{!%v2iw8qDKXG0c^m#%Xjc9BC@b;^DvdMhjUR;>zVBwG*6qFk$9We}DEpSR8HZP|V z$(##L8MO9jWKKJ5V8BvrfNM0ZX(?5Nk!&{yl^TdNfMcA*{l80{LF+FDqg+E4fX7d}V9=o<{|iJ8hTAr}vbWPIc0vz7Qi zB-LDIe@#8ZcXOn3XSp(&kS#k-m$Ue$Ns+cbCr&LGnabHwfJbODvYD$@a)rk!Cjafz zFWzH+QDl(&-=0y$qzg}xLz6;wxCA&&|4O0(}v05IjOiGM}Osw*cBjSF3pv=l0 zaT7=yAI$^034OExTzyv!vpTP(@t36L%?ADPaV9-EB3RMP>(Vg;g$036bS2dA=q`7O zSTZ|A_eMedD9N+F$MJC(t#}mdz^xTxNEjwrcY4fZiPVyvizlTXJh^{ZSr7|}&0+Z& zhfW=r{XPJowvsl$+d_F|`fnBa?mZjWhcoD_-M)Ul1t5RhPvSPsQ;le^?n00Qk=U z0$vZAU?r%4pPI#MP~f|+&Bd7$|;Q`A)VEi_Gpx73W73fnh9;g(zYD3iZrlQ;g zDp%b`wO_o+hH$xx%E~ujVC%@Y-w@LUMYXvFqam z$fm^l0p{&*f9AdEhT2r|tY@Q9+&P7|t3UBdX21$z6%JTxK)`ss%xuyIp&kBFij(t9><2n*Npwm4e38PBYYbe)aw0ZrR%@$}0bA6ows8Tz!YRVkm=FB_yau^}SCUGeJ1W$a($7W(yQ zqW9rZaPwLSHR!HjeAbjeHysb8jTNv&KwJW-2MPp}LR6^y;F$PAIlspBD3|$Y!XcG9 zj-rv|0m=en9h>28Qnf6;s_AY05IGrps79DU6@A;p^j?>7K zc?r1Z(NXC4X$0seNbm;S&{koTXA&_4?bD6eavH}*L7d}i>gjKvzj1*iC# z&Xh`ezk*H``S*$ocLcDk*Zww*0;~c+;ri3`3NXw72kcKXG$6=3BYmBTS_Af);+$i3 z(KPKW$OAO6E;^R|9qGKe+CNkKe_|kBJq+3dfA7aaA^s@QMf16{sI>s%Fvb2%+;>vIYeqdxP7qa=AU% zy{zg+8!(~hk5}LwVIJ;z2b(n8_Px&2BAbUB=gQN7zxxeQ^TCzt0Cj~GK4n0;mDYz) zqm7}gb?vGPc59IFDDos9ret-Ip8Dn(JG4e(S(#frst@GZ*3mnSuax{_c_=4RczED! z_fm_;$S>SIK$i?j@ZeMW5RaCEEMq@bN3!vyL}ig#!UG9O4ZAh;1?4+* zrP>v5Bp5$r?~&p(sz!IE&jt`LiavZ45c<_{1FYs0!GqEZ%slDn%AWDtkiN`vx9G7l zRQWJJMT%%4Xil)Pn`Yi%y<*k3_27N0BOyWKDr?9CV|rG^tKs&YHt2krD_iuoC-WM$ zEHw(2!g2|~{hzHpw>zNK&inm(;?p-@5XpGGOW-EkmyE_T5-Jh}lZ>E+Jy!GjTY5-E z4J?mp!GJkNoe8$Z9HhlC5^(PZQBw6A57%YK%_z<`6?gI+C+~+S0w`WG% zyRKbZC;MlI%pn-{&O|;=7gVkxQ7Mmoj>EM((x1bW7Gls!VA!j|C)}yAJqdTLBEqeWN*}nTf0VpEVDFm0|RD2;HIMv$T9})g2MfvrL~EzH4%ATjMZ&EYwmcSv`R)E z&s7vnZpRt9rjIAS*eHp!w%gp|+{lct-r;Pk=yG(OK)_f=t@kkl$BONUE8+AkE$Gk` zuCOoVpExDVx{C)8kt$vl(27rho+<#bxKmfm9GzNU@1y4qc{B%pzTD+DdhmPow+9S$ zWM)dB`u9E9V;T+VYthfRgGXuri!ftC*n=7r5c#*pSd_kY zJotQKdo=ra>&uv|{A_{;h*(;->Gu zxH&{tBs?%>aKAf7nD07kq-3tfHtTKJBhw6f59L$#oLwTVZ7%}QLG4SCZG&&M0Q3Cs z`me#$f0F8*jJ=h{8kMjvVv8uS!bUpO=pH$%pvQ^HU7GHBl+N)Xr-wH3ZGbr7{6{{xh2fvZN(=nI@Xad+$Fg@ZHQhC9gB@xs#7fqYv8kJ zTfmcZ%)0IN@1b)U#dOZ504)SP+ik^MJ5dP+N4|_RI&m!tg+@`!2Dy};q|eu3RYe9Q z>{6^R)=kYc4|#W_AMwOX#lS1t%BalNxn*Sbv`hr5=_1=8S{5Plw@BdKoUpj>A^Exy z^3q)98=Az!eX^QQW2(xiBDr6qLc=(PZYLN9)E9LX{IoxHym(!o#ieWT>FJbVgh}erJB_~BXY}kpa!Xn zx=k-t_sO|%F0|TyfK9(FU5*31(sNa8c-fELOA3FPN25Ah(02@fX=^s@KSe(i{!LNy zlX%j+1a$8&^4Ju3Pw$ojZ1r>L^e1zo#(8cAca~-ruRT4u%klvI&yN$9z@sHXpT>pa zKOE0*leng6EAK(JC$46G`Z-}RNQ-E>gDLKA`P6T=8g-(87sL_XE^+}3jMrjukPc{b9 zO4*-L@C`Jy8G`G8_7KF&%X3}wZa$vWDb0v@f#9a^qli%ljz(nBrAu#eaN=eqD%dZhzCd<-gSA9lHX*%Z+^Wlc8r<0$HiRdnW=LsfQt!B zFHDwpJVp=CTrW1Y#AvUI!5}PK?8#BZ32OQ!g-7;&5SETgEpgi}bKX(_25O{T7%oQ| zVqjHiV2DS+4Me3+$nZu-P7%d(U0vM8Q;OHWM6wS>D-G?fy~Locchehxi3iUP10pBs zPZ7N0Cn~=inSb?VJm}$p#JT&x#f^x(f2~(cQ`hUgye)cs@VgE;;hn>U@1I)jd{w|W z0IZbnX#6-=f7oFH3qI!%B4MfLX7vmhe#Jy3c1+E%Q{v96Fc$%M6AzqqsEV-ZN-p`}ZxjrWAn%ZoLT09{oDO#)7R9O{soHK7M@6gE!*ic1{rt3{ zG@9>otT8f`o;NaZcPjlwwokIx2kbm*MBzZ|H3>UtiUCQQhBc_q(`1YD)s|+3`Qd7( z+49l5Z(H-jKvfE?{PX9!Wt-7g|Eb>|yCuulHoCQQ1Qo&}2$O)u@$v{OjGpl`x|FU@ zHQGixcguI;QEQ(=v^?IR5UaICJDerHi|*Cjg-l%3EeLP)?7^>I6ymc)z%RA}(-{WOfeW=w zClIcP9*o~ML_oz@NJ~M zj7DK%3{}ri&durta6myVFF;bExw=5_6K8VpNRqnQ{isHtvg#>ZUrV`=|cPE zW-U@pG~C~!ALx2hPc|>~&gKYWT1TZx)S;%h;cD?heCXMd%^f@3;VAV^YbZyTbbATL79K9utc+0|N7->S2PdyZTiy?ftUjQR^|KC>gE zNtKObFqh|f!XZ}jWKUVTa_iLE3x=Zz#@~A28Pe+39=Hs_8d;g1 z)DQxRPxr$II#FCY>v%N!SjL^p01Lm3A6p4y{$}$bLG4f&<}C8@^ocosA&ZB#H=T!9 zM2?u!&@bc)N|piJwc-~kKe%JBCE%Fg4=J>K4_&w_aoANh&IPeD6Q>2v!Id+g`$S1XybF?69k8>RU}pv9n#~iXxYX6SxuelV0$_}T}B5{=m(nO z#ECS|^1$e=p&DP!*mZMSaFW*Wr}T2d%PJ&C#*E|dbZOPw!?d0Thtg0mhIbCN$iAZf zF8x_UA2!&dAzt)=HMc9splkibID#{;>Sby1Y8GwVRceUqj2I>XbsWdlnfT~EHX z3FtZEn`R$0Gho`CrpUq#VZ>&l!6Z-^O(TOR1F2tGRkwPgh^kc~PWT6>7gaRw**iF^ zL54dkBRc_li~jPifaA{gZ4d6*%+%IDj=+m`$w01k&DvG<mTydrKy{SmLy+~> z;FQp?RFrMGtgeb>`kEVAp*K~kH|O7mQZ{6C4Wyh<(1-WsmT{AH_sU92zx$ei!zI9k zgu5gx9GR@MBN6+&>a-y#3Bz}39$jA43UPP_!&^2slz0~;DdP30K{MC6_%YM<8YHR$ zM(A?4_?}67f~4Pv9mG4zPQaXnSr7F>35Dt3xQ|yeSsN^BFI4umM33jV7=yRl9!8RW z-uHyDB~Ck$iOeM|4U2~Xsw|h#Vn90OYy)fOrwn8=SXX1X?9|hk^vy}<*@*8%jhm4B z@vspZo0Pr^=A;8w_dyCd!?bR-fd60_@cO*=asofDoi6w5qS^J)JT`O%TqO{y-- z*Su+Y13NKo2hP^G@t2Db^HcA_BweDHB0IFebcsxA{6Fe%RB9Jd(}q=YqpL!;^`dDCbyY8OGPxj4v%D+4_dcev{#y+{*@awnzc@}|SCrf608R#vsMSmNy3*AxAp`ga#;h;O zNLx!1&KlCXea`4QFgXHORChKQ;(Yi}#oA7L+n!t-C7SV+m|s}X_Kh$D$SgK4lMl0c>c{Z%v0zvHvGZGMzHMf0vXhg>$;B(-|7$n5}+-tqgJI3gxLmc zWf`LO2q8OSwO5-*EV_j=;M@aVbz7Qx=vdZTfvP~k4Z*=$6N>B99yyRiPP!~6Egbc_ zy?*O-E@*r(E-Dc@nfpi=Pv|tDc|)@YH;Mqp^wp6&?TDOOEVK7z6Gb08B!Iw$ovU?3 zGZjY(vLoAG%~mr^hj>-_T_Q!*Q)uYH*9jYY+>6Jm=Pl5*@w3M8i(Va!U;ROBp>Ac5 zD(@(u8Ad)-V8h?A76sF=nt=V*&b;zU$Wy+GPh&rYeuc|pRAGp>j9Q^*$yfm$3$6w# zI;(wJN?$TbGzF^fQMLedOb3pg@(FF%8%!>2Ra}L6tBHu7Lqr%%!iF(A$)7%J&be&v z0nSC2xr%hi^cuzK&Gyz;7(e6BWEv{b7EWb1^~%SLyMs=2N^UQdKf6&78~lNF@I*UiImn%0fBte5UR0j zU*#UAr*Diks*r;Zi)>Z29>wwNw+-aHdB;~VY!ues_mL^t?*TV~-LNR*PC_ghX&Za{{3Th8I6 z6a1%5`x*LK4~QhLpEacew|us|cGi3vVAuoA>?m$LrTcsFnT6RR_c^n-e{7Ra-_3Kk zoV6o#RYQ)~ z0mNeFKl1?kArjBlLhY8VRpL;TYmL>P5%;hEnK6)zK9C9neH)kz523V4UYVH+^8GUj zApeZ(cD)37V}F6Td^5=czzV_sjwXimIJCZed{M7y@YJ$a_(M!9_LOeq^Rw4n*UtZk7a;NHei(~*X=Di9Kp!d84Mu+6emal2WkRphre{mz#nGY=5z8(Re%4v7C+-`hNL6PS54J5~I{@aCT- zE-QTxiGFM_pOW{jo+r~Oe5rVI5Ab_!6jbvg#^;b49WM5k3PKf^{@KDXKWe1HK&Vr> zF>@@lexO$`m>?n8C@y;IUCato%97(mka6#ZEOA*NboB+#v9Dvge5YU^a~3m-aKg?0 z#n5N=h>h;q1WmmzjYu|G=rg^8Sxryk4*y2`_wWBXIbWNn*B8Ob3WuE|i$Ge;WSfmq zrQM0wE;#x6brb!hF4`+AgPL9XwvlM8=1fpvPJ^a>buyY52i)OgCrq{BYA!U(Txp8_ zkAJx}4?jg%F%l;XqZb+}Bj2{D-pHjLccD!#v(R_u+?T1YwCU2VedrgJ{=Pdp=W!SF zI;9Tx=}UTu(T(AGa6omW7qyyOFTtKbgrs@FN`Q&;=3YbS;qJi>Ki)enNEE&u1TYc16>Bh z!{?6SB;FSQ4+1;~A{2jeP!O)9GfOqZ1+)}?O%TlpFtaazm5;(Z)OaGe)N|t{h=CS! zBc~}#wMrVLPD@x3XwIQf!&TG37P>A-WNK+{|J6zTh}tclG~n@RPNUSH_(rjAjB%DTO*tz-O78tT>+f~ zezHUplv*%Jo0S^jmM-?1C6cMczi}tvUoZ8=`>jpysV@i#pE`Ywga ziGQe=#%t-H;0<7EVsa_lU0;6_l>PU*r- z(SVpk^H3hm_@WmN&;!O=)VWs!{mVQlbCxnrIkrQ z^K9T<1mT;Ua%q6k4@CRuK#&O4PG5`Xi9@hGINPzghGi$hhs5ULvHyLsj>NTN>(=3i z>`6G?!|NHf>o`wC&%&?vn5*cPO~2v1@v0ad(#t9v#iO3zI5%#h!IT&{mvPw|3;FHy z2;fD_6AbKAN8iv!-$zrBtrJH)H!j-{7j|%4myjKwKg`xb#0M89g$|8U+g}AS%jdDH zwV%jx&;S9S!-0$-4N?@c7)9SE~CuCe88W=d^*~bJMgU&^14m;A7k5)q5 z2#xpd*ULU>rgaVN+s8UWu^S!wa^<&T^I5;>qwjx40592VXlnckqz(Ih@y?dQ5>s&R zsUUVi_@z}5PA4>!mm)zK|DM%)$JqD~(ad)imn$8}xA4T^h0)InEK1eZ6AcQcChq~=*!;fBMC9`UPmqw-m{Py4*6JNp_i zfXM+On=IpMbOR90iO={i(zy$a79R_JOpSiOq(0n;DPZV`lJ$UOXyfC}XRpG7gjH># z>T)3W6PeY}9|>3W12x+x2O0>h7N&~y0z-xG4H$UTl(FkA=LcEVKKCOZ}-EJpTKOu@NOJW=C+>V4zT8Hq%APfM6>)-p63%Lz!w}|DqL1 zi*<1{gorvdnM4LVPx&ezP%~wkF(CQVK>|$NrA5$e(pb? zEp8}0mTghw3L`|Rsy8a~j!73HlLx=M7O3Qozh$6p{77n;)hZ(wlmwE|=<_TvsJlik zwk^RBx+h_VyyFBKPh?><%wLdTd7-2IPB`^`I^#!=H-wRIeSy}xhpl1WW9Kf=i>p8% z&p&yY0eMq|lNHdr2=8s2g70CEw&8F|a=b&T)9p98BqJkkPTB73F$_l^qiSuh$5pfq z&GBkR!MsN~Lb91`*)_M2v*5aNc}^fdkTsU5Ue!Fz^e7~U@Q!}B?f7}B=X)6-6n5sN zzXFmzUBj1c{QHAI=o}-7G`TnqkI}AI-9!+WmY8#$wa#~%i1iMjl)-2iPBXY1KjTE( zqPhBX<{Ef*ZEAa-{F2;>n;9+Ck>Q-)>HR^7VvKb;Ouf@Qo-l} zd2_AydT&s#N@k+e>Wl`E6Y|jI1Hr9HbYXOYTn%iiEpNO4jD*GJRBHS)06KT$teN(d zob#|l9cOSrTm$MvQ+cJhfz5aLbL61gkc|3P=15v|&UF=%a&nz@J-0on7rIe&6Cqb| zH9H$aoEnG^Vcj%aG1`0za^p^NJlO7z{qk3lZ(Dg!<3iryL>9d(UYi5@Z?QJ~{AC=H zsFYN5_n7n&hT``5uyq4F@RaGfXktpVR^QtJ8{3eNG;EeDd~;Ku^q#ZF+%?bb6f>8z z{$-yHBENrK$Z4j`+Ca2FhT;Gkkoi*Mtm^b!pz2~x!m83V*DhelW1EARMo zDX51e*6oE}%N^x$Zo{jIY%mDy-f6|d(tnIK|Mp6YH<3PT0Y&9V1okO^x=KGJ6Mq!> z<<8&HILQbCwfX>VFwoZdOc|B{=#308I)R`Hkm|io2c!$={vKL98L7a$r8EFH_vhP} zfQ>WXK;tkyA8HuT{j zqsB`f&KQy^wJC%9H-J{+uQA^REFLq4{i?$KU+leSR8!j(nl zAcx1K-3iWS;P)GLAMwG%{K9dp=Mv>_?-2gtLNd#LIj%Y5fSBL8uTyt^ zn-K8V*gpZy|NjI3;}8FTGKs8<5H=UeHpYNH@itVi$o7T5@EEz&uN@=VikAGt)tEP( zSw^)408ikGdF0uc)`R#X;RPZ9Z zp_@=ojRd^=ZDVNRjRlPI?A4NGvLz*??&``m z9{eP*q798k&-~ePZ@~~2rybT%gCLK@4TMTeS7Wb~XE2-FrrK0xBdH0(U68JRRF>or zZ-#|CK^$Rk{nJbo&gUH?!{3vQgdI;$4B0$~-~lKKAmczjt^ARHs*) zkcZ?fdXI!P}n(3%6Ul3(keiASOs#@K;sMI`Q`sqas@e zS6#H{azq_JZG+VlPMyYx8;ddLjTS6kob-O$b|JN;g8g z!B}zcT_BxLKF}np+75&igE;Yj`~Dh1}7zTFWj;WJu%2{J`uC#8QF zaCBsxqUTg72TUd1@SjPR^AJauyNmACdSK5Ce)-Em~)#R1IdNkGDTTN!IMUs0F#vYHD1`$2t zn)~~!#*8AbHsGp@oIKh*9}Hr8vs7S30!mMK1~{h&L>*xGNfv^hVchN@MlrdCBXtWd z$!?rn5&{%g=#(zfNbPPn{}>hg3{}}v4SAa zHgTXEBpMj96eTkyy=`5Ocb0rPVCr`WE@!Np zJZ@@XiA|Ol|_3C;!tM{~Nsvd!@SI#Om}Jg#v=^q?t8-T7e!fLeKBK?n0`% z{h6(;LLMVc&>&LR^ME)_w3zhsT!Ls6E?)H^cIUk}tRJI!}&v(8u@Hc8;K(CvSGZ6i(8&R%UH)_B~yFO!Ks@rq+=LR;|V`J ztq2Pxv4ZWD9+jXc;=4^X5}9yma2g^8=cvr(*L2$saC$IQ0e3QY>vu=;3z6cokLvTVTT zL=Sw$8gR*@KLE<+!&vW~Heqp0uMv5=#ky2%0;elrKHu#~!;=pdr61EqJuL^1#Ba2} zipy{)*y2@Ftvkb9_lOyCKOL&emmxv(H}gc(Q)HuB?~N~Mbc+gwTMRh6Kp%YvT3R3A zSt+#YSU_u1AX&(hR9XWf=JyaT)fQBGl~f?M8*4&$ODUpoe&NfGx<(<|$fD$5lj}DL z*lO2uTbPdd6gsuN8D1%cB*cy#R6W|*JqXV)IVYZ1{?{Ca zQe_n~+Sqzi`;Kvc3;Hh7`*w^x0N?~gcj4z?txS-)(qP&6*W@=DGZ zZ?E58#hAJs<vDw(g-9~_E$X08EIK^OK z;^J2l{97T`(Oqyx^wQP_-_~$;{HgeDp2aj}syV%f3Hf%q^Dgi6ePbj0HXm-4EHQlK znipM%)rVe!rj~pck+ANOQM-WT%6lTE`;7m_sUk>iVTXR3#2tPjURIy=z`~E`2$j3N zNDS7^i&+RiCt3{JB*#_WH%0FUupl_dfLb5=1XC!2I!7o@m=XMGa$7 z%E4?T!o6wtD!lS+cB-qu<4Ho;2Wu>o zz2x8H3UIGXJ04crb1lnVm#&b%7W!_FjUnF3b~oA8dA<64#}cY@CXt7m+$_?fcOhdj zxYhYO>TMFIvDT+(RoiTuf{4j9_H~yKr=W+k)m@t7mrFNj-xM>a%4BoUYtO^j-W1L; zeer+-2qp=_#m`JO41&a_n0M}AtTvjqV-woT0yJq$ z&9?X+Y?h%o8($Z7j#yOTBFV2cBY6nAWw z-X{olXOCGJ0slrktB$0#TLD(NB}A8ozJ4uN_!xQs#V&I0z$=3I$o{X>Gr^QC;(GkBN;8TyDlh^5c;``Il>hbx zec5-AuVJ<{H~jTX#~(GMA0!bCtlDA=n;8HPG#CS%+_D>q2QS*fb1PiD6e_OK_VtL_ zHGK2;GR_K2Eh9A&!Qf7M2#OVDb)o?XU}$edNF=`+vX@8S{sb&J>TR7Vk`n7I*?zSI z@qJC;8Dr^nFl#;~@g5+Jxrx_KJi;E7p={CaKuC|wJ3#p)7Bc+>5!gzmXtV+{H$SKE zEkWj;b6s6L=m}K|M2{gLyboo7#)-CeTzLp&9m8E&MyQo5kt6*jUDQmm~l2`+nf*Qsl9*_(sLMBC9y z)+gTYQQ>%2gl77;5gMbbQnsV_^tgRlj^1?+BB&~Ey}VTB>AgU#IXsW!x2O2M|3HS% zoy<_JXxHnddPh%PBUQk_vEwV@dNU9f&2UW!&G0?Uhbgw_`KvY~_|(J_q*~)~VWzqa zvSS|2`J@rixONz`cvWl}f8x+0XYp#NW4xR@`WCoRrZ5?^yFSK4ft|6E@ib2^8T2^K zm##we&MMsB>I z?fDF99cqB{o(H&!Y|vOkMkga+K^TIop*o4>o^qR? z^2Mr+v*AI0603HWhZd#LpTL5v|2#xjrimo@A}gr%LJO8}7TrFD@_>YbEELBZ6ZA|C zysf}`8#R@sdR6of7Gz-X)9O@Z?mFliXf7$F1fV%ChWOLqD74bX@^CKL?G>Haj*0O^ z{_LEpxD|QNr`kKI5TyAN`hnmCfpYXZ(Fjvug)LPy-w#cy8?49Ih)-~+9L^0;xSEis z8%qj$Di~akvr55(4dn1Hiz@~S6=G7o_SWhbNd({BsbFZwn0$>X|6~83Ok$uy4^olh zxT$1I{u{c zaCW0C;s+K3xpO@dhF@3vJ~xRl60p~V@PBy=v34*x5)JX#vxF28LPQl5zcjB5+Ps}3 zh@mTUDva<)#|rf2YH`Lwj}|bcLr^+q9DrZ;md{OyYX&#E4d6UxpiEGuC_A*-2&&a{KlfXlX?#BUTImUp7*Qf$Ny)0j;E6@!9)c zy*>CTRS@7VR3Lgt4u{v;oT=!eU=o-hTZDWKY@Yj5&o*wIK2haezMbE2JT1s&9gAb5 ziytJ1HR?G7ts3K^_tq6D8yenM=bod0Ssi?P#NhtXwku-;KDBXbrDrHx@gji(>qE-& z-I?g#uT0c!uaL%@JWqw! zcpP^Jdu^}DP8ET*$f0Hd*upaMufshltNlTZ4?@Lz`0(KxeEkRFr%{kZ zD-)79o4i{jy}pmnQ*GG%3@kS9BlRsw^$R9OE(Lz;)#u#^_MO_??*%G8gY&`YuJeVu zsb{;U^qVhDQtGIO;GTQS=f}{Iy9*SlwsibjR^B!}r#}Lr6j&{ojs%9E@a6apIM!F) zutSphwNG_L^(e>o-Wp^iOhabh1M@I6d4Zv1@9YAhJE+=2hst|z=c5K!YT}y1=S_XE zWJ7e#E|uK=6d6p+Uufz?lAnjKH-kI=aQ=Yo`MJiXy|&IXW&q4ApaSC>1Ve8+9Dh$p zya#Dkj6%$N?R!E8d8v`g&9w+WD`u*O0<+K7lUfiex9mpf{L|3Yjh7ljkLVkEM^WER z!K0b-cq7LGOb`6=J8K_?WbcShIDU_!gtcrv1vj0$$`Yidd{#-}i(~CwNNe?Eb>pJJ zj6{+Fg(%IhU9F3 zuy?dlv166_ukJeCeH-iJoF;7@fitrJl@NRYx)rV4vBT8n;3rvfrSB>tI3U%U`ypbqcNJR^j2CpN)U%P7*x#(T zv)L;WoEBmiTsa9r8JQY=xVTy%__vFTpP7K2tX>l2Ktl~~OKXYy0y*N?@Ro8VP?Z&B z+vHi4KL}_$sll#VD5nq|m>hP)Vu2i?W}4JK_aZ;gSnKHBTvfgd!Cr14;+mEMSEJDq z#(?n|hhQ#}u483%@8a!gObisP#sGOYned{6OMSwq7{5Zd2$G+pGRp@bbnzqB_OC3B z+5BHQ9Rd-z3zR;(YVuufR`7EwA(Q}xacxgb70;p~w&7|8%IqDa+9&vhb-ir&O6}wG zjei4AgW$PGtlv8ulkrI;ybJ1Ua7@0tzA}sHSZSjH<_WraT4-x%Y=Hiwh6{RF>f(px z7i5c94u?A4aCxG0I`c{dZJB#yHnMoBdbjLykz^ff>zzvJbs6Gvi%9CL9(vhDql(ni za=#BDacrpD_EZbqTRqgy29I+@?X963$GdNP4bNL!-Xb|-v|;+GFzW>fpJ!;QF58@W zX_A!d{j}iRbD`qy>KTY^K!riHMg_lqB8oiItC}Ifbc8>41I34f##%Xsv>9qGj~C}B zF~>WM&z|c6DQQI!o`Tnz#;6R(qK%~P@hBfi-#Mk51CqH<2QK~$@zCzRE}PjN&0&0d zm42~W5xTk!|LkrLpo9)X90aCk6w`>^c6q08?GAqYGjQ2l z9P9lZNB@oY$tAs>PK;qUMMi@B8E`jugEiNSP5hn*IL+$;RY@v?Yw!dbsWi9|J|=(b z<2TEX)IHT!P8HfnI>%A6;!L#ln=ES-oi-L?B;mIQ$k*K51J6A0<7LJqJb7G3#k<&; z9(EDxf-N;TKGj4#N!8w|m+d`@)hym>8re1?VwhjNW^JJzfQ@h;JYb{YwwQQ{>}BC1 z5o&~}?scfdOMdlr7W@QoF6phompZ!;;{)E+g<^Y~5W0k+Uj>^f^LlnPMM;?kU^r`K z1(WHXF1wz*X`Zo6wnWotwTwN>KuX5C@;4yLUAW!RE(^1maG-3;e0%{TgLv|oV6|7e zwj8(+cN`iwCs5iaF!Os8Dl)C>5!w7;adye&WPG1C%x74s*1Gt#bKwJga^~yfmX44ySRnQm4wfLkx(?lZ`wzdK?K9^NP-&_m}Q1CJ(jhw z<#zik{C*7*a-&j3^YL+X69Ot}e!CzCv+ak$r8!(^sjv7*3xY|%hbr7*cJ(?$`**Ak zq)a*oGI+<5-GNz)dDIMs_mknC%sMZRs*CRLwi*zxZ+EsJhRIA~W1m&Fm%Ly?MTkKfP1I_>djD=qUQMeqe#j%(O*?d>`(}nAu`n2z3cn%{R2ol2TqigtA z?|^B@!^?K`9zVLaPzcvUc+lsT62@jb_oJ(`@!&Nl(1PF%_SMc=zJ1Eix$x-*vKyoW ztgm>l#bm>@E-IeF9mJIKd#b?dANN?hPB1m{%u{OFBRv6cf=_P1-J;aPjF_}NuU}BS zygJe~(&_gI5@sPdt#!ikytO3yRMLkIeOV?4a6y(A84E7E6FM*=T-SwoQrHg{?$7_` zH5F(D>FO*W&xPt2oouXf)uD6|!*9Gl=dL61%f9P0j<^W`?qP%~ci2wj*QiU!X$R;# z1U(=i1$thkwM(Ss!1~b-LoEz(Pn*xmBm-X-0J5uqqfGW}D{$@2d9NU3G z9Phi%>yk)oOpyShb5OfeEu;m}Kwm`ZEx_7|DUYT4({>YL_>*-Cn!I#|>=wB0PFe_q zo=Be1^{oHX8W^EEY;-+IXlzr)M1vo=*VxpnW87d6h(n7}o&58OMg&1(V(S69C))x% zGj9wVO1OgwFq=Qm5M-FVOZ>wh`d0oX1;YpMe*o&~!9`IN1Kk_Yt*LtRaK>^$6?iPD zg@KkzBW*q6b(qp5g6cN(V5TF$2s8q8jnRXw*FlK|eFsmO!68NDc?#xZ_SP3+I4Pf9 z>2h+ZWw}7(e|``26x>U%umggq^0j~Qva%faX$o`M{0!lP5T7kUd+flaapqR4udwv` zZ(qmXpOyEl?8lpB$b_>#_&?sTB*chg_FOQ$9pczDYb2N|&i(^I*A23CI-9g(xpP+W z-%mL2PJCPnu?jf0r7#XMpe!?A-?m%or*f0`@=g6|3?lCQJ3KUEz7Y?2O?;`?%F$e! z0U>t&_tF3nZ!WUE8uJUhrvu<5kOb}DVYh)^Ml5qySuTMJvWfX^w*B4IS0cW}Q;<$J z|IJ=sufXKx|BhFSu#6w$CADqVN1ggVhNVTwcOV5H@QEd;{Q%XHQ%J;g$Q0zEEGx(~ zjCz6gsSW^%%>L~(Xh@T}C5R4ncY7tllEwc$p1p+0UA%XTW9q==*D?Vlqys%Lf0j8u z8;~I2dcxUCEx_D>|Nfh48**y4qwO(B=lbvPK)0EHArn4zT{3=s77W~+{_i|TVng?U zf&6lQ*6$#U?CBS~ zgY$?l1%wFG(#@lAQ~#vYb({a~3weT1S<8;+h^C} z6^z+cY$SjZCR;+ir~lr#rwCzDM}-gID(ZM`!o{&=v2=jDW0Af5vB>jok4R}N8t1nl zCcwZ2kHY>|73t6PTrt6$oTd36Oj)&U)#VzUYtL(_f7C8Y+#DQ^4wMe!Vh>;7bIgo; zyI3$>(dFl-dhnVc4!KeHwCZNYDNuIJBC%pGL0WshEn1t!V*;@tCt%%9A0S2M0D-$-`1n7OB*9j&(fMKg@$C zDxm{nLXKj~=MxY2w%U!!3ckqtqIbg5zp=os2j5kX^>&+N-0cH{@*e!)Vz}kHu|0$< zxK~zrd+a*_TXvFQ+whKR9U0UmYI5ZIiy*!MzDJy5oc(>zxMX4=+}#VJ`OK5TXY6Wk zfc5gr>nv${CwpkLrAo3Omr^z}Z9b^Tk=5_`whJrti!;MwpN=3@sV9Z2uFd{E+=D4K zg39an3yVQVckD)rkZv}H*N&reQhieSq&RHot#IxHYfjhf04e|FSZ!u?4?(R*-&f8^UURVC;V`cF!aDa9s{3!WkKUG?G5Sk0?ctNw;S9_uV}jyT)5K-~_TG z#+8oSORSY+&-NGs zjp^ z=%T8xp2VjXgdkZYq!k2sF!e+m=9mka1$cMk{gxrn6!`Y6zoDs*8}yIs+p z>tepnr-?3v;w(7iira6gd#_kV@K8sTMK7$JZ;Yp6>e)|sJjn~nI*6~L5wM95)wfkn zx`S1={Qfl-w*He2so%w{w`2xFz%`{;Gs11kO%Q6^HZdTp)TU?wSkH)?Yv+RFow_{| zT6JYxUa4+?EE^P?fXL>lhg!!S`vMRq^mJKpXRy?7Oq156&Oh89!@3?v+0;7>r7jfS zjdr_SG~;A^M@-(@_E!_%OW%UldKb0v4W3Z^+o2lTE@A01F7578gD_t+jpa+IvF7*m zD;T32O{8$Oa4Dft`L~!-lNMzuIr& zzISK7#ri*pTP)xx--6HYrX!!O+|nukT)lJe8BUH*_mb*@Sc8LEK@W22_iX}p{%stf zt^hXm~@y)S~)-2dL4nj82cCJDou_hLT#S~?9yOunV?5PQbTsQttiBPwrg>;FD{ z9efyME!~ Joz5+=jWPyHBqdWh{_`siQ%@*8eCBcL}w-JBclIHuie6nDYJY*bZ8( z=YHaQRqMkHMe>7 zpB2~c=^{abZ*FaQAE-!FDAelv+~J>Xad&DGeo}aSncHhYFm=J}C&#M6&j5;-1SsR{ zE3C7SApvsOZQZOW1*?O6ugz#HL7(^6K-8~i+m)fkI+HI)v6eSQ+bRE1f6AbCc~O*N zv>Z{YAtBKK|K1};LXDNs+T)MFbE@naNMVm$1+%_h5=hQ~Sa!|y(ZWWDws$@1fmXmD z%&iL=#gbmQ1DDqDZ;sVK3*yAv-EZvrVvCQyhJU&jsqPWWI2qBr{5?hLtJ$f|)Kk=&oemyZkor{9g z8*S|;7gHMJaD33(C`oLnP4>$bRuE0ac)Ov3+xiU~*XFOhFZb;ZG{tU3R_s0!+Zt8% zQnd%-4r%qmdafnvg*JtKX%E`J|FSk{;*M!lPTwkrSF8q$l=rDsjLZck?Y0YXtJ1pH zG_z~Ilst(@?dV5xsjT>S(=<8mMus(zV_dfrKub<;npWmx)YLBZGV7s(SZnb-Tn=HoSr-QcM{v@qu{s^Q(H2TdIX z;#B%tZ(gcj&6PuhBr&^Xk%mBwK-4_G6w{|=x!eXLnBEM`ME z51FLrfXH2RK+vON)xg1qPZh1DkUqWH>oAfJyrA9 z3YT1jQV`#jPsT5yD#GR^To)@3q*ZFKDX6rP90w4Nx;%GLbb^XswA!;aPmR3^tYJxe z>i;v}%9!^Whm?s*(#P7jy%FA5Cl_nZulIMGNLFAZKiWv&U3Vd znnsT=PUmi(Xym_2@X_o#7kN6zCj(nMI*+lh@E}zh)#aVW2;xFB{sfX5X zv+LS}oE`1&U$R;`c=Y@q2lu?JRf}E1#cr+su%j^cY+Lk)no=Xe{_#ySAI(<&z0j2twG&ch|3JMU@5*mA zj@U3atmJ3Y;M5Qbdl@ovrxBZ(V3q+fja}_7d;am~;}C@%jz~=UWdhWd%r(7RVp%D( z!!;zZj`d&S)4Vm+h zJTI-DFHttKQ9mr*WA##Xx|t0wV(U+Ib|5;!Q8`-oWQFp<#CR~W3QAyd;_YCrM%jz1 z(QI$U!gIHHTS0SI25-0{sYt;Cmkx3hurKHX&JMxhWb}~p!b$x%}kT_b+-%E z!5B1X5TccKD|H;{L7a$6fzrhjR~$j~Pr9Z}bgUql3YK?*kkC+%rDnPHglwFEC5}cg z8ebxGCwEJDx&voG5DEv^Dqxb+&R~*#`ej@;5FX&udNPh1j9SXJ=OR7re}|WOf)X*F zZrk`$X<67{RD3mP%2a#ZUD{*4ObB}iI^Sf}Cm8pvl;U)PW}@+8BA)0yb@hqXlmom! z`)A&35(Yk6PC{E0PXL}zhq#By=Bb>*wjdnN@tZNNj^@8Q9_ivg!$?jfI0ogp`|=T; zLn3WlaN5zSg&&$|5nfTG$m7{{e0k!%j#UwKK`Oucx-t=Wvch~(ImEd z^$EOyLhnXTl>5U8I{kMM3|mTnospYxLs0RLwn;PI(W&ojo>2WzP5B*rhwAEUvDHB% zt;`33C8kg0)td;XhfD5jQ?qMyh5Irbk;@6x13zpR+Cde6_h55TB1s7B>KN$77B=xa zOu7I_%t4wC%*}T;?MP1h`7(C$9(OusQSwu$A%y#C+r~Opde+B9Ubd+``=brYCgSY# zd~=oV{>L_l0hU&iD$mepPjZ0^wS!)eWL>F;6C4IsOP%E3fV*7&fK4jQIQj-K%&huo zS&PlTiKx?+8Z7YW^-tlol2D!_voKJj6B6>p6t$G&eo0PblOH`NLaUtS46R3WgvLIn z!HY=Sx=S2Wxls>mL?!;Z1b*0g1HX)4Bw3H+WIXow9JUK)FwQtaGxmv(c?iw{dwI2g zlWEQFzLQweYSla>-moTYxG`+vbRUt}0N^f!U3$u4+SNJ(1M7Ew6d)1lvBnaNSm7-9 z%FWuDCVyS1VNZM9S!gZ{-rRLh{}bKgllew69vzQ;boa7Dn6om~zX(J(EQc#Z&qq+^ zf$ZOYtqVFee{C#e(aZ8{l@vTuGaai-6pF9YLh9wobQj; zh6(FuJ$FX#!S?b}1yb88OWE#lccH)TFSowXebif2r}#i%%F?-NPvq0yks09n$d+Jc z`i&|l=qptmUBDSa_q%EjnsXEcH>}RZQoHcs=pyu)mlv-V&im`;JWz{RVC_pE?AZJ1 z8tRfg8FVciX+69>D!0Y{;;)q&PqPDmZL!iESYX#B-Kihe!vbG|OiK@cC{Pq{SbV=0 zoeV|Of4Z7!P9wl)-7To@@R~-XM3HUKFa@3`j{FjvGWTt^?pu#2+UJ+dLsr#LT8vlIUuQkh#-;00sD-hO zDFjFomNRr@*>Sg6iQS3PA5G_9s7jc$j>#mBKm~cW?)m8_mmcy+YY8Y$6lKkPE8(QJ zCvvFLK{~OB4do%<_gpVlq%~dt@{6+{ zpTX`?e>%e{k&n0{8^LwP!fR;T?v`}cnLKOuJ6|-k-m6D-j3ZYu4>nBO^w@vGcN1v| z+Sg!qxUpF?+lev{+7??vc4)dXw=0Vfk<^>ZM!~Be{hpVicLQ!7iVB0Ny<}2w>+z{p z@(#3N`2-K2%#4lpG8ORrT4z(+A;W~s8_~W=V(Hb<4|>|ZCEw3}n-CBwF-S$#i*J0}>Qpg8qg&}E2z7@PX*v?CbIfeLBO zylOV@!Tr=jcetgGaXcztQb~9mDBRWN#Etc5$|qzG*jUp=QsLq?x#f9bq!_vF>{v}T z-#~8#1c3Uw^^5kN?RG`z*ZW}`BbV+&ut~uSzVfKXD+I%|8kHg6a`9aMddVz1Q6P_sN)czbQ7i3NpENOb#^;OFH%2y7TMAMvgJQ)jV8MjR` z>o;48f2&;_XiHkRJ9zdUD5CgBX3*Z!)eec4T{qHCs++v5K46TQ5WGSC-Btsm*gkGJ&k`8S* z&*|@G5;eHRS{tPs7lZw7mrCT@d|Djy03F-B5CfFQX3305)i_gF_f7Q@X)dXlbG(G(G0XU7aud! zp;cT4ZwHqq^$N$$|m_20{zb0Q@C z5))i^(^RSb;Dac|yVnS!z%R>IN!m%&zGmP`?;u8CN_L(ibp4oM70GRp00%kpy_q2pMv4I@l9C>SSNo=%iL9FH2 z$v1Ds#qxs5!h3e$QpJS>Mp16nXqe@4?{L$68Lz zrX3d6hM>Syy3=O!5IunG?7O``4aq^04~~Cb=c^|+9dD}|uvGUXp~3c1)BpMRdw}K? z))fT)IfeBfKSkgEv;A3gGePp#IO`9h|Ys%)APvaPG8PG%yn@f6W92TtJcr zyiDpoQ8|<%S6kNI3RPa+Xj!6TpRFSQ7&*~%CCcEt^=OCVpHm{TYYXM_pty+Q}N9b~_mk6(V0A&+P zn8)6nW%$#77WCs$1-ULGiW~+I7*)h;S`k`FbCy$0(z0tc;)?;lKkb+`XR&-I~x~nJityvDkPH zDNnQx-N7^OgtOJ`1F=W`9MbM>j>{%% zE&JW?gofy%$b`?`RM&W|W{O|0+S+6bZo`1`;@S(#E@r_-cE~X)3i9Pn(goWFB?hc; z)lW%D55e;1HrgIp+wi@j)~48K=+(B}o*`IOdkDZHx~Uac}+R#WZyXq$* z(H(ZQAUx47s>zhkpoe~zIvjj%yVNEl>&jp}M5M+61sb%ftoE21Xn zRu7h!2eUOMU<2{Iy*z~1bt!>CnqMDsZpF-;TBn@HJg{q;pP5kbaDb^ABm0UJh-s0l z&AEJiMrv8|70N2>cDam?ImmxyU}>zq8#3D^9_7>r51o$is(#_!<~ob9MTSnoF}1h; z7Uks^65)5T!AO|<;f6^gRuP%VI7rR5T+6r@SS|l)>4{4JW3g4|tceJQ8`D{D-WIS6 zD>n=-i)z_tow*)h7iAjsK($;U-<=_V(|8)wQjcubfrVuO*Qe*=wJ@C9-?;G!4k)4 zQ^!#xv~p*Ce@Jh`E;F|mssrgP|5=kZTup?JF&gyW^gX$4=j2OTw_1qD2Cl)!!82yf zndIw+D3W0GL1jwv5aDY(8^*40$RKHoJ#HD>tKe>J-;a&o9=Ok=N8q^nk@BhJDg`Yf zQtu>u4C8%&5Oy{SYM4#>Znp|t*RjXzu=JibKBdo$Q9wtTi_7bi#kTCyFe%bBF3mBU z=(fpKH(y`i5fwH9I1OU0f4h zxI_Tn5ZUU>N-V|2rj~`S-M-l8Wfr&>-VBA=!;f513lYF(M&vz+2KMFv9od$nswW#Z zJFwxy~vu|!@^i(-I(EGbk$ZoqV$dMbDHfC22VlQa*cb2<#?_B zQq+5IdlaH?9(u(0i?G@_jaMQ_{Vk)_WS^tQD35Ryd7s#VFIh+E-br@Mk9PaOW+O3! zaVQ$G;6?IdF%uxyALw10-?tu=mg*3F#RR}rGPiHLQ zn~f*Rp)Fco2*D>u=z8z<;IfBCzcA5T1%=iU)-Fo!-M+XA0C!#ovo}e^yB*0aSBe#t zU@?qQzF5Sx9*ORv%7E5hd>RyufDLY)sVy*y-fJ1C_g!&2+S`45c)!>(QX!+H-?{@4 zxWz&;{oX6H$A`OZF7~xegI8FPXv5(eiboUhrxyOkl=;sT;Z%nPZ#T0#fg#(1@R|dw&>b>0!6q% zm#x48fOov8jaG~JdRAL_+|j}ti+H}N9#a&Y{hWB&O)_-ya@kUsTv`lpIuPlKUVg?} z+W#KLDEl&(H&-N=MH$GQb=P6!&qOJ?yhoyH?H~-zuibe$8 z>`12fgI$(ct8<%p4ne37Yy#GDlyyrn>vu~oapX$q>>|*|%NZQ0#0E?luY!Vch zjLu($444WFzRi=?8$C!Kx33r-dn4O-_zlM#c};p~PjazO=cu z0hd%h+XdK0qLVM}--n{kCVsY~ZzM}`Io>&Mx|6x5>{tGS{EPjr+=X{|1}iKu(VX!6 zGS|uNHz?KB;=hwdz=K}@T*Oifh{~1MnaswgD-}6<&~6g#)|K+qkOD$hp0UN}s56{B z$45WXsgTuv>7@t9iayR3>r>ZWpD4;FP^`D!Zk4DQP)hmhScCelF*&dhum?|ff2D6@ zT*cy~>w+n7%71qjaUE34hANu!jp_~hVHL}Pf#E?-TS7o|d}{u~-A*4Ew5qH6U^v6P z-ETHnZ+@udYT0&T+({vdO%r7yqiD&0bcjemq^iJ3>%nze!OaCSN4HJ6*B(d0iUIRO z#f*cw_Ovs}K9GdN@lJSBo~@yvl2HN4t#L_ zr%&b06)u{qbnLw*r$``0T(MwT%5MQE^q}c}|9{g0X&Q^Rf71D^FB<+wH3a_|CQ4_u z)Mj+%UyXA-oa^&2&WtKU!wWTT-(EXOX0xN>w7`0pn=Fl&=Wrq*!(VPZ!DLrtQQLRRI4imI zUfK`~GfN^n9QOTBKb>wydHVWp^xYttu3JU7nxvTR=Jk#p-Xk&Zgo+gpSqVDn|l#_3&UaEXF0S0pZUe zXE^4ccRQ_ul{H+CzaXAtj7xR-tU{RF>dHgn7RIe)(_tWaqI2%{!uUa1hQ_|74Sj7L zt1G7?S$WryV8%46>#I^kU}`6vKDr{}GfJu>zGA=bbMm*E{xOjRpHV|ycSqYN2(Oko zwEIHw{go(0Kg`JGEVANOe4wp0R!-kvEBbz)0p;nNAf}(^F>wOm8e; z%Y)Gj7R-buUxYUz?kRGPOq?5$Pp&hhS>YzQ%@vWD!Y9v6)Ycx&+8^c|oCFQ!zvuJF zwvybF5^Bs}MLmSI{lCh)(x|4AZ5>dgaiSFz5P@zKPzFK5Afw`dGJ^^T$Rq++nM8(= zFsV4S1JJ0*BoP}M86+SOKoSN~1VN09(Zn1D1R6qefJ{J4xRubi-@R|G`+mOl-n#jd z_`+WV_rRb{AKmh~Sq5n)S2(WIz&x@0%`cC)9LZybJGe8-Gmn z+gKxfHb8)~s%5ig-7R1E4;m(S5<=3uG!Y#70W%7!@BGIqv%C%f*`c4~7wIGy&krdU zsbR|sx-Ro!xpcm4DyK8eVgHQm+_xNoQ6pu>Qu_ zYj|Ti2{kihWv8O72q>KbF{b;HzR?CJ?qQ~X+cR~Fpcge9SiTzfGVt!wEZ75V(Q0YC z&@0p-EN^my1f8i%^r=*@s|2|;lyBe-l*^0d?^JxS*-rxrDE!*qAE0jCtOV3c5 zp-rV0^mK>oIGPtQX}SSmK1g;5GD#cQh3ZjMwEI{k|ki zAS-rGdYqQDs8g!kALmw-%Y@59l#~%Kcq{gduZdH;7L;91j~JFuMDrs-WB^WhDk3U> z?Zgut=K=E_Hs>(ap*b;ke?CO0ILCK?$rih`X@+Tpfw(uD7@u-894TpDo3vm~#rYr9 zod%lncaLk}v-M#TahS@wSGo^0LsN=*MM1CtGg{$5MMVhPCrDpr^h^vqw;f%50?1ba z-q^jK>Fk+jx}&GKX|t)3t+DD!oc$gSGdhdPZG)=ImaPW-DBD`Y=!uynJ8~bzj+WVLJOv+XpF$l(MrZv-JfGU z=ZBNr=MNrvRIeP3eZ(YAv=tn;X)cQyDr2Q_X)~kWy`qWBn_l!=W*HcZo%^D9UzxF~ zlrO>gXmuUhs&dr}VOF_y=9g%cSxizifV(pa<#-g6yoap_P^UD*tYUaA& zX60dMgC*&Na>I1y#-K02i6Iz!J$ZV(B-G=}xA>x-)0Fw@C5fBdRo z$a*y0`vxmgcu$ZC4*y)iKZ@S5m_|;i69%`4PA4gs^l#mH_PvYUXkWYr`H;)d7<*NE zO|M`7-cEB<&7)fHWJ?Y@7w?p57Z7$?o=8dFX(Nx{nHM!&CKzn8?2(IO3w;&`#sn_a z+c!brv|Y4TdCoS-3h>(t5MV6AeX)t!91phsC+Ggflr@Lj+{t_P9mTef|BZ#Ax8{is)Tpl9$;Hsh{_s zl;s^Fw(`@@PndzF^7h%UH0!UvxvC&}q)M3Jn#Y+WJYI3%n&U6q_t6b8(^q{rxI2@-Ca8Q1X<3T^wfx1kli8pj%9QhgP*V1PEJ| zNR=k88$b#{dx1zwu>Q7EFIGs~cJX2s{}L(?qnWFQ_5zq#Je$nf0&_&o9SRfHs0S5y(4J?X@`82iw1nobQ2&;gD4gX6LswhHEnA$4G z6`|fhV|aFg+NM^nP1~uY<2k%eysV_Ac(n`k1=tu8z;tCks1Xejq00=r|a&40E$l z{%j{H%!4eKD$|}p(l(>nI*mU z@>5F@@FW`A?htjHJPDofY&3g6r7HBj4&V~rT%qckN<7V%mVU)LNDy2EiuW&=a{hJD zxk6@q5seW*ofUrfUHZWL6GR4GQnKwBh%~6046`3e9t`S<;cXT7OvH}8YH<1b*a>>( z!&&CEik=mLwL~W(k!kttzn?QzAP~0hO8bY8Ck%#3K&yC zhxhd5MjYzU@4ot{#;N-@zEg4mSC$wV;7OzI0&upGQM+TYWkt`AaLBnWp6T|r#5#SX z`!C?2z$G1j>t0FUeEH&Qh1rMJE(jZFaH^u#pl%IZN;BaM1BnK`#5zZLu)?r z$KL~|&{Z$-~#Rqk`4k?|a`qJn5>o)w}(!*A^VK zwI;1*E>c77&8s6skrs^cndtR%ejvPum7}H1b)N8z(~>1?&UX)Pusmj^)4L0I{APD- z-IfySKD!gNZ@aF{UddkDSU%QXYow>)h?FUay%z3pvPfacWBPojP2+q+aa?N?4%J-G zR+q7IKdkIPtG?ygaMAj{QmrH118v$@v*B=;mlxS8cP(E~Z1-aBd$x5B)^hzKI1DX% z!Gwi>Ux^yx)}*vA#xa4@vgT4>LIT8S5hg5fu55fUNN6AH$sD>54W+r)X>~XLScyyD zW{Mr$D~&(h+^p^pl>JO)(72A0F57=?c$IQrTFrGRTP=NPtL|jgy;dqiD2zO-T6fkb z)N)87vi$wwakTamPO?hld2V*k^qFvS?`|tDv)iTk*g9I0$0>MPXYo9Bo6Kvu^T>=b_L7!J=_GsY-Nj+D*&zO8=I;Uuqu5*d*i!k|5ynmId4dq;lS zvMWyIIrD9O#NF+Qs2wf`u$6g!pImuPFy;^%;Fo_>K)e|b{Nq+9i4Ze{&0jWe>`Bhr zI`NiR5w%cpFmed*P3QVn_BzAE#7UY*Q%UFpnWEY@WrF`uB=1Gq@<)b{!|M8Ohsv7w z3{>ArOZ23m7y*wXh`k=>x)1JEXQihoUE4ggwfAOtBAG0}a)N%vb#V1(nXe9fk)}(w z^+u!lOkNaksko2mlL!$9cXNv7z$`#8U&2_6xout^&M4dxd)^S?9b8PJB3t+e2 zvXulPr2E(O4o888#!#F_ZKbp@%0)Wm(y?~`py^>5D)odT;r;8L&9Lc8j_c7PPX?b_YngI1&mXn+hIwBZk|%*(Yk&I3ERs`?PDEoC6Ebg=hK2+zEU9<$Eycxy`ONF zs#?0Uhto5ID{R@9UU8&k`xkBGhoqjhb0cp^i&>!24cAbNxQm^VqQ6Q+3u7?A1-$)A zD+Bfb!eI^UyaBYrDZ^S0t`?XDmo(B)PuKj>|4kNWm|)Ho6+@2YRM^UYuP3URMAByh0PlP7nM;3pMhL{ZuVaw=Xo4`7@v6m( zsYbG{7Zp@$HX0VXn)#KTV|LQU7SCfuwLn-iePt51_@}fVT~r^o^6b4Ms^bB0%6BWX zX#W?O^w3kB4U>S1TjtDB=9|2*{_nw+k|Nrrg#YH%fC5VXG3BeMDo&nd0E-oaCe9#zMUpA<`OY>AsK_DKqk;C|FjQl?U zIU6=6qMPeuzV-l{eLdiB&V;{~1M3^{f9rE%Zdu~dI{6nJXIzIt$uwe*oQgdg7#nOJ zbTJrwAq))+k$MJ3dIlyZ4Uy&sCgywh>KGW98yM7KlA8X7AR_AQxe)ZfC+N_6y#^5M j`s)p`=OTh*Vgn;C{Ck?ahU1?^^0GVPd>D5y;L6_tD?L~p diff --git a/contracts/snip20_derivative/Cargo.toml b/contracts/snip20_derivative/Cargo.toml deleted file mode 100644 index 23566a9..0000000 --- a/contracts/snip20_derivative/Cargo.toml +++ /dev/null @@ -1,52 +0,0 @@ -[package] -name = "snip20_derivative" -version = "1.0.0" -authors = ["David Rodriguez "] -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] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0", default-features = false } -cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.0.0" } -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ - "admin", - "query_auth", - "snip20", - "storage_plus", -] } -secret-toolkit = { version = "0.10.0", features = [ - "permit", - "viewing-key", -] } -secret-toolkit-crypto = { version = "0.10.0", features = ["rand", "hash"]} - -schemars = "0.8.11" -serde = { version = "1.0.114", default-features = false, features = ["derive"] } -base64 = "0.13.0" - -[dev-dependencies] -cosmwasm-schema = { version = "1.1.8" } diff --git a/contracts/snip20_derivative/Developing.md b/contracts/snip20_derivative/Developing.md deleted file mode 100644 index 45baccd..0000000 --- a/contracts/snip20_derivative/Developing.md +++ /dev/null @@ -1,153 +0,0 @@ -# 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 -``` - -### Using macos? - -You'll need to install LLVM using Homebrew: -```sh -brew install llvm -echo 'export PATH="/usr/local/opt/llvm/bin:$PATH"' >> ~/.profile -echo 'export CC=/usr/local/opt/llvm/bin/clang' >> ~/.profile -echo 'export AR=/usr/local/opt/llvm/bin/llvm-ar' >> ~/.profile -source ~/.profile -``` - -## 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/snip20_derivative/Importing.md b/contracts/snip20_derivative/Importing.md deleted file mode 100644 index e367b65..0000000 --- a/contracts/snip20_derivative/Importing.md +++ /dev/null @@ -1,62 +0,0 @@ -# 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/snip20_derivative/LICENSE b/contracts/snip20_derivative/LICENSE deleted file mode 100644 index d645695..0000000 --- a/contracts/snip20_derivative/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - 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/snip20_derivative/Makefile b/contracts/snip20_derivative/Makefile deleted file mode 100644 index c630b22..0000000 --- a/contracts/snip20_derivative/Makefile +++ /dev/null @@ -1,89 +0,0 @@ -SECRETCLI = docker exec -it secretdev /usr/bin/secretcli - -.PHONY: all -all: clippy test - -.PHONY: check -check: - cargo check - -.PHONY: check-receiver -check-receiver: - $(MAKE) -C tests/example-receiver check - -.PHONY: clippy -clippy: - cargo clippy - -.PHONY: clippy-receiver -clippy-receiver: - $(MAKE) -C tests/example-receiver clippy - -.PHONY: test -test: unit-test unit-test-receiver integration-test - -.PHONY: unit-test -unit-test: - RUST_BACKTRACE=1 cargo test - -.PHONY: unit-test-nocapture -unit-test-nocapture: - RUST_BACKTRACE=1 cargo test -- --nocapture - -.PHONY: unit-test-receiver -unit-test-receiver: - $(MAKE) -C tests/example-receiver unit-test - -.PHONY: integration-test -integration-test: compile-optimized compile-optimized-receiver - if tests/integration.sh; then echo -n '\a'; else echo -n '\a'; sleep 0.125; echo -n '\a'; fi - -compile-optimized-receiver: - $(MAKE) -C tests/example-receiver compile-optimized - -.PHONY: list-code -list-code: - $(SECRETCLI) query compute list-code - -.PHONY: compile _compile -compile: _compile contract.wasm.gz -_compile: - cargo build --target wasm32-unknown-unknown --locked - cp ./target/wasm32-unknown-unknown/debug/*.wasm ./contract.wasm - -.PHONY: compile-optimized _compile-optimized -compile-optimized: _compile-optimized contract.wasm.gz -_compile-optimized: - RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown - @# The following line is not necessary, may work only on linux (extra size optimization) - wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm -o ./contract.wasm - -.PHONY: compile-optimized-reproducible -compile-optimized-reproducible: - docker run --rm -v "$$(pwd)":/contract \ - --mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/code/target \ - --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - enigmampc/secret-contract-optimizer:1.0.9 - -contract.wasm.gz: contract.wasm - cat ./contract.wasm | gzip -9 > ./contract.wasm.gz - -contract.wasm: - cp ./target/wasm32-unknown-unknown/release/snip20_reference_impl.wasm ./contract.wasm - -.PHONY: start-server -start-server: # CTRL+C to stop - docker run -it --rm \ - -p 9091:9091 -p 26657:26657 -p 26656:26656 -p 1317:1317 -p 5000:5000 \ - -v $$(pwd):/root/code \ - --name secretdev ghcr.io/scrtlabs/localsecret:v1.6.0-alpha.4 - -.PHONY: schema -schema: - cargo run --example schema - -.PHONY: clean -clean: - cargo clean - rm -f ./contract.wasm ./contract.wasm.gz - $(MAKE) -C tests/example-receiver clean diff --git a/contracts/snip20_derivative/NOTICE b/contracts/snip20_derivative/NOTICE deleted file mode 100644 index f18150b..0000000 --- a/contracts/snip20_derivative/NOTICE +++ /dev/null @@ -1,13 +0,0 @@ -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/snip20_derivative/Publishing.md b/contracts/snip20_derivative/Publishing.md deleted file mode 100644 index 35f5212..0000000 --- a/contracts/snip20_derivative/Publishing.md +++ /dev/null @@ -1,115 +0,0 @@ -# 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/snip20_derivative/README.md b/contracts/snip20_derivative/README.md deleted file mode 100644 index 44dd8cf..0000000 --- a/contracts/snip20_derivative/README.md +++ /dev/null @@ -1,901 +0,0 @@ -# Derivative minter contract - -This contract enables users to send SHD (or any SNIP-20) and receive a staking derivative token that can later be sent to the contract to unbond the sent amount's value in SHD (SNIP-20). - -## Index - -#### [Engineering Design Diagram](#design) - -#### [How to deploy](#deploy) - -#### [Instantiation message](#init) - -**Messages** - -- [Stake](#Stake) -- [Unbond](#Unbond) -- [TransferStaked](#TransferStaked) -- [Claim](#Claim) -- [CompoundRewards](#CompoundRewards) -- [UpdateFees](#UpdateFees) -- [PanicUnbond](#PanicUnbond) -- [PanicWithdraw](#PanicWithdraw) -- [SetContractStatus](#SetContractStatus) - -**Queries** - -- [Holdings](#Holdings) -- [StakingInfo](#StakingInfo) -- [Unbondings](#Unbondings) -- [FeeInfo](#FeeInfo) -- [ContractStatus](#ContractStatus) - - - -## Engineering Design Diagram - -![Engineering Design Diagram](./.images/engineering-diagram.png) - - - -## How to deploy - -### Requirements - -- [SecretCLI installed and configured](https://docs.scrt.network/secret-network-documentation/development/tools-and-libraries/secret-cli) -- Account with funds (~6 scrt) -- Derivative (SNIP-20) deployed - -#### Steps - -1. Make sure you have the docker demon turned on. -2. Open project in a terminal. -3. Compile and optimized contract. In the root folder run this command: - -```shell -make compile-optimized-reproducible -``` - -4. Store contract on chain. - -```shell -secretcli tx compute store contract.wasm.gz --from -y --gas 3000000 | jq -``` - -5. Query contract code id - -```shell -CODE_ID=$(secretcli q compute list-code | jq '.[-1].code_id') -``` - -6. Instantiate a new contract - -```shell -TX_HASH=$(secretcli tx compute instantiate ${CODE_ID} '' --from -y --gas 3000000 --label $(openssl rand -base64 12 | tr -d /=+ | cut -c -16) | jq '.txhash' | tr -d '"') && echo ${TX_HASH} -``` - -7. Query contract's address - -```shell -ADDRESS=$(secretcli q compute tx ${TX_HASH} | jq '.output_logs[0].attributes[0].value' | tr -d '"') && echo ${ADDRESS} -``` - -8. Set staking derivative as minter of derivative -```shell -secretcli tx compute execute '{"set_minters":{"minters":["'${ADDRESS}'"]}}' --from -y | jq -``` - -9. Whitelist staking derivative in staking contract -``` -secretcli tx compute execute '{"add_transfer_whitelist":{"user":"'${ADDRESS}'"}}' -``` - -#### Troubleshooting - -- Query transaction's status - -```shell -secretcli q compute tx ${TX_HASH} | jq -``` - - - -## Init Message - -```ts -import { Binary } from "cosmwasm-stargate"; - -interface ContractInfo { - address: string; - code_hash: string; - entropy?: string | null; -} - -interface Fee { - collector: string; - rate: number; - decimal_places: number; -} - -interface FeeInfo { - staking: Fee; - unbonding: Fee; -} - -interface InstantiateMsg { - prng_seed: Binary; - staking: ContractInfo; - query_auth: ContractInfo; - derivative: ContractInfo; - token: ContractInfo; - admin: ContractInfo; - fees: FeeInfo; -} -``` - -```json -{ - "prng_seed": "base64-encoded binary", - "staking": { - "address": "secret1abcdefghjklmnopqrstuvwxyz", - "code_hash": "string", - "entropy": "string or null" - }, - "query_auth": { - "address": "secret1abcdefghjklmnopqrstuvwxyz", - "code_hash": "string", - "entropy": "string or null" - }, - "derivative": { - "address": "secret1abcdefghjklmnopqrstuvwxyz", - "code_hash": "string", - "entropy": "string or null" - }, - "token": { - "address": "secret1abcdefghjklmnopqrstuvwxyz", - "code_hash": "string", - "entropy": "string or null" - }, - "admin": { - "address": "secret1abcdefghjklmnopqrstuvwxyz", - "code_hash": "string", - "entropy": "string or null" - }, - "fees": { - "staking": { - "collector": "secret1abcdefghjklmnopqrstuvwxyz", - "rate": 0, - "decimal_places": 0 - }, - "unbonding": { - "collector": "secret1abcdefghjklmnopqrstuvwxyz", - "rate": 0, - "decimal_places": 0 - } - } -} -``` - -## Messages - -### Stake - -Calculates the equivalent amount of derivative per token sent. -Triggered by the receiver interface when sending SHD tokens. - -🌐 Anyone can use this feature. - -**Request** - -```typescript -interface ExecuteSendMsg { - recipient: string; - amount: string; - msg: string; // '{"stake":{}}' Base64 encoded - padding?: string; -} - -interface ExecuteStakeMsg { - send: ExecuteSendMsg; -} -``` - -```json -{ - "send": { - "recipient": "secret1b1b1b1bb1b1b1b1b1b1", - "amount": "100000000", - "msg": "eyJzdGFrZSI6e319", - "padding": "random string" - } -} -``` - -**Response** - -```typescript -interface StakeResponse { - shd_staked: string; - tokens_returned: string; -} -interface StakeMsgResponse { - stake: StakeResponse; -} -``` - -```json -{ - "stake": { - "shd_staked": "50000000", - "tokens_returned": "50000000" - } -} -``` - -**Errors** - -| Message | Cause | How to solve it | -| ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ----------------------------------------- | -| Sender is not SHD contract | The token sent is not the same as indicated at contract's instantiation | Send the appropriate token | -| No SHD was sent for staking | You send 0 tokens to the contract (if that's possible) | Send more than 0 | -| The amount of SHD deposited is not enough to receive any of the derivative token at the current price | The price is high causing that the amount sent is not enough to buy 1 derivative. | Send more SHD to the contract than before | - -### Unbond - -Calculates the equivalent amount of SHD per derivative sent. -Triggered by Receiver interface when sending derivative tokens. - -🌐 Anyone can use this feature. - -**Request** - -```typescript -interface ExecuteSendMsg { - recipient: string; - amount: string; - msg: string; // '{"unbond":{}}' Base64 encoded - padding?: string; -} - -interface ExecuteUnbondMsg { - send: ExecuteSendMsg; -} -``` - -```json -{ - "send": { - "recipient": "secret1b1b1b1bb1b1b1b1b1b1", - "amount": "100000000", - "msg": "eyJ1bmJvbmQiOnt9fQ==", - "padding": "random string" - } -} -``` - -**Response** - -```typescript -interface UnbondResponse { - tokens_redeemed: string; - shd_to_be_received: string; - estimated_time_of_maturity: string; -} -interface UnbondMsgResponse { - unbond: UnbondResponse; -} -``` - -```json -{ - "unbond": { - "tokens_redeemed": "50000000", - "shd_to_be_received": "50000000", - "estimated_time_of_maturity": "50000000" - } -} -``` - -**Errors** - -| Message | Cause | How to solve it | -| ---------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------- | -| Sender is not derivative (SNIP20) contract | The token sent is not the same as indicated at contract's instantiation | Send the appropriate tokens | -| 0 amount sent to unbond | You send 0 tokens to the contract (if that's possible) | Send more than 0 | -| Redeeming derivative tokens would be worth less than 1 SHD | The price is high causing that the amount sent is not enough to buy 1 SHD. | Send more derivatives to the contract than before | - -### TransferStaked - -Calculates the equivalent amount of SHD per derivative sent. Then sends this SHD as staked position to the sender. -Triggered by Receiver interface when sending derivative tokens. - -🌐 Anyone can use this feature. - -**Request** - -```typescript -interface ExecuteSendMsg { - recipient: string; - amount: string; - msg: string; // '{"transfer_staked":{"receiver":"opt_receiver_address"}}' Base64 encoded - padding?: string; -} - -interface ExecuteUnbondMsg { - send: ExecuteSendMsg; -} -``` - -```json -{ - "send": { - "recipient": "secret1b1b1b1bb1b1b1b1b1b1", - "amount": "100000000", - "msg": "eyJ0cmFuc2Zlcl9zdGFrZWQiOnsicmVjZWl2ZXIiOiJzZWNyZXQxcjZ5OXBxdXkwc3l4a2tndXJodXBnN2tzY3NuMDd6Mmo3ZGo3NyJ9fQ==", - "padding": "random string" - } -} -``` - -**Response** - -```typescript -interface TransferStakedResponse { - tokens_returned: string; - amount_sent: string; -} -interface TransferStakedMsgResponse { - transfer_staked: TransferStakedResponse; -} -``` - -```json -{ - "transfer_staked": { - "amount_sent": "50000000", - "tokens_returned": "50000000" - } -} -``` - -**Errors** - -| Message | Cause | How to solve it | -| ---------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------- | -| Sender is not derivative (SNIP20) contract | The token sent is not the same as indicated at contract's instantiation | Send the appropriate tokens | -| 0 amount sent to unbond | You send 0 tokens to the contract (if that's possible) | Send more than 0 | -| Redeeming derivative tokens would be worth less than 1 SHD | The price is high causing that the amount sent is not enough to buy 1 SHD. | Send more derivatives to the contract than before | - -### Claim - -This message claims user's mature unbondings in case there is any. - -🌐 Anyone can use this feature. - -**Request** - -```typescript -interface ExecuteClaimMsg { - claim: {}; -} -``` - -```json -{ - "claim": {} -} -``` - -**Response** - -```typescript -interface ClaimMsgResponse { - claim: { - amount_claimed: string; - }; -} -``` - -```json -{ - "claim": { - "amount_claimed": "200000000" - } -} -``` - -**Errors** - -| Message | Cause | How to solve it | -| ----------------------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | -| No mature unbondings to claim | None of your unbondings in progress are matured or you haven't unbonded any SHD | Query your unbondings to see when they will matured or unbond some SHD | - -### CompoundRewards - -Claims SHD rewards generated and re-stake them. Claims non-SHD rewards and sends them to fee's collector. - -🌐 Anyone can use this feature. - -**Request** - -```typescript -interface ExecuteCompoundRewardsMsg { - compound_rewards: {}; -} -``` - -```json -{ - "compound_rewards": {} -} -``` - -**Response** - -```typescript -import ResponseStatus from "shade-protocol"; - -interface CompoundRewardsMsgResponse { - compound_rewards: { - status: ResponseStatus; - }; -} -``` - -```json -{ - "compound_rewards": { - "status": "success" - } -} -``` - -**Errors** - -There are no errors triggered by this contract but the `staking contract`. - -### UpdateFees - -Updates fee's collector, percentage or decimal places. - -👥 Only admin(s) can use this feature. - -**Request** - -```typescript -interface Fee { - rate: number; - decimal_places: number; -} - -interface UpdateFeesMsg { - staking?: Fee; - unbonding?: Fee; - collector?: string; -} - -interface ExecuteUpdateFeesMsg { - update_fees: UpdateFeesMsg; -} -``` - -```json -{ - "update_fees": { - "collector": "secretb1b1b1b1b1b1b1b1b1b1b1b1", - "staking": { - "rate": 50000, - "decimal_places": 5 - }, - "unbonding": { - "rate": 50000, - "decimal_places": 5 - } - } -} -``` - -**Response** - -```typescript -import ResponseStatus from "shade-protocol"; - -interface Fee { - rate: number; - decimal_places: number; -} - -interface FeeInfo { - staking: Fee; - unbonding: Fee; - collector: string; -} - -interface UpdateFeesMsgResponse { - update_fees: { - status: ResponseStatus; - fee: FeeInfo; - }; -} -``` - -```json -{ - "update_fees": { - "status": "success", - "fee": { - "collector": "secretb1b1b1b1b1b1b1b1b1b1b1b1", - "staking": { - "rate": 50000, - "decimal_places": 5 - }, - "unbonding": { - "rate": 50000, - "decimal_places": 5 - } - } - } -} -``` - -**Errors** - -| Message | Cause | How to solve it | -| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------- | -| [Unauthorize admin](https://github.com/securesecrets/shade/blob/basic-staking/packages/shade_protocol/src/contract_interfaces/admin/errors.rs#L51-L56) | Sender is not part of the admins list | Use an admin account to perform this action. | - -### PanicUnbond - -Unbonds X amount staked from staking contract. - -👥 Only admin(s) can use this feature. - -**Request** - -```typescript -interface ExecutePanicUnbondMsg { - panic_unbond: { - amount: string; - }; -} -``` - -```json -{ - "panic_unbond": { - "amount": "100000000" - } -} -``` - -**Response** - -_Default response_ - -**Errors** - -| Message | Cause | How to solve it | -| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------- | -| [Unauthorize admin](https://github.com/securesecrets/shade/blob/basic-staking/packages/shade_protocol/src/contract_interfaces/admin/errors.rs#L51-L56) | Sender is not part of the admins list | Use an admin account to perform this action. | - -### PanicWithdraw - -Withdraws all rewards, matured unbondings and SHD balance. -This funds will be sent to `super admin`. - -👥 Only admin(s) can use this feature. - -**Request** - -```typescript -interface ExecutePanicWithdrawMsg { - panic_withdraw: {}; -} -``` - -```json -{ - "panic_withdraw": {} -} -``` - -**Response** - -_Default response_ - -**Errors** - -| Message | Cause | How to solve it | -| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------- | -| [Unauthorize admin](https://github.com/securesecrets/shade/blob/basic-staking/packages/shade_protocol/src/contract_interfaces/admin/errors.rs#L51-L56) | Sender is not part of the admins list | Use an admin account to perform this action. | - -### SetContractStatus - -Sets contract status. - -👥 Only admin(s) can use this feature. - -**Request** - -```typescript -enum ContractStatusLevel { - NormalRun, - Panicked - StopAll, -} - -interface ExecuteSetContractStatusMsg { - set_contract_status: { - level: ContractStatusLevel; - padding?: string; - }; -} -``` - -```json -{ - "set_contract_status": { - "level": "stop_all", - "padding": "random string" - } -} -``` - -**Response** - -```typescript -import ResponseStatus from "shade-protocol"; - -interface ContractStatusMsgResponse { - set_contract_status: { - status: ResponseStatus; - }; -} -``` - -```json -{ - "set_contract_status": { - "status": "success" - } -} -``` - -**Errors** - -| Message | Cause | How to solve it | -| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------- | -| [Unauthorize admin](https://github.com/securesecrets/shade/blob/basic-staking/packages/shade_protocol/src/contract_interfaces/admin/errors.rs#L51-L56) | Sender is not part of the admins list | Use an admin account to perform this action. | - -## Queries - -### Holdings - -Queries user's claimable total amount and unbonding total amount. - -**Request** - -```typescript -interface HoldingsQuery { - holdings: { - address: string; - viewing_key: string; - }; -} -``` - -```json -{ - "holdings": { - "address": "secret1b1b1b1b1b1b1b1b1", - "viewing_key": "password" - } -} -``` - -**Response** - -```typescript -interface HoldingsQueryResponse { - holdings: { - derivative_claimable: string; - derivative_unbonding: string; - }; -} -``` - -```json -{ - "holdings": { - "derivative_claimable": "100000000", - "derivative_unbonding": "200000000" - } -} -``` - -### StakingInfo - -Queries contract's balances and price information. - -**Request** - -```typescript -interface StakingInfoQuery { - staking_info: {}; -} -``` - -```json -{ - "staking_info": {} -} -``` - -**Response** - -```typescript -interface StakingInfoQueryResponse { - staking_info: { - unbonding_time: string; - bonded_shd: string; - available_shd: string; - rewards: string; - total_derivative_token_supply: string; - price: string; - }; -} -``` - -```json -{ - "staking_info": { - "unbonding_time": "36000", - "bonded_shd": "100000000", - "available_shd": "0", - "rewards": "320400", - "total_derivative_token_supply": "100000000", - "price": "102000000" - } -} -``` - -### Unbondings - -Queries user's unbondings in progress. - -**Request** - -```typescript -interface UnbondingsQuery { - unbondings: { - address: string; - viewing_key: string; - }; -} -``` - -```json -{ - "unbondings": { - "address": "secret1b1b1b1b1b1b1b1b1", - "viewing_key": "password" - } -} -``` - -**Response** - -```typescript -import Unbonding from "shade-protocol"; -interface UnbondingsQueryResponse { - unbondings: { - unbonds: Unbonding[]; - }; -} -``` - -```json -{ - "unbondings": { - "unbonds": [ - { - "id": "1", - "amount": "400000000", - "complete": "39478094578404" - } - ] - } -} -``` - -### FeeInfo - -Queries staking and unbonding fees configuration. - -**Request** - -```typescript -interface FeeInfoQuery { - fee_info: {}; -} -``` - -```json -{ - "fee_info": {} -} -``` - -**Response** - -```typescript -interface Fee { - rate: number; - decimal_places: number; -} - -interface FeeInfoQueryResponse { - fee_info: { - collector: string; - staking: Fee; - unbonding: Fee; - }; -} -``` - -```json -{ - "fee_info": { - "collector": "secret1b1b1b1b1b1b1b1b1", - "staking": { - "rate": 100, - "decimal_places": 3 - }, - "unbonding": { - "rate": 100, - "decimal_places": 3 - } - } -} -``` - -### ContractStatus - -Queries contracts status. - -**Request** - -```typescript -interface ContractStatusQuery { - contract_status: {}; -} -``` - -```json -{ - "contract_status": {} -} -``` - -**Response** - -```typescript -interface ContractStatusLevel { - NormalRun; - StopAll; -} - -interface ContractStatusQueryResponse { - contract_status: { - status: ContractStatusLevel; - }; -} -``` - -```json -{ - "contract_status": { - "status": "stop_all" - } -} -``` diff --git a/contracts/snip20_derivative/examples/schema.rs b/contracts/snip20_derivative/examples/schema.rs deleted file mode 100644 index cba5853..0000000 --- a/contracts/snip20_derivative/examples/schema.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::env::current_dir; -use std::fs::create_dir_all; - -use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; - -use derivative_snip_20_contract::msg::{ - ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, -}; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InstantiateMsg), &out_dir); - export_schema(&schema_for!(ExecuteMsg), &out_dir); - export_schema(&schema_for!(ExecuteAnswer), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(QueryAnswer), &out_dir); -} diff --git a/contracts/snip20_derivative/src/contract.rs b/contracts/snip20_derivative/src/contract.rs deleted file mode 100644 index 43b1cfb..0000000 --- a/contracts/snip20_derivative/src/contract.rs +++ /dev/null @@ -1,2660 +0,0 @@ -use std::ops::Add; - -use crate::{ - msg::{ - status_level_to_u8, Config, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, - InProcessUnbonding, InstantiateMsg, PanicUnbond, QueryAnswer, QueryMsg, QueryWithPermit, - ReceiverMsg, ResponseStatus::Success, - }, - staking_interface::{transfer_staked_msg, Reward, Rewards, Token}, - state::{ContractsVksStore, REWARDED_TOKENS_LIST}, -}; - -#[allow(unused_imports)] -use crate::staking_interface::{ - balance_query as staking_balance_query, claim_rewards_msg, compound_msg, config_query, - rewards_query, unbond_msg, withdraw_msg, Action, RawContract, StakingConfig, UnbondResponse, - Unbonding, WithdrawResponse, -}; - -use crate::state::{ - UnbondingIdsStore, UnbondingStore, CONFIG, CONTRACT_STATUS, PANIC_UNBONDS, - PANIC_UNBOND_REPLY_ID, PANIC_WITHDRAW_REPLY_ID, PENDING_UNBONDING, RESPONSE_BLOCK_SIZE, - UNBOND_REPLY_ID, -}; -/// This contract implements SNIP-20 standard: -/// https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md -use cosmwasm_std::{ - entry_point, from_binary, to_binary, Addr, Binary, CosmosMsg, CustomQuery, Deps, DepsMut, Env, - MessageInfo, QuerierWrapper, Reply, Response, StdError, StdResult, Storage, SubMsg, - SubMsgResult, Uint128, Uint256, -}; - -#[allow(unused_imports)] -use secret_toolkit::{ - snip20::{ - balance_query, burn_msg, mint_msg, register_receive_msg, send_msg, set_viewing_key_msg, - token_info_query, TokenInfo, - }, - utils::{pad_handle_result, pad_query_result}, -}; - -use secret_toolkit_crypto::{sha_256, ContractPrng}; -use serde::de::DeserializeOwned; -use shade_protocol::query_auth::QueryPermit; - -use crate::msg::{Fee, FeeInfo}; -#[allow(unused_imports)] -use shade_protocol::{ - admin::{ - helpers::{validate_admin, AdminPermissions}, - ConfigResponse, QueryMsg as AdminQueryMsg, - }, - query_auth::helpers::{authenticate_permit, authenticate_vk}, - utils::Query, - Contract, -}; - -#[entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - let token_info = get_token_info( - deps.querier, - RESPONSE_BLOCK_SIZE, - msg.token.code_hash.clone(), - msg.token.address.to_string(), - false, - )?; - let derivative_info = get_token_info( - deps.querier, - RESPONSE_BLOCK_SIZE, - msg.derivative.code_hash.clone(), - msg.derivative.address.to_string(), - true, - )?; - - if token_info.decimals != derivative_info.decimals { - return Err(StdError::generic_err( - "Derivative and token contracts should have the same amount of decimals", - )); - } - // Generate viewing key for staking contract - let entropy: String = msg - .staking - .entropy - .clone() - .unwrap_or_else(|| msg.prng_seed.to_string()); - let (staking_contract_vk, new_seed) = - new_viewing_key(&info.sender, &env, &msg.prng_seed.0, entropy.as_ref()); - - // Generate viewing key for SHD contract - let entropy: String = msg - .token - .entropy - .clone() - .unwrap_or_else(|| msg.prng_seed.to_string()); - let (token_contract_vk, _new_seed) = - new_viewing_key(&info.sender, &env, &new_seed, entropy.as_ref()); - - CONFIG.save( - deps.storage, - &Config { - prng_seed: msg.prng_seed, - staking_contract_vk: staking_contract_vk.clone(), - token_contract_vk: token_contract_vk.clone(), - query_auth: msg.query_auth.clone(), - token: msg.token.clone(), - derivative: msg.derivative.clone(), - staking: msg.staking, - fees: msg.fees, - contract_address: env.contract.address.clone(), - admin: msg.admin, - }, - )?; - CONTRACT_STATUS.save(deps.storage, &ContractStatusLevel::NormalRun)?; - - let msgs: Vec = vec![ - // Register receive Derivative contract needed for Unbond functionality - register_receive_msg( - env.contract.code_hash.clone(), - msg.derivative.entropy.clone(), - RESPONSE_BLOCK_SIZE, - msg.derivative.code_hash.clone(), - msg.derivative.address.to_string(), - )?, - // Register receive SHD contract - register_receive_msg( - env.contract.code_hash, - msg.token.entropy.clone(), - RESPONSE_BLOCK_SIZE, - msg.token.code_hash.clone(), - msg.token.address.to_string(), - )?, - // Set viewing key for SHD - set_viewing_key_msg( - token_contract_vk, - msg.token.entropy, - RESPONSE_BLOCK_SIZE, - msg.token.code_hash, - msg.token.address.to_string(), - )?, - // Set viewing key for staking contract - set_viewing_key_msg( - staking_contract_vk, - msg.query_auth.entropy, - RESPONSE_BLOCK_SIZE, - msg.query_auth.code_hash, - msg.query_auth.address.to_string(), - )?, - ]; - - Ok(Response::default().add_messages(msgs)) -} - -#[entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - let response = match msg { - // Messages always available - ExecuteMsg::SetContractStatus { level, .. } => { - set_contract_status(deps, info, level, ContractStatusLevel::StopAll) - } - // Messages available during panic mode - ExecuteMsg::Claim {} => try_claim(deps, env, info, ContractStatusLevel::Panicked), - ExecuteMsg::PanicUnbond { amount } => { - try_panic_unbond(env, deps, info, amount, ContractStatusLevel::Panicked) - } - ExecuteMsg::PanicWithdraw {} => { - try_panic_withdraw(deps, env, info, ContractStatusLevel::Panicked) - } - ExecuteMsg::UpdateFees { - staking, - unbonding, - collector, - } => update_fees( - deps, - info, - staking, - unbonding, - collector, - ContractStatusLevel::Panicked, - ), - - // Messages available when status is normal - ExecuteMsg::Receive { - sender: _, - from, - amount, - msg, - } => receive(deps, env, info, from, amount, msg), - ExecuteMsg::CompoundRewards {} => { - try_compound_rewards(deps, ContractStatusLevel::NormalRun) - } - }; - - pad_handle_result(response, RESPONSE_BLOCK_SIZE) -} - -#[entry_point] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - pad_query_result( - match msg { - QueryMsg::StakingInfo {} => query_staking_info(&deps, &env), - QueryMsg::FeeInfo {} => query_fee_info(&deps), - QueryMsg::ContractStatus {} => query_contract_status(deps.storage), - QueryMsg::WithPermit { permit } => permit_queries(deps, &env, permit), - _ => viewing_keys_queries(deps, &env, msg), - }, - RESPONSE_BLOCK_SIZE, - ) -} - -#[entry_point] -pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> StdResult { - match (msg.id, msg.result) { - (UNBOND_REPLY_ID, SubMsgResult::Ok(s)) => match s.data { - Some(x) => { - let result: UnbondResponse = from_binary(&x)?; - // Unbonding stored in try_unbond function - // Because of here you can't access the sender of the TX this was stored previously - let pending_unbonding = PENDING_UNBONDING.may_load(deps.storage)?; - - if let Some(unbonding_processing) = pending_unbonding { - // Set properly id for this unbonding - let unbond = Unbonding { - id: result.unbond.id, - amount: unbonding_processing.amount, - complete: unbonding_processing.complete, - }; - UnbondingStore::save(deps.storage, result.unbond.id.clone().u128(), &unbond)?; - - // Add unbonding id to user's unbondings IDs - let mut users_unbondings_ids = - UnbondingIdsStore::load(deps.storage, &unbonding_processing.owner); - users_unbondings_ids.push(result.unbond.id.u128()); - UnbondingIdsStore::save( - deps.storage, - &unbonding_processing.owner, - users_unbondings_ids, - )?; - - Ok(Response::default()) - } else { - Err(StdError::generic_err( - "Unexpected error: pending unbond storage is empty.", - )) - } - } - None => Err(StdError::generic_err("Unknown reply id")), - }, - - (PANIC_WITHDRAW_REPLY_ID, SubMsgResult::Ok(s)) => match s.data { - Some(x) => { - let config = CONFIG.load(deps.storage)?; - let result: WithdrawResponse = from_binary(&x)?; - let withdrawn = result.withdraw.withdrawn; - let addr = get_super_admin(&deps.querier, &config)?; - - Ok(Response::default().add_message(send_msg( - addr.to_string(), - withdrawn, - None, - Some("Panic withdraw {} tokens".to_string()), - config.token.entropy, - RESPONSE_BLOCK_SIZE, - config.token.code_hash, - config.token.address.to_string(), - )?)) - } - None => Err(StdError::generic_err("Unknown reply id")), - }, - - (PANIC_UNBOND_REPLY_ID, SubMsgResult::Ok(s)) => match s.data { - Some(x) => { - let result: UnbondResponse = from_binary(&x)?; - let mut panic_unbonds = PANIC_UNBONDS.may_load(deps.storage)?.unwrap_or_default(); - let last = panic_unbonds.pop(); - - // Validate there is at least 1 element is storage to update - // should never happen but you never know - if let Some(mut last_unbond) = last { - // Update latest panic unbond id - last_unbond.id = result.unbond.id; - panic_unbonds.push(last_unbond); - //Save list of panic unbonds in storage - PANIC_UNBONDS.save(deps.storage, &panic_unbonds)?; - } - Ok(Response::default()) - } - None => Err(StdError::generic_err("Unknown reply id")), - }, - _ => Err(StdError::generic_err("Unknown reply id")), - } -} -/************ HANDLES ************/ -/// It takes a list of unbonding ids, and if they are mature, it removes them from storage and sends the -/// tokens to the user -/// -/// Arguments: -/// -/// * `deps`: DepsMut - This is the dependency struct that contains all the dependencies that the -/// handler needs. -/// * `env`: Env - This is the environment that the transaction is being executed in. It contains -/// information about the block, the transaction, and the message. -/// * `info`: MessageInfo - contains the sender of the message, the sent amount, and the sent memo -/// -/// Returns: -/// -/// StdResult -fn try_claim( - deps: DepsMut, - env: Env, - info: MessageInfo, - priority: ContractStatusLevel, -) -> StdResult { - check_status(deps.storage, priority)?; - let sender = info.sender; - let time = Uint128::from(env.block.time.seconds()); - let user_unbondings_ids = UnbondingIdsStore::load(deps.storage, &sender); - let config = CONFIG.load(deps.storage)?; - let mut to_claim_ids: Vec = vec![]; - let mut amount_claimed = Uint128::zero(); - - for id in user_unbondings_ids.iter() { - let opt_unbonding = UnbondingStore::may_load(deps.storage, *id); - if let Some(unbonding) = opt_unbonding { - // Handle mature unbondings - if time >= unbonding.complete { - to_claim_ids.push(unbonding.id.u128()); - amount_claimed += unbonding.amount; - - // Remove unbonding from storage - UnbondingStore::remove(deps.storage, *id)?; - } - } - } - if to_claim_ids.is_empty() { - return Err(StdError::generic_err("No mature unbondings to claim")); - } - let (fee, deposit) = get_fee(amount_claimed, &config.fees.unbonding)?; - - let users_new_pending_unbondings: Vec = user_unbondings_ids - .into_iter() - .filter(|id| !to_claim_ids.contains(id)) - .collect(); - UnbondingIdsStore::save(deps.storage, &sender, users_new_pending_unbondings)?; - - let to_claim_ids_uint128: Vec = to_claim_ids.into_iter().map(Uint128::from).collect(); - - let config: Config = CONFIG.load(deps.storage)?; - let messages: Vec = vec![ - withdraw_msg( - config.staking.code_hash, - config.staking.address.to_string(), - Some(to_claim_ids_uint128), - )?, - send_msg( - config.fees.collector.to_string(), - fee, - None, - Some(base64::encode(&"Payment of fee for unbonding SHD")), - config.token.entropy.clone(), - RESPONSE_BLOCK_SIZE, - config.token.code_hash.clone(), - config.token.address.to_string(), - )?, - send_msg( - sender.to_string(), - deposit, - None, - Some(format!("Claiming {} SHD tokens", { deposit })), - config.token.entropy, - RESPONSE_BLOCK_SIZE, - config.token.code_hash, - config.token.address.to_string(), - )?, - ]; - - Ok(Response::default() - .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::Claim { - amount_claimed: deposit, - })?)) -} - -/// It creates a `compound_msg` and returns it as a `Response` -/// -/// Arguments: -/// -/// * `deps`: DepsMut - This is the dependencies object that contains the storage, querier, and other -/// useful things. -/// -/// Returns: -/// -/// StdResult -fn try_compound_rewards(deps: DepsMut, priority: ContractStatusLevel) -> StdResult { - check_status(deps.storage, priority)?; - let config = CONFIG.load(deps.storage)?; - let staked = get_staked_shd(deps.querier, &config.contract_address, &config)?; - let rewards = query_rewards(deps.querier, &config.contract_address, &config)?; - - let mut messages: Vec = vec![]; - if staked > 0 { - messages.push(compound_msg( - config.staking.code_hash, - config.staking.address.to_string(), - )?); - } - let response = Response::default(); - let rewarded_tokens_list = REWARDED_TOKENS_LIST - .may_load(deps.storage)? - .unwrap_or_default(); - for addr in rewarded_tokens_list.into_iter() { - let token = ContractsVksStore::may_load(deps.storage, &addr); - if let Some(t) = token { - let balance = balance_query( - deps.querier, - config.contract_address.to_string(), - t.viewing_key, - RESPONSE_BLOCK_SIZE, - t.code_hash.clone(), - t.address.to_string(), - )?; - - let item = rewards - .rewards - .iter() - .find(|r| r.token.address == t.address); - - let amount = if let Some(reward) = item { - balance.amount.add(reward.amount) - } else { - balance.amount - }; - - if amount > Uint128::zero() { - messages.push(send_msg( - config.fees.collector.to_string(), - amount, - None, - Some(format!( - "Sending {} rewards to ShadeDAO", - t.address.to_string() - )), - None, - RESPONSE_BLOCK_SIZE, - t.code_hash.clone(), - t.address.to_string(), - )?); - } - } - } - - Ok(response.add_messages(messages)) -} - -/// It checks if the sender is an admin, and if so, it sends a message to the staking contract to unbond -/// the given amount -/// -/// Arguments: -/// -/// * `deps`: DepsMut - This is the dependencies object that contains the storage, querier, and other -/// useful objects. -/// * `info`: MessageInfo - this is a struct that contains the sender, sent_funds, and sent_funds_count. -/// * `amount`: The amount of tokens to unbond. -/// -/// Returns: -/// -/// StdResult. -fn try_panic_unbond( - env: Env, - deps: DepsMut, - info: MessageInfo, - amount: Uint128, - priority: ContractStatusLevel, -) -> StdResult { - check_status(deps.storage, priority)?; - let config = CONFIG.load(deps.storage)?; - check_if_admin( - &deps.querier, - AdminPermissions::DerivativeAdmin, - info.sender.to_string(), - &config.admin, - )?; - // Store panic unbond - let staking_config = get_staking_contract_config(deps.querier, &config)?; - let complete: Uint128 = - Uint128::from(env.block.time.seconds()).checked_add(staking_config.unbond_period)?; - let mut panic_unbonds: Vec = - PANIC_UNBONDS.may_load(deps.storage)?.unwrap_or_default(); - panic_unbonds.push(PanicUnbond { - id: Uint128::zero(), - amount, - complete, - }); - PANIC_UNBONDS.save(deps.storage, &panic_unbonds)?; - - let msg = unbond_msg( - amount, - config.staking.code_hash, - config.staking.address.to_string(), - Some(false), - )?; - Ok(Response::default().add_submessage(SubMsg::reply_always(msg, PANIC_UNBOND_REPLY_ID))) -} - -/// It sends a message to the staking contract to claim rewards, then sends a message to the staking contract -/// to withdraw the rewards and then sends available SHD balance to the super admin address -/// -/// Arguments: -/// -/// * `deps`: DepsMut, -/// * `env`: The environment of the contract. -/// * `info`: MessageInfo - contains the sender, sent_funds, and sent_funds_attachment -/// * `ids`: Option> -/// -/// Returns: -/// -/// StdResult. -fn try_panic_withdraw( - deps: DepsMut, - env: Env, - info: MessageInfo, - priority: ContractStatusLevel, -) -> StdResult { - check_status(deps.storage, priority)?; - let config = CONFIG.load(deps.storage)?; - check_if_admin( - &deps.querier, - AdminPermissions::DerivativeAdmin, - info.sender.to_string(), - &config.admin, - )?; - let addr = get_super_admin(&deps.querier, &config)?; - let rewards = get_rewards(deps.querier, &env.contract.address, &config)?; - let balance = get_available_shd(deps.querier, &env.contract.address, &config)?; - let amount = Uint128::from(rewards + balance); - let mut response = Response::default().add_messages(vec![ - claim_rewards_msg( - config.staking.code_hash.clone(), - config.staking.address.to_string(), - )?, - send_msg( - addr.to_string(), - amount, - None, - Some("Panic withdraw {} tokens".to_string()), - config.token.entropy, - RESPONSE_BLOCK_SIZE, - config.token.code_hash, - config.token.address.to_string(), - )?, - ]); - let panic_unbonds = PANIC_UNBONDS.may_load(deps.storage)?; - if let Some(unbonds) = panic_unbonds { - let time = Uint128::from(env.block.time.seconds()); - let mut to_withdraw: Vec = vec![]; - let mut pending_unbonds: Vec = vec![]; - - for u in unbonds.into_iter() { - if time >= u.complete { - to_withdraw.push(u.id); - } else { - pending_unbonds.push(u); - } - } - - PANIC_UNBONDS.save(deps.storage, &pending_unbonds)?; - - response = response.add_submessage(SubMsg::reply_on_success( - withdraw_msg( - config.staking.code_hash, - config.staking.address.to_string(), - Some(to_withdraw.clone()), - )?, - PANIC_WITHDRAW_REPLY_ID, - )); - } - - Ok(response) -} - -/// `update_fees` updates the fees for staking and unbonding -/// -/// Arguments: -/// -/// * `deps`: DepsMut - This is the dependency object that contains the storage, querier, and logger. -/// * `info`: MessageInfo - this is the information about the message that was sent to the contract. -/// * `staking`: The fee for staking. -/// * `unbonding`: Option -/// -/// Returns: -/// -/// StdResult. -fn update_fees( - deps: DepsMut, - info: MessageInfo, - staking: Option, - unbonding: Option, - collector: Option, - priority: ContractStatusLevel, -) -> StdResult { - check_status(deps.storage, priority)?; - let mut config = CONFIG.load(deps.storage)?; - check_if_admin( - &deps.querier, - AdminPermissions::DerivativeAdmin, - info.sender.to_string(), - &config.admin, - )?; - let fees: FeeInfo = FeeInfo { - staking: staking.unwrap_or(config.fees.staking), - unbonding: unbonding.unwrap_or(config.fees.unbonding), - collector: collector.unwrap_or(config.fees.collector), - }; - config.fees = fees.clone(); - CONFIG.save(deps.storage, &config)?; - - Ok( - Response::default().set_data(to_binary(&ExecuteAnswer::UpdateFees { - status: Success, - fee: fees, - })?), - ) -} - -/// If the message is a `Stake` message, call `try_stake`, if it's an `Unbond` message, call -/// `try_unbond`, otherwise return an error -/// -/// Arguments: -/// -/// * `deps`: This is a struct that contains all the dependencies that the contract needs to run. -/// * `env`: The environment of the transaction. -/// * `info`: MessageInfo - contains information about the message that was sent to the contract -/// * `from`: The address of the sender -/// * `amount`: The amount of tokens sent to the contract. -/// -/// Returns: -/// -/// Response::default() -fn receive( - deps: DepsMut, - env: Env, - info: MessageInfo, - from: Addr, - amount: Uint256, - msg: Option, -) -> StdResult { - if let Some(x) = msg { - match from_binary(&x)? { - ReceiverMsg::Stake {} => try_stake( - deps, - env, - info, - from, - amount, - ContractStatusLevel::NormalRun, - ), - ReceiverMsg::Unbond {} => try_unbond( - deps, - env, - info, - from, - amount, - ContractStatusLevel::NormalRun, - ), - ReceiverMsg::TransferStaked { receiver } => try_transfer_staked( - deps, - env, - info, - from, - amount, - receiver, - ContractStatusLevel::NormalRun, - ), - #[allow(unreachable_patterns)] - _ => Err(StdError::generic_err(format!( - "Invalid msg provided, expected {} , {} or {}", - to_binary(&ReceiverMsg::Stake {})?, - to_binary(&ReceiverMsg::Unbond {})?, - to_binary(&ReceiverMsg::TransferStaked { receiver: None })? - ))), - } - } else { - Ok(Response::default()) - } -} - -/// `try_stake` takes a deposit of SHD and returns the equivalent mint of the derivative token -/// -/// Arguments: -/// -/// * `deps`: DepsMut, -/// * `env`: The environment of the contract. -/// * `info`: MessageInfo - contains information about the message that was sent to the contract -/// * `from`: The address of the staker -/// * `amt`: The amount of SHD to stake. -/// -/// Returns: -/// -/// The amount of tokens that were minted for the staking transaction. -fn try_stake( - deps: DepsMut, - env: Env, - info: MessageInfo, - from: Addr, - amt: Uint256, - priority: ContractStatusLevel, -) -> StdResult { - check_status(deps.storage, priority)?; - let config = CONFIG.load(deps.storage)?; - let amount = Uint128::try_from(amt)?; - if info.sender != config.token.address { - return Err(StdError::generic_err("Sender is not SHD contract")); - } - - if amount == Uint128::zero() { - return Err(StdError::generic_err("No SHD was sent for staking")); - } - - let rewards = query_rewards(deps.querier, &config.contract_address, &config)?; - - let mut non_shd_rewards: Vec = vec![]; - let mut shd_rewards: Option = None; - - for r in rewards.rewards.into_iter() { - if r.token.address == config.token.address { - shd_rewards = Some(r); - } else { - non_shd_rewards.push(r) - } - } - let rewards_amount = if let Some(r) = shd_rewards { - r.amount.u128() - } else { - 0_128 - }; - - let available = get_available_shd(deps.querier, &config.contract_address, &config)?; - let (fee, deposit) = get_fee(amount, &config.fees.staking)?; - - // get available SHD + available rewards - let claiming = available + rewards_amount; - - // get staked SHD - let bonded = get_staked_shd(deps.querier, &env.contract.address, &config)?; - let starting_pool = (claiming + bonded).saturating_sub(deposit.u128() + fee.u128()); - - let token_info = get_token_info( - deps.querier, - RESPONSE_BLOCK_SIZE, - config.derivative.code_hash.clone(), - config.derivative.address.to_string(), - true, - )?; - let total_supply = token_info.total_supply.unwrap_or(Uint128::zero()); - // mint appropriate amount - let mint = if starting_pool == 0 || total_supply.is_zero() { - deposit - } else { - // unwrap is ok because multiplying 2 u128 ints can not overflow a u256 - let numer = Uint256::from(deposit) - .checked_mul(Uint256::from(total_supply)) - .unwrap(); - // unwrap is ok because starting pool can not be zero - Uint128::try_from(numer.checked_div(Uint256::from(starting_pool)).unwrap())? - }; - if mint == Uint128::zero() { - return Err(StdError::generic_err("The amount of SHD deposited is not enough to receive any of the derivative token at the current price")); - } - // Sync rewarded tokens - let mut messages = sync_rewarded_tokens(&env, deps, info, &non_shd_rewards, &config)?; - - // Mint derivatives in exchange - messages.push(mint_msg( - from.to_string(), - mint, - Some(format!( - "Minted {} u_{} to stake {} SHD", - mint, token_info.symbol, deposit - )), - config.derivative.entropy.clone(), - RESPONSE_BLOCK_SIZE, - config.derivative.code_hash.clone(), - config.derivative.address.to_string(), - )?); - - // send fee to collector - messages.push(send_msg( - config.fees.collector.to_string(), - fee, - None, - Some(base64::encode(format!( - "Payment of fee for staking SHD using contract {}", - env.contract.address.clone() - ))), - config.token.entropy.clone(), - RESPONSE_BLOCK_SIZE, - config.token.code_hash.clone(), - config.token.address.to_string(), - )?); - - // Stake available SHD - if deposit > Uint128::zero() { - messages.push(generate_stake_msg(deposit, Some(true), &config)?); - } - - Ok(Response::new() - .add_attribute("derivative_returned", mint) - .set_data(to_binary(&ExecuteAnswer::Stake { - shd_staked: deposit, - tokens_returned: mint, - })?) - .add_messages(messages)) -} - -fn try_transfer_staked( - deps: DepsMut, - env: Env, - info: MessageInfo, - from: Addr, - amt: Uint256, - receiver: Option, - priority: ContractStatusLevel, -) -> StdResult { - check_status(deps.storage, priority)?; - let config = CONFIG.load(deps.storage)?; - let amount = Uint128::try_from(amt)?; - - let derivative_token_info = get_token_info( - deps.querier, - RESPONSE_BLOCK_SIZE, - config.derivative.code_hash.clone(), - config.derivative.address.to_string(), - true, - )?; - - if info.sender != config.derivative.address { - return Err(StdError::generic_err( - "Sender is not derivative (SNIP20) contract", - )); - } - - if amount == Uint128::zero() { - return Err(StdError::generic_err("0 amount sent to unbond")); - } - - let (_, rewards, delegatable) = get_delegatable(deps.querier, &env.contract.address, &config)?; - let staked = get_staked_shd(deps.querier, &env.contract.address, &config)?; - let pool = delegatable + staked; - // unwrap is ok because multiplying 2 u128 ints can not overflow a u256 - let number = Uint256::from(amount) - .checked_mul(Uint256::from(pool)) - .unwrap(); - // unwrap is ok because derivative token supply could not have been 0 if we were able - // to burn - let unbond_amount = Uint128::try_from( - number - .checked_div(Uint256::from(derivative_token_info.total_supply.unwrap())) - .unwrap(), - )?; - // calculate the amount going to the user and fee's collector - let (fee, deposit) = get_fee(unbond_amount, &config.fees.unbonding)?; - - if deposit.is_zero() { - return Err(StdError::generic_err(format!( - "Redeeming {} derivative tokens would be worth less than 1 SHD", - amount - ))); - } - let recipient: String = receiver.unwrap_or(from).to_string(); - Ok(Response::default() - .add_messages([ - // Claim rewards - claim_rewards_msg( - config.staking.code_hash.clone(), - config.staking.address.to_string(), - )?, - // Re-stake rewards - generate_stake_msg( - Uint128::from(rewards).saturating_sub(fee), - Some(true), - &config, - )?, - // Burn derivatives sent - burn_msg( - amount, - Some(format!( - "Burn {} derivatives to receive {} SHD", - amount, deposit - )), - config.derivative.entropy, - RESPONSE_BLOCK_SIZE, - config.derivative.code_hash.clone(), - config.derivative.address.to_string(), - )?, - //Sends fee collector - send_msg( - config.fees.collector.to_string(), - fee, - None, - Some(base64::encode(format!( - "Payment of fee for transfer staked SHD using contract {}", - env.contract.address - ))), - config.token.entropy.clone(), - RESPONSE_BLOCK_SIZE, - config.token.code_hash.clone(), - config.token.address.to_string(), - )?, - // Transfer staked - transfer_staked_msg( - config.staking.code_hash, - config.staking.address.to_string(), - deposit, - recipient, - Some(true), - )?, - ]) - .set_data(to_binary(&ExecuteAnswer::TransferStaked { - tokens_returned: deposit, - amount_sent: amount, - })?)) -} - -/// `try_unbond` is called when a user sends a derivative token to the contract. The contract then -/// calculates the amount of SHD that the user will receive and unbonds it from the staking contract -/// this amount will be maturing in the unbondings for X amount of time -/// -/// Arguments: -/// -/// * `deps`: DepsMut, -/// * `env`: The environment in which the contract is running. -/// * `info`: MessageInfo - this is the information about the message that was sent to the contract. -/// * `from`: The address of the user who is unbonding -/// * `amt`: The amount of derivative tokens to be redeemed. -/// -/// Returns: -/// -/// The amount of SHD that will be received when the unbonding period is over. -fn try_unbond( - deps: DepsMut, - env: Env, - info: MessageInfo, - from: Addr, - amt: Uint256, - priority: ContractStatusLevel, -) -> StdResult { - check_status(deps.storage, priority)?; - let mut response = Response::new(); - let config = CONFIG.load(deps.storage)?; - let derivative_token_info = get_token_info( - deps.querier, - RESPONSE_BLOCK_SIZE, - config.derivative.code_hash.clone(), - config.derivative.address.to_string(), - true, - )?; - let staking_info = get_staking_contract_config(deps.querier, &config)?; - let amount = Uint128::try_from(amt)?; - if info.sender != config.derivative.address { - return Err(StdError::generic_err( - "Sender is not derivative (SNIP20) contract", - )); - } - - if amount == Uint128::zero() { - return Err(StdError::generic_err("0 amount sent to unbond")); - } - - let (_, _, delegatable) = get_delegatable(deps.querier, &env.contract.address, &config)?; - - let staked = get_staked_shd(deps.querier, &env.contract.address, &config)?; - let pool = delegatable + staked; - // unwrap is ok because multiplying 2 u128 ints can not overflow a u256 - let number = Uint256::from(amount) - .checked_mul(Uint256::from(pool)) - .unwrap(); - // unwrap is ok because derivative token supply could not have been 0 if we were able - // to burn - let unbond_amount = Uint128::try_from( - number - .checked_div(Uint256::from(derivative_token_info.total_supply.unwrap())) - .unwrap(), - )?; - // calculate the amount going to the user - let (_, shd_to_be_received) = get_fee(unbond_amount, &config.fees.unbonding)?; - - if shd_to_be_received.is_zero() { - return Err(StdError::generic_err(format!( - "Redeeming {} derivative tokens would be worth less than 1 SHD", - amount - ))); - } - - // Store unbonding temporarily - // This unbonding is used in unbond sub-message reply handler - // Due to that in reply handler you can't access sender information - // and this is required to store user's unbondings - let unbonding = InProcessUnbonding { - id: Uint128::zero(), - amount: shd_to_be_received, - owner: from, - complete: Uint128::from(env.block.time.seconds()) - .checked_add(staking_info.unbond_period)?, - }; - PENDING_UNBONDING.save(deps.storage, &unbonding)?; - CONFIG.save(deps.storage, &config)?; - - response = response.add_submessage(SubMsg::reply_always( - unbond_msg( - shd_to_be_received, - config.staking.code_hash.clone(), - config.staking.address.to_string(), - Some(true), - )?, - UNBOND_REPLY_ID, - )); - - Ok(response - .add_attribute("unbonded_amount", shd_to_be_received) - .add_message(burn_msg( - amount, - Some(format!( - "Burn {} derivatives to receive {} SHD", - amount, shd_to_be_received - )), - config.derivative.entropy, - RESPONSE_BLOCK_SIZE, - config.derivative.code_hash.clone(), - config.derivative.address.to_string(), - )?) - .set_data(to_binary(&ExecuteAnswer::Unbond { - shd_to_be_received, - tokens_redeemed: amount, - estimated_time_of_maturity: Uint128::from(env.block.time.seconds()) - .checked_add(staking_info.unbond_period)?, - })?)) -} - -/// It queries the token's balance of the contract address -/// -/// Arguments: -/// -/// * `querier`: The querier object that will be used to query the blockchain. -/// * `contract_addr`: The address of the contract that we want to query. -/// * `config`: The configuration object that contains the token contract address, token contract -/// verifier key, and token code hash. -/// -/// Returns: -/// -/// The available SHD balance of the contract. -#[cfg(not(test))] -#[allow(dead_code)] -fn get_available_shd( - querier: QuerierWrapper, - contract_addr: &Addr, - config: &Config, -) -> StdResult { - let balance = balance_query( - querier, - contract_addr.to_string(), - config.token_contract_vk.clone(), - RESPONSE_BLOCK_SIZE, - config.token.code_hash.to_string(), - config.token.address.to_string(), - )?; - - let available = balance.amount; - Ok(available.u128()) -} -#[cfg(test)] -fn get_available_shd( - _: QuerierWrapper, - _: &Addr, - _: &Config, -) -> StdResult { - Ok(100000000_u128) -} - -/// It queries the staking contract to get the amount of staked SHD for the given contract address -/// -/// Arguments: -/// -/// * `querier`: The querier object that will be used to query the blockchain. -/// * `contract_addr`: The address of the contract that is being queried. -/// * `config`: The configuration of the contract. -/// -/// Returns: -/// -/// The balance of the staking contract. -#[cfg(not(test))] -fn get_staked_shd( - querier: QuerierWrapper, - contract_addr: &Addr, - config: &Config, -) -> StdResult { - let balance = staking_balance_query( - contract_addr.to_string(), - config.staking_contract_vk.clone(), - querier, - config.staking.code_hash.to_string(), - config.staking.address.to_string(), - )?; - - Ok(balance.amount.u128()) -} -#[cfg(test)] -fn get_staked_shd(_: QuerierWrapper, _: &Addr, _: &Config) -> StdResult { - Ok(300000000) -} - -/// It queries the rewards generated for this contract address. -/// Filters out to the token contract rewards and returns them as u128 -/// -/// Arguments: -/// -/// * `querier`: The querier object that will be used to query the blockchain. -/// * `contract_addr`: The address of the contract that we want to query. -/// * `config`: The configuration file that contains the contract addresses and other parameters. -/// -/// Returns: -/// -/// The rewards for the staking contract. -#[cfg(not(test))] -fn get_rewards( - querier: QuerierWrapper, - contract_addr: &Addr, - config: &Config, -) -> StdResult { - let rewards = query_rewards(querier, contract_addr, config)?; - let item = rewards - .rewards - .iter() - .find(|r| r.token.address == config.token.address); - - if let Some(reward) = item { - Ok(reward.amount.u128()) - } else { - Ok(0) - } -} -#[cfg(test)] -#[allow(dead_code)] -// Allow warn code because mock queries make warnings to show up -fn get_rewards(_: QuerierWrapper, _: &Addr, _: &Config) -> StdResult { - Ok(100000000) -} - -#[cfg(not(test))] -#[allow(dead_code)] -// Allow warn code because mock queries make warnings to show up -fn query_rewards( - querier: QuerierWrapper, - contract_addr: &Addr, - config: &Config, -) -> StdResult { - rewards_query( - contract_addr.to_string(), - config.staking_contract_vk.clone(), - querier, - config.staking.code_hash.to_string(), - config.staking.address.to_string(), - ) -} - -#[cfg(test)] -#[allow(dead_code)] -// Allow warn code because mock queries make warnings to show up -fn query_rewards(_: QuerierWrapper, _: &Addr, _: &Config) -> StdResult { - use crate::staking_interface::RewardToken; - - Ok(Rewards { - rewards: vec![Reward { - token: RewardToken { - address: Addr::unchecked("shade_contract_info_address"), - code_hash: String::from("shade_contract_info_code_hash"), - }, - amount: Uint128::from(100000000_u128), - }], - }) -} - -#[cfg(test)] -#[allow(dead_code)] -// Allow warn code because mock queries make warnings to show up -fn get_staking_contract_config( - _: QuerierWrapper, - _: &Config, -) -> StdResult { - Ok(StakingConfig { - admin_auth: RawContract { - address: String::from("mock_address"), - code_hash: String::from("mock_code_hash"), - }, - query_auth: RawContract { - address: String::from("mock_address"), - code_hash: String::from("mock_code_hash"), - }, - unbond_period: Uint128::from(300_u32), - max_user_pools: Uint128::from(5_u32), - reward_cancel_threshold: Uint128::from(0_u32), - }) -} - -/// It queries the staking contract for its configuration -/// -/// Arguments: -/// -/// * `querier`: The querier object that will be used to query the contract. -/// * `staking_info`: The staking contract information. -/// -/// Returns: -/// -/// StakingConfig -#[cfg(not(test))] -fn get_staking_contract_config( - querier: QuerierWrapper, - staking_info: &Config, -) -> StdResult { - config_query( - querier, - staking_info.staking.code_hash.clone(), - staking_info.staking.address.to_string(), - ) -} - -/// It gets the available and rewards balances, and returns the sum of the two -/// -/// Arguments: -/// -/// * `querier`: The querier object that will be used to query the contract. -/// * `contract_addr`: The address of the contract that you want to query. -/// * `config`: The configuration of the contract. -/// -/// Returns: -/// -/// a tuple of three values: -/// - The first value is the amount of available SHD -/// - The second value is the amount of rewards -/// - The third value is the sum of the first two values -#[cfg(not(test))] -fn get_delegatable( - querier: QuerierWrapper, - contract_addr: &Addr, - config: &Config, -) -> StdResult<(u128, u128, u128)> { - let rewards = get_rewards(querier, contract_addr, config)?; - - let available = get_available_shd(querier, contract_addr, config)?; - Ok((available, rewards, rewards + available)) -} - -#[cfg(test)] -fn get_delegatable( - _: QuerierWrapper, - _: &Addr, - _: &Config, -) -> StdResult<(u128, u128, u128)> { - Ok((100000000, 50000000, 100000000 + 50000000)) -} - -#[cfg(test)] -fn get_super_admin(_: &QuerierWrapper, _: &Config) -> StdResult { - Ok(Addr::unchecked("super_admin")) -} -/// It queries the `admin` contract for the `super_admin` address -/// -/// Arguments: -/// -/// * `querier`: The querier object that will be used to query the blockchain. -/// * `config`: The configuration of the current contract. -/// -/// Returns: -/// -/// The address of the super admin. -#[cfg(not(test))] -fn get_super_admin(querier: &QuerierWrapper, config: &Config) -> StdResult { - let response: StdResult = - AdminQueryMsg::GetConfig {}.query(querier, &config.admin); - - match response { - Ok(resp) => Ok(resp.super_admin), - Err(err) => Err(err), - } -} - -/// It takes an amount and a fee config, and returns the fee and the remainder -/// -/// Arguments: -/// -/// * `amount`: The amount of tokens to be transferred -/// * `fee_config`: The fee configuration for the transaction. -/// -/// Returns: -/// -/// A tuple of two Uint128 values. -pub fn get_fee(amount: Uint128, fee_config: &Fee) -> StdResult<(Uint128, Uint128)> { - // first unwrap is ok because multiplying a u128 by a u32 can not overflow a u256 - // second unwrap is ok because we know we aren't dividing by zero - let _fee = Uint256::from(amount) - .checked_mul(Uint256::from(fee_config.rate)) - .unwrap() - .checked_div(Uint256::from(10_u32.pow(fee_config.decimal_places as u32))) - .unwrap(); - let fee = Uint128::try_from(_fee)?; - let remainder = amount.saturating_sub(fee); - Ok((fee, remainder)) -} -/// It queries the token contract for the token info, and -/// if the total supply is not public, it returns an error -/// -/// Arguments: -/// -/// * `querier`: The querier object that will be used to query the blockchain. -/// * `block_size`: The number of blocks to look back for the token's price. -/// * `callback_code_hash`: The code hash of the contract that will be called when the derivative token -/// is redeemed. -/// * `contract_addr`: The address of the contract that holds the token. -/// -/// Returns: -/// -/// A TokenInfo struct -#[cfg(not(test))] -fn get_token_info( - querier: QuerierWrapper, - block_size: usize, - callback_code_hash: String, - contract_addr: String, - check_public_supply: bool, -) -> StdResult { - let token_info = token_info_query(querier, block_size, callback_code_hash, contract_addr)?; - if check_public_supply && token_info.total_supply.is_none() { - return Err(StdError::generic_err( - "Token supply must be public on derivative token", - )); - } - - Ok(token_info) -} - -#[cfg(test)] -fn get_token_info( - _querier: QuerierWrapper, - _block_size: usize, - _callback_code_hash: String, - _contract_addr: String, - _check_public_supply: bool, -) -> StdResult { - Ok(TokenInfo { - name: String::from("STKD-SHD"), - symbol: String::from("STKDSHD"), - decimals: 6, - total_supply: Some(Uint128::from(2000_u128)), - }) -} -/// It checks if the user is an admin, and if so, it returns `Ok(())` -/// -/// Arguments: -/// -/// * `querier`: The querier object that can be used to query the state of the blockchain. -/// * `permission`: The permission you want to check for. -/// * `user`: The user to check if they are an admin. -/// * `admin_auth`: The contract that holds the admin permissions. -/// -/// Returns: -/// -/// A StdResult<()> -#[cfg(not(test))] -fn check_if_admin( - querier: &QuerierWrapper, - permission: AdminPermissions, - user: String, - admin_auth: &Contract, -) -> StdResult<()> { - validate_admin(querier, permission, user, admin_auth) -} - -#[cfg(test)] -fn check_if_admin( - _: &QuerierWrapper, - _: AdminPermissions, - user: String, - _: &Contract, -) -> StdResult<()> { - if user != String::from("admin") { - return Err(StdError::generic_err( - "This is an admin command. Admin commands can only be run from admin address", - )); - } - - Ok(()) -} - -/// It takes an amount of SHD to stake, a boolean indicating whether or not to compound the stake, and a -/// configuration object, and returns a CosmosMsg object that can be used to send a transaction to the -/// staking contract -/// -/// Arguments: -/// -/// * `amount`: The amount of SHD to stake -/// * `compound`: Whether to compound the interest or not. -/// * `config`: The configuration file that contains the staking contract address, token address, token -/// code hash, and entropy. -/// -/// Returns: -/// -/// A CosmosMsg -fn generate_stake_msg( - amount: Uint128, - compound: Option, - config: &Config, -) -> StdResult { - let memo = - Some(to_binary(&format!("Staking {} SHD into staking contract", amount))?.to_base64()); - let msg = Some(to_binary(&Action::Stake { compound })?); - send_msg( - config.staking.address.to_string(), - amount, - msg, - memo, - config.token.entropy.clone(), - RESPONSE_BLOCK_SIZE, - config.token.code_hash.clone(), - config.token.address.to_string(), - ) -} - -/// It checks if the sender is an admin, and if so, it sets the contract status to the value passed in -/// -/// Arguments: -/// -/// * `deps`: DepsMut - This is the set of dependencies that the contract needs to run. -/// * `info`: MessageInfo - contains the sender, sent_funds, and sent_funds_count -/// * `status_level`: ContractStatusLevel -/// -/// Returns: -/// -/// The response is being returned. -fn set_contract_status( - deps: DepsMut, - info: MessageInfo, - status_level: ContractStatusLevel, - priority: ContractStatusLevel, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - check_status(deps.storage, priority)?; - check_if_admin( - &deps.querier, - AdminPermissions::DerivativeAdmin, - info.sender.to_string(), - &config.admin, - )?; - - CONTRACT_STATUS.save(deps.storage, &status_level)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::SetContractStatus { - status: Success, - })?), - ) -} - -// Copied from secret-toolkit-viewing-key-0.7.0 -pub fn new_viewing_key( - sender: &Addr, - env: &Env, - seed: &[u8], - entropy: &[u8], -) -> (String, [u8; 32]) { - pub const VIEWING_KEY_PREFIX: &str = "api_key_"; - // 16 here represents the lengths in bytes of the block height and time. - let entropy_len = 16 + sender.to_string().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(sender.as_bytes()); - rng_entropy.extend_from_slice(entropy); - - let mut rng = ContractPrng::new(seed, &rng_entropy); - - let rand_slice = rng.rand_bytes(); - - let key = sha_256(&rand_slice); - - let viewing_key = VIEWING_KEY_PREFIX.to_string() + &base64::encode(key); - (viewing_key, rand_slice) -} - -pub fn sync_rewarded_tokens( - env: &Env, - deps: DepsMut, - info: MessageInfo, - rewarded_tokens: &Vec, - config: &Config, -) -> StdResult> { - let mut messages = vec![]; - let mut no_registered_tokens: Vec = vec![]; - - for r in rewarded_tokens.into_iter() { - let is_registered = ContractsVksStore::may_load(deps.storage, &r.token.address); - - if is_registered.is_none() { - // This contract isn't registered. - no_registered_tokens.push(r.token.address.clone()); - // Generated a vk for this token and store it - let (new_vk, _) = new_viewing_key( - &info.sender, - &env, - &config.prng_seed, - config.staking.entropy.clone().unwrap_or_default().as_ref(), - ); - - let token: Token = Token { - address: r.token.address.clone(), - code_hash: r.token.code_hash.clone(), - viewing_key: new_vk.clone(), - }; - ContractsVksStore::save(deps.storage, &r.token.address, &token)?; - - messages.push(set_viewing_key_msg( - new_vk, - None, - RESPONSE_BLOCK_SIZE, - r.token.code_hash.clone(), - r.token.address.to_string(), - )?) - } - } - - if no_registered_tokens.len() > 0 { - let registered_tokens = REWARDED_TOKENS_LIST - .may_load(deps.storage)? - .unwrap_or_default(); - let new_rewarded_tokens = [registered_tokens, no_registered_tokens].concat(); - - REWARDED_TOKENS_LIST.save(deps.storage, &new_rewarded_tokens)?; - } - - Ok(messages) -} - -/// If the contract admin has disabled the contract, then this function will return an error -/// -/// Arguments: -/// -/// * `storage`: The storage object that is passed to the contract. -/// * `priority`: The priority of the action being performed. -/// -/// Returns: -/// -/// Ok is messages is allowed. -fn check_status(storage: &dyn Storage, priority: ContractStatusLevel) -> StdResult<()> { - let contract_status = CONTRACT_STATUS.load(storage)?; - - if status_level_to_u8(priority) < status_level_to_u8(contract_status) { - return Err(StdError::generic_err( - "The contract admin has temporarily disabled this action", - )); - } - Ok(()) -} -/************ QUERIES ************/ - -fn permit_queries(deps: Deps, env: &Env, permit: QueryPermit) -> Result { - // Validate permit content - let config = CONFIG.load(deps.storage)?; - let (addr, query) = validate_permit::(&deps.querier, &config, permit)?; - - // Permit validated! We can now execute the query. - match query { - QueryWithPermit::Unbondings {} => query_unbondings(&deps, addr), - QueryWithPermit::Holdings {} => query_holdings(&deps, env, addr), - #[allow(unreachable_patterns)] - _ => Err(StdError::generic_err("Invalid query message")), - } -} - -pub fn validate_permit( - querier: &QuerierWrapper, - config: &Config, - permit: QueryPermit, -) -> StdResult<(Addr, T)> { - let authenticator = Contract { - address: config.query_auth.address.clone(), - code_hash: config.query_auth.code_hash.clone(), - }; - let response = authenticate_permit::(permit, querier, authenticator)?; - - if response.revoked { - return Err(StdError::generic_err("Permit was revoked")); - } - - Ok((response.sender, response.data)) -} -#[cfg(not(test))] -pub fn validate_viewing_key( - querier: &QuerierWrapper, - config: &Config, - address: Addr, - key: String, -) -> StdResult<()> { - let authenticator = Contract { - address: config.query_auth.address.clone(), - code_hash: config.query_auth.code_hash.clone(), - }; - let is_valid = authenticate_vk(address, key, querier, &authenticator)?; - - if !is_valid { - return Err(StdError::generic_err("Invalid viewing key")); - } - - Ok(()) -} -#[cfg(test)] -pub fn validate_viewing_key( - _querier: &QuerierWrapper, - _config: &Config, - _address: Addr, - key: String, -) -> StdResult<()> { - if key != "password".to_string() { - return Err(StdError::generic_err("Invalid viewing key")); - } - - Ok(()) -} - -pub fn viewing_keys_queries(deps: Deps, env: &Env, msg: QueryMsg) -> StdResult { - let (addresses, key) = msg.get_validation_params(deps.api)?; - let config = CONFIG.load(deps.storage)?; - for address in addresses { - validate_viewing_key(&deps.querier, &config, address, key)?; - - return match msg { - QueryMsg::Unbondings { address, .. } => query_unbondings(&deps, address), - QueryMsg::Holdings { address, .. } => query_holdings(&deps, env, address), - _ => Err(StdError::generic_err( - "This query type does not require authentication", - )), - }; - } - - to_binary(&QueryAnswer::ViewingKeyError { - msg: "Wrong viewing key for this address or viewing key not set".to_string(), - }) -} - -/// It loads all the unbonding ids for the given address, then for each unbonding id, it loads the -/// unbonding, and if the unbonding is complete, it adds the amount to the claimable amount, otherwise -/// it adds the amount to the unbonding amount -/// -/// Arguments: -/// -/// * `deps`: &Deps - this is the dependencies object that contains the storage, querier, and logger. -/// * `env`: The environment of the current transaction. -/// * `addr`: The address of the account to query -/// -/// Returns: -/// -/// The holdings of the address. -fn query_holdings(deps: &Deps, env: &Env, addr: Addr) -> StdResult { - let mut derivative_claimable = Uint128::zero(); - let mut derivative_unbonding = Uint128::zero(); - - let time = Uint128::from(env.block.time.seconds()); - - let unbondings_ids = UnbondingIdsStore::load(deps.storage, &addr); - - for id in unbondings_ids.into_iter() { - let opt_unbonding = UnbondingStore::may_load(deps.storage, id); - if let Some(unbonding) = opt_unbonding { - if time >= unbonding.complete { - derivative_claimable += unbonding.amount; - } else { - derivative_unbonding += unbonding.amount; - } - } - } - to_binary(&QueryAnswer::Holdings { - derivative_claimable, - derivative_unbonding, - }) -} - -/// It loads all unbonding ids for a given address, then loads all unbonding structs for those ids, and -/// returns the result -/// -/// Arguments: -/// -/// * `deps`: &Deps - this is the dependencies object that contains the storage, querier, and logger. -/// * `addr`: The address of the user whose unbondings we want to query. -/// -/// Returns: -/// -/// A vector of unbonding structs. -fn query_unbondings(deps: &Deps, addr: Addr) -> StdResult { - let user_unbonds_ids = UnbondingIdsStore::load(deps.storage, &addr); - let mut unbonds: Vec = vec![]; - - for id in user_unbonds_ids.into_iter() { - let opt_unbonding = UnbondingStore::may_load(deps.storage, id); - - if let Some(unbond) = opt_unbonding { - unbonds.push(unbond) - } - } - - to_binary(&QueryAnswer::Unbondings { unbonds }) -} - -/// It loads the contract status from the storage and returns it as a query answer -/// -/// Arguments: -/// -/// * `storage`: &dyn Storage - this is the storage that the contract will use to store and retrieve -/// data. -/// -/// Returns: -/// -/// A QueryAnswer::ContractStatus -fn query_contract_status(storage: &dyn Storage) -> StdResult { - let contract_status = CONTRACT_STATUS.load(storage)?; - - to_binary(&QueryAnswer::ContractStatus { - status: contract_status, - }) -} - -/// It queries the staking contract for the amount of SHD bonded, the amount of SHD available, the -/// amount of SHD in rewards, the total supply of the derivative token, and the price of the derivative -/// token -/// -/// Arguments: -/// -/// * `deps`: &Deps - this is a struct that contains all the dependencies that the contract needs to -/// run. -/// * `env`: The environment of the contract. -/// -/// Returns: -/// -/// The query_staking_info function returns the staking information of the contract. -fn query_staking_info(deps: &Deps, env: &Env) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - let derivative_info = get_token_info( - deps.querier, - RESPONSE_BLOCK_SIZE, - config.derivative.code_hash.clone(), - config.derivative.address.to_string(), - true, - )?; - let bonded = get_staked_shd(deps.querier, &env.contract.address, &config)?; - let rewards = get_rewards(deps.querier, &env.contract.address, &config)?; - let available = get_available_shd(deps.querier, &env.contract.address, &config)?; - - let total_supply = derivative_info.total_supply.unwrap_or(Uint128::zero()); - - let pool = bonded + rewards + available; - let price = if total_supply == Uint128::zero() || pool == 0 { - Uint128::from(10_u128.pow(derivative_info.decimals as u32)) - } else { - // unwrap is ok because multiplying a u128 by 1 mill can not overflow u256 - let number = Uint256::from(pool) - .checked_mul(Uint256::from(10_u128.pow(derivative_info.decimals as u32))) - .unwrap(); - // unwrap is ok because we already checked if the total supply is 0 - Uint128::try_from(number.checked_div(Uint256::from(total_supply)).unwrap())? - }; - - let staking_contract_config = get_staking_contract_config(deps.querier, &config)?; - - to_binary(&QueryAnswer::StakingInfo { - unbonding_time: staking_contract_config.unbond_period, - bonded_shd: Uint128::from(bonded), - available_shd: Uint128::from(available), - rewards: Uint128::from(rewards), - total_derivative_token_supply: total_supply, - price, - }) -} - -/// It loads the fee configuration from the storage, and returns it as a binary -/// -/// Arguments: -/// -/// * `deps`: &Deps -/// -/// Returns: -/// -/// The fee information for staking and unbonding. -fn query_fee_info(deps: &Deps) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - to_binary(&QueryAnswer::FeeInfo { - staking: config.fees.staking, - unbonding: config.fees.unbonding, - collector: config.fees.collector, - }) -} - -#[cfg(test)] -mod tests { - use std::any::Any; - - use cosmwasm_std::testing::*; - use cosmwasm_std::{from_binary, OwnedDeps, QueryResponse}; - use shade_protocol::Contract; - - use crate::msg::{ContractInfo as CustomContractInfo, Fee, FeeInfo}; - - use super::*; - - fn init_helper() -> ( - StdResult, - OwnedDeps, - ) { - let mut deps = mock_dependencies_with_balance(&[]); - let env = mock_env(); - let info = mock_info("instantiator", &[]); - - let init_msg = InstantiateMsg { - prng_seed: Binary::from("lolz fun yay".as_bytes()), - derivative: CustomContractInfo { - address: Addr::unchecked("derivative_snip20_info_address"), - code_hash: String::from("derivative_snip20_info_codehash"), - entropy: Some(String::from("4359o74nd8dnkjerjrh")), - }, - staking: CustomContractInfo { - address: Addr::unchecked("staking_contract_info_address"), - code_hash: String::from("staking_contract_info_code_hash"), - entropy: Some(String::from("4359o74nd8dnkjerjrh")), - }, - query_auth: CustomContractInfo { - address: Addr::unchecked("authentication_contract_info_address"), - code_hash: String::from("authentication_contract_info_code_hash"), - entropy: Some(String::from("ljkdsfgh9548605874easfnd")), - }, - token: CustomContractInfo { - address: Addr::unchecked("shade_contract_info_address"), - code_hash: String::from("shade_contract_info_code_hash"), - entropy: Some(String::from("5sa4d6aweg473g87766h7712")), - }, - admin: Contract { - address: Addr::unchecked("shade_contract_info_address"), - code_hash: String::from("shade_contract_info_code_hash"), - }, - fees: FeeInfo { - staking: Fee { - rate: 5, - decimal_places: 2_u8, - }, - unbonding: Fee { - rate: 5, - decimal_places: 2_u8, - }, - collector: Addr::unchecked("collector_address"), - }, - }; - - (instantiate(deps.as_mut(), env, info, init_msg), deps) - } - fn extract_error_msg(error: StdResult) -> String { - match error { - Ok(response) => { - let bin_err = (&response as &dyn Any) - .downcast_ref::() - .expect("An error was expected, but no error could be extracted"); - match from_binary(bin_err).unwrap() { - QueryAnswer::ViewingKeyError { msg } => msg, - _ => panic!("Unexpected query answer"), - } - } - Err(err) => match err { - StdError::GenericErr { msg, .. } => msg, - _ => panic!("Unexpected result from init"), - }, - } - } - - #[test] - fn test_init_sanity() { - let (init_result, deps) = init_helper(); - let env = mock_env(); - let info = mock_info("instantiator", &[]); - let prnd = Binary::from("lolz fun yay".as_bytes()); - let staking = CustomContractInfo { - address: Addr::unchecked("staking_contract_info_address"), - code_hash: String::from("staking_contract_info_code_hash"), - entropy: Some(String::from("4359o74nd8dnkjerjrh")), - }; - - let authentication_contract = CustomContractInfo { - address: Addr::unchecked("authentication_contract_info_address"), - code_hash: String::from("authentication_contract_info_code_hash"), - entropy: Some(String::from("ljkdsfgh9548605874easfnd")), - }; - let token = CustomContractInfo { - address: Addr::unchecked("shade_contract_info_address"), - code_hash: String::from("shade_contract_info_code_hash"), - entropy: Some(String::from("5sa4d6aweg473g87766h7712")), - }; - let derivative = CustomContractInfo { - address: Addr::unchecked("derivative_snip20_info_address"), - code_hash: String::from("derivative_snip20_info_codehash"), - entropy: Some(String::from("4359o74nd8dnkjerjrh")), - }; - - // Generate viewing key for staking contract - let entropy: String = staking.entropy.clone().unwrap(); - let (staking_contract_vk, new_seed) = - new_viewing_key(&info.sender.clone(), &env, &prnd.0, entropy.as_ref()); - - // Generate viewing key for SHD contract - let entropy: String = token.entropy.clone().unwrap(); - let (token_contract_vk, _new_seed) = - new_viewing_key(&info.sender.clone(), &env, &new_seed, entropy.as_ref()); - - let msgs: Vec = vec![ - // Register receive Derivative contract - register_receive_msg( - env.contract.code_hash.clone(), - derivative.entropy, - RESPONSE_BLOCK_SIZE, - derivative.code_hash, - derivative.address.to_string(), - ) - .unwrap(), - // Register receive SHD contract - register_receive_msg( - env.contract.code_hash, - token.entropy.clone(), - RESPONSE_BLOCK_SIZE, - token.code_hash.clone(), - token.address.to_string(), - ) - .unwrap(), - // Set viewing key for SHD - set_viewing_key_msg( - token_contract_vk, - token.entropy, - RESPONSE_BLOCK_SIZE, - token.code_hash, - token.address.to_string(), - ) - .unwrap(), - // Set viewing key for staking contract - set_viewing_key_msg( - staking_contract_vk, - authentication_contract.entropy, - RESPONSE_BLOCK_SIZE, - authentication_contract.code_hash, - authentication_contract.address.to_string(), - ) - .unwrap(), - ]; - assert_eq!(init_result.unwrap(), Response::default().add_messages(msgs)); - - assert_eq!( - CONTRACT_STATUS.load(&deps.storage).unwrap(), - ContractStatusLevel::NormalRun - ); - } - - #[test] - fn test_panic_messages_when_contract_panicked() { - let (init_result, mut deps) = init_helper(); - let info = mock_info("admin", &[]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::SetContractStatus { - level: ContractStatusLevel::Panicked, - padding: None, - }; - let handle_result = execute(deps.as_mut(), mock_env(), info.clone(), handle_msg); - assert!( - handle_result.is_ok(), - "handle() failed: {}", - handle_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::PanicWithdraw {}; - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - assert!( - handle_result.is_ok(), - "handle() failed: {}", - handle_result.err().unwrap() - ); - } - - #[test] - fn test_handle_set_contract_status() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::SetContractStatus { - level: ContractStatusLevel::StopAll, - padding: None, - }; - let info = mock_info("admin", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!( - handle_result.is_ok(), - "handle() failed: {}", - handle_result.err().unwrap() - ); - - let contract_status = CONTRACT_STATUS.load(&deps.storage).unwrap(); - assert!(matches!( - contract_status, - ContractStatusLevel::StopAll { .. } - )); - } - #[test] - fn test_receive_msg_sender_is_not_shd_contract() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked(""), - from: Addr::unchecked(""), - amount: Uint256::from(100000000 as u32), - msg: Some(to_binary(&ReceiverMsg::Stake {}).unwrap()), - }; - let info = mock_info("giannis", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!(handle_result.is_err()); - let error = extract_error_msg(handle_result); - - assert_eq!(error, "Sender is not SHD contract"); - } - - #[test] - fn test_receive_stake_msg_successfully() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked(""), - from: Addr::unchecked(""), - amount: Uint256::from(100000000 as u32), - msg: Some(to_binary(&ReceiverMsg::Stake {}).unwrap()), - }; - let info = mock_info("shade_contract_info_address", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!( - handle_result.is_ok(), - "handle() failed: {}", - handle_result.err().unwrap() - ); - } - - #[test] - fn test_receive_unbond_msg_sender_is_not_derivative_contract() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked(""), - from: Addr::unchecked(""), - amount: Uint256::from(100000000 as u32), - msg: Some(to_binary(&ReceiverMsg::Unbond {}).unwrap()), - }; - let info = mock_info("giannis", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!(handle_result.is_err()); - let error = extract_error_msg(handle_result); - - assert_eq!(error, "Sender is not derivative (SNIP20) contract"); - } - - #[test] - fn test_receive_unbond_msg_successfully() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked(""), - from: Addr::unchecked(""), - amount: Uint256::from(100000000 as u32), - msg: Some(to_binary(&ReceiverMsg::Unbond {}).unwrap()), - }; - let info = mock_info("derivative_snip20_info_address", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!( - handle_result.is_ok(), - "handle() failed: {}", - handle_result.err().unwrap() - ); - } - - #[test] - fn test_receive_transfer_staked_msg_successfully() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked(""), - from: Addr::unchecked(""), - amount: Uint256::from(100000000 as u32), - msg: Some(to_binary(&ReceiverMsg::TransferStaked { receiver: None }).unwrap()), - }; - let info = mock_info("derivative_snip20_info_address", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!( - handle_result.is_ok(), - "handle() failed: {}", - handle_result.err().unwrap() - ); - } - - #[test] - fn test_receive_transfer_staked_msg_sender_is_not_derivative_contract() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked(""), - from: Addr::unchecked(""), - amount: Uint256::from(100000000 as u32), - msg: Some(to_binary(&ReceiverMsg::TransferStaked { receiver: None }).unwrap()), - }; - let info = mock_info("giannis", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!(handle_result.is_err()); - let error = extract_error_msg(handle_result); - - assert_eq!(error, "Sender is not derivative (SNIP20) contract"); - } - #[test] - fn test_unbonding_query_not_unbonds() { - let (init_result, deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - // Query unbondings - let query_msg = QueryMsg::Unbondings { - address: Addr::unchecked("david"), - viewing_key: String::from("password"), - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let unbonds = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::Unbondings { unbonds } => unbonds, - other => panic!("Unexpected: {:?}", other), - }; - - assert_eq!(unbonds, vec![]); - } - - #[test] - fn test_holdings_query_not_funds() { - let (init_result, deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - // Query unbondings - let query_msg = QueryMsg::Holdings { - address: Addr::unchecked("david"), - viewing_key: String::from("password"), - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let (derivative_claimable, derivative_unbonding) = - match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::Holdings { - derivative_claimable, - derivative_unbonding, - } => (derivative_claimable, derivative_unbonding), - other => panic!("Unexpected: {:?}", other), - }; - - assert_eq!(derivative_claimable, Uint128::zero()); - assert_eq!(derivative_unbonding, Uint128::zero()); - } - - #[test] - fn test_sanity_unbonding_processing_storage() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - let env = mock_env(); - // Unbond from bob account - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked(""), - from: Addr::unchecked("bob"), - amount: Uint256::from(100000000 as u32), - msg: Some(to_binary(&ReceiverMsg::Unbond {}).unwrap()), - }; - let info = mock_info("derivative_snip20_info_address", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!( - handle_result.is_ok(), - "handle() failed: {}", - handle_result.err().unwrap() - ); - - let unbonding_processing = PENDING_UNBONDING.load(&deps.storage).unwrap(); - - assert_eq!( - unbonding_processing, - InProcessUnbonding { - id: Uint128::zero(), - owner: Addr::unchecked("bob"), - amount: Uint128::from(21375000000000_u128), - complete: Uint128::from(env.block.time.seconds()) - .checked_add(Uint128::from(300_u32)) - .unwrap(), - } - ) - } - - #[test] - fn test_update_fees_should_fail_no_admin_sender() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::UpdateFees { - staking: None, - unbonding: None, - collector: None, - }; - let info = mock_info("not_admin", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!(handle_result.is_err()); - let error = extract_error_msg(handle_result); - - assert_eq!( - error, - "This is an admin command. Admin commands can only be run from admin address" - ); - } - - #[test] - fn test_update_fees_successfully_sender_is_admin_no_new_config_provided() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - let config_before_tx = CONFIG.load(&deps.storage).unwrap(); - let handle_msg = ExecuteMsg::UpdateFees { - staking: None, - unbonding: None, - collector: None, - }; - let info = mock_info("admin", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!( - handle_result.is_ok(), - "handle() failed: {}", - handle_result.err().unwrap() - ); - let config_after_tx = CONFIG.load(&deps.storage).unwrap(); - - assert_eq!(config_before_tx.fees, config_after_tx.fees); - } - - #[test] - fn test_update_fees_successfully_sender_is_admin() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - let config_before_tx = CONFIG.load(&deps.storage).unwrap(); - let handle_msg = ExecuteMsg::UpdateFees { - staking: Some(Fee { - rate: 5_u32, - decimal_places: 2_u8, - }), - collector: Some(Addr::unchecked("new_collector")), - unbonding: None, - }; - let info = mock_info("admin", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!( - handle_result.is_ok(), - "handle() failed: {}", - handle_result.err().unwrap() - ); - let config_after_tx = CONFIG.load(&deps.storage).unwrap(); - - assert_ne!(config_before_tx.fees, config_after_tx.fees); - - let answer: ExecuteAnswer = from_binary(&handle_result.unwrap().data.unwrap()).unwrap(); - let fee_info_returned = match answer { - ExecuteAnswer::UpdateFees { fee, status: _ } => fee, - _ => panic!("NOPE"), - }; - let fees = CONFIG.load(&deps.storage).unwrap().fees; - - assert_eq!(fee_info_returned, fees) - } - - #[test] - fn test_staking_returned_tokens() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::Receive { - sender: Addr::unchecked(""), - from: Addr::unchecked("bob"), - amount: Uint256::from(300000000 as u32), - msg: Some(to_binary(&ReceiverMsg::Stake {}).unwrap()), - }; - let info = mock_info("shade_contract_info_address", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!( - handle_result.is_ok(), - "handle() failed: {}", - handle_result.err().unwrap() - ); - let expected_tokens_return = Uint128::from(2850_u128); - let (_, tokens_returned) = match from_binary(&handle_result.unwrap().data.unwrap()).unwrap() - { - ExecuteAnswer::Stake { - shd_staked, - tokens_returned, - } => (shd_staked, tokens_returned), - other => panic!("Unexpected: {:?}", other), - }; - assert_eq!(tokens_returned, expected_tokens_return) - } - - #[test] - fn test_staking_info_query() { - let (init_result, deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - let query_msg = QueryMsg::StakingInfo {}; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let ( - unbonding_time, - bonded_shd, - available_shd, - rewards, - total_derivative_token_supply, - price, - ) = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::StakingInfo { - unbonding_time, - bonded_shd, - available_shd, - rewards, - total_derivative_token_supply, - price, - } => ( - unbonding_time, - bonded_shd, - available_shd, - rewards, - total_derivative_token_supply, - price, - ), - other => panic!("Unexpected: {:?}", other), - }; - - assert_eq!(unbonding_time, Uint128::from(300_u32)); - assert_eq!(bonded_shd, Uint128::from(300000000_u128)); - assert_eq!(available_shd, Uint128::from(100000000_u128)); - assert_eq!(rewards, Uint128::from(100000000_u128)); - assert_eq!(total_derivative_token_supply, Uint128::from(2000_u128)); - assert_eq!(price, Uint128::from(250000000000_u128)); - } - - #[test] - fn test_fee_info() { - let (init_result, deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - let query_msg = QueryMsg::FeeInfo {}; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let (staking, unbonding, collector) = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::FeeInfo { - staking, - unbonding, - collector, - } => (staking, unbonding, collector), - other => panic!("Unexpected: {:?}", other), - }; - - assert_eq!( - staking, - Fee { - rate: 5, - decimal_places: 2_u8, - } - ); - assert_eq!( - unbonding, - Fee { - rate: 5, - decimal_places: 2_u8, - } - ); - - assert_eq!(collector, Addr::unchecked("collector_address")); - } - #[test] - fn test_handle_claim_not_mature_unbonds() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::Claim {}; - let info = mock_info("x", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - assert!(handle_result.is_err()); - let error = extract_error_msg(handle_result); - - assert_eq!(error, "No mature unbondings to claim"); - } - - #[test] - fn test_handle_compound_rewards_msg() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::CompoundRewards {}; - let info = mock_info("x", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - assert!( - handle_result.is_ok(), - "handle() failed: {}", - handle_result.err().unwrap() - ); - - let config = CONFIG.load(&deps.storage).unwrap(); - - let msgs = vec![ - compound_msg(config.staking.code_hash, config.staking.address.to_string()).unwrap(), - ]; - - assert_eq!( - handle_result.unwrap(), - Response::default().add_messages(msgs) - ); - } - - #[test] - fn test_handle_panic_unbond_not_admin_user() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - let handle_msg = ExecuteMsg::PanicUnbond { - amount: Uint128::from(100000000_u128), - }; - let info = mock_info("bob", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!(handle_result.is_err()); - let error = extract_error_msg(handle_result); - - assert_eq!( - error, - "This is an admin command. Admin commands can only be run from admin address" - ); - } - - #[test] - fn test_handle_panic_unbond_msg() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::PanicUnbond { - amount: Uint128::from(100000000_u128), - }; - let info = mock_info("admin", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - assert!( - handle_result.is_ok(), - "handle() failed: {}", - handle_result.err().unwrap() - ); - - let config = CONFIG.load(&deps.storage).unwrap(); - - let msg = SubMsg::reply_always( - unbond_msg( - Uint128::from(100000000_u128), - config.staking.code_hash, - config.staking.address.to_string(), - Some(false), - ) - .unwrap(), - PANIC_UNBOND_REPLY_ID, - ); - - assert_eq!( - handle_result.unwrap(), - Response::default().add_submessage(msg) - ); - } - - #[test] - fn test_handle_panic_withdraw_not_admin_user() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - let handle_msg = ExecuteMsg::PanicWithdraw {}; - let info = mock_info("bob", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!(handle_result.is_err()); - let error = extract_error_msg(handle_result); - - assert_eq!( - error, - "This is an admin command. Admin commands can only be run from admin address" - ); - } - - #[test] - fn test_handle_panic_withdraw_msg() { - let (init_result, mut deps) = init_helper(); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::PanicWithdraw {}; - let info = mock_info("admin", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - assert!( - handle_result.is_ok(), - "handle() failed: {}", - handle_result.err().unwrap() - ); - - let config = CONFIG.load(&deps.storage).unwrap(); - let rewards = 100000000_u128; - let balance = 100000000_u128; - let amount = Uint128::from(rewards + balance); - assert_eq!( - handle_result.unwrap(), - Response::default().add_messages(vec![ - claim_rewards_msg( - config.staking.code_hash.clone(), - config.staking.address.to_string(), - ) - .unwrap(), - send_msg( - Addr::unchecked("super_admin").to_string(), - amount, - None, - Some("Panic withdraw {} tokens".to_string()), - config.token.entropy, - RESPONSE_BLOCK_SIZE, - config.token.code_hash, - config.token.address.to_string(), - ) - .unwrap(), - ]) - ); - } -} diff --git a/contracts/snip20_derivative/src/lib.rs b/contracts/snip20_derivative/src/lib.rs deleted file mode 100644 index 2d972fe..0000000 --- a/contracts/snip20_derivative/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod contract; -pub mod msg; -pub mod staking_interface; -pub mod state; diff --git a/contracts/snip20_derivative/src/msg.rs b/contracts/snip20_derivative/src/msg.rs deleted file mode 100644 index 6dc84b0..0000000 --- a/contracts/snip20_derivative/src/msg.rs +++ /dev/null @@ -1,277 +0,0 @@ -#![allow(clippy::field_reassign_with_default)] // This is triggered in `#[derive(JsonSchema)]` -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128, Uint256}; -use shade_protocol::{query_auth::QueryPermit, Contract}; - -use crate::staking_interface::Unbonding; - -#[derive(Serialize, Debug, Deserialize, Clone, JsonSchema)] -#[cfg_attr(test, derive(Eq, PartialEq))] -pub struct Config { - pub prng_seed:Binary, - // Staking contract (SHADE-CUSTOM) information - pub staking: ContractInfo, - pub staking_contract_vk: String, - // Staking authentication contract (SHADE-CUSTOM) information - pub query_auth: ContractInfo, - // SHD (SNIP-20) information - pub token: ContractInfo, - pub token_contract_vk: String, - // Derivative SNIP-20 - pub derivative: ContractInfo, - // Fee collector and rate information - pub fees: FeeInfo, - pub contract_address: Addr, - pub admin: Contract, -} - -#[cfg_attr(test, derive(Eq, PartialEq))] -#[derive(Serialize, Deserialize, Clone, JsonSchema, Debug)] -pub struct Fee { - pub rate: u32, - pub decimal_places: u8, -} - -#[cfg_attr(test, derive(Eq, PartialEq))] -#[derive(Serialize, Deserialize, Clone, JsonSchema, Debug)] -pub struct FeeInfo { - pub staking: Fee, - pub unbonding: Fee, - pub collector: Addr, -} - -#[cfg_attr(test, derive(Eq, PartialEq))] -#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] -pub struct ContractInfo { - pub address: Addr, - #[serde(default)] - pub code_hash: String, - // Optional entropy use to any transaction required to execute in this contract - pub entropy: Option, -} - -#[derive(Serialize, Deserialize, JsonSchema)] -pub struct InstantiateMsg { - pub prng_seed: Binary, - pub staking: ContractInfo, - pub query_auth: ContractInfo, - pub derivative: ContractInfo, - pub token: ContractInfo, - pub admin: Contract, - pub fees: FeeInfo, -} - -#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - Claim {}, - CompoundRewards {}, - UpdateFees { - staking: Option, - unbonding: Option, - collector: Option, - }, - PanicUnbond { - amount: Uint128, - }, - PanicWithdraw {}, - //Receiver interface - Receive { - sender: Addr, - from: Addr, - amount: Uint256, - #[serde(default)] - msg: Option, - }, - SetContractStatus { - level: ContractStatusLevel, - padding: Option, - }, -} - -#[derive(Serialize, Deserialize, JsonSchema, Debug)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteAnswer { - TransferStaked { - amount_sent: Uint128, - tokens_returned: Uint128, - }, - Claim { - amount_claimed: Uint128, - }, - CompoundRewards { - status: ResponseStatus, - }, - Stake { - /// amount of SHD staked - shd_staked: Uint128, - /// amount of derivative token minted - tokens_returned: Uint128, - }, - /// redeem derivative tokens to unbond SCRT - Unbond { - /// amount of derivative tokens redeemed - tokens_redeemed: Uint128, - /// amount of shd to be unbonded - shd_to_be_received: Uint128, - /// estimated time of maturity - estimated_time_of_maturity: Uint128, - }, - CreateViewingKey { - key: String, - }, - SetViewingKey { - status: ResponseStatus, - }, - ChangeAdmin { - status: ResponseStatus, - }, - SetContractStatus { - status: ResponseStatus, - }, - UpdateFees { - status: ResponseStatus, - fee: FeeInfo, - }, - // Permit - RevokePermit { - status: ResponseStatus, - }, -} - -#[derive(Serialize, Deserialize, JsonSchema, Debug)] -#[serde(rename_all = "snake_case")] -pub enum ReceiverMsg { - Stake {}, - Unbond {}, - TransferStaked { receiver: Option }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] -#[cfg_attr(test, derive(PartialEq))] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - Holdings { address: Addr, viewing_key: String }, - StakingInfo {}, - FeeInfo {}, - ContractStatus {}, - Unbondings { address: Addr, viewing_key: String }, - WithPermit { permit: QueryPermit }, -} - -impl QueryMsg { - pub fn get_validation_params(&self, api: &dyn Api) -> StdResult<(Vec, String)> { - match self { - Self::Unbondings { - address, - viewing_key, - } => { - let address = api.addr_validate(address.as_str())?; - Ok((vec![address], viewing_key.clone())) - } - Self::Holdings { - address, - viewing_key, - } => { - let address = api.addr_validate(address.as_str())?; - Ok((vec![address], viewing_key.clone())) - } - _ => panic!("This query type does not require authentication"), - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] -#[cfg_attr(test, derive(Eq, PartialEq))] -#[serde(rename_all = "snake_case")] -pub enum QueryWithPermit { - Unbondings {}, - Holdings {}, -} - -#[derive(Serialize, Deserialize, JsonSchema, Debug)] -#[serde(rename_all = "snake_case")] -pub enum QueryAnswer { - Holdings { - derivative_claimable: Uint128, - derivative_unbonding: Uint128, - }, - Unbondings { - unbonds: Vec, - }, - StakingInfo { - /// unbonding time - unbonding_time: Uint128, - /// amount of bonded SHD - bonded_shd: Uint128, - /// amount of available SHD not reserved for mature unbondings - available_shd: Uint128, - /// unclaimed staking rewards - rewards: Uint128, - /// total supply of derivative token - total_derivative_token_supply: Uint128, - /// price of derivative token in SHD to 6 decimals - price: Uint128, - }, - FeeInfo { - staking: Fee, - unbonding: Fee, - collector: Addr, - }, - ContractStatus { - status: ContractStatusLevel, - }, - ViewingKeyError { - msg: String, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -pub struct InProcessUnbonding { - pub id: Uint128, - pub owner: Addr, - pub amount: Uint128, - pub complete: Uint128, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -pub struct PanicUnbond { - pub id: Uint128, - pub amount: Uint128, - pub complete: Uint128, -} - -#[derive(Serialize, Deserialize, Clone, JsonSchema, Debug)] -#[cfg_attr(test, derive(Eq, PartialEq))] -#[serde(rename_all = "snake_case")] -pub enum ResponseStatus { - Success, - Failure, -} - -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] -#[serde(rename_all = "snake_case")] -pub enum ContractStatusLevel { - NormalRun, - Panicked, - StopAll, -} - -pub fn status_level_to_u8(status_level: ContractStatusLevel) -> u8 { - match status_level { - ContractStatusLevel::NormalRun => 0, - ContractStatusLevel::Panicked => 1, - ContractStatusLevel::StopAll => 2, - } -} - -pub fn u8_to_status_level(status_level: u8) -> StdResult { - match status_level { - 0 => Ok(ContractStatusLevel::NormalRun), - 1 => Ok(ContractStatusLevel::Panicked), - 2 => Ok(ContractStatusLevel::StopAll), - _ => Err(StdError::generic_err("Invalid state level")), - } -} diff --git a/contracts/snip20_derivative/src/staking_interface.rs b/contracts/snip20_derivative/src/staking_interface.rs deleted file mode 100644 index 3f58878..0000000 --- a/contracts/snip20_derivative/src/staking_interface.rs +++ /dev/null @@ -1,388 +0,0 @@ -use crate::{msg::ResponseStatus, state::RESPONSE_BLOCK_SIZE}; -use core::fmt; -use cosmwasm_std::{ - to_binary, Addr, CosmosMsg, CustomQuery, QuerierWrapper, QueryRequest, StdError, StdResult, - Uint128, WasmMsg, WasmQuery, -}; -use schemars::JsonSchema; -use secret_toolkit::utils::space_pad; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; - -// HANDLES -#[derive(Serialize, Clone, Debug, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum Action { - // Messages allowed when sending SHD to Staking contract - // Deposit rewards to be distributed - Stake { compound: Option }, -} - -// Staking contract handles -#[derive(Serialize, Clone, Debug, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum StakingMsg { - // Claims rewards generated - Claim {}, - // Unbond X amount - Unbond { - amount: Uint128, - compound: Option, - }, - // Claims mature unbondings - Withdraw { - ids: Option>, - }, - // Claims available rewards and re-stake them - Compound {}, - TransferStake { - amount: Uint128, - recipient: String, - compound: Option, - }, -} - -impl StakingMsg { - /// Returns a StdResult used to execute a contract function - /// - /// # Arguments - /// - /// * `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, code_hash: String, contract_addr: String) -> StdResult { - let mut msg = to_binary(self)?; - space_pad(&mut msg.0, RESPONSE_BLOCK_SIZE); - let funds = Vec::new(); - let execute = WasmMsg::Execute { - contract_addr, - code_hash, - msg, - funds, - }; - Ok(execute.into()) - } -} - -pub fn transfer_staked_msg( - callback_code_hash: String, - contract_addr: String, - amount: Uint128, - recipient: String, - compound: Option, -) -> StdResult { - StakingMsg::TransferStake { - amount, - recipient, - compound, - } - .to_cosmos_msg(callback_code_hash, contract_addr) -} -/// Returns a StdResult used to execute Claim -/// Claims all rewards generated on the Staking Contract -/// # Arguments -/// * `callback_code_hash` - String holding the code hash of the contract being called -/// * `contract_addr` - address of the contract being called -pub fn claim_rewards_msg( - callback_code_hash: String, - contract_addr: String, -) -> StdResult { - StakingMsg::Claim {}.to_cosmos_msg(callback_code_hash, contract_addr) -} - -/// Returns a StdResult used to execute Unbond -/// Unbonds X amount; This can later be claim with a "withdraw" message -/// # Arguments -/// * `amount` - amount of SHD to unbond -/// * `callback_code_hash` - String holding the code hash of the contract being called -/// * `contract_addr` - address of the contract being called -pub fn unbond_msg( - amount: Uint128, - callback_code_hash: String, - contract_addr: String, - compound: Option, -) -> StdResult { - StakingMsg::Unbond { amount, compound }.to_cosmos_msg(callback_code_hash, contract_addr) -} - -/// Returns a StdResult used to execute Withdraw -/// Claims any mature unbondings -/// # Arguments -/// * `callback_code_hash` - String holding the code hash of the contract being called -/// * `contract_addr` - address of the contract being called -pub fn withdraw_msg( - callback_code_hash: String, - contract_addr: String, - ids: Option>, -) -> StdResult { - StakingMsg::Withdraw { ids }.to_cosmos_msg(callback_code_hash, contract_addr) -} - -/// Returns a StdResult used to execute Compound -/// Claims available rewards and re-stake them -/// # Arguments -/// * `callback_code_hash` - String holding the code hash of the contract being called -/// * `contract_addr` - address of the contract being called -pub fn compound_msg(callback_code_hash: String, contract_addr: String) -> StdResult { - StakingMsg::Compound {}.to_cosmos_msg(callback_code_hash, contract_addr) -} - -// QUERIES - -#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct RawContract { - pub address: String, - pub code_hash: String, -} - -#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct StakingConfig { - pub admin_auth: RawContract, - pub query_auth: RawContract, - pub unbond_period: Uint128, - pub max_user_pools: Uint128, - pub reward_cancel_threshold: Uint128, -} - -#[derive(Serialize, Clone, Debug, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct ViewingKey { - key: String, - address: String, -} -#[derive(Serialize, Clone, Debug, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct Auth { - viewing_key: ViewingKey, - // Removed since contract's don't support permits - // to communicate between them - // Permit(QueryPermit), -} - -#[derive(Serialize, Clone, Debug, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum StakingQuery { - Config {}, - Staked { - auth: Auth, - }, - Rewards { - auth: Auth, - }, - Unbonding { - auth: Auth, - ids: Option>, - }, -} - -impl fmt::Display for StakingQuery { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - StakingQuery::Config {} => write!(f, "Config"), - StakingQuery::Staked { .. } => write!(f, "Staked"), - StakingQuery::Rewards { .. } => write!(f, "Rewards"), - StakingQuery::Unbonding { .. } => write!(f, "Unbonding"), - } - } -} - -impl StakingQuery { - /// Returns a StdResult, where T is the "Response" type that wraps the query answer - /// - /// # Arguments - /// - /// * `querier` - a reference to the Querier dependency of the querying contract - /// * `callback_code_hash` - String holding the code hash of the contract being queried - /// * `contract_addr` - address of the contract being queried - pub fn query( - &self, - querier: QuerierWrapper, - code_hash: String, - contract_addr: String, - ) -> StdResult { - let mut msg = to_binary(self)?; - space_pad(&mut msg.0, RESPONSE_BLOCK_SIZE); - querier - .query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr, - code_hash, - msg, - })) - .map_err(|err| { - StdError::generic_err(format!("Error performing {} query: {}", self, err)) - }) - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, PartialEq, Eq)] -pub struct Unbonding { - pub id: Uint128, - pub amount: Uint128, - pub complete: Uint128, -} -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub struct RewardToken { - pub address: Addr, - pub code_hash: String, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub struct Token { - pub address: Addr, - pub code_hash: String, - pub viewing_key: String -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub struct Reward { - pub token: RewardToken, - pub amount: Uint128, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum QueryResponse { - Config { config: StakingConfig }, - Staked { amount: Uint128 }, - Rewards { rewards: Vec }, - Unbonding { unbondings: Vec }, - ViewingKeyError { msg: String }, -} -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -pub struct Staked { - pub amount: Uint128, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -pub struct Rewards { - pub rewards: Vec, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -pub struct Unbondings { - pub unbondings: Vec, -} - -/// Returns a StdResult from performing Balance query -/// -/// # Arguments -/// * `address` - the address whose balance should be displayed -/// * `key` - String holding the authentication key needed to view the balance -/// * `querier` - a reference to the Querier dependency of the querying contract -/// * `callback_code_hash` - String holding the code hash of the contract being queried -/// * `contract_addr` - address of the contract being queried -pub fn balance_query( - address: String, - key: String, - querier: QuerierWrapper, - callback_code_hash: String, - contract_addr: String, -) -> StdResult { - let auth = Auth { - viewing_key: ViewingKey { address, key }, - }; - let answer: QueryResponse = - StakingQuery::Staked { auth }.query(querier, callback_code_hash, contract_addr)?; - match answer { - QueryResponse::Staked { amount } => Ok(Staked { amount }), - QueryResponse::ViewingKeyError { .. } => Err(StdError::generic_err("unauthorized")), - _ => Err(StdError::generic_err("Invalid Balance query response")), - } -} - -/// Returns a StdResult from performing rewards query -/// -/// # Arguments -/// * `address` - the address whose balance should be displayed -/// * `key` - String holding the authentication key needed to view the balance -/// * `querier` - a reference to the Querier dependency of the querying contract -/// * `callback_code_hash` - String holding the code hash of the contract being queried -/// * `contract_addr` - address of the contract being queried -pub fn rewards_query( - address: String, - key: String, - querier: QuerierWrapper, - callback_code_hash: String, - contract_addr: String, -) -> StdResult { - let auth = Auth { - viewing_key: ViewingKey { address, key }, - }; - let answer: QueryResponse = - StakingQuery::Rewards { auth }.query(querier, callback_code_hash, contract_addr)?; - match answer { - QueryResponse::Rewards { rewards } => Ok(Rewards { rewards }), - QueryResponse::ViewingKeyError { .. } => Err(StdError::generic_err("unauthorized")), - _ => Err(StdError::generic_err("Invalid Rewards query response")), - } -} - -/// Returns a StdResult from performing rewards query -/// -/// # Arguments -/// * `address` - the address whose balance should be displayed -/// * `key` - String holding the authentication key needed to view the balance -/// * `querier` - a reference to the Querier dependency of the querying contract -/// * `callback_code_hash` - String holding the code hash of the contract being queried -/// * `contract_addr` - address of the contract being queried -pub fn unbondings_query( - address: String, - key: String, - ids: Option>, - querier: QuerierWrapper, - callback_code_hash: String, - contract_addr: String, -) -> StdResult { - let auth = Auth { - viewing_key: ViewingKey { address, key }, - }; - let answer: QueryResponse = - StakingQuery::Unbonding { auth, ids }.query(querier, callback_code_hash, contract_addr)?; - match answer { - QueryResponse::Unbonding { unbondings } => Ok(Unbondings { unbondings }), - QueryResponse::ViewingKeyError { .. } => Err(StdError::generic_err("unauthorized")), - _ => Err(StdError::generic_err("Invalid Unbonding query response")), - } -} - -pub fn config_query( - querier: QuerierWrapper, - callback_code_hash: String, - contract_addr: String, -) -> StdResult { - let answer: QueryResponse = - StakingQuery::Config {}.query(querier, callback_code_hash, contract_addr)?; - match answer { - QueryResponse::Config { config } => Ok(config), - _ => Err(StdError::generic_err("Invalid Rewards query response")), - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(rename_all = "snake_case")] -pub struct Unbond { - pub id: Uint128, - pub status: ResponseStatus, -} -#[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(rename_all = "snake_case")] -pub struct UnbondResponse { - pub unbond: Unbond, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(rename_all = "snake_case")] -pub struct Withdraw { - pub withdrawn: Uint128, - pub status: ResponseStatus, -} -#[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(rename_all = "snake_case")] -pub struct WithdrawResponse { - pub withdraw: Withdraw, -} diff --git a/contracts/snip20_derivative/src/state.rs b/contracts/snip20_derivative/src/state.rs deleted file mode 100644 index 5df951f..0000000 --- a/contracts/snip20_derivative/src/state.rs +++ /dev/null @@ -1,69 +0,0 @@ -use cosmwasm_std::{Addr, StdResult, Storage}; -use secret_toolkit::storage::Item; -use secret_toolkit::{serialization::Json, storage::Keymap}; - -use crate::msg::{Config, ContractStatusLevel, InProcessUnbonding, PanicUnbond}; -use crate::staking_interface::{Unbonding, Token}; - -pub const KEY_CONFIG: &[u8] = b"config"; -pub const KEY_STAKING_INFO: &[u8] = b"staking_info"; -pub const KEY_PANIC_UNBONDS: &[u8] = b"panic_unbonds_ids"; -pub const KEY_PENDING_UNBONDING: &[u8] = b"last_unbonding_id"; -pub const KEY_CONTRACT_STATUS: &[u8] = b"contract_status"; -pub const PREFIX_UNBONDINGS_IDS: &[u8] = b"unbondings_ids"; -pub const PREFIX_UNBONDINGS: &[u8] = b"unbondings"; -pub const RESPONSE_BLOCK_SIZE: usize = 256; -pub const UNBOND_REPLY_ID: u64 = 1_u64; -pub const PANIC_WITHDRAW_REPLY_ID: u64 = 2_u64; -pub const PANIC_UNBOND_REPLY_ID: u64 = 3_u64; -// Circle back rewards storage keys -pub const KEY_REWARDED_TOKENS_LIST: &[u8] = b"rewarded_tokens"; -pub const PREFIX_CONTRACTS_VKS: &[u8] = b"contracts_vks"; -pub static REWARDED_TOKENS_LIST: Item> = Item::new(KEY_REWARDED_TOKENS_LIST); -pub static CONTRACTS_VKS: Keymap = Keymap::new(PREFIX_CONTRACTS_VKS); - -pub static CONTRACT_STATUS: Item = Item::new(KEY_CONTRACT_STATUS); -pub static CONFIG: Item = Item::new(KEY_CONFIG); -pub static PANIC_UNBONDS: Item> = Item::new(KEY_PANIC_UNBONDS); -pub static PENDING_UNBONDING: Item = Item::new(KEY_PENDING_UNBONDING); -pub static UNBONDINGS_IDS: Item> = Item::new(PREFIX_UNBONDINGS_IDS); -pub static UNBONDING: Keymap = Keymap::new(PREFIX_UNBONDINGS); - -pub struct ContractsVksStore {} -impl ContractsVksStore { - pub fn may_load(store: &dyn Storage, contract_addr: &Addr) -> Option { - CONTRACTS_VKS.get(store, &contract_addr) - } - - pub fn save(store: &mut dyn Storage, contract_addr: &Addr, token: &Token) -> StdResult<()> { - CONTRACTS_VKS.insert(store, &contract_addr, token) - } -} - -pub struct UnbondingIdsStore {} -impl UnbondingIdsStore { - pub fn load(store: &dyn Storage, account: &Addr) -> Vec { - let unbondings_ids = UNBONDINGS_IDS.add_suffix(account.as_str().as_bytes()); - unbondings_ids.load(store).unwrap_or_default() - } - - pub fn save(store: &mut dyn Storage, account: &Addr, ids: Vec) -> StdResult<()> { - let unbondings_ids = UNBONDINGS_IDS.add_suffix(account.as_str().as_bytes()); - unbondings_ids.save(store, &ids) - } -} - -pub struct UnbondingStore {} -impl UnbondingStore { - pub fn may_load(store: &dyn Storage, id: u128) -> Option { - UNBONDING.get(store, &id) - } - - pub fn save(store: &mut dyn Storage, id: u128, unbond: &Unbonding) -> StdResult<()> { - UNBONDING.insert(store, &id, unbond) - } - - pub fn remove(store: &mut dyn Storage, id: u128) -> StdResult<()> { - UNBONDING.remove(store, &id) - } -} diff --git a/contracts/snip20_derivative/tests/integration.rs b/contracts/snip20_derivative/tests/integration.rs deleted file mode 100644 index 35fb6b8..0000000 --- a/contracts/snip20_derivative/tests/integration.rs +++ /dev/null @@ -1,3 +0,0 @@ -#[test] -#[ignore] -fn empty_test() {} diff --git a/contracts/snip20_derivative/tests/integration.sh b/contracts/snip20_derivative/tests/integration.sh deleted file mode 100755 index f546133..0000000 --- a/contracts/snip20_derivative/tests/integration.sh +++ /dev/null @@ -1,1699 +0,0 @@ -#!/bin/bash - -set -eu -set -o pipefail # If anything in a pipeline fails, the pipe's exit status is a failure -#set -x # Print all commands for debugging - -declare -a KEY=(a b c d) - -declare -A FROM=( - [a]='-y --from a' - [b]='-y --from b' - [c]='-y --from c' - [d]='-y --from d' -) - -# This means we don't need to configure the cli since it uses the preconfigured cli in the docker. -# We define this as a function rather than as an alias because it has more flexible expansion behavior. -# In particular, it's not possible to dynamically expand aliases, but `tx_of` dynamically executes whatever -# we specify in its arguments. -function secretcli() { - docker exec secretdev /usr/bin/secretd "$@" -} - -# Just like `echo`, but prints to stderr -function log() { - echo "$@" >&2 -} - -# suppress all output to stdout for the command described in the arguments -function quiet() { - "$@" >/dev/null -} - -# suppress all output to stdout and stderr for the command described in the arguments -function silent() { - "$@" >/dev/null 2>&1 -} - -# Pad the string in the first argument to 256 bytes, using spaces -function pad_space() { - printf '%-256s' "$1" -} - -function assert_eq() { - set -e - local left="$1" - local right="$2" - local message - - if [[ "$(echo $left | xargs)" != "$(echo $right | xargs)" ]]; then - if [ -z ${3+x} ]; then - local lineno="${BASH_LINENO[0]}" - message="assertion failed on line $lineno - both sides differ. left: ${left@Q}, right: ${right@Q}" - else - message="$3" - fi - log "$message" - return 1 - fi - - return 0 -} - -function assert_ne() { - set -e - local left="$1" - local right="$2" - local message - - if [[ "$left" == "$right" ]]; then - if [ -z ${3+x} ]; then - local lineno="${BASH_LINENO[0]}" - message="assertion failed on line $lineno - both sides are equal. left: ${left@Q}, right: ${right@Q}" - else - message="$3" - fi - - log "$message" - return 1 - fi - - return 0 -} - -# what the fuck is this? -function assert_contains() { - set -e - local str="$1" - local substr="$2" - local message - - if [[ "$str" == "$substr" ]]; then - if [ -z ${3+x} ]; then - local lineno="${BASH_LINENO[0]}" - message="assertion failed on line $lineno - str doesn't contain substr. str: ${str@Q}, substr: ${substr@Q}" - else - message="$3" - fi - log "$message" - return 1 - fi - - return 0 -} - -declare -A ADDRESS=( - [a]="$(secretcli keys show --address a)" - [b]="$(secretcli keys show --address b)" - [c]="$(secretcli keys show --address c)" - [d]="$(secretcli keys show --address d)" -) - -declare -A VK=([a]='' [b]='' [c]='' [d]='') - -# Generate a label for a contract with a given code id -# This just adds "contract_" before the code id. -function label_by_id() { - local id="$1" - echo "contract_$id" -} - -# Keep polling the blockchain until the tx completes. -# The first argument is the tx hash. -# The second argument is a message that will be logged after every failed attempt. -# The tx information will be returned. -function wait_for_tx() { - local tx_hash="$1" - local message="$2" - - local result - - log "waiting on tx: $tx_hash" - # secretcli will only print to stdout when it succeeds - until result="$(secretcli query tx "$tx_hash" 2>/dev/null)"; do - log "$message" - sleep 1 - done - - # log out-of-gas events - if quiet jq -e '.raw_log | startswith("execute contract failed: Out of gas: ") or startswith("out of gas:")' <<<"$result"; then - log "$(jq -r '.raw_log' <<<"$result")" - fi - - echo "$result" -} - -# This is a wrapper around `wait_for_tx` that also decrypts the response, -# and returns a nonzero status code if the tx failed -function wait_for_compute_tx() { - local tx_hash="$1" - local message="$2" - local return_value=0 - local result - local decrypted - - result="$(wait_for_tx "$tx_hash" "$message")" - # log "$result" - if quiet jq -e '.logs == null' <<<"$result"; then - return_value=1 - fi - decrypted="$(secretcli query compute tx "$tx_hash")" || return - log "$decrypted" - echo "$decrypted" - - return "$return_value" -} - -# If the tx failed, return a nonzero status code. -# The decrypted error or message will be echoed -function check_tx() { - local tx_hash="$1" - local result - local return_value=0 - - result="$(secretcli query tx "$tx_hash")" - if quiet jq -e '.logs == null' <<<"$result"; then - return_value=1 - fi - decrypted="$(secretcli query compute tx "$tx_hash")" || return - log "$decrypted" - echo "$decrypted" - - return "$return_value" -} - -# Extract the tx_hash from the output of the command -function tx_of() { - "$@" | jq -r '.txhash' -} - -# Extract the output_data_as_string from the output of the command -function data_of() { - local result="$("$@" | jq -ec '.answers[0].output_data_as_string' | sed 's/\\//g' | sed 's/ //g' | sed 's/^"\(.*\)"$/\1/')" - echo "$result" -} - -function extract_exec_error() { - set -e - local search_pattern - local error_msg - - error_msg="$(jq -r '.output_error' <<<"$1")" - search_pattern=${error_msg#*"$2"} - echo $search_pattern -} - -# Send a compute transaction and return the tx hash. -# All arguments to this function are passed directly to `secretcli tx compute execute`. -function compute_execute() { - tx_of secretcli tx compute execute "$@" -} - -# Send a query to the contract. -# All arguments to this function are passed directly to `secretcli query compute query`. -function compute_query() { - secretcli query compute query "$@" -} - -function upload_code() { - set -e - local directory="$1" - local tx_hash - local code_id - - tx_hash="$(tx_of secretcli tx compute store "code/$directory/contract.wasm.gz" ${FROM[a]} --gas 10000000)" - code_id="$( - wait_for_tx "$tx_hash" 'waiting for contract upload' | - jq -r '.logs[0].events[0].attributes[] | select(.key == "code_id") | .value' - )" - - log "uploaded contract #$code_id" - - echo "$code_id" -} - -function instantiate() { - set -e - local code_id="$1" - local init_msg="$2" - - log 'sending init message:' - log "${init_msg@Q}" - - local tx_hash - tx_hash="$(tx_of secretcli tx compute instantiate "$code_id" "$init_msg" --label "$(label_by_id "$code_id")" ${FROM[a]} --gas 10000000)" - wait_for_tx "$tx_hash" 'waiting for init to complete' -} - -# This function uploads and instantiates a contract, and returns the new contract's address -function create_contract() { - set -e - local dir="$1" - local init_msg="$2" - - local code_id - code_id="$(upload_code "$dir")" - - local init_result - init_result="$(instantiate "$code_id" "$init_msg")" - - if quiet jq -e '.logs == null' <<<"$init_result"; then - log "$(secretcli query compute tx "$(jq -r '.txhash' <<<"$init_result")")" - return 1 - fi - - jq -r '.logs[0].events[0].attributes[] | select(.key == "contract_address") | .value' <<<"$init_result" -} - -function deposit() { - set -e - local contract_addr="$1" - local key="$2" - local amount="$3" - - local deposit_message='{"deposit":{"padding":":::::::::::::::::"}}' - local tx_hash - local deposit_response - tx_hash="$(compute_execute "$contract_addr" "$deposit_message" --amount "${amount}uscrt" ${FROM[$key]} --gas 250000)" - deposit_response="$(wait_for_compute_tx "$tx_hash" "waiting for deposit to \"$key\" to process" | jq -ec '.answers[0].output_data_as_string' | sed 's/\\//g' | sed 's/ //g' | sed 's/^"\(.*\)"$/\1/')" - assert_eq "$deposit_response" "$(pad_space '{"deposit":{"status":"success"}}' | sed 's/ //g')" - log "deposited ${amount}uscrt to \"$key\" successfully" - echo "$tx_hash" -} - -function mint() { - set -e - local contract_addr="$1" - local key="$2" - local recipient="$3" - local amount="$4" - - local mint_message='{"mint":{"recipient":"'"$recipient"'","amount":"'"$amount"'","padding":":::::::::::::::::"}}' - local tx_hash - local deposit_response - tx_hash="$(compute_execute "$contract_addr" "$mint_message" ${FROM[$key]} --gas 251000)" - echo "$tx_hash" - deposit_response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for mint to \"$recipient\" to process")" - assert_eq "$deposit_response" "$(pad_space '{"mint":{"status":"success"}}' | sed 's/ //g')" - log "minted ${amount}uscrt for \"$recipient\" successfully" -} - -function burn() { - set -e - local contract_addr="$1" - local key="$2" - local amount="$3" - - local burn_message='{"burn":{"amount":"'"$amount"'"}}' - local tx_hash - local burn_response - tx_hash="$(compute_execute "$contract_addr" "$burn_message" ${FROM[$key]} --gas 250000)" - echo "$tx_hash" - burn_response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for burn for \"$key\" to process")" - log "$burn_response" - assert_eq "$burn_response" "$(pad_space '{"burn":{"status":"success"}}' | sed 's/ //g')" - log "burned ${amount}uscrt for \"$key\" successfully" -} - -function get_balance() { - set -e - local contract_addr="$1" - local key="$2" - - log "querying balance for \"$key\"" - local balance_query='{"balance":{"address":"'"${ADDRESS[$key]}"'","key":"'"${VK[$key]}"'"}}' - local balance_response - balance_response="$(compute_query "$contract_addr" "$balance_query")" - log "balance response was: $balance_response" - jq -r '.balance.amount' <<<"$balance_response" -} - -# Redeem some SCRT from an account -# As you can see, verifying this is happening correctly requires a lot of code -# so I separated it to its own function, because it's used several times. -function redeem() { - set -e - local contract_addr="$1" - local key="$2" - local amount="$3" - - local redeem_message - local tx_hash - local redeem_tx - local transfer_attributes - local redeem_response - - log "redeeming \"$key\"" - redeem_message='{"redeem":{"amount":"'"$amount"'","denom":"uscrt"}}' - tx_hash="$(compute_execute "$contract_addr" "$redeem_message" ${FROM[$key]} --gas 150000)" - redeem_tx="$(wait_for_tx "$tx_hash" "waiting for redeem from \"$key\" to process")" - transfer_attributes="$(jq -r '.logs[0].events[] | select(.type == "transfer") | .attributes' <<<"$redeem_tx")" - assert_eq "$(jq -r '.[] | select(.key == "recipient") | .value' <<<"$transfer_attributes")" "${ADDRESS[$key]}" - assert_eq "$(jq -r '.[] | select(.key == "amount") | .value' <<<"$transfer_attributes")" "${amount}uscrt" - log "redeem response for \"$key\" returned ${amount}uscrt" - - redeem_response="$(data_of check_tx "$tx_hash")" - assert_eq "$redeem_response" "$(pad_space '{"redeem":{"status":"success"}}' | sed 's/ //g')" - log "redeemed ${amount} from \"$key\" successfully" - echo "$tx_hash" -} - -function get_token_info() { - set -e - local contract_addr="$1" - - local token_info_query='{"token_info":{}}' - compute_query "$contract_addr" "$token_info_query" -} - -function increase_allowance() { - set -e - local contract_addr="$1" - local owner_key="$2" - local spender_key="$3" - local amount="$4" - - local owner_address="${ADDRESS[$owner_key]}" - local spender_address="${ADDRESS[$spender_key]}" - local allowance_message='{"increase_allowance":{"spender":"'"$spender_address"'","amount":"'"$amount"'"}}' - local allowance_response - - tx_hash="$(compute_execute "$contract_addr" "$allowance_message" ${FROM[$owner_key]} --gas 150000)" - allowance_response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for the increase of \"$spender_key\"'s allowance to \"$owner_key\"'s funds to process")" - assert_eq "$(jq -r '.increase_allowance.spender' <<<"$allowance_response")" "$spender_address" - assert_eq "$(jq -r '.increase_allowance.owner' <<<"$allowance_response")" "$owner_address" - jq -r '.increase_allowance.allowance' <<<"$allowance_response" - log "Increased allowance given to \"$spender_key\" from \"$owner_key\" by ${amount}uscrt successfully" -} - -function decrease_allowance() { - set -e - local contract_addr="$1" - local owner_key="$2" - local spender_key="$3" - local amount="$4" - - local owner_address="${ADDRESS[$owner_key]}" - local spender_address="${ADDRESS[$spender_key]}" - local allowance_message='{"decrease_allowance":{"spender":"'"$spender_address"'","amount":"'"$amount"'"}}' - local allowance_response - - tx_hash="$(compute_execute "$contract_addr" "$allowance_message" ${FROM[$owner_key]} --gas 150000)" - allowance_response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for the decrease of \"$spender_key\"'s allowance to \"$owner_key\"'s funds to process")" - assert_eq "$(jq -r '.decrease_allowance.spender' <<<"$allowance_response")" "$spender_address" - assert_eq "$(jq -r '.decrease_allowance.owner' <<<"$allowance_response")" "$owner_address" - jq -r '.decrease_allowance.allowance' <<<"$allowance_response" - log "Decreased allowance given to \"$spender_key\" from \"$owner_key\" by ${amount}uscrt successfully" -} - -function get_allowance() { - set -e - local contract_addr="$1" - local owner_key="$2" - local spender_key="$3" - - log "querying allowance given to \"$spender_key\" by \"$owner_key\"" - local owner_address="${ADDRESS[$owner_key]}" - local spender_address="${ADDRESS[$spender_key]}" - local allowance_query='{"allowance":{"spender":"'"$spender_address"'","owner":"'"$owner_address"'","key":"'"${VK[$owner_key]}"'"}}' - local allowance_response - allowance_response="$(compute_query "$contract_addr" "$allowance_query")" - log "allowance response was: $allowance_response" - assert_eq "$(jq -r '.allowance.spender' <<<"$allowance_response")" "$spender_address" - assert_eq "$(jq -r '.allowance.owner' <<<"$allowance_response")" "$owner_address" - jq -r '.allowance.allowance' <<<"$allowance_response" -} - -# This function is the same as above, but it also checks the expiration -function check_allowance() { - set -e - local contract_addr="$1" - local owner_key="$2" - local spender_key="$3" - local expiration="$4" - - log "querying allowance given to \"$spender_key\" by \"$owner_key\"" - local owner_address="${ADDRESS[$owner_key]}" - local spender_address="${ADDRESS[$spender_key]}" - local allowance_query='{"allowance":{"spender":"'"$spender_address"'","owner":"'"$owner_address"'","key":"'"${VK[$owner_key]}"'"}}' - local allowance_response - allowance_response="$(compute_query "$contract_addr" "$allowance_query")" - log "allowance response was: $allowance_response" - assert_eq "$(jq -r '.allowance.spender' <<<"$allowance_response")" "$spender_address" - assert_eq "$(jq -r '.allowance.owner' <<<"$allowance_response")" "$owner_address" - assert_eq "$(jq -r '.allowance.expiration' <<<"$allowance_response")" "$expiration" - jq -r '.allowance.allowance' <<<"$allowance_response" -} - -function log_test_header() { - log " ########### Starting ${FUNCNAME[1]} ###############################################################################################################################################" -} - -function extract_viewing_key_from_result() { - set -e - local tx_hash="$1" - local key="$2" - local viewing_key - - viewing_key_response="$(wait_for_compute_tx "$tx_hash" "waiting for viewing key for \"$key\" to be created")" - viewing_key="$(jq -ec '.answers[0].output_data_as_string' <<<"$viewing_key_response" | cut -d'\' -f 6 | cut -c2-)" - - log "viewing key for \"$key\" set to ${viewing_key}" - echo "$viewing_key" -} - -function test_viewing_key() { - set -e - local contract_addr="$1" - - log_test_header - - # common variables - local result - local tx_hash - - # query balance. Should fail. - local wrong_key - wrong_key="$(xxd -ps <<<'wrong-key')" - local balance_query - local expected_error='{"viewing_key_error":{"msg":"Wrong viewing key for this address or viewing key not set"}}' - for key in "${KEY[@]}"; do - log "querying balance for \"$key\" with wrong viewing key" - balance_query='{"balance":{"address":"'"${ADDRESS[$key]}"'","key":"'"$wrong_key"'"}}' - result="$(compute_query "$contract_addr" "$balance_query")" - assert_eq "$result" "$expected_error" - done - - # Create viewing keys - local create_viewing_key_message='{"create_viewing_key":{"entropy":"MyPassword123"}}' - local viewing_key_response - for key in "${KEY[@]}"; do - log "creating viewing key for \"$key\"" - tx_hash="$(compute_execute "$contract_addr" "$create_viewing_key_message" ${FROM[$key]} --gas 1400000)" - VK[$key]="$(extract_viewing_key_from_result "$tx_hash" "$key")" - - - if [[ "${VK[$key]}" =~ ^api_key_ ]]; then - log "viewing key \"$key\" seems valid" - else - log 'viewing key is invalid' - return 1 - fi - done - - # Check that all viewing keys are different despite using the same entropy - assert_ne "${VK[a]}" "${VK[b]}" - assert_ne "${VK[b]}" "${VK[c]}" - assert_ne "${VK[c]}" "${VK[d]}" - - # query balance. Should succeed. - local balance_query - for key in "${KEY[@]}"; do - balance_query='{"balance":{"address":"'"${ADDRESS[$key]}"'","key":"'"${VK[$key]}"'"}}' - log "querying balance for \"$key\" with correct viewing key" - result="$(compute_query "$contract_addr" "$balance_query")" - if ! silent jq -e '.balance.amount | tonumber' <<<"$result"; then - log "Balance query returned unexpected response: ${result@Q}" - return 1 - fi - done - - # Change viewing keys - local vk2_a - - log 'creating new viewing key for "a"' - tx_hash="$(compute_execute "$contract_addr" "$create_viewing_key_message" ${FROM[a]} --gas 1400000)" - vk2_a="$(extract_viewing_key_from_result "$tx_hash" "$key")" - assert_ne "${VK[a]}" "$vk2_a" - - # query balance with old keys. Should fail. - log 'querying balance for "a" with old viewing key' - local balance_query_a='{"balance":{"address":"'"${ADDRESS[a]}"'","key":"'"${VK[a]}"'"}}' - result="$(compute_query "$contract_addr" "$balance_query_a")" - assert_eq "$result" "$expected_error" - - # query balance with new keys. Should succeed. - log 'querying balance for "a" with new viewing key' - balance_query_a='{"balance":{"address":"'"${ADDRESS[a]}"'","key":"'"$vk2_a"'"}}' - result="$(compute_query "$contract_addr" "$balance_query_a")" - if ! silent jq -e '.balance.amount | tonumber' <<<"$result"; then - log "Balance query returned unexpected response: ${result@Q}" - return 1 - fi - - # Set the vk for "a" to the original vk - log 'setting the viewing key for "a" back to the first one' - local set_viewing_key_message='{"set_viewing_key":{"key":"'"${VK[a]}"'"}}' - tx_hash="$(compute_execute "$contract_addr" "$set_viewing_key_message" ${FROM[a]} --gas 1400000)" - viewing_key_response="$(wait_for_compute_tx "$tx_hash" "waiting for viewing key for "a" to be set")" - viewing_key_response="$(jq -ec '.answers[0].output_data_as_string' <<<"$viewing_key_response" | cut -d'\' -f 6 | cut -c2-)" - assert_eq "$viewing_key_response" "success" - - # try to use the new key - should fail - log 'querying balance for "a" with new viewing key' - balance_query_a='{"balance":{"address":"'"${ADDRESS[a]}"'","key":"'"$vk2_a"'"}}' - result="$(compute_query "$contract_addr" "$balance_query_a")" - assert_eq "$result" "$expected_error" - - # try to use the old key - should succeed - log 'querying balance for "a" with old viewing key' - balance_query_a='{"balance":{"address":"'"${ADDRESS[a]}"'","key":"'"${VK[a]}"'"}}' - result="$(compute_query "$contract_addr" "$balance_query_a")" - if ! silent jq -e '.balance.amount | tonumber' <<<"$result"; then - log "Balance query returned unexpected response: ${result@Q}" - return 1 - fi -} - -function test_permit() { - set -e - local contract_addr="$1" - - log_test_header - - # common variables - local result - local tx_hash - - # fail due to token not in permit - secretcli keys delete banana -yf || true - secretcli keys add banana - local wrong_contract=$(secretcli keys show -a banana) - - local permit - permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$wrong_contract"'"],"permissions":["balance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' - local permit_query - local expected_error="Error: query result: Generic error: Permit doesn't apply to token \"$contract_addr\", allowed tokens: [\"$wrong_contract\"]" - for key in "${KEY[@]}"; do - log "permit querying balance for \"$key\" with wrong permit for that contract" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'") - permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$wrong_contract"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' - result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" - assert_eq "$result" "$expected_error" - done - - # fail due to revoked permit - local permit - permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"to_be_revoked","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' - local permit_query - local expected_error - for key in "${KEY[@]}"; do - log "permit querying balance for \"$key\" with a revoked permit" - tx_hash="$(compute_execute "$contract_addr" '{"revoke_permit":{"permit_name":"to_be_revoked"}}' ${FROM[$key]} --gas 250000)" - wait_for_compute_tx "$tx_hash" "waiting for revoke_permit from \"$key\" to process" - - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'") - permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"to_be_revoked","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' - expected_error="Error: query result: Generic error: Permit \"to_be_revoked\" was revoked by account \"${ADDRESS[$key]}" - result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" - assert_eq "$result" "$expected_error" - done - - # fail due to params not matching params that were signed on - local permit - permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' - local permit_query - local expected_error - for key in "${KEY[@]}"; do - log "permit querying balance for \"$key\" with params not matching permit" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'") - permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"not_blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' - expected_error="Error: query result: Generic error: Failed to verify signatures for the given permit" - result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" - assert_eq "$result" "$expected_error" - done - - # fail balance query due to no balance permission - local permit_conf - permit_conf='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' - local permit - local permit_query - local expected_error - for key in "${KEY[@]}"; do - log "permit querying balance for \"$key\" without the right permission" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'") - permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}' - expected_error="Error: query result: Generic error: No permission to query balance, got permissions [History]" - result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" - assert_eq "$result" "$expected_error" - done - - # fail history query due to no history permission - local permit_conf - permit_conf='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' - local permit - local permit_query - local expected_error - for key in "${KEY[@]}"; do - log "permit querying history for \"$key\" without the right permission" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'") - - permit_query='{"with_permit":{"query":{"transfer_history":{"page_size":10}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' - expected_error="Error: query result: Generic error: No permission to query history, got permissions [Balance]" - result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" - assert_eq "$result" "$expected_error" - - permit_query='{"with_permit":{"query":{"transaction_history":{"page_size":10}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' - expected_error="Error: query result: Generic error: No permission to query history, got permissions [Balance]" - result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" - assert_eq "$result" "$expected_error" - done - - # fail allowance query due to no allowance permission - local permit_conf - permit_conf='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' - local permit - local permit_query - local expected_error - for key in "${KEY[@]}"; do - log "permit querying allowance for \"$key\" without the right permission" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'") - permit_query='{"with_permit":{"query":{"allowance":{"owner":"'"${ADDRESS[$key]}"'","spender":"'"${ADDRESS[$key]}"'"}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}' - expected_error="Error: query result: Generic error: No permission to query allowance, got permissions [History]" - result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" - assert_eq "$result" "$expected_error" - done - - # fail allowance query due to no permit signer not owner or spender - local permit - wrong_permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["allowance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' - local permit_query - local expected_error - log "permit querying allowance without signer being the owner or spender" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$wrong_permit"') --from a") - permit_query='{"with_permit":{"query":{"allowance":{"owner":"'"$wrong_contract"'","spender":"'"$wrong_contract"'"}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["allowance"]},"signature":'"$permit"'}}}' - expected_error="Error: query result: Generic error: Cannot query allowance. Requires permit for either owner \"$wrong_contract\" or spender \"$wrong_contract\", got permit for \"${ADDRESS[a]}" - result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" - assert_eq "$result" "$expected_error" - - # succeed balance query - local permit - local good_permit - good_permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' - local permit_query - local expected_output - for key in "${KEY[@]}"; do - log "permit querying balance for \"$key\"" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'") - permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' - expected_output="{\"balance\":{\"amount\":\"0\"}}" - result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" - assert_eq "$result" "$expected_output" - done - - # succeed history queries - local permit - local good_permit - good_permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' - local permit_query - local expected_output - for key in "${KEY[@]}"; do - log "permit querying history for \"$key\"" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'") - - permit_query='{"with_permit":{"query":{"transfer_history":{"page_size":10}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}' - expected_output="{\"transfer_history\":{\"txs\":[],\"total\":0}}" - result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" - assert_eq "$result" "$expected_output" - - permit_query='{"with_permit":{"query":{"transaction_history":{"page_size":10}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}' - expected_output="{\"transaction_history\":{\"txs\":[],\"total\":0}}" - result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" - assert_eq "$result" "$expected_output" - done - - # succeed allowance query - local permit - local good_permit - good_permit='{"account_number":"0","sequence":"0","chain_id":"blabla","msgs":[{"type":"query_permit","value":{"permit_name":"test","allowed_tokens":["'"$contract_addr"'"],"permissions":["allowance"]}}],"fee":{"amount":[{"denom":"uscrt","amount":"0"}],"gas":"1"},"memo":""}' - local permit_query - local expected_output - for key in "${KEY[@]}"; do - log "permit querying history for \"$key\"" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'") - - permit_query='{"with_permit":{"query":{"allowance":{"owner":"'"${ADDRESS[$key]}"'","spender":"'"${ADDRESS[$key]}"'"}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["allowance"]},"signature":'"$permit"'}}}' - expected_output="{\"allowance\":{\"spender\":\"${ADDRESS[$key]}\",\"owner\":\"${ADDRESS[$key]}\",\"allowance\":\"0\",\"expiration\":null}}" - result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" - assert_eq "$result" "$expected_output" - done -} - -function test_deposit() { - set -e - local contract_addr="$1" - - log_test_header - - local tx_hash - - local -A deposits=([a]=1000000 [b]=2000000 [c]=3000000 [d]=4000000) - local tx_hash - local native_tx - local timestamp - local block_height - for key in "${KEY[@]}"; do - tx_hash="$(deposit "$contract_addr" "$key" "${deposits[$key]}")" - native_tx="$(secretcli q tx "$tx_hash")" - - timestamp="$(unix_time_of_tx "$native_tx")" - block_height="$(jq -r '.height' <<<"$native_tx")" - quiet check_latest_tx_history_deposit "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" "${deposits[$key]}" "$timestamp" "$block_height" - done - - # Query the balances of the accounts and make sure they have the right balances. - for key in "${KEY[@]}"; do - assert_eq "$(get_balance "$contract_addr" "$key")" "${deposits[$key]}" - done - - # Try to overdraft - local redeem_message - local overdraft - local redeem_response - for key in "${KEY[@]}"; do - overdraft="$((deposits[$key] + 1))" - redeem_message='{"redeem":{"amount":"'"$overdraft"'","denom":"uscrt"}}' - tx_hash="$(compute_execute "$contract_addr" "$redeem_message" ${FROM[$key]} --gas 150000)" - # Notice the `!` before the command - it is EXPECTED to fail. - ! redeem_response="$(wait_for_compute_tx "$tx_hash" "waiting for overdraft from \"$key\" to process")" - log "trying to overdraft from \"$key\" was rejected" - assert_eq \ - "$(extract_exec_error "$redeem_response" "error: ")" \ - "insufficient funds to redeem: balance=${deposits[$key]}, required=$overdraft" - done - - # Withdraw Everything - local tx_hash - local native_tx - local timestamp - local block_height - for key in "${KEY[@]}"; do - tx_hash="$(redeem "$contract_addr" "$key" "${deposits[$key]}")" - native_tx="$(secretcli q tx "$tx_hash")" - timestamp="$(unix_time_of_tx "$native_tx")" - block_height="$(jq -r '.height' <<<"$native_tx")" - quiet check_latest_tx_history_redeem "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" "${deposits[$key]}" "$timestamp" "$block_height" - done - - # Check the balances again. They should all be empty - for key in "${KEY[@]}"; do - assert_eq "$(get_balance "$contract_addr" "$key")" 0 - done -} - -function unix_time_of_tx() { - set -e - local tx="$1" - - date -d "$(jq -r '.timestamp' <<<"$tx")" '+%s' -} - -function get_transfer_history() { - set -e - local contract_addr="$1" - local account="$2" - local viewing_key="$3" - local page_size="$4" - local page="$5" - - local transfer_history_query - local transfer_history_response - transfer_history_query='{"transfer_history":{"address":"'"$account"'","key":"'"$viewing_key"'","page_size":'"$page_size"',"page":'"$page"'}}' - transfer_history_response="$(compute_query "$contract_addr" "$transfer_history_query")" - log "$transfer_history_response" - # There's no good way of tracking the exact expected value, - # so we just check that the `total` field is a number - quiet jq -e '.transfer_history.total | numbers' <<<"$transfer_history_response" - jq -r '.transfer_history.txs' <<<"$transfer_history_response" -} - -# This function checks that the latest tx history for the account matches -# the expected parameters. -# The id of the tx is printed out. -function check_latest_transfer_history() { - set -e - local contract_addr="$1" - local account="$2" - local viewing_key="$3" - local sender="$4" - local from="$5" - local receiver="$6" - local amount="$7" - local block_time="$8" - local block_height="$9" - - local txs - local tx - - txs="$(get_transfer_history "$contract_addr" "$account" "$viewing_key" 1 0)" - silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response - tx="$(jq -r '.[0]' <<<"$txs")" - assert_eq "$(jq -r '.sender' <<<"$tx")" "$sender" - assert_eq "$(jq -r '.from' <<<"$tx")" "$from" - assert_eq "$(jq -r '.receiver' <<<"$tx")" "$receiver" - assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" - assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' - assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" - assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" - - jq -r '.id' <<<"$tx" -} - -function get_transaction_history() { - set -e - local contract_addr="$1" - local account="$2" - local viewing_key="$3" - local page_size="$4" - local page="$5" - - local transaction_history_query - local transaction_history_response - transaction_history_query='{"transaction_history":{"address":"'"$account"'","key":"'"$viewing_key"'","page_size":'"$page_size"',"page":'"$page"'}}' - transaction_history_response="$(compute_query "$contract_addr" "$transaction_history_query")" - log "$transaction_history_response" - # There's no good way of tracking the exact expected value, - # so we just check that the `total` field is a number - quiet jq -e '.transaction_history.total | numbers' <<<"$transaction_history_response" - jq -r '.transaction_history.txs' <<<"$transaction_history_response" -} - -function check_latest_tx_history_transfer() { - set -e - local contract_addr="$1" - local account="$2" - local viewing_key="$3" - local sender="$4" - local from="$5" - local recipient="$6" - local amount="$7" - local block_time="$8" - local block_height="$9" - - local txs - local tx - - txs="$(get_transaction_history "$contract_addr" "$account" "$viewing_key" 1 0)" - silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response - tx="$(jq -r '.[0]' <<<"$txs")" - assert_eq "$(jq -r '.action.transfer.sender' <<<"$tx")" "$sender" - assert_eq "$(jq -r '.action.transfer.from' <<<"$tx")" "$from" - assert_eq "$(jq -r '.action.transfer.recipient' <<<"$tx")" "$recipient" - assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" - assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' - assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" - assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" - - jq -r '.id' <<<"$tx" -} - -function check_latest_tx_history_mint() { - set -e - local contract_addr="$1" - local account="$2" - local viewing_key="$3" - local minter="$4" - local recipient="$5" - local amount="$6" - local block_time="$7" - local block_height="$8" - - local txs - local tx - - txs="$(get_transaction_history "$contract_addr" "$account" "$viewing_key" 1 0)" - silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response - tx="$(jq -r '.[0]' <<<"$txs")" - assert_eq "$(jq -r '.action.mint.minter' <<<"$tx")" "$minter" - assert_eq "$(jq -r '.action.mint.recipient' <<<"$tx")" "$recipient" - assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" - assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' - assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" - assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" - - jq -r '.id' <<<"$tx" -} - -function check_latest_tx_history_burn() { - set -e - local contract_addr="$1" - local account="$2" - local viewing_key="$3" - local burner="$4" - local owner="$5" - local amount="$6" - local block_time="$7" - local block_height="$8" - - local txs - local tx - - txs="$(get_transaction_history "$contract_addr" "$account" "$viewing_key" 1 0)" - silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response - tx="$(jq -r '.[0]' <<<"$txs")" - assert_eq "$(jq -r '.action.burn.burner' <<<"$tx")" "$burner" - assert_eq "$(jq -r '.action.burn.owner' <<<"$tx")" "$owner" - assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" - assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' - assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" - assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" - - jq -r '.id' <<<"$tx" -} - -function check_latest_tx_history_deposit() { - set -e - local contract_addr="$1" - local account="$2" - local viewing_key="$3" - local amount="$4" - local block_time="$5" - local block_height="$6" - - local txs - local tx - - txs="$(get_transaction_history "$contract_addr" "$account" "$viewing_key" 1 0)" - silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response - tx="$(jq -r '.[0]' <<<"$txs")" - quiet jq -e '.action.deposit | objects' <<<"$tx" - assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" - assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'uscrt' - assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" - assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" - - jq -r '.id' <<<"$tx" -} - -function check_latest_tx_history_redeem() { - set -e - local contract_addr="$1" - local account="$2" - local viewing_key="$3" - local amount="$4" - local block_time="$5" - local block_height="$6" - - local txs - local tx - - txs="$(get_transaction_history "$contract_addr" "$account" "$viewing_key" 1 0)" - silent jq -e 'length == 1' <<<"$txs" # just make sure we're not getting a weird response - tx="$(jq -r '.[0]' <<<"$txs")" - quiet jq -e '.action.redeem | objects' <<<"$tx" - assert_eq "$(jq -r '.coins.amount' <<<"$tx")" "$amount" - assert_eq "$(jq -r '.coins.denom' <<<"$tx")" 'SSCRT' - assert_eq "$(jq -r '.block_time' <<<"$tx")" "$block_time" - assert_eq "$(jq -r '.block_height' <<<"$tx")" "$block_height" - - jq -r '.id' <<<"$tx" -} - -function test_transfer() { - set -e - local contract_addr="$1" - - log_test_header - - local tx_hash - - # Check "a" and "b" don't have any funds - assert_eq "$(get_balance "$contract_addr" 'a')" 0 - assert_eq "$(get_balance "$contract_addr" 'b')" 0 - - # Deposit to "a" - quiet deposit "$contract_addr" 'a' 1000000 - - # Try to transfer more than "a" has - log 'transferring funds from "a" to "b", but more than "a" has' - local transfer_message='{"transfer":{"recipient":"'"${ADDRESS[b]}"'","amount":"1000001"}}' - local transfer_response - tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[a]} --gas 150000)" - # Notice the `!` before the command - it is EXPECTED to fail. - ! transfer_response="$(wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "b" to process')" - log "trying to overdraft from \"a\" to transfer to \"b\" was rejected" - assert_eq "$(extract_exec_error "$transfer_response" "error: ")" "insufficient funds: balance=1000000, required=1000001" - - # Check both a and b, that their last transaction is not for 1000001 uscrt - local txs - for key in a b; do - log "querying the transfer history of \"$key\"" - txs="$(get_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" 1 0)" - silent jq -e 'length <= 1' <<<"$txs" # just make sure we're not getting a weird response - if silent jq -e 'length == 1' <<<"$txs"; then - assert_ne "$(jq -r '.[0].coins.amount' <<<"$txs")" 1000001 - fi - done - - # Transfer from "a" to "b" - log 'transferring funds from "a" to "b"' - local transfer_message='{"transfer":{"recipient":"'"${ADDRESS[b]}"'","amount":"400000"}}' - local transfer_response - tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[a]} --gas 200000)" - transfer_response="$(data_of wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "b" to process')" - assert_eq "$transfer_response" "$(pad_space '{"transfer":{"status":"success"}}' | sed 's/ //g')" - - local native_tx - native_tx="$(secretcli q tx "$tx_hash")" - local timestamp - timestamp="$(unix_time_of_tx "$native_tx")" - local block_height - block_height="$(jq -r '.height' <<<"$native_tx")" - - # Check for both "a" and "b" that they recorded the transfer - local -A tx_ids - local tx_id - for key in a b; do - log "querying the transfer history of \"$key\"" - tx_ids[$key]="$( - check_latest_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ - "${ADDRESS[a]}" "${ADDRESS[a]}" "${ADDRESS[b]}" 400000 "$timestamp" "$block_height" - )" - tx_id="$( - check_latest_tx_history_transfer "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ - "${ADDRESS[a]}" "${ADDRESS[a]}" "${ADDRESS[b]}" 400000 "$timestamp" "$block_height" - )" - assert_eq "$tx_id" "${tx_ids[$key]}" - done - - assert_eq "${tx_ids[a]}" "${tx_ids[b]}" - log 'The transfer was recorded correctly in the transaction history' - - # Check that "a" has fewer funds - assert_eq "$(get_balance "$contract_addr" 'a')" 600000 - - # Check that "b" has the funds that "a" deposited - assert_eq "$(get_balance "$contract_addr" 'b')" 400000 - - # Redeem both accounts - redeem "$contract_addr" a 600000 - redeem "$contract_addr" b 400000 - # Send the funds back - quiet secretcli tx bank send b "${ADDRESS[a]}" 400000uscrt -y -b block -} - -RECEIVER_ADDRESS='' - -function create_receiver_contract() { - set -e - local init_msg - - if [[ "$RECEIVER_ADDRESS" != '' ]]; then - log 'Receiver contract already exists' - echo "$RECEIVER_ADDRESS" - return 0 - fi - - init_msg='{"count":0}' - RECEIVER_ADDRESS="$(create_contract 'tests/example-receiver' "$init_msg")" - - log "uploaded receiver contract to $RECEIVER_ADDRESS" - echo "$RECEIVER_ADDRESS" -} - -# This function exists so that we can reset the state as much as possible between different tests -function redeem_receiver() { - set -e - local receiver_addr="$1" - local snip20_addr="$2" - local to_addr="$3" - local amount="$4" - - local tx_hash - local redeem_tx - local transfer_attributes - - log 'fetching snip20 hash' - local snip20_hash - snip20_hash="$(secretcli query compute contract-hash "$snip20_addr")" - - local redeem_message='{"redeem":{"addr":"'"$snip20_addr"'","hash":"'"${snip20_hash:2}"'","to":"'"$to_addr"'","amount":"'"$amount"'","denom":"uscrt"}}' - tx_hash="$(compute_execute "$receiver_addr" "$redeem_message" ${FROM[a]} --gas 300000)" - redeem_tx="$(wait_for_tx "$tx_hash" "waiting for redeem from receiver at \"$receiver_addr\" to process")" - # log "$redeem_tx" - transfer_attributes="$(jq -r '.logs[0].events[] | select(.type == "transfer") | .attributes' <<<"$redeem_tx")" - assert_eq "$(jq -r '.[] | select(.key == "recipient") | .value' <<<"$transfer_attributes")" "$receiver_addr"$'\n'"$to_addr" - assert_eq "$(jq -r '.[] | select(.key == "amount") | .value' <<<"$transfer_attributes")" "${amount}uscrt"$'\n'"${amount}uscrt" - log "redeem response for \"$receiver_addr\" returned ${amount}uscrt" -} - -function register_receiver() { - set -e - local receiver_addr="$1" - local snip20_addr="$2" - - local tx_hash - - log 'fetching snip20 hash' - local snip20_hash - snip20_hash="$(secretcli query compute contract-hash "$snip20_addr")" - - log 'registering with snip20' - local register_message='{"register":{"reg_addr":"'"$snip20_addr"'","reg_hash":"'"${snip20_hash:2}"'"}}' - tx_hash="$(compute_execute "$receiver_addr" "$register_message" ${FROM[a]} --gas 300000)" - - # we throw away the output since we know it's empty - local register_tx - register_tx="$(wait_for_compute_tx "$tx_hash" 'Waiting for receiver registration')" - - assert_eq \ - "$(jq -r '.output_logs[] | select(.type == "wasm") | .attributes[] | select(.key == "register_status ") | .value' <<<"$register_tx")" \ - 'success' - log 'receiver registered successfully' -} - -function test_send() { - set -e - local contract_addr="$1" - local skip_register_receiver="$2" - - log_test_header - - local receiver_addr - receiver_addr="$(create_receiver_contract)" - local receiver_hash - receiver_hash="$(secretcli q compute contract-hash $receiver_addr | sed 's/^0x//')" - - if [ "$skip_register_receiver" != "skip-register" ]; then - register_receiver "$receiver_addr" "$contract_addr" - fi - - local tx_hash - - # Check "a" and "b" don't have any funds - assert_eq "$(get_balance "$contract_addr" 'a')" 0 - assert_eq "$(get_balance "$contract_addr" 'b')" 0 - - # Deposit to "a" - quiet deposit "$contract_addr" 'a' 1000000 - - # Try to send more than "a" has - log 'sending funds from "a" to "b", but more than "a" has' - local send_message - send_message='{"send":{"recipient":"'"${ADDRESS[b]}"'","amount":"1000001"}}' - local send_response - tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[a]} --gas 150000)" - # Notice the `!` before the command - it is EXPECTED to fail. - ! send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to "b" to process')" - log "trying to overdraft from \"a\" to send to \"b\" was rejected" - - assert_eq "$(extract_exec_error "$send_response" "error: ")" "insufficient funds: balance=1000000, required=1000001" - - # Check both a and b, that their last transaction is not for 1000001 uscrt - local txs - for key in a b; do - log "querying the transfer history of \"$key\"" - txs="$(get_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" 1 0)" - silent jq -e 'length <= 1' <<<"$txs" # just make sure we're not getting a weird response - if silent jq -e 'length == 1' <<<"$txs"; then - assert_ne "$(jq -r '.[0].coins.amount' <<<"$txs")" 1000001 - fi - done - - # Query receiver state before Send - local receiver_state - local receiver_state_query='{"get_count":{}}' - receiver_state="$(compute_query "$receiver_addr" "$receiver_state_query")" - local original_count - original_count="$(jq -r '.count' <<<"$receiver_state")" - - # Send from "a" to the receiver with message to the Receiver - log 'sending funds from "a" to the Receiver, with message to the Receiver' - local receiver_msg='{"increment":{}}' - receiver_msg="$(base64 <<<"$receiver_msg")" - - if [ "$skip_register_receiver" = "skip-register" ]; then - send_message='{"send":{"recipient":"'"$receiver_addr"'","recipient_code_hash":"'"$receiver_hash"'","amount":"400000","msg":"'$receiver_msg'"}}' - else - send_message='{"send":{"recipient":"'"$receiver_addr"'","amount":"400000","msg":"'$receiver_msg'"}}' - fi - - local send_response - - tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[a]} --gas 300000)" - send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to the Receiver to process')" - assert_eq \ - "$(jq -r '.output_logs[0].attributes[] | select(.key == "count") | .value' <<<"$send_response")" \ - "$((original_count + 1))" - log 'received send response' - - local native_tx - native_tx="$(secretcli q tx "$tx_hash")" - local timestamp - timestamp="$(unix_time_of_tx "$native_tx")" - local block_height - block_height="$(jq -r '.height' <<<"$native_tx")" - - # Check that the receiver got the message - log 'checking whether state was updated in the receiver' - receiver_state_query='{"get_count":{}}' - receiver_state="$(compute_query "$receiver_addr" "$receiver_state_query")" - local new_count - new_count="$(jq -r '.count' <<<"$receiver_state")" - assert_eq "$((original_count + 1))" "$new_count" - log 'receiver contract received the message' - - # Check that "a" recorded the transfer - log 'querying the transfer history of "a"' - local tx_id1 - local tx_id2 - tx_id1="$(check_latest_transfer_history "$contract_addr" "${ADDRESS[a]}" "${VK[a]}" \ - "${ADDRESS[a]}" "${ADDRESS[a]}" "$receiver_addr" 400000 "$timestamp" "$block_height")" - tx_id2="$(check_latest_tx_history_transfer "$contract_addr" "${ADDRESS[a]}" "${VK[a]}" \ - "${ADDRESS[a]}" "${ADDRESS[a]}" "$receiver_addr" 400000 "$timestamp" "$block_height")" - assert_eq "$tx_id1" "$tx_id2" - - # Check that "a" has fewer funds - assert_eq "$(get_balance "$contract_addr" 'a')" 600000 - - # Test that send callback failure also denies the transfer - log 'sending funds from "a" to the Receiver, with a "Fail" message to the Receiver' - receiver_msg='{"fail":{}}' - receiver_msg="$(base64 <<<"$receiver_msg")" - - if [ "$skip_register_receiver" = "skip-register" ]; then - send_message='{"send":{"recipient":"'"$receiver_addr"'","recipient_code_hash":"'"$receiver_hash"'","amount":"400000","msg":"'$receiver_msg'"}}' - else - send_message='{"send":{"recipient":"'"$receiver_addr"'","amount":"400000","msg":"'$receiver_msg'"}}' - fi - - tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[a]} --gas 300000)" - # Notice the `!` before the command - it is EXPECTED to fail. - ! send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to the Receiver to process')" - assert_eq "$(extract_exec_error "$send_response" "error: ")" 'intentional failure' # This comes from the receiver contract - - # Check that "a" does not have fewer funds - assert_eq "$(get_balance "$contract_addr" 'a')" 600000 # This is the same balance as before - - log 'a failure in the callback caused the transfer to roll back, as expected' - - # redeem both accounts - redeem "$contract_addr" 'a' 600000 - redeem_receiver "$receiver_addr" "$contract_addr" "${ADDRESS[a]}" 400000 -} - -function set_minters() { - # set -e - local minters - - if (( $# == 0 )); then - minters='[]' - else - minters='["' - - for minter in "${@:1:$(($# - 1))}"; do - minters="${minters}${minter}"'","' - done - - minters="${minters}${*: -1}"'"]' - fi - - log "$minters" - - local set_minters_message='{"set_minters":{"minters":'"$minters"'}}' - local tx_hash - local response - tx_hash="$(compute_execute "$contract_addr" "$set_minters_message" ${FROM[a]} --gas 150000)" - response="$(data_of wait_for_compute_tx "$tx_hash" "waiting for minter set to update")" - assert_eq "$response" "$(pad_space '{"set_minters":{"status":"success"}}' | sed 's/ //g')" - log "set the minters to these addresses: $*" -} - -# This test also tests TokenInfo -function test_burn() { - set -e - local contract_addr="$1" - - log_test_header - - local token_info_response - local burn_response - local tx_hash - - set_minters "${ADDRESS[b]}" - - # minting from the wrong account should fail - set +e; tx_hash="$(mint "$contract_addr" 'a' "${ADDRESS[a]}" 1000000)" - local _res="$?"; set -e; - if (( _res != 0 )); then - assert_eq "$(extract_exec_error "$(secretcli query compute tx "$tx_hash")" "error: ")" 'Minting is allowed to minter accounts only' - log 'minting from the wrong account failed as expected' - else - log 'minting was allowed from a non-minter address!' - return 1 - fi - - # Mint to a using b - local native_tx - local timestamp - local block_height - tx_hash="$(mint "$contract_addr" 'b' "${ADDRESS[a]}" 1000000)" - native_tx="$(secretcli q tx "$tx_hash")" - timestamp="$(unix_time_of_tx "$native_tx")" - block_height="$(jq -r '.height' <<<"$native_tx")" - check_latest_tx_history_mint "$contract_addr" "${ADDRESS[a]}" "${VK[a]}" \ - "${ADDRESS[b]}" "${ADDRESS[a]}" 1000000 "$timestamp" "$block_height" - - # Check total supply - token_info_response="$(get_token_info "$contract_addr")" - log 'token info response was' "$token_info_response" - assert_eq "$(jq -r '.token_info.total_supply' <<<"$token_info_response")" 1000000 - - # Try to over-burn - local burn_message='{"burn":{"amount":"10000000"}}' # 110% - local burn_response - tx_hash="$(compute_execute "$contract_addr" "$burn_message" ${FROM[a]} --gas 150000)" - ! burn_response="$(wait_for_compute_tx "$tx_hash" 'waiting for burn for "a" to process')" - assert_eq "$(extract_exec_error "$burn_response" "error: ")" 'insufficient funds to burn: balance=1000000, required=10000000' - - # Check "a" balance - should not have changes - assert_eq "$(get_balance "$contract_addr" 'a')" 1000000 - - # Check total supply - token_info_response="$(get_token_info "$contract_addr")" - log 'token info response was' "$token_info_response" - assert_eq "$(jq -r '.token_info.total_supply' <<<"$token_info_response")" 1000000 - - # Try to burn - quiet burn "$contract_addr" 'a' 100000 # 10% - - # Check "a" balance - assert_eq "$(get_balance "$contract_addr" 'a')" 900000 - - # Check total supply - token_info_response="$(get_token_info "$contract_addr")" - log 'token info response was' "$token_info_response" - assert_eq "$(jq -r '.token_info.total_supply' <<<"$token_info_response")" 900000 - - # Burn the rest of the balance - tx_hash="$(burn "$contract_addr" 'a' 900000)" - log "the tx_hash is $tx_hash" - native_tx="$(secretcli q tx "$tx_hash")" - timestamp="$(unix_time_of_tx "$native_tx")" - block_height="$(jq -r '.height' <<<"$native_tx")" - check_latest_tx_history_burn "$contract_addr" "${ADDRESS[a]}" "${VK[a]}" \ - "${ADDRESS[a]}" "${ADDRESS[a]}" 900000 "$timestamp" "$block_height" - - token_info_response="$(get_token_info "$contract_addr")" - log 'token info response was' "$token_info_response" - assert_eq "$(jq -r '.token_info.total_supply' <<<"$token_info_response")" 0 -} - -function test_transfer_from() { - set -e - local contract_addr="$1" - - log_test_header - - local tx_hash - - # Check "a", "b", and "c" don't have any funds - assert_eq "$(get_balance "$contract_addr" 'a')" 0 - assert_eq "$(get_balance "$contract_addr" 'b')" 0 - assert_eq "$(get_balance "$contract_addr" 'c')" 0 - - # Check that the allowance given to "b" by "a" is zero - assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 0 - - # Deposit to "a" - quiet deposit "$contract_addr" 'a' 1000000 - - # Make "a" give allowance to "b" - assert_eq "$(increase_allowance "$contract_addr" 'a' 'b' 1000000)" 1000000 - assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 1000000 - - # Try to transfer from "a", using "b" more than "a" has allowed - log 'transferring funds from "a" to "c" using "b", but more than "a" has allowed' - local transfer_message='{"transfer_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"${ADDRESS[c]}"'","amount":"1000001"}}' - local transfer_response - tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[b]} --gas 150000)" - # Notice the `!` before the command - it is EXPECTED to fail. - ! transfer_response="$(wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "c" by "b" to process')" - log "trying to overdraft from \"a\" to transfer to \"c\" using \"b\" was rejected" - assert_eq "$(extract_exec_error "$transfer_response" "error: ")" "insufficient allowance: allowance=1000000, required=1000001" - - # Check both "a", "b", and "c", that their last transaction is not for 1000001 uscrt - local txs - for key in a b c; do - log "querying the transfer history of \"$key\"" - txs="$(get_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" 1 0)" - silent jq -e 'length <= 1' <<<"$txs" # just make sure we're not getting a weird response - if silent jq -e 'length == 1' <<<"$txs"; then - assert_ne "$(jq -r '.[0].coins.amount' <<<"$txs")" 1000001 - fi - done - - # Transfer from "a" to "c" using "b" - log 'transferring funds from "a" to "c" using "b"' - local transfer_message='{"transfer_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"${ADDRESS[c]}"'","amount":"400000"}}' - local transfer_response - tx_hash="$(compute_execute "$contract_addr" "$transfer_message" ${FROM[b]} --gas 250000)" - transfer_response="$(data_of wait_for_compute_tx "$tx_hash" 'waiting for transfer from "a" to "c" by "b" to process')" - assert_eq "$transfer_response" "$(pad_space '{"transfer_from":{"status":"success"}}' | sed 's/ //g')" - - local native_tx - native_tx="$(secretcli q tx "$tx_hash")" - local timestamp - timestamp="$(unix_time_of_tx "$native_tx")" - local block_height - block_height="$(jq -r '.height' <<<"$native_tx")" - - # Check for both "a", "b", and "c" that they recorded the transfer - local -A tx_ids - local tx_id - for key in a b c; do - log "querying the transfer history of \"$key\"" - tx_ids[$key]="$( - check_latest_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ - "${ADDRESS[b]}" "${ADDRESS[a]}" "${ADDRESS[c]}" 400000 "$timestamp" "$block_height" - )" - tx_id="$( - check_latest_tx_history_transfer "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ - "${ADDRESS[b]}" "${ADDRESS[a]}" "${ADDRESS[c]}" 400000 "$timestamp" "$block_height" - )" - assert_eq "$tx_id" "${tx_ids[$key]}" - done - - assert_eq "${tx_ids[a]}" "${tx_ids[b]}" - assert_eq "${tx_ids[b]}" "${tx_ids[c]}" - log 'The transfer was recorded correctly in the transaction history' - - # Check that "a" has fewer funds - assert_eq "$(get_balance "$contract_addr" 'a')" 600000 - - # Check that "b" has the same funds still, but less allowance - assert_eq "$(get_balance "$contract_addr" 'b')" 0 - assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 600000 - - # Check that "c" has the funds that "b" deposited from "a" - assert_eq "$(get_balance "$contract_addr" 'c')" 400000 - - # Redeem both accounts - redeem "$contract_addr" a 600000 - redeem "$contract_addr" c 400000 - # Reset allowance - assert_eq "$(decrease_allowance "$contract_addr" 'a' 'b' 600000)" 0 - assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 0 - # Send the funds back - quiet secretcli tx bank send c "${ADDRESS[a]}" 400000uscrt -y -b block -} - -function test_send_from() { - set -e - local contract_addr="$1" - local skip_register_receiver="$2" - - log_test_header - - local receiver_addr - receiver_addr="$(create_receiver_contract)" - local receiver_hash - receiver_hash="$(secretcli q compute contract-hash $receiver_addr | sed 's/^0x//')" - - if [ "$skip_register_receiver" != "skip-register" ]; then - register_receiver "$receiver_addr" "$contract_addr" - fi - - local tx_hash - - # Check "a" and "b" don't have any funds - assert_eq "$(get_balance "$contract_addr" 'a')" 0 - assert_eq "$(get_balance "$contract_addr" 'b')" 0 - assert_eq "$(get_balance "$contract_addr" 'c')" 0 - - # Check that the allowance given to "b" by "a" is zero - assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 0 - - # Deposit to "a" - quiet deposit "$contract_addr" 'a' 1000000 - - # Make "a" give allowance to "b" - assert_eq "$(increase_allowance "$contract_addr" 'a' 'b' 1000000)" 1000000 - assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 1000000 - - # TTry to send from "a", using "b" more than "a" has allowed - log 'sending funds from "a" to "c" using "b", but more than "a" has allowed' - local send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"${ADDRESS[c]}"'","amount":"1000001"}}' - local send_response - tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[b]} --gas 150000)" - # Notice the `!` before the command - it is EXPECTED to fail. - ! send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to "c" by "b" to process')" - log "trying to overdraft from \"a\" to send to \"c\" using \"b\" was rejected" - assert_eq "$(extract_exec_error "$send_response" "error: ")" "insufficient allowance: allowance=1000000, required=1000001" - - # Check both a and b, that their last transaction is not for 1000001 uscrt - local txs - for key in a b c; do - log "querying the transfer history of \"$key\"" - txs="$(get_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" 1 0)" - silent jq -e 'length <= 1' <<<"$txs" # just make sure we're not getting a weird response - if silent jq -e 'length == 1' <<<"$txs"; then - assert_ne "$(jq -r '.[0].coins.amount' <<<"$txs")" 1000001 - fi - done - - # Query receiver state before Send - local receiver_state - local receiver_state_query='{"get_count":{}}' - receiver_state="$(compute_query "$receiver_addr" "$receiver_state_query")" - local original_count - original_count="$(jq -r '.count' <<<"$receiver_state")" - - # Send from "a", using "b", to the receiver with message to the Receiver - log 'sending funds from "a", using "b", to the Receiver, with message to the Receiver' - local receiver_msg='{"increment":{}}' - receiver_msg="$(base64 <<<"$receiver_msg")" - - local send_message - if [ "$skip_register_receiver" = "skip-register" ]; then - send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"$receiver_addr"'","recipient_code_hash":"'"$receiver_hash"'","amount":"400000","msg":"'$receiver_msg'"}}' - else - send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"$receiver_addr"'","amount":"400000","msg":"'$receiver_msg'"}}' - fi - - local send_response - tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[b]} --gas 302000)" - send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to the Receiver to process')" - assert_eq \ - "$(jq -r '.output_logs[0].attributes[] | select(.key == "count") | .value' <<<"$send_response")" \ - "$((original_count + 1))" - log 'received send response' - - local native_tx - native_tx="$(secretcli q tx "$tx_hash")" - local timestamp - timestamp="$(unix_time_of_tx "$native_tx")" - local block_height - block_height="$(jq -r '.height' <<<"$native_tx")" - - # Check that the receiver got the message - log 'checking whether state was updated in the receiver' - receiver_state_query='{"get_count":{}}' - receiver_state="$(compute_query "$receiver_addr" "$receiver_state_query")" - local new_count - new_count="$(jq -r '.count' <<<"$receiver_state")" - assert_eq "$((original_count + 1))" "$new_count" - log 'receiver contract received the message' - - # Check that "a" recorded the transfer - local -A tx_ids - local tx_id - for key in a b; do - log "querying the transfer history of \"$key\"" - tx_ids[$key]="$( - check_latest_transfer_history "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ - "${ADDRESS[b]}" "${ADDRESS[a]}" "$receiver_addr" 400000 "$timestamp" "$block_height" - )" - tx_id="$( - check_latest_tx_history_transfer "$contract_addr" "${ADDRESS[$key]}" "${VK[$key]}" \ - "${ADDRESS[b]}" "${ADDRESS[a]}" "$receiver_addr" 400000 "$timestamp" "$block_height" - )" - assert_eq "$tx_id" "${tx_ids[$key]}" - done - - assert_eq "${tx_ids[a]}" "${tx_ids[b]}" - log 'The transfer was recorded correctly in the transaction history' - - # Check that "a" has fewer funds - assert_eq "$(get_balance "$contract_addr" 'a')" 600000 - - # Check that "b" has the same funds still, but less allowance - assert_eq "$(get_balance "$contract_addr" 'b')" 0 - assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 600000 - - # Test that send callback failure also denies the transfer - log 'sending funds from "a", using "b", to the Receiver, with a "Fail" message to the Receiver' - receiver_msg='{"fail":{}}' - receiver_msg="$(base64 <<<"$receiver_msg")" - - if [ "$skip_register_receiver" = "skip-register" ]; then - send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"$receiver_addr"'","recipient_code_hash":"'"$receiver_hash"'","amount":"400000","msg":"'$receiver_msg'"}}' - else - send_message='{"send_from":{"owner":"'"${ADDRESS[a]}"'","recipient":"'"$receiver_addr"'","amount":"400000","msg":"'$receiver_msg'"}}' - fi - - tx_hash="$(compute_execute "$contract_addr" "$send_message" ${FROM[b]} --gas 300000)" - # Notice the `!` before the command - it is EXPECTED to fail. - ! send_response="$(wait_for_compute_tx "$tx_hash" 'waiting for send from "a" to the Receiver to process')" - assert_eq "$(extract_exec_error "$send_response" "error: ")" 'intentional failure' # This comes from the receiver contract - - # Check that "a" does not have fewer funds - assert_eq "$(get_balance "$contract_addr" 'a')" 600000 # This is the same balance as before - - log 'a failure in the callback caused the transfer to roll back, as expected' - - # redeem both accounts - redeem "$contract_addr" 'a' 600000 - redeem_receiver "$receiver_addr" "$contract_addr" "${ADDRESS[a]}" 400000 - # Reset allowance - assert_eq "$(decrease_allowance "$contract_addr" 'a' 'b' 600000)" 0 - assert_eq "$(get_allowance "$contract_addr" 'a' 'b')" 0 -} - -function main() { - log ' <####> Starting integration tests <####>' - log "secretcli version in the docker image is: $(secretcli version)" - - secretcli tx bank send a $(secretcli keys show -a c) 100000000000uscrt -y -b block > /dev/null - secretcli tx bank send a $(secretcli keys show -a d) 100000000000uscrt -y -b block > /dev/null - - local prng_seed - prng_seed="$(base64 <<<'enigma-rocks')" - local init_msg - init_msg='{"name":"secret-secret","admin":"'"${ADDRESS[a]}"'","symbol":"SSCRT","decimals":6,"initial_balances":[],"prng_seed":"'"$prng_seed"'","config":{"public_total_supply":true,"enable_deposit":true,"enable_redeem":true,"enable_mint":true,"enable_burn":true},"supported_denoms":["uscrt"]}' - contract_addr="$(create_contract '.' "$init_msg")" - - # To make testing faster, check the logs and try to reuse the deployed contract and VKs from previous runs. - # Remember to comment out the contract deployment and `test_viewing_key` if you do. -# local contract_addr='secret18vd8fpwxzck93qlwghaj6arh4p7c5n8978vsyg' -# VK[a]='api_key_U6FcuhP2km6UHtYeFSyaZbggcgMJQAiTMlNWV3X4iXQ=' -# VK[b]='api_key_YoQlmqnOkkEoh81XzFkiZ3z7+ZAJh9kyFXvtaMBhiFU=' -# VK[c]='api_key_/cdkitEbzaHZA41OB6cGcz1XGnQk6LYTAfSBWTOU5aQ=' -# VK[d]='api_key_WQYkuGOco/mSHgtKWG0f7b2UcrSG3s1fIqm1X/wIGDo=' - - log "contract address: $contract_addr" - - wait_for_tx "$(tx_of secretcli tx bank send "${ADDRESS[a]}" "${ADDRESS[b]}" 100000000uscrt -y)" "waiting for send to b" - wait_for_tx "$(tx_of secretcli tx bank send "${ADDRESS[a]}" "${ADDRESS[c]}" 100000000uscrt -y)" "waiting for send to c" - wait_for_tx "$(tx_of secretcli tx bank send "${ADDRESS[a]}" "${ADDRESS[d]}" 100000000uscrt -y)" "waiting for send to d" - - # This first test also sets the `VK[*]` global variables that are used in the other tests - test_viewing_key "$contract_addr" - test_permit "$contract_addr" - test_deposit "$contract_addr" - test_transfer "$contract_addr" - test_send "$contract_addr" register - test_send "$contract_addr" skip-register - test_burn "$contract_addr" - test_transfer_from "$contract_addr" - test_send_from "$contract_addr" register - test_send_from "$contract_addr" skip-register - - log 'Tests completed successfully' - - # If everything else worked, return successful status - return 0 -} - -main "$@" diff --git a/contracts/snip20_migration/Cargo.toml b/contracts/snip20_migration/Cargo.toml deleted file mode 100644 index f653566..0000000 --- a/contracts/snip20_migration/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "snip20_migration" -version = "0.1.0" -authors = ["jackb7"] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] -debug-print = ["shade-protocol/debug-print"] - -[dependencies] -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ - "snip20", - "snip20_migration", - "admin", -] } -schemars = "0.7" - -[dev-dependencies] -rstest = "0.15" -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["multi-test", "admin"] } -serde_json = { version = "1.0.67" } -shade-multi-test = { version = "0.1.0", path = "../../packages/multi_test", features = [ "snip20", "snip20_migration", "admin" ] } diff --git a/contracts/snip20_migration/Makefile b/contracts/snip20_migration/Makefile deleted file mode 100644 index 5f026e8..0000000 --- a/contracts/snip20_migration/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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 - -# 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 \ - ghcr.io/scrtlabs/localsecret:v1.6.0-rc.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 9091:9091 -p 26657:26657 -p 26656:26656 -p 1317:1317 -p 5000:5000 \ - -v $$(pwd):/root/code \ - --name secretdev ghcr.io/scrtlabs/localsecret:v1.6.0-rc.3 - -# 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/snip20_migration/src/contract.rs b/contracts/snip20_migration/src/contract.rs deleted file mode 100644 index f24c3c7..0000000 --- a/contracts/snip20_migration/src/contract.rs +++ /dev/null @@ -1,175 +0,0 @@ -use crate::storage::*; -use shade_protocol::{ - admin::helpers::{validate_admin, AdminPermissions}, - c_std::{ - shd_entry_point, - to_binary, - Addr, - Binary, - CosmosMsg, - Deps, - DepsMut, - Env, - MessageInfo, - Response, - StdError, - StdResult, - Uint128, - }, - contract_interfaces::snip20_migration::{ - Config, - ExecuteMsg, - InstantiateMsg, - QueryAnswer, - QueryMsg, - }, - snip20::helpers::{burn_msg, mint_msg, register_receive}, - snip20_migration::{ExecuteAnswer, RegisteredToken}, - utils::generic_response::ResponseStatus, -}; - -#[shd_entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - CONFIG.save(deps.storage, &Config { admin: msg.admin })?; - - match msg.tokens { - Some(tokens) => Ok(Response::default().add_message(register_tokens(deps, env, tokens)?)), - None => Ok(Response::default()), - } -} - -#[shd_entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - ExecuteMsg::UpdateConfig { admin, .. } => { - let mut config = CONFIG.load(deps.storage)?; - validate_admin( - &deps.querier, - AdminPermissions::Snip20MigrationAdmin, - info.sender.to_string(), - &config.admin, - )?; - config.admin = admin.into_valid(deps.api)?; - CONFIG.save(deps.storage, &config)?; - Ok( - Response::default().set_data(to_binary(&ExecuteAnswer::SetConfig { - status: ResponseStatus::Success, - config, - })?), - ) - } - - ExecuteMsg::Receive { from, amount, .. } => { - let from_addr = deps.api.addr_validate(&from)?; - try_burn_and_mint(deps, info, from_addr, amount) - } - ExecuteMsg::RegisterMigrationTokens { - burn_token, - mint_token, - burnable, - .. - } => { - let config = CONFIG.load(deps.storage)?; - validate_admin( - &deps.querier, - AdminPermissions::Snip20MigrationAdmin, - info.sender.to_string(), - &config.admin, - )?; - let tokens = RegisteredToken { - burn_token: burn_token.into_valid(deps.api)?, - mint_token: mint_token.into_valid(deps.api)?, - burnable, - }; - Ok(Response::default().add_message(register_tokens(deps, env, tokens)?)) - } - } -} - -#[shd_entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&QueryAnswer::Config { - config: CONFIG.load(deps.storage)?, - }), - QueryMsg::Metrics { token } => to_binary(&QueryAnswer::Metrics { - amount_minted: match AMOUNT_MINTED - .may_load(deps.storage, deps.api.addr_validate(&token)?)? - { - Some(minted_amount) => { - let mut amount_minted = Uint128::zero(); - // Round to nearest 10,000 - if !minted_amount.lt(&Uint128::new(100_000_000_000)) { - let rounded_minted_amount = - minted_amount.checked_div(Uint128::new(100_000_000_000))?; - amount_minted = - rounded_minted_amount.checked_mul(Uint128::new(100_000_000_000))?; - } - Ok(amount_minted) - } - None => Err(StdError::generic_err("token not found")), - }?, - }), - QueryMsg::RegistrationStatus { token } => to_binary(&QueryAnswer::RegistrationStatus { - status: REGISTERD_TOKENS.load(deps.storage, deps.api.addr_validate(&token)?)?, - }), - } -} - -pub fn try_burn_and_mint( - deps: DepsMut, - info: MessageInfo, - from: Addr, - burn_amount: Uint128, -) -> StdResult { - let mut msgs = vec![]; - - let registered_token = REGISTERD_TOKENS.load(deps.storage, info.sender.clone())?; - - match registered_token.burnable { - Some(burnable) => { - if burnable { - msgs.push(burn_msg( - burn_amount.clone(), - None, - None, - ®istered_token.burn_token, - )?); - } - } - None => {} - } - - msgs.push(mint_msg( - from.clone(), - burn_amount, - None, - None, - ®istered_token.mint_token, - )?); - - let metrics = AMOUNT_MINTED.load(deps.storage, registered_token.mint_token.address.clone())?; - AMOUNT_MINTED.save( - deps.storage, - registered_token.mint_token.clone().address, - &(metrics + burn_amount), - )?; - - Ok(Response::default().add_messages(msgs)) -} - -pub fn register_tokens(deps: DepsMut, env: Env, tokens: RegisteredToken) -> StdResult { - REGISTERD_TOKENS.save(deps.storage, tokens.clone().burn_token.address, &tokens)?; - AMOUNT_MINTED.save( - deps.storage, - tokens.clone().mint_token.address, - &Uint128::zero(), - )?; - let msg = register_receive(env.contract.code_hash, None, &tokens.burn_token)?; - StdResult::Ok(msg) -} diff --git a/contracts/snip20_migration/src/lib.rs b/contracts/snip20_migration/src/lib.rs deleted file mode 100644 index 85a5c6a..0000000 --- a/contracts/snip20_migration/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod contract; -pub mod storage; - -#[cfg(test)] -pub mod test; diff --git a/contracts/snip20_migration/src/storage.rs b/contracts/snip20_migration/src/storage.rs deleted file mode 100644 index 5f72d61..0000000 --- a/contracts/snip20_migration/src/storage.rs +++ /dev/null @@ -1,10 +0,0 @@ -use shade_protocol::{ - c_std::{Addr, Uint128}, - contract_interfaces::snip20_migration::Config, - secret_storage_plus::{Item, Map}, - snip20_migration::RegisteredToken, -}; - -pub const CONFIG: Item = Item::new("config"); -pub const REGISTERD_TOKENS: Map<'static, Addr, RegisteredToken> = Map::new("registered_tokens"); -pub const AMOUNT_MINTED: Map<'static, Addr, Uint128> = Map::new("amount_minted"); diff --git a/contracts/snip20_migration/src/test.rs b/contracts/snip20_migration/src/test.rs deleted file mode 100644 index 535e2ea..0000000 --- a/contracts/snip20_migration/src/test.rs +++ /dev/null @@ -1,271 +0,0 @@ -use shade_multi_test::multi::{admin::Admin, snip20::Snip20, snip20_migration::Snip20Migration}; -use shade_protocol::{ - admin, - c_std::{to_binary, Addr, StdResult, Uint128}, - contract_interfaces::snip20_migration, - multi_test::App, - snip20::{self, InitialBalance}, - utils::{ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -#[test] -pub fn migration_test() { - let mut chain = App::default(); - - let admin = Addr::unchecked("admin"); - - let admin_auth = admin::InstantiateMsg { - super_admin: Some(admin.clone().to_string()), - } - .test_init( - Admin::default(), - &mut chain, - admin.clone(), - "admin_auth", - &[], - ) - .unwrap(); - - let token0 = snip20::InstantiateMsg { - name: "token0".into(), - admin: Some(admin.clone().into()), - symbol: "TZERO".into(), - decimals: 6, - initial_balances: Some(vec![InitialBalance { - address: admin.clone().into(), - amount: Uint128::new(1_000_000_000_000_000_000_000), - }]), - prng_seed: to_binary("").ok().unwrap(), - query_auth: None, - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: Some(true), - enable_burn: Some(true), - enable_transfer: Some(true), - }), - } - .test_init(Snip20::default(), &mut chain, admin.clone(), "token0", &[]) - .unwrap(); - - snip20::ExecuteMsg::SetViewingKey { - key: "vk".into(), - padding: None, - } - .test_exec(&token0, &mut chain, admin.clone(), &[]) - .unwrap(); - - let token1 = snip20::InstantiateMsg { - name: "token1".into(), - admin: Some(admin.clone().into()), - symbol: "TONE".into(), - decimals: 6, - initial_balances: None, - prng_seed: to_binary("").ok().unwrap(), - query_auth: None, - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(true), - enable_redeem: Some(true), - enable_mint: Some(true), - enable_burn: Some(false), - enable_transfer: Some(true), - }), - } - .test_init(Snip20::default(), &mut chain, admin.clone(), "token1", &[]) - .unwrap(); - - snip20::ExecuteMsg::SetViewingKey { - key: "vk".into(), - padding: None, - } - .test_exec(&token1, &mut chain, admin.clone(), &[]) - .unwrap(); - - let migration_contract = snip20_migration::InstantiateMsg { - admin: admin_auth.clone().into(), - tokens: None, - } - .test_init( - Snip20Migration::default(), - &mut chain, - admin.clone().into(), - "migration", - &[], - ) - .unwrap(); - - match (snip20_migration::QueryMsg::Config {} - .test_query(&migration_contract, &mut chain) - .unwrap()) - { - snip20_migration::QueryAnswer::Config { config } => { - let expected_config = snip20_migration::Config { - admin: admin_auth.clone().into(), - }; - assert!(config == expected_config, "conifg matches expected"); - } - _ => panic!("config not found"), - } - - match (snip20_migration::QueryMsg::Metrics { - token: "lala".into(), - } - .test_query::(&migration_contract, &mut chain)) - { - Ok(some_val) => panic!("token found when not expected to be found"), - _ => assert!(true, "metrics query errored"), - } - - match (snip20_migration::QueryMsg::RegistrationStatus { - token: "lala".into(), - } - .test_query::(&migration_contract, &mut chain)) - { - Ok(some_val) => panic!("token was found when not expected to be found"), - _ => assert!(true, "registration status query error"), - } - - snip20::ExecuteMsg::AddMinters { - minters: vec![migration_contract.address.clone().into()], - padding: None, - } - .test_exec(&token1, &mut chain, admin.clone().into(), &[]) - .unwrap(); - - snip20_migration::ExecuteMsg::RegisterMigrationTokens { - burn_token: token0.clone().into(), - mint_token: token1.clone().into(), - burnable: Some(true), - padding: None, - } - .test_exec(&migration_contract, &mut chain, admin.clone().into(), &[]) - .unwrap(); - - match (snip20_migration::QueryMsg::RegistrationStatus { - token: token0.address.clone().into(), - } - .test_query(&migration_contract, &mut chain) - .unwrap()) - { - snip20_migration::QueryAnswer::RegistrationStatus { status } => { - assert!( - status.burn_token.clone() == token0.clone().into() - && status.mint_token == token1.clone().into(), - "token is registered" - ); - } - _ => panic!("registration status query error"), - } - - match (snip20_migration::QueryMsg::Metrics { - token: token1.address.clone().into(), - } - .test_query(&migration_contract, &mut chain) - .unwrap()) - { - snip20_migration::QueryAnswer::Metrics { amount_minted } => { - assert!(amount_minted == Uint128::zero(), "metrics is zero"); - } - _ => panic!("metrics query error"), - } - - snip20::ExecuteMsg::Send { - recipient: migration_contract.clone().address.into(), - recipient_code_hash: None, - amount: Uint128::new(1000000), - msg: None, - memo: None, - padding: None, - } - .test_exec(&token0, &mut chain, admin.clone().into(), &[]) - .unwrap(); - - match (snip20::QueryMsg::Balance { - address: admin.clone().into(), - key: "vk".into(), - } - .test_query(&token1, &mut chain) - .unwrap()) - { - snip20::QueryAnswer::Balance { amount } => assert!( - amount == Uint128::new(1000000), - "balance is expected amount" - ), - _ => panic!("wrong query answer"), - } - - match (snip20_migration::QueryMsg::Metrics { - token: token1.address.clone().into(), - } - .test_query(&migration_contract, &mut chain) - .unwrap()) - { - snip20_migration::QueryAnswer::Metrics { amount_minted } => { - assert!( - amount_minted == Uint128::zero(), - "metrics equals the minted amount" - ); - } - _ => panic!("wrong type"), - } - - snip20::ExecuteMsg::Send { - recipient: migration_contract.clone().address.into(), - recipient_code_hash: None, - amount: Uint128::new(1_000_000_000_000_000), - msg: None, - memo: None, - padding: None, - } - .test_exec(&token0, &mut chain, admin.clone().into(), &[]) - .unwrap(); - - match (snip20::QueryMsg::Balance { - address: admin.clone().into(), - key: "vk".into(), - } - .test_query(&token1, &mut chain) - .unwrap()) - { - snip20::QueryAnswer::Balance { amount } => { - assert!( - amount == Uint128::new(1_000_000_001_000_000), - "balance is expected amount" - ) - } - _ => panic!("wrong query answer"), - } - - match (snip20_migration::QueryMsg::Metrics { - token: token1.address.clone().into(), - } - .test_query(&migration_contract, &mut chain) - .unwrap()) - { - snip20_migration::QueryAnswer::Metrics { amount_minted } => { - assert!( - amount_minted == Uint128::new(1_000_000_000_000_000), - "metrics equals the minted amount" - ); - } - _ => panic!("wrong type"), - } - - match (snip20::QueryMsg::TokenInfo {} - .test_query(&token0, &mut chain) - .unwrap()) - { - snip20::QueryAnswer::TokenInfo { total_supply, .. } => match total_supply { - Some(total_supply) => { - assert!( - Uint128::new(999998999999999000000) == total_supply, - "total supply not expected value" - ); - } - None => panic!("no total_supply unexpected"), - }, - _ => panic!("wrong type"), - } -} diff --git a/contracts/snip20_migration/tests/integration.rs b/contracts/snip20_migration/tests/integration.rs deleted file mode 100644 index 1ebeecf..0000000 --- a/contracts/snip20_migration/tests/integration.rs +++ /dev/null @@ -1,111 +0,0 @@ -use shade_multi_test::multi::{ - admin::{init_admin_auth, Admin}, - snip20::Snip20, - snip20_migration::Snip20Migration, -}; -use shade_protocol::{ - c_std::{to_binary, Addr, BlockInfo, Timestamp, Uint128}, - contract_interfaces::{snip20, snip20_migration}, - multi_test::App, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -#[test] -fn test_admin() { - let mut app = App::default(); - - let admin_user = Addr::unchecked("admin"); - let not_admin = Addr::unchecked("not_admin"); - - let admin_contract = init_admin_auth(&mut app, &admin_user); - - let snip20_migration_contract = snip20_migration::InstantiateMsg { - admin: admin_contract.clone().into(), - tokens: None, - } - .test_init( - Snip20Migration::default(), - &mut app, - admin_user.clone(), - "snip20_migration", - &[], - ) - .unwrap(); - - let token0 = snip20::InstantiateMsg { - name: "burn_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "BURN".into(), - decimals: 6, - initial_balances: Some(vec![snip20::InitialBalance { - amount: Uint128::new(100000000000), - address: admin_user.to_string(), - }]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(true), - enable_burn: Some(true), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - let token1 = snip20::InstantiateMsg { - name: "mint_token".into(), - admin: Some(admin_user.to_string().clone()), - symbol: "MINT".into(), - decimals: 6, - initial_balances: Some(vec![snip20::InitialBalance { - amount: Uint128::new(100000000000), - address: admin_user.to_string(), - }]), - query_auth: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: Some(false), - enable_redeem: Some(false), - enable_mint: Some(true), - enable_burn: Some(true), - enable_transfer: Some(true), - }), - } - .test_init( - Snip20::default(), - &mut app, - admin_user.clone(), - "stake_token", - &[], - ) - .unwrap(); - - let msg_resp = snip20_migration::ExecuteMsg::RegisterMigrationTokens { - burn_token: token0.clone().into(), - mint_token: token1.clone().into(), - burnable: None, - padding: None, - } - .test_exec(&snip20_migration_contract, &mut app, admin_user.clone(), &[ - ]) - .unwrap(); - - let msg_resp = snip20_migration::ExecuteMsg::RegisterMigrationTokens { - burn_token: token0.into(), - mint_token: token1.into(), - burnable: None, - padding: None, - } - .test_exec(&snip20_migration_contract, &mut app, not_admin.clone(), &[]) - .unwrap_err(); -} diff --git a/launch/Cargo.toml b/launch/Cargo.toml deleted file mode 100644 index a2bd598..0000000 --- a/launch/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -name = "launch" -version = "0.1.0" -authors = ["Guy Garcia "] -edition = "2021" - -[[bin]] -name = "shade" -path = "src/shade.rs" - -[[bin]] -name = "airdrop" -path = "src/airdrop.rs" - -[features] -default = [] - -[dependencies] -colored = "2.0.0" -shade-protocol = { version = "0.1.0", path = "../packages/shade_protocol", features = [ - "dex", - "airdrop", - "bonds", - "governance-impl", - "mint", - "mint_router", - "oracles", - "scrt_staking", - "snip20_staking", - "treasury", -] } -secretcli = { version = "0.1.0", path = "../packages/secretcli" } -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -serde_json = { version = "1.0.67" } - -rs_merkle = { git = "https://github.com/FloppyDisck/rs-merkle", branch = "node_export" } -query-authentication = { git = "https://github.com/securesecrets/query-authentication", tag = "v1.3.0" } - -[target.'cfg(not(target_arch="wasm32"))'.dependencies] -rand = { version = "0.8.4" } -chrono = "0.4.19" \ No newline at end of file diff --git a/makefile b/makefile index c6303b0..966c6f4 100755 --- a/makefile +++ b/makefile @@ -13,26 +13,17 @@ rm ./$(1).wasm endef CONTRACTS = \ - airdrop treasury treasury_manager scrt_staking \ - snip20 query_auth admin \ - mock_sienna_pair mock_adapter \ - mock_stkd_derivative basic_staking snip20_migration stkd_scrt \ - snip20_derivative + airdrop \ -PACKAGES = shade_protocol contract_harness cosmwasm_math_compat +PACKAGES = shade_protocol contract_harness cosmwasm_math_compat ethereum_verify release: setup ${build-release} @$(MAKE) compress_all -dao: treasury treasury_manager scrt_staking - compress_all: setup @$(MAKE) $(addprefix compress-,$(CONTRACTS)) -compress-snip20_staking: setup - $(call opt_and_compress,snip20_staking,spip_stkd_0) - compress-%: setup $(call opt_and_compress,$*,$*) @@ -43,22 +34,12 @@ $(CONTRACTS): setup $(PACKAGES): (cd packages/$@; cargo build) -snip20: setup - (cd contracts/snip20; ${build-release}) - @$(MAKE) $(addprefix compress-,snip20) - -snip20_staking: setup - (cd contracts/snip20_staking; ${build-release}) - @$(MAKE) $(addprefix compress-,snip20_staking) - test: @$(MAKE) $(addprefix test-,$(CONTRACTS)) test-%: % (cargo test -p $*) -dao-cov: - (cargo llvm-cov --html -p treasury -p treasury_manager; xdg-open target/llvm-cov/html/index.html) cov: (cargo llvm-cov --html; xdg-open target/llvm-cov/html/index.html) diff --git a/packages/multi_test/Cargo.toml b/packages/multi_test/Cargo.toml index af3b535..17f9ced 100644 --- a/packages/multi_test/Cargo.toml +++ b/packages/multi_test/Cargo.toml @@ -12,51 +12,10 @@ crate-type = ["cdylib", "rlib"] [features] default = [] airdrop = ["dep:airdrop"] -admin = ["dep:admin", "shade-protocol/admin"] -snip20 = ["dep:snip20"] -#liability_mint = ["dep:liability_mint"] -#mint = ["dep:mint"] -#oracle = ["dep:oracle"] -#mock_band= ["dep:mock_band"] -mock_stkd = ["dep:mock_stkd"] -mock_sienna = ["dep:mock_sienna"] -# governance = ["dep:governance"] -# snip20_staking = ["dep:spip_stkd_0"] -# scrt_staking = ["dep:scrt_staking"] -# bonds = ["dep:bonds"] -query_auth = ["dep:query_auth"] -basic_staking = ["dep:basic_staking"] -scrt_staking = ["dep:scrt_staking"] -treasury = ["dep:treasury"] -treasury_manager = ["dep:treasury_manager"] -stkd_scrt = ["dep:stkd_scrt"] -dao = ["mock_adapter", "treasury", "treasury_manager", "snip20"] -# shade-oracles = ["dep:shade-oracles"] -# peg_stability = ["dep:peg_stability"] -snip20_migration = ["dep:snip20_migration"] [dependencies] airdrop = { path = "../../contracts/airdrop", optional = true } -snip20 = { version = "0.1.0", path = "../../contracts/snip20", optional = true } -#liability_mint = { version = "0.1.0", path = "../../contracts/liability_mint", optional = true } -#mint = { version = "0.1.0", path = "../../contracts/mint", optional = true } -#oracle = { version = "0.1.0", path = "../../contracts/oracle", optional = true } -#mock_band = { version = "0.1.0", path = "../../contracts/mock_band", optional = true } -# governance = { version = "0.1.0", path = "../../contracts/governance", optional = true } -basic_staking = { version = "0.1.0", path = "../../contracts/basic_staking", optional = true } -# spip_stkd_0 = { version = "0.1.0", path = "../../contracts/snip20_staking", optional = true } -# bonds = { version = "0.1.0", path = "../../contracts/bonds", optional = true } -query_auth = { version = "0.1.0", path = "../../contracts/query_auth", optional = true } -mock_adapter = { version = "0.1.0", path = "../../contracts/mock/mock_adapter", optional = true } -stkd_scrt = { version = "0.1.0", path = "../../contracts/dao/stkd_scrt", optional = true } -scrt_staking = { version = "0.1.0", path = "../../contracts/dao/scrt_staking", optional = true } -treasury = { version = "0.1.0", path = "../../contracts/dao/treasury", optional = true } -treasury_manager = { version = "0.1.0", path = "../../contracts/dao/treasury_manager", optional = true } -admin = { version = "0.2.0", path = "../../contracts/admin", optional = true } -# peg_stability = { version = "0.1.0", path = "../../contracts/peg_stability", optional = true } -mock_stkd = { version = "0.1.0", package = "mock_stkd_derivative", path = "../../contracts/mock/mock_stkd_derivative", optional = true } -mock_sienna = { version = "0.1.0", package = "mock_sienna_pair", path = "../../contracts/mock/mock_sienna_pair", optional = true } -snip20_migration = { version = "0.1.0", path = "../../contracts/snip20_migration", optional = true } + shade-protocol = { path = "../shade_protocol", features = ["multi-test"] } [target.'cfg(not(target_arch="wasm32"))'.dependencies] diff --git a/packages/multi_test/src/interfaces/dao.rs b/packages/multi_test/src/interfaces/dao.rs deleted file mode 100644 index 0680e5d..0000000 --- a/packages/multi_test/src/interfaces/dao.rs +++ /dev/null @@ -1,490 +0,0 @@ -use crate::{ - interfaces::{ - snip20, - treasury, - treasury_manager, - utils::{DeployedContracts, SupportedContracts}, - }, - multi::mock_adapter::MockAdapter, -}; -use mock_adapter; -use shade_protocol::{ - c_std::{Addr, StdError, StdResult, Uint128}, - contract_interfaces::dao::{ - adapter, - treasury::AllowanceType, - treasury_manager::AllocationType, - }, - multi_test::App, - utils::{self, asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -pub fn init_dao( - chain: &mut App, - sender: &str, - contracts: &mut DeployedContracts, - treasury_start_bal: Uint128, - snip20_symbol: &str, - allowance_type: Vec, - cycle: Vec, - allowance_amount: Vec, - allowance_tolerance: Vec, - tm_allowance_type: Vec>, - tm_allocation_amount: Vec>, - tm_allocation_tolerance: Vec>, - is_instant_unbond: bool, - do_update: bool, -) -> StdResult<()> { - let num_managers = allowance_amount.len(); - treasury::init(chain, sender, contracts)?; - let mut offset = 0; - snip20::init(chain, sender, contracts, "snip20_1", snip20_symbol, 6, None)?; - treasury::register_asset_exec(chain, sender, contracts, snip20_symbol)?; - snip20::send_exec( - chain, - sender, - contracts, - snip20_symbol, - contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - .address - .to_string(), - treasury_start_bal, - None, - )?; - for i in 0..num_managers { - let num_adapters = tm_allocation_amount[i].len(); - treasury_manager::init(chain, sender, contracts, i)?; - treasury_manager::register_asset_exec( - chain, - "admin", - contracts, - snip20_symbol, - SupportedContracts::TreasuryManager(i), - )?; - treasury::register_manager_exec(chain, sender, contracts, i)?; - treasury::allowance_exec( - chain, - sender, - contracts, - snip20_symbol, - i, - allowance_type[i].clone(), - cycle[i].clone(), - allowance_amount[i].clone(), - allowance_tolerance[i].clone(), - true, - )?; - for j in 0..num_adapters { - let mock_adap_contract = Contract::from( - match (mock_adapter::contract::Config { - owner: contracts - .get(&SupportedContracts::TreasuryManager(i)) - .unwrap() - .clone() - .address, - instant: is_instant_unbond, - token: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone(), - } - .test_init( - MockAdapter::default(), - chain, - Addr::unchecked(sender), - "mock_adapter", - &[], - )) { - Ok(contract_info) => contract_info, - Err(e) => return Err(StdError::generic_err(e.to_string())), - }, - ); - contracts.insert( - SupportedContracts::MockAdapter(j + offset), - mock_adap_contract, - ); - treasury_manager::allocate_exec( - chain, - sender, - contracts, - snip20_symbol, - Some(j.to_string()), - &SupportedContracts::MockAdapter(j + offset), - tm_allowance_type.clone()[i][j].clone(), - tm_allocation_amount[i][j].clone(), - tm_allocation_tolerance[i][j].clone(), - i, - )?; - } - offset += num_adapters + 1; - } - if do_update { - update_dao(chain, sender, contracts, snip20_symbol, num_managers).unwrap(); - } - Ok(()) -} - -pub fn update_dao( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - snip20_symbol: &str, - num_managers: usize, -) -> StdResult<()> { - treasury::update_exec(chain, sender, contracts, snip20_symbol)?; - for i in 0..num_managers { - treasury_manager::update_exec( - chain, - sender, - contracts, - snip20_symbol, - SupportedContracts::TreasuryManager(i), - )?; - } - Ok(()) -} - -pub fn system_balance_reserves( - chain: &App, - contracts: &DeployedContracts, - snip20_symbol: &str, -) -> (Uint128, Vec<(Uint128, Vec)>) { - let mut ret_struct = (Uint128::zero(), vec![]); - ret_struct.0 = treasury::reserves_query(chain, contracts, snip20_symbol.clone()).unwrap(); - let mut i = 0; - let mut j; - let mut offset = 0; - loop { - let mut manager_tuple = (Uint128::zero(), vec![]); - if contracts.get(&SupportedContracts::TreasuryManager(i)) == None { - break; - } else { - manager_tuple.0 = match treasury_manager::reserves_query( - chain, - contracts, - snip20_symbol.clone(), - SupportedContracts::TreasuryManager(i), - SupportedContracts::Treasury, - ) { - Ok(bal) => bal, - Err(_) => { - i += 1; - continue; - } - }; - j = 0; - loop { - if contracts.get(&SupportedContracts::MockAdapter(j + offset)) == None { - offset += j + 1; - break; - } else { - manager_tuple.1.push( - reserves_query( - chain, - contracts, - snip20_symbol.clone(), - SupportedContracts::MockAdapter(j + offset), - ) - .unwrap(), - ); - } - j += 1; - } - } - ret_struct.1.push(manager_tuple); - i += 1; - } - ret_struct -} - -pub fn system_balance_unbondable( - chain: &App, - contracts: &DeployedContracts, - snip20_symbol: &str, -) -> (Uint128, Vec<(Uint128, Vec)>) { - let mut ret_struct = (Uint128::zero(), vec![]); - ret_struct.0 = treasury::reserves_query(chain, contracts, snip20_symbol.clone()).unwrap(); - let mut i = 0; - let mut j; - let mut offset = 0; - loop { - let mut manager_tuple = (Uint128::zero(), vec![]); - if contracts.get(&SupportedContracts::TreasuryManager(i)) == None { - break; - } else { - manager_tuple.0 = match treasury_manager::reserves_query( - chain, - contracts, - snip20_symbol.clone(), - SupportedContracts::TreasuryManager(i), - SupportedContracts::Treasury, - ) { - Ok(bal) => bal, - Err(_) => { - i += 1; - continue; - } - }; - j = 0; - loop { - if contracts.get(&SupportedContracts::MockAdapter(j + offset)) == None { - offset += j + 1; - break; - } else { - manager_tuple.1.push( - unbondable_query( - chain, - contracts, - snip20_symbol.clone(), - SupportedContracts::MockAdapter(j + offset), - ) - .unwrap(), - ); - } - j += 1; - } - } - ret_struct.1.push(manager_tuple); - i += 1; - } - ret_struct -} - -pub fn claimable_query( - chain: &App, - contracts: &DeployedContracts, - snip20_symbol: &str, - adapter_contract: SupportedContracts, -) -> StdResult { - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Claimable { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .address - .to_string(), - }) - .test_query( - &contracts.get(&adapter_contract).unwrap().clone().into(), - &chain, - )? { - adapter::QueryAnswer::Claimable { amount } => Ok(amount), - _ => Err(StdError::generic_err(format!( - "Failed to.test_query adapter claimable", - ))), - } -} - -pub fn unbonding_query( - chain: &App, - contracts: &DeployedContracts, - snip20_symbol: &str, - adapter_contract: SupportedContracts, -) -> StdResult { - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbonding { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .address - .to_string(), - }) - .test_query( - &contracts.get(&adapter_contract).unwrap().clone().into(), - &chain, - )? { - adapter::QueryAnswer::Unbonding { amount } => Ok(amount), - _ => Err(StdError::generic_err( - "Failed to.test_query adapter unbonding", - )), - } -} - -pub fn unbondable_query( - chain: &App, - contracts: &DeployedContracts, - snip20_symbol: &str, - adapter_contract: SupportedContracts, -) -> StdResult { - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Unbondable { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .address - .to_string(), - }) - .test_query( - &contracts.get(&adapter_contract).unwrap().clone().into(), - &chain, - )? { - adapter::QueryAnswer::Unbondable { amount } => Ok(amount), - _ => Err(StdError::generic_err( - "Failed to.test_query adapter unbondable", - )), - } -} - -pub fn reserves_query( - chain: &App, - contracts: &DeployedContracts, - snip20_symbol: &str, - adapter_contract: SupportedContracts, -) -> StdResult { - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Reserves { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .address - .to_string(), - }) - .test_query( - &contracts.get(&adapter_contract).unwrap().clone().into(), - &chain, - )? { - adapter::QueryAnswer::Reserves { amount } => Ok(amount), - _ => Err(StdError::generic_err( - "Failed to.test_query adapter unbondable", - )), - } -} - -pub fn balance_query( - chain: &App, - contracts: &DeployedContracts, - snip20_symbol: &str, - adapter_contract: SupportedContracts, -) -> StdResult { - match adapter::QueryMsg::Adapter(adapter::SubQueryMsg::Balance { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .address - .to_string(), - }) - .test_query( - &contracts.get(&adapter_contract).unwrap().clone().into(), - &chain, - )? { - adapter::QueryAnswer::Balance { amount } => Ok(amount), - _ => Err(StdError::generic_err( - "Failed to.test_query adapter balance", - )), - } -} - -pub fn claim_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - snip20_symbol: &str, - adapter_contract: SupportedContracts, -) -> StdResult<()> { - let res = adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Claim { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .address - .to_string(), - }) - .test_exec( - &contracts.get(&adapter_contract).unwrap().clone().into(), - chain, - Addr::unchecked(sender), - &[], - ); - match res { - Ok(_) => Ok(()), - Err(e) => Err(StdError::generic_err(e.to_string())), - } -} - -pub fn update_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - snip20_symbol: &str, - adapter_contract: SupportedContracts, -) -> StdResult<()> { - match adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Update { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .address - .to_string(), - }) - .test_exec( - &contracts.get(&adapter_contract).unwrap().clone().into(), - chain, - Addr::unchecked(sender), - &[], - ) { - Ok(_) => Ok(()), - Err(e) => Err(StdError::generic_err(e.to_string())), - } -} - -pub fn unbond_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - snip20_symbol: &str, - amount: Uint128, - adapter_contract: SupportedContracts, -) -> StdResult<()> { - match adapter::ExecuteMsg::Adapter(adapter::SubExecuteMsg::Unbond { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .address - .to_string(), - amount, - }) - .test_exec( - &contracts.get(&adapter_contract).unwrap().clone().into(), - chain, - Addr::unchecked(sender), - &[], - ) { - Ok(_) => Ok(()), - Err(e) => Err(StdError::generic_err(e.to_string())), - } -} - -pub fn mock_adapter_sub_tokens( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - amount: Uint128, - adapter_contract: SupportedContracts, -) -> StdResult<()> { - match (mock_adapter::contract::ExecuteMsg::GiveMeMoney { amount }.test_exec( - &contracts.get(&adapter_contract).unwrap().clone().into(), - chain, - Addr::unchecked(sender), - &[], - )) { - Ok(_) => Ok(()), - Err(e) => Err(StdError::generic_err(e.to_string())), - } -} - -pub fn mock_adapter_complete_unbonding( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - adapter_contract: SupportedContracts, -) -> StdResult<()> { - match (mock_adapter::contract::ExecuteMsg::CompleteUnbonding {}.test_exec( - &contracts.get(&adapter_contract).unwrap().clone().into(), - chain, - Addr::unchecked(sender), - &[], - )) { - Ok(_) => Ok(()), - Err(e) => Err(StdError::generic_err(e.to_string())), - } -} diff --git a/packages/multi_test/src/interfaces/mod.rs b/packages/multi_test/src/interfaces/mod.rs index 085b96f..1558805 100644 --- a/packages/multi_test/src/interfaces/mod.rs +++ b/packages/multi_test/src/interfaces/mod.rs @@ -1,19 +1,19 @@ -#[cfg(feature = "dao")] -pub mod dao; -/* -#[cfg(feature = "dao")] -pub mod manager; -#[cfg(feature = "dao")] -pub mod adapter; -*/ -#[cfg(feature = "snip20")] -pub mod snip20; -#[cfg(feature = "treasury")] -pub mod treasury; -#[cfg(feature = "treasury_manager")] -pub mod treasury_manager; +// #[cfg(feature = "dao")] +// pub mod dao; +// /* +// #[cfg(feature = "dao")] +// pub mod manager; +// #[cfg(feature = "dao")] +// pub mod adapter; +// */ +// #[cfg(feature = "snip20")] +// pub mod snip20; +// #[cfg(feature = "treasury")] +// pub mod treasury; +// #[cfg(feature = "treasury_manager")] +// pub mod treasury_manager; -#[cfg(feature = "scrt_staking")] -pub mod scrt_staking; +// #[cfg(feature = "scrt_staking")] +// pub mod scrt_staking; pub mod utils; diff --git a/packages/multi_test/src/interfaces/scrt_staking.rs b/packages/multi_test/src/interfaces/scrt_staking.rs deleted file mode 100644 index 6ff8435..0000000 --- a/packages/multi_test/src/interfaces/scrt_staking.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::{ - interfaces::{ - snip20, - treasury, - treasury_manager, - utils::{DeployedContracts, SupportedContracts}, - }, - multi::{admin::init_admin_auth, scrt_staking::ScrtStaking}, -}; -use shade_protocol::{ - c_std::{Addr, StdError, StdResult}, - contract_interfaces::dao::scrt_staking, - multi_test::App, - utils::{asset::Contract, InstantiateCallback, MultiTestable}, -}; - -pub fn init( - chain: &mut App, - sender: &str, - contracts: &mut DeployedContracts, - validator_bounds: Option, - manager: usize, -) -> StdResult<()> { - let treasury_manager = match contracts.get(&SupportedContracts::TreasuryManager(manager)) { - Some(manager) => manager.clone(), - None => { - treasury_manager::init(chain, sender, contracts, manager)?; - contracts - .get(&SupportedContracts::TreasuryManager(manager)) - .unwrap() - .clone() - } - }; - let _treasury = match contracts.get(&SupportedContracts::Treasury) { - Some(treasury) => treasury.clone(), - None => { - treasury::init(chain, sender, contracts)?; - contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - } - }; - let admin_auth = match contracts.get(&SupportedContracts::AdminAuth) { - Some(admin) => admin.clone(), - None => { - let contract = Contract::from(init_admin_auth(chain, &Addr::unchecked(sender))); - contracts.insert(SupportedContracts::AdminAuth, contract.clone()); - contract - } - }; - let sscrt = match contracts.get(&SupportedContracts::Snip20("SSCRT".to_string())) { - Some(snip20) => snip20.clone(), - None => { - snip20::init(chain, sender, contracts, "secretSCRT", "SSCRT", 6, None)?; - contracts - .get(&SupportedContracts::Snip20("SSCRT".to_string())) - .unwrap() - .clone() - } - }; - let scrt_staking = Contract::from( - match (scrt_staking::InstantiateMsg { - admin_auth: admin_auth.into(), - owner: treasury_manager.address.into(), - sscrt: sscrt.into(), - validator_bounds, - viewing_key: "viewing_key".into(), - } - .test_init( - ScrtStaking::default(), - chain, - Addr::unchecked(sender), - "scrt_staking", - &[], - )) { - Ok(contract_info) => contract_info, - Err(e) => return Err(StdError::generic_err(e.to_string())), - }, - ); - contracts.insert(SupportedContracts::ScrtStaking, scrt_staking); - Ok(()) -} diff --git a/packages/multi_test/src/interfaces/snip20.rs b/packages/multi_test/src/interfaces/snip20.rs deleted file mode 100644 index 63bf6bd..0000000 --- a/packages/multi_test/src/interfaces/snip20.rs +++ /dev/null @@ -1,186 +0,0 @@ -use crate::{ - interfaces::utils::{DeployedContracts, SupportedContracts}, - multi::snip20::Snip20, -}; -use shade_protocol::{ - c_std::{Addr, Binary, Coin, StdError, StdResult, Uint128}, - contract_interfaces::snip20, - multi_test::App, - utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, -}; - -pub fn init( - chain: &mut App, - sender: &str, - contracts: &mut DeployedContracts, - name: &str, - snip20_symbol: &str, - decimals: u8, - config: Option, -) -> StdResult<()> { - let snip20 = Contract::from( - match (snip20::InstantiateMsg { - name: name.to_string(), - admin: Some(sender.into()), - symbol: snip20_symbol.to_string(), - decimals, - initial_balances: Some(vec![snip20::InitialBalance { - address: sender.into(), - amount: Uint128::from(1_000_000_000 * 10 ^ decimals as u128), - }]), - prng_seed: Binary::default(), - query_auth: None, - config, - } - .test_init( - Snip20::default(), - chain, - Addr::unchecked(sender), - "snip20", - &[], - )) { - Ok(contract_info) => contract_info, - Err(e) => return Err(StdError::generic_err(e.to_string())), - }, - ); - contracts.insert( - SupportedContracts::Snip20(snip20_symbol.to_string()), - snip20, - ); - Ok(()) -} - -pub fn deposit_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - snip20_symbol: &str, - coins: &Vec, -) -> StdResult<()> { - match (snip20::ExecuteMsg::Deposit { padding: None }.test_exec( - &contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - coins, - )) { - Ok(_) => Ok(()), - Err(e) => Err(StdError::generic_err(e.to_string())), - } -} - -pub fn set_viewing_key_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - snip20_symbol: &str, - key: String, -) -> StdResult<()> { - match (snip20::ExecuteMsg::SetViewingKey { key, padding: None }.test_exec( - &contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - &[], - )) { - Ok(_) => Ok(()), - Err(e) => Err(StdError::generic_err(e.to_string())), - } -} - -pub fn send_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - snip20_symbol: &str, - recipient: String, - amount: Uint128, - msg: Option, -) -> StdResult<()> { - match (snip20::ExecuteMsg::Send { - recipient, - amount, - msg, - memo: None, - padding: None, - recipient_code_hash: None, - } - .test_exec( - &contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - &[], - )) { - Ok(_) => Ok(()), - Err(_) => Err(StdError::generic_err("snip20 send failed")), - } -} - -pub fn send_from_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - snip20_symbol: &str, - owner: String, - recipient: String, - amount: Uint128, - msg: Option, -) -> StdResult<()> { - match (snip20::ExecuteMsg::SendFrom { - owner, - recipient, - amount, - msg, - memo: None, - padding: None, - recipient_code_hash: None, - } - .test_exec( - &contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - &[], - )) { - Ok(_) => Ok(()), - Err(_) => Err(StdError::generic_err("snip20 send failed")), - } -} - -pub fn balance_query( - chain: &App, - sender: &str, - contracts: &DeployedContracts, - snip20_symbol: &str, - key: String, -) -> StdResult { - let res = snip20::QueryMsg::Balance { - address: sender.to_string(), - key, - } - .test_query( - &contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .into(), - chain, - )?; - match res { - snip20::QueryAnswer::Balance { amount } => Ok(amount), - _ => Err(StdError::generic_err("SetViewingKey failed")), - } -} diff --git a/packages/multi_test/src/interfaces/treasury.rs b/packages/multi_test/src/interfaces/treasury.rs deleted file mode 100644 index fee0a7c..0000000 --- a/packages/multi_test/src/interfaces/treasury.rs +++ /dev/null @@ -1,469 +0,0 @@ -use crate::{ - interfaces::utils::{DeployedContracts, SupportedContracts}, - multi::{admin::init_admin_auth, treasury::Treasury}, -}; -use shade_protocol::{ - c_std::{Addr, StdError, StdResult, Uint128}, - contract_interfaces::dao::treasury, - multi_test::App, - utils::{ - asset::{Contract, RawContract}, - cycle::Cycle, - storage::plus::period_storage::Period, - ExecuteCallback, - InstantiateCallback, - MultiTestable, - Query, - }, -}; - -pub fn init(chain: &mut App, sender: &str, contracts: &mut DeployedContracts) -> StdResult<()> { - let admin = match contracts.get(&SupportedContracts::AdminAuth) { - Some(admin) => admin.clone(), - None => { - let contract = Contract::from(init_admin_auth(chain, &Addr::unchecked(sender))); - contracts.insert(SupportedContracts::AdminAuth, contract.clone()); - contract - } - }; - let treasury = Contract::from( - match (treasury::InstantiateMsg { - multisig: admin.address.clone().to_string(), - admin_auth: admin.clone().into(), - viewing_key: "viewing_key".to_string(), - } - .test_init( - Treasury::default(), - chain, - Addr::unchecked(sender), - "treasury", - &[], - )) { - Ok(contract_info) => contract_info, - Err(e) => return Err(StdError::generic_err(e.to_string())), - }, - ); - contracts.insert(SupportedContracts::Treasury, treasury); - Ok(()) -} - -pub fn config_query(chain: &App, contracts: &DeployedContracts) -> StdResult { - let res = treasury::QueryMsg::Config {}.test_query( - &contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - .into(), - chain, - )?; - match res { - treasury::QueryAnswer::Config { config } => Ok(config), - _ => Err(StdError::generic_err("query failed")), - } -} - -pub fn allowance_query( - chain: &App, - contracts: &DeployedContracts, - snip20_symbol: &str, - spender: SupportedContracts, -) -> StdResult { - let res = treasury::QueryMsg::Allowance { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .address - .to_string(), - spender: contracts.get(&spender).unwrap().clone().address.to_string(), - } - .test_query( - &contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - .into(), - chain, - )?; - match res { - treasury::QueryAnswer::Allowance { amount } => Ok(amount), - _ => Err(StdError::generic_err("query failed")), - } -} - -pub fn allowances_query( - chain: &App, - contracts: &DeployedContracts, - snip20_symbol: &str, -) -> StdResult> { - let res = treasury::QueryMsg::Allowances { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .address - .to_string(), - } - .test_query( - &contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - .into(), - chain, - )?; - match res { - treasury::QueryAnswer::Allowances { allowances } => Ok(allowances), - _ => Err(StdError::generic_err("query failed")), - } -} - -pub fn assets_query(chain: &App, contracts: &DeployedContracts) -> StdResult> { - let res = treasury::QueryMsg::Assets {}.test_query( - &contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - .into(), - chain, - )?; - match res { - treasury::QueryAnswer::Assets { assets } => Ok(assets), - _ => Err(StdError::generic_err("query failed")), - } -} - -pub fn reserves_query( - chain: &App, - contracts: &DeployedContracts, - snip20_symbol: &str, -) -> StdResult { - let res = treasury::QueryMsg::Reserves { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .address - .to_string(), - } - .test_query( - &contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - .into(), - chain, - )?; - match res { - treasury::QueryAnswer::Reserves { amount } => Ok(amount), - _ => Err(StdError::generic_err("query failed")), - } -} - -pub fn balance_query( - chain: &App, - contracts: &DeployedContracts, - snip20_symbol: &str, -) -> StdResult { - let res = treasury::QueryMsg::Balance { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .address - .to_string(), - } - .test_query( - &contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - .into(), - chain, - )?; - match res { - treasury::QueryAnswer::Balance { amount } => Ok(amount), - _ => Err(StdError::generic_err("query failed")), - } -} - -pub fn run_level_query( - chain: &App, - contracts: &DeployedContracts, -) -> StdResult { - let res = treasury::QueryMsg::RunLevel {}.test_query( - &contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - .into(), - chain, - )?; - match res { - treasury::QueryAnswer::RunLevel { run_level } => Ok(run_level), - _ => Err(StdError::generic_err("query failed")), - } -} - -pub fn metrics_query( - chain: &App, - contracts: &DeployedContracts, - date: Option, - epoch: Option, - period: Period, -) -> StdResult> { - let res = treasury::QueryMsg::Metrics { - date, - epoch, - period, - } - .test_query( - &contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - .into(), - chain, - )?; - match res { - treasury::QueryAnswer::Metrics { metrics } => Ok(metrics), - _ => Err(StdError::generic_err("query failed")), - } -} - -pub fn batch_balance_query( - chain: &App, - contracts: &DeployedContracts, - snip20_symbols: Vec<&str>, -) -> StdResult> { - let assets = { - let mut vec = vec![]; - for symbols in snip20_symbols { - vec.push( - contracts - .get(&SupportedContracts::Snip20(symbols.to_string())) - .unwrap() - .clone() - .address - .to_string(), - ); - } - vec - }; - match (treasury::QueryMsg::BatchBalance { assets }.test_query( - &contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - .into(), - chain, - )) { - Ok(a) => Ok(a), - _ => Err(StdError::generic_err("query failed")), - } -} - -pub fn register_asset_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - snip20_symbol: &str, -) -> StdResult<()> { - match (treasury::ExecuteMsg::RegisterAsset { - contract: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .into(), - } - .test_exec( - &contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - &[], - )) { - Ok(_) => Ok(()), - Err(_) => Err(StdError::generic_err("register wrap failed")), - } -} - -pub fn register_manager_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - manager_id: usize, -) -> StdResult<()> { - match (treasury::ExecuteMsg::RegisterManager { - contract: contracts - .get(&SupportedContracts::TreasuryManager(manager_id)) - .unwrap() - .clone() - .into(), - } - .test_exec( - &contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - &[], - )) { - Ok(_) => Ok(()), - Err(_) => Err(StdError::generic_err("register wrap failed")), - } -} - -pub fn register_wrap_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - denom: String, - contract: RawContract, -) -> StdResult<()> { - match (treasury::ExecuteMsg::RegisterWrap { denom, contract }.test_exec( - &contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - &[], - )) { - Ok(_) => Ok(()), - Err(_) => Err(StdError::generic_err("register wrap failed")), - } -} - -pub fn allowance_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - snip20_symbol: &str, - manager_id: usize, - allowance_type: treasury::AllowanceType, - cycle: Cycle, - amount: Uint128, - tolerance: Uint128, - refresh_now: bool, -) -> StdResult<()> { - match (treasury::ExecuteMsg::Allowance { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .address - .to_string(), - allowance: treasury::RawAllowance { - spender: contracts - .get(&SupportedContracts::TreasuryManager(manager_id)) - .unwrap() - .clone() - .address - .to_string(), - allowance_type, - cycle, - amount, - tolerance, - }, - refresh_now, - } - .test_exec( - &contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - &[], - )) { - Ok(_) => Ok(()), - Err(_) => Err(StdError::generic_err("allowance exec failed")), - } -} - -pub fn set_run_level_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - run_level: treasury::RunLevel, -) -> StdResult<()> { - match (treasury::ExecuteMsg::SetRunLevel { run_level }.test_exec( - &contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - &[], - )) { - Ok(_) => Ok(()), - Err(e) => { - return Err(StdError::generic_err(e.to_string())); - } - } -} - -pub fn set_config( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - admin_auth: Option, - multisig: Option, -) -> StdResult<()> { - match (treasury::ExecuteMsg::UpdateConfig { - admin_auth, - multisig, - } - .test_exec( - &contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - &[], - )) { - Ok(_) => Ok(()), - Err(e) => Err(StdError::generic_err(e.to_string())), - } -} - -pub fn update_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - snip20_symbol: &str, -) -> StdResult<()> { - let res = treasury::ExecuteMsg::Update { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .address - .to_string(), - } - .test_exec( - &contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - &[], - ); - match res { - Ok(_) => Ok(()), - Err(e) => Err(StdError::generic_err(e.to_string())), - } -} diff --git a/packages/multi_test/src/interfaces/treasury_manager.rs b/packages/multi_test/src/interfaces/treasury_manager.rs deleted file mode 100644 index 2bf2b91..0000000 --- a/packages/multi_test/src/interfaces/treasury_manager.rs +++ /dev/null @@ -1,667 +0,0 @@ -use crate::{ - interfaces::{ - treasury, - utils::{DeployedContracts, SupportedContracts}, - }, - multi::{admin::init_admin_auth, treasury_manager::TreasuryManager}, -}; -use shade_protocol::{ - c_std::{Addr, StdError, StdResult, Uint128}, - contract_interfaces::dao::{manager, treasury_manager}, - multi_test::App, - utils::{ - asset::{Contract, RawContract}, - storage::plus::period_storage::Period, - ExecuteCallback, - InstantiateCallback, - MultiTestable, - Query, - }, -}; - -pub fn init( - chain: &mut App, - sender: &str, - contracts: &mut DeployedContracts, - id: usize, -) -> StdResult<()> { - let treasury = match contracts.get(&SupportedContracts::Treasury) { - Some(treasury) => treasury.clone(), - None => { - treasury::init(chain, sender, contracts)?; - contracts - .get(&SupportedContracts::Treasury) - .unwrap() - .clone() - } - }; - let admin_auth = match contracts.get(&SupportedContracts::AdminAuth) { - Some(admin) => admin.clone(), - None => { - let contract = Contract::from(init_admin_auth(chain, &Addr::unchecked(sender))); - contracts.insert(SupportedContracts::AdminAuth, contract.clone()); - contract - } - }; - let treasury_manager = Contract::from( - match (treasury_manager::InstantiateMsg { - admin_auth: admin_auth.into(), - viewing_key: "viewing_key".to_string(), - treasury: treasury.address.into(), - } - .test_init( - TreasuryManager::default(), - chain, - Addr::unchecked(sender), - "manager", - &[], - )) { - Ok(contract_info) => contract_info, - Err(e) => return Err(StdError::generic_err(e.to_string())), - }, - ); - contracts.insert(SupportedContracts::TreasuryManager(id), treasury_manager); - Ok(()) -} - -pub fn claimable_query( - chain: &App, - contracts: &DeployedContracts, - snip20_symbol: &str, - treasury_manager_contract: SupportedContracts, - holder: SupportedContracts, -) -> StdResult { - match treasury_manager::QueryMsg::Manager(manager::SubQueryMsg::Claimable { - holder: contracts.get(&holder).unwrap().address.to_string(), - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .address - .to_string(), - }) - .test_query( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - &chain, - )? { - manager::QueryAnswer::Claimable { amount } => Ok(amount), - _ => Err(StdError::generic_err( - "Failed to test query treasury_manager claimable", - )), - } -} - -pub fn config_query( - chain: &App, - contracts: &DeployedContracts, - treasury_manager_contract: SupportedContracts, -) -> StdResult { - let res = treasury_manager::QueryMsg::Config {}.test_query( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - &chain, - )?; - match res { - treasury_manager::QueryAnswer::Config { config } => Ok(config), - _ => Err(StdError::generic_err(format!( - "Failed to.test_query treasury_manager claimable", - ))), - } -} - -pub fn pending_allowance_query( - chain: &App, - contracts: &DeployedContracts, - treasury_manager_contract: SupportedContracts, - snip20_symbol: &str, -) -> StdResult { - let res = treasury_manager::QueryMsg::PendingAllowance { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .address - .to_string(), - } - .test_query( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - &chain, - )?; - match res { - treasury_manager::QueryAnswer::PendingAllowance { amount } => Ok(amount), - _ => Err(StdError::generic_err(format!( - "Failed to.test_query treasury_manager pending_allowance", - ))), - } -} - -pub fn holding_query( - chain: &App, - contracts: &DeployedContracts, - treasury_manager_contract: SupportedContracts, - holder: String, -) -> StdResult { - let res = treasury_manager::QueryMsg::Holding { holder }.test_query( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - &chain, - )?; - match res { - treasury_manager::QueryAnswer::Holding { holding } => Ok(holding), - _ => Err(StdError::generic_err(format!( - "Failed to.test_query treasury_manager claimable", - ))), - } -} - -pub fn holders_query( - chain: &App, - contracts: &DeployedContracts, - treasury_manager_contract: SupportedContracts, -) -> StdResult> { - let res = treasury_manager::QueryMsg::Holders {}.test_query( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - &chain, - )?; - match res { - treasury_manager::QueryAnswer::Holders { holders } => Ok(holders), - _ => Err(StdError::generic_err(format!( - "Failed to.test_query treasury_manager holders", - ))), - } -} - -pub fn assets_query( - chain: &App, - contracts: &DeployedContracts, - treasury_manager_contract: SupportedContracts, -) -> StdResult> { - let res = treasury_manager::QueryMsg::Assets {}.test_query( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - &chain, - )?; - match res { - treasury_manager::QueryAnswer::Assets { assets } => Ok(assets), - _ => Err(StdError::generic_err(format!( - "Failed to.test_query treasury_manager holders", - ))), - } -} - -pub fn allocations_query( - chain: &App, - contracts: &DeployedContracts, - treasury_manager_contract: SupportedContracts, - snip20_symbol: &str, -) -> StdResult> { - let res = treasury_manager::QueryMsg::Allocations { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .address - .to_string(), - } - .test_query( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - &chain, - )?; - match res { - treasury_manager::QueryAnswer::Allocations { allocations } => Ok(allocations), - _ => Err(StdError::generic_err(format!( - "Failed to.test_query treasury_manager allocations", - ))), - } -} - -pub fn metrics_query( - chain: &App, - contracts: &DeployedContracts, - treasury_manager_contract: SupportedContracts, - date: Option, - epoch: Option, - period: Period, -) -> StdResult> { - let res = treasury_manager::QueryMsg::Metrics { - date, - epoch, - period, - } - .test_query( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - &chain, - )?; - match res { - treasury_manager::QueryAnswer::Metrics { metrics } => Ok(metrics), - _ => Err(StdError::generic_err(format!( - "Failed to.test_query treasury_manager metrics", - ))), - } -} - -pub fn unbonding_query( - chain: &App, - contracts: &DeployedContracts, - snip20_symbol: &str, - treasury_manager_contract: SupportedContracts, - holder: SupportedContracts, -) -> StdResult { - match treasury_manager::QueryMsg::Manager(manager::SubQueryMsg::Unbonding { - holder: contracts.get(&holder).unwrap().address.to_string(), - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .address - .to_string(), - }) - .test_query( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - &chain, - )? { - manager::QueryAnswer::Unbonding { amount } => Ok(amount), - _ => Err(StdError::generic_err( - "Failed to.test_query treasury_manager unbonding", - )), - } -} - -pub fn unbondable_query( - chain: &App, - contracts: &DeployedContracts, - snip20_symbol: &str, - treasury_manager_contract: SupportedContracts, - holder: SupportedContracts, -) -> StdResult { - match treasury_manager::QueryMsg::Manager(manager::SubQueryMsg::Unbondable { - holder: contracts.get(&holder).unwrap().address.to_string(), - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .address - .to_string(), - }) - .test_query( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - &chain, - )? { - manager::QueryAnswer::Unbondable { amount } => Ok(amount), - _ => Err(StdError::generic_err( - "Failed to.test_query treasury_manager unbondable", - )), - } -} - -pub fn reserves_query( - chain: &App, - contracts: &DeployedContracts, - snip20_symbol: &str, - treasury_manager_contract: SupportedContracts, - holder: SupportedContracts, -) -> StdResult { - match manager::QueryMsg::Manager(manager::SubQueryMsg::Reserves { - holder: contracts.get(&holder).unwrap().address.to_string(), - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .address - .to_string(), - }) - .test_query( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - &chain, - )? { - manager::QueryAnswer::Reserves { amount } => Ok(amount), - _ => Err(StdError::generic_err( - "Failed to query treasury_manager reserves", - )), - } -} - -pub fn batch_balance_query( - chain: &App, - contracts: &DeployedContracts, - snip20_symbols: Vec<&str>, - treasury_manager_contract: SupportedContracts, - holder: SupportedContracts, -) -> StdResult> { - let assets = { - let mut vec = vec![]; - for symbols in snip20_symbols { - vec.push( - contracts - .get(&SupportedContracts::Snip20(symbols.to_string())) - .unwrap() - .clone() - .address - .to_string(), - ); - } - vec - }; - match manager::QueryMsg::Manager(manager::SubQueryMsg::BatchBalance { - holder: contracts.get(&holder).unwrap().address.to_string(), - assets, - }) - .test_query( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - &chain, - )? { - manager::QueryAnswer::BatchBalance { amounts } => Ok(amounts), - _ => Err(StdError::generic_err( - "Failed to query treasury_manager reserves", - )), - } -} - -pub fn balance_query( - chain: &App, - contracts: &DeployedContracts, - snip20_symbol: &str, - treasury_manager_contract: SupportedContracts, - holder: SupportedContracts, -) -> StdResult { - match treasury_manager::QueryMsg::Manager(manager::SubQueryMsg::Balance { - holder: contracts.get(&holder).unwrap().address.to_string(), - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .address - .to_string(), - }) - .test_query( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - &chain, - )? { - manager::QueryAnswer::Balance { amount } => Ok(amount), - _ => Err(StdError::generic_err( - "Failed to query treasury_manager balance", - )), - } -} - -pub fn update_config_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - treasury_manager_contract: SupportedContracts, - admin_auth: Option, - treasury: Option, -) -> StdResult<()> { - match (treasury_manager::ExecuteMsg::UpdateConfig { - admin_auth, - treasury, - } - .test_exec( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - &[], - )) { - Ok(_) => Ok(()), - Err(_) => Err(StdError::generic_err("claim in treasury manager failed")), - } -} - -pub fn claim_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - snip20_symbol: &str, - treasury_manager_contract: SupportedContracts, -) -> StdResult<()> { - match treasury_manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Claim { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .address - .to_string(), - }) - .test_exec( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - &[], - ) { - Ok(_) => Ok(()), - Err(e) => Err(StdError::generic_err(e.to_string())), - } -} - -pub fn unbond_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - snip20_symbol: &str, - treasury_manager_contract: SupportedContracts, - amount: Uint128, -) -> StdResult<()> { - match treasury_manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Unbond { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .address - .to_string(), - amount, - }) - .test_exec( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - &[], - ) { - Ok(_) => Ok(()), - Err(_) => Err(StdError::generic_err("update in treasury manager failed")), - } -} - -pub fn update_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - snip20_symbol: &str, - treasury_manager_contract: SupportedContracts, -) -> StdResult<()> { - match treasury_manager::ExecuteMsg::Manager(manager::SubExecuteMsg::Update { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .address - .to_string(), - }) - .test_exec( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - &[], - ) { - Ok(_) => Ok(()), - Err(e) => Err(StdError::generic_err(e.to_string())), - } -} - -pub fn register_holder_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - treasury_manager_contract: SupportedContracts, - holder: &str, -) -> StdResult<()> { - match (treasury_manager::ExecuteMsg::AddHolder { - holder: holder.to_string(), - } - .test_exec( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - &[], - )) { - Ok(_) => Ok(()), - Err(_) => Err(StdError::generic_err( - "register_holder in treasury manager failed", - )), - } -} - -pub fn remove_holder_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - treasury_manager_contract: SupportedContracts, - holder: &str, -) -> StdResult<()> { - match (treasury_manager::ExecuteMsg::RemoveHolder { - holder: holder.to_string(), - } - .test_exec( - &contracts - .get(&treasury_manager_contract) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - &[], - )) { - Ok(_) => Ok(()), - Err(_) => Err(StdError::generic_err( - "register_holder in treasury manager failed", - )), - } -} - -pub fn register_asset_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - snip20_symbol: &str, - tm_contract: SupportedContracts, -) -> StdResult<()> { - match (treasury_manager::ExecuteMsg::RegisterAsset { - contract: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .into(), - } - .test_exec( - &contracts.get(&tm_contract).unwrap().clone().into(), - chain, - Addr::unchecked(sender), - &[], - )) { - Ok(_) => Ok(()), - Err(e) => Err(StdError::generic_err(e.to_string())), - } -} - -pub fn allocate_exec( - chain: &mut App, - sender: &str, - contracts: &DeployedContracts, - snip20_symbol: &str, - nickname: Option, - contract_to_allocate_to: &SupportedContracts, - alloc_type: treasury_manager::AllocationType, - amount: Uint128, - tolerance: Uint128, - id: usize, -) -> StdResult<()> { - match (treasury_manager::ExecuteMsg::Allocate { - asset: contracts - .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) - .unwrap() - .clone() - .address - .to_string(), - allocation: treasury_manager::RawAllocation { - nick: nickname, - contract: RawContract::from(contracts.get(contract_to_allocate_to).unwrap().clone()), - alloc_type, - amount, - tolerance, - }, - } - .test_exec( - &contracts - .get(&SupportedContracts::TreasuryManager(id)) - .unwrap() - .clone() - .into(), - chain, - Addr::unchecked(sender), - &[], - )) { - Ok(_) => Ok(()), - Err(e) => Err(StdError::generic_err(e.to_string())), - } -} diff --git a/packages/multi_test/src/interfaces/utils.rs b/packages/multi_test/src/interfaces/utils.rs index f8b7524..0c40210 100644 --- a/packages/multi_test/src/interfaces/utils.rs +++ b/packages/multi_test/src/interfaces/utils.rs @@ -3,12 +3,7 @@ use std::collections::HashMap; #[derive(Clone, Eq, PartialEq, Hash)] pub enum SupportedContracts { - AdminAuth, - Snip20(String), - Treasury, - TreasuryManager(usize), MockAdapter(usize), - ScrtStaking, } pub type DeployedContracts = HashMap; diff --git a/packages/multi_test/src/multi.rs b/packages/multi_test/src/multi.rs index 9296174..f227745 100644 --- a/packages/multi_test/src/multi.rs +++ b/packages/multi_test/src/multi.rs @@ -1,140 +1,140 @@ -#[cfg(feature = "admin")] -pub mod admin { - pub use admin; - use shade_protocol::{admin::InstantiateMsg, multi_test::App, utils::InstantiateCallback}; - multi_derive::implement_multi!(Admin, admin); - - // Multitest helper - pub fn init_admin_auth(app: &mut App, superadmin: &Addr) -> ContractInfo { - InstantiateMsg { - super_admin: Some(superadmin.clone().to_string()), - } - .test_init(Admin::default(), app, superadmin.clone(), "admin_auth", &[]) - .unwrap() - } -} - -#[cfg(feature = "snip20")] -pub mod snip20 { - use snip20; - multi_derive::implement_multi!(Snip20, snip20); -} - -#[cfg(feature = "liability_mint")] -pub mod liability_mint { - use liability_mint; - multi_derive::implement_multi!(LiabilityMint, liability_mint); -} - -#[cfg(feature = "stkd_scrt")] -pub mod stkd_scrt { - use stkd_scrt; - multi_derive::implement_multi!(StkdScrt, stkd_scrt); -} - -// #[cfg(feature = "mint")] -// pub mod mint { -// use mint; -// multi_derive::implement_multi!(Mint, mint); +// #[cfg(feature = "admin")] +// pub mod admin { +// pub use admin; +// use shade_protocol::{admin::InstantiateMsg, multi_test::App, utils::InstantiateCallback}; +// multi_derive::implement_multi!(Admin, admin); + +// // Multitest helper +// pub fn init_admin_auth(app: &mut App, superadmin: &Addr) -> ContractInfo { +// InstantiateMsg { +// super_admin: Some(superadmin.clone().to_string()), +// } +// .test_init(Admin::default(), app, superadmin.clone(), "admin_auth", &[]) +// .unwrap() +// } // } -// #[cfg(feature = "oracle")] -// pub mod oracle { -// use oracle; -// multi_derive::implement_multi!(Oracle, oracle); +// #[cfg(feature = "snip20")] +// pub mod snip20 { +// use snip20; +// multi_derive::implement_multi!(Snip20, snip20); // } -// #[cfg(feature = "mock_band")] -// pub mod mock_band { -// use crate::multi_derive; -// use mock_band; +// #[cfg(feature = "liability_mint")] +// pub mod liability_mint { +// use liability_mint; +// multi_derive::implement_multi!(LiabilityMint, liability_mint); +// } + +// #[cfg(feature = "stkd_scrt")] +// pub mod stkd_scrt { +// use stkd_scrt; +// multi_derive::implement_multi!(StkdScrt, stkd_scrt); +// } + +// // #[cfg(feature = "mint")] +// // pub mod mint { +// // use mint; +// // multi_derive::implement_multi!(Mint, mint); +// // } + +// // #[cfg(feature = "oracle")] +// // pub mod oracle { +// // use oracle; +// // multi_derive::implement_multi!(Oracle, oracle); +// // } + +// // #[cfg(feature = "mock_band")] +// // pub mod mock_band { +// // use crate::multi_derive; +// // use mock_band; + +// // pub struct MockBand; +// // multi_derive::implement_multi!(MockBand, mock_band); +// // } + +// #[cfg(feature = "governance")] +// pub mod governance { +// use governance; + +// multi_derive::implement_multi_with_reply!(Governance, governance); +// } + +// // #[cfg(feature = "snip20_staking")] +// // pub mod snip20_staking { +// // use spip_stkd_0; +// // +// // multi_derive::implement_multi!(Snip20Staking, spip_stkd_0); +// // } + +// // #[cfg(feature = "bonds")] +// // pub mod bonds { +// // use crate::multi_derive; +// // use bonds; + +// // pub struct Bonds; +// // multi_derive::implement_multi!(Bonds, bonds); +// // } -// pub struct MockBand; -// multi_derive::implement_multi!(MockBand, mock_band); +// #[cfg(feature = "query_auth")] +// pub mod query_auth { +// use query_auth; + +// multi_derive::implement_multi!(QueryAuth, query_auth); +// } + +// #[cfg(feature = "treasury_manager")] +// pub mod treasury_manager { +// use treasury_manager; +// multi_derive::implement_multi!(TreasuryManager, treasury_manager); // } -#[cfg(feature = "governance")] -pub mod governance { - use governance; +// #[cfg(feature = "treasury")] +// pub mod treasury { +// use treasury; +// multi_derive::implement_multi!(Treasury, treasury); +// } - multi_derive::implement_multi_with_reply!(Governance, governance); -} +// #[cfg(feature = "mock_adapter")] +// pub mod mock_adapter { +// use mock_adapter; +// multi_derive::implement_multi!(MockAdapter, mock_adapter); +// } + +// #[cfg(feature = "scrt_staking")] +// pub mod scrt_staking { +// use scrt_staking; +// multi_derive::implement_multi!(ScrtStaking, scrt_staking); +// } -// #[cfg(feature = "snip20_staking")] -// pub mod snip20_staking { -// use spip_stkd_0; -// -// multi_derive::implement_multi!(Snip20Staking, spip_stkd_0); +// #[cfg(feature = "basic_staking")] +// pub mod basic_staking { +// use basic_staking; +// multi_derive::implement_multi!(BasicStaking, basic_staking); // } -// #[cfg(feature = "bonds")] -// pub mod bonds { -// use crate::multi_derive; -// use bonds; +// #[cfg(feature = "peg_stability")] +// pub mod peg_stability { +// use peg_stability; -// pub struct Bonds; -// multi_derive::implement_multi!(Bonds, bonds); +// multi_derive::implement_multi!(PegStability, peg_stability); // } -#[cfg(feature = "query_auth")] -pub mod query_auth { - use query_auth; - - multi_derive::implement_multi!(QueryAuth, query_auth); -} - -#[cfg(feature = "treasury_manager")] -pub mod treasury_manager { - use treasury_manager; - multi_derive::implement_multi!(TreasuryManager, treasury_manager); -} - -#[cfg(feature = "treasury")] -pub mod treasury { - use treasury; - multi_derive::implement_multi!(Treasury, treasury); -} - -#[cfg(feature = "mock_adapter")] -pub mod mock_adapter { - use mock_adapter; - multi_derive::implement_multi!(MockAdapter, mock_adapter); -} - -#[cfg(feature = "scrt_staking")] -pub mod scrt_staking { - use scrt_staking; - multi_derive::implement_multi!(ScrtStaking, scrt_staking); -} - -#[cfg(feature = "basic_staking")] -pub mod basic_staking { - use basic_staking; - multi_derive::implement_multi!(BasicStaking, basic_staking); -} - -#[cfg(feature = "peg_stability")] -pub mod peg_stability { - use peg_stability; - - multi_derive::implement_multi!(PegStability, peg_stability); -} - -#[cfg(feature = "mock_stkd")] -pub mod mock_stkd { - pub use mock_stkd; - multi_derive::implement_multi!(MockStkd, mock_stkd); -} - -#[cfg(feature = "mock_sienna")] -pub mod mock_sienna { - pub use mock_sienna; - multi_derive::implement_multi!(MockSienna, mock_sienna); -} - -#[cfg(feature = "snip20_migration")] -pub mod snip20_migration { - use snip20_migration; - - multi_derive::implement_multi!(Snip20Migration, snip20_migration); -} +// #[cfg(feature = "mock_stkd")] +// pub mod mock_stkd { +// pub use mock_stkd; +// multi_derive::implement_multi!(MockStkd, mock_stkd); +// } + +// #[cfg(feature = "mock_sienna")] +// pub mod mock_sienna { +// pub use mock_sienna; +// multi_derive::implement_multi!(MockSienna, mock_sienna); +// } + +// #[cfg(feature = "snip20_migration")] +// pub mod snip20_migration { +// use snip20_migration; + +// multi_derive::implement_multi!(Snip20Migration, snip20_migration); +// } diff --git a/packages/shade_protocol/Cargo.toml b/packages/shade_protocol/Cargo.toml index 9e307df..dfec136 100644 --- a/packages/shade_protocol/Cargo.toml +++ b/packages/shade_protocol/Cargo.toml @@ -14,8 +14,7 @@ name = "schemas" path = "src/schemas.rs" # Must have all of the contract_interfaces required-features = [ - "admin", "airdrop", "bonds", "dao", "dex", "governance-impl", "mint", "oracles", - "peg_stability", "query_auth", "sky", "snip20", "staking", "mint_router", "snip20_migration", + "airdrop", ] [lib] diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs index 287977a..88680e1 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs @@ -1,24 +1,26 @@ -use crate::contract_interfaces::airdrop::errors::permit_rejected; use crate::c_std::Uint128; -use crate::c_std::{Addr, StdResult, Api}; +use crate::c_std::{Addr, Api, StdResult}; +use crate::contract_interfaces::airdrop::errors::permit_rejected; use crate::query_authentication::{ permit::{bech32_to_canonical, Permit}, viewing_keys::ViewingKey, }; -use cosmwasm_schema::{cw_serde}; +use cosmwasm_schema::cw_serde; #[cw_serde] pub struct Account { pub addresses: Vec, - pub total_claimable: Uint128, + pub eth_pubkey: String, + pub claimed: bool, } impl Default for Account { fn default() -> Self { Self { addresses: vec![], - total_claimable: Uint128::zero(), + eth_pubkey: "".to_string(), + claimed: false } } } @@ -38,6 +40,7 @@ pub struct AccountPermitMsg { pub struct FillerMsg { pub coins: Vec, pub contract: String, + pub eth_pubkey: String, pub execute_msg: EmptyMsg, pub sender: String, } @@ -49,6 +52,7 @@ impl Default for FillerMsg { contract: "".to_string(), sender: "".to_string(), execute_msg: EmptyMsg {}, + eth_pubkey: "".to_string(), } } } @@ -60,7 +64,11 @@ pub struct EmptyMsg {} // Used to prove ownership over IBC addresses pub type AddressProofPermit = Permit; -pub fn authenticate_ownership(api: &dyn Api, permit: &AddressProofPermit, permit_address: &str) -> StdResult<()> { +pub fn authenticate_ownership( + api: &dyn Api, + permit: &AddressProofPermit, + permit_address: &str, +) -> StdResult<()> { let signer_address = permit .validate(api, Some("wasm/MsgExecuteContract".to_string()))? .as_canonical(); diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/claim_info.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/claim_info.rs index 64e05a1..30becec 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/claim_info.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/claim_info.rs @@ -1,8 +1,8 @@ -use crate::c_std::{Uint128, Addr}; -use cosmwasm_schema::{cw_serde}; +// use crate::c_std::{Uint128, Addr}; +// use cosmwasm_schema::{cw_serde}; -#[cw_serde] -pub struct RequiredTask { - pub address: Addr, - pub percent: Uint128, -} +// #[cw_serde] +// pub struct RequiredTask { +// pub address: Addr, +// pub percent: Uint128, +// } diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/errors.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/errors.rs index ab9defb..4d91022 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/errors.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/errors.rs @@ -15,6 +15,7 @@ pub enum Error { PermitKeyRevoked, PermitRejected, NotAdmin, + AlreadyClaimed, AccountAlreadyCreated, AccountDoesntExist, NothingToClaim, @@ -28,6 +29,8 @@ pub enum Error { AirdropEnded, InvalidViewingKey, UnexpectedError, + WrongLength, + FailedVerification, } impl_into_u8!(Error); @@ -47,6 +50,7 @@ impl CodeType for Error { Error::NotAdmin => build_string("Can only be accessed by {}", context), Error::AccountAlreadyCreated => build_string("Account already exists", context), Error::AccountDoesntExist => build_string("Account does not exist", context), + Error::AlreadyClaimed => build_string("Already claimed", context), Error::NothingToClaim => build_string("Amount to claim is 0", context), Error::DecayClaimed => build_string("Decay already claimed", context), Error::NoDecaySet => build_string("Decay has not been set", context), @@ -62,6 +66,8 @@ impl CodeType for Error { Error::AirdropEnded => build_string("Airdrop ended on {}, its currently {}", context), Error::InvalidViewingKey => build_string("Provided viewing key is invalid", context), Error::UnexpectedError => build_string("Something unexpected happened", context), + Error::WrongLength => build_string("Wrong Length", context), + Error::FailedVerification => build_string("Verification Failed", context), } } } @@ -69,9 +75,11 @@ impl CodeType for Error { const AIRDROP_TARGET: &str = "airdrop"; pub fn invalid_task_percentage(percentage: &str) -> StdError { - DetailedError::from_code(AIRDROP_TARGET, Error::InvalidTaskPercentage, vec![ - percentage, - ]) + DetailedError::from_code( + AIRDROP_TARGET, + Error::InvalidTaskPercentage, + vec![percentage], + ) .to_error() } @@ -82,20 +90,20 @@ pub fn invalid_dates( item_b: &str, item_b_amount: &str, ) -> StdError { - DetailedError::from_code(AIRDROP_TARGET, Error::InvalidDates, vec![ - item_a, - item_a_amount, - precedence, - item_b, - item_b_amount, - ]) + DetailedError::from_code( + AIRDROP_TARGET, + Error::InvalidDates, + vec![item_a, item_a_amount, precedence, item_b, item_b_amount], + ) .to_error() } pub fn permit_contract_mismatch(contract: &str, expected: &str) -> StdError { - DetailedError::from_code(AIRDROP_TARGET, Error::PermitContractMismatch, vec![ - contract, expected, - ]) + DetailedError::from_code( + AIRDROP_TARGET, + Error::PermitContractMismatch, + vec![contract, expected], + ) .to_error() } @@ -148,9 +156,11 @@ pub fn invalid_partial_tree() -> StdError { } pub fn airdrop_not_started(start: &str, current: &str) -> StdError { - DetailedError::from_code(AIRDROP_TARGET, Error::AirdropNotStarted, vec![ - start, current, - ]) + DetailedError::from_code( + AIRDROP_TARGET, + Error::AirdropNotStarted, + vec![start, current], + ) .to_error() } @@ -165,3 +175,14 @@ pub fn invalid_viewing_key() -> StdError { pub fn unexpected_error() -> StdError { DetailedError::from_code(AIRDROP_TARGET, Error::UnexpectedError, vec![]).to_error() } + +pub fn already_claimed() -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::AlreadyClaimed, vec![]).to_error() +} + +pub fn wrong_length() -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::ExpectedMemo, vec![]).to_error() +} +pub fn failed_verification() -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::ExpectedMemo, vec![]).to_error() +} diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs index 54d4d1b..a3bf497 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs @@ -4,10 +4,7 @@ pub mod errors; use crate::{ c_std::{Addr, Binary, Uint128}, - contract_interfaces::airdrop::{ - account::{AccountPermit, AddressProofPermit}, - claim_info::RequiredTask, - }, + contract_interfaces::airdrop::account::{AccountPermit, AddressProofPermit}, utils::{asset::Contract, generic_response::ResponseStatus}, }; @@ -24,7 +21,7 @@ pub struct Config { // The snip20 to be minted pub airdrop_snip20: Contract, // An optional, second snip20 to be minted - pub airdrop_snip20_optional: Option, + pub airdrop_snip20_optional: Contract, // Airdrop amount pub airdrop_amount: Uint128, // Required tasks @@ -34,18 +31,16 @@ pub struct Config { // Airdrop stops at end date if there is one pub end_date: Option, // Starts to decay at this date - // pub decay_start: Option, + pub decay_start: Option, // This is necessary to validate the airdrop information // tree root - pub merkle_root: Binary, + pub merkle_root: String, // tree height pub total_accounts: u32, - // max possible reward amount; used to prevent collision possibility - pub max_amount: Uint128, + // {wallet} + pub claim_msg_plaintext: String, // Protects from leaking user information by limiting amount detail pub query_rounding: Uint128, - - pub claim_msg_plaintext: String, } #[cw_serde] @@ -54,30 +49,24 @@ pub struct InstantiateMsg { // Where the decayed tokens will be dumped, if none then nothing happens pub dump_address: Option, pub airdrop_token: Contract, + // An optional, second snip20 to be minted + pub airdrop_snip20_optional: Contract, // Airdrop amount pub airdrop_amount: Uint128, - // An optional, second snip20 to be minted - pub airdrop_snip20_optional: Option, // The airdrop time limit pub start_date: Option, // Can be set to never end pub end_date: Option, // Starts to decay at this date - // pub decay_start: Option, + pub decay_start: Option, // Base64 encoded version of the tree root - pub merkle_root: Binary, + pub merkle_root: String, // Root height pub total_accounts: u32, - // Max possible reward amount - pub max_amount: Uint128, - // Default gifted amount - // pub default_claim: Uint128, - // The task related claims - // pub task_claim: Vec, - // Protects from leaking user information by limiting amount detail - pub query_rounding: Uint128, /// {wallet} pub claim_msg_plaintext: String, + // Protects from leaking user information by limiting amount detail + pub query_rounding: Uint128, } impl InstantiateCallback for InstantiateMsg { @@ -85,7 +74,7 @@ impl InstantiateCallback for InstantiateMsg { } #[cw_serde] -pub enum sExecuteMsg { +pub enum ExecuteMsg { UpdateConfig { admin: Option, dump_address: Option, @@ -95,15 +84,9 @@ pub enum sExecuteMsg { decay_start: Option, padding: Option, }, - // AddTasks { - // tasks: Vec, - // padding: Option, - // }, - // CompleteTask { - // address: Addr, - // padding: Option, - // }, Account { + eth_pubkey: String, + amount: Option, addresses: Vec, partial_tree: Vec, padding: Option, @@ -137,22 +120,12 @@ pub enum ExecuteAnswer { UpdateConfig { status: ResponseStatus, }, - // AddTask { - // status: ResponseStatus, - // }, - // CompleteTask { - // status: ResponseStatus, - // }, - // Account { - // status: ResponseStatus, - // // Total eligible - // total: Uint128, - // // Total claimed - // claimed: Uint128, - // finished_tasks: Vec, - // // Addresses claimed - // addresses: Vec, - // }, + Account { + status: ResponseStatus, + addresses: Vec, + eth_pubkey: String, + claimed: bool, + }, DisablePermitKey { status: ResponseStatus, }, @@ -161,17 +134,13 @@ pub enum ExecuteAnswer { }, Claim { status: ResponseStatus, - // Total eligible - total: Uint128, - // Total claimed - claimed: Uint128, - // finished_tasks: Vec, - // Addresses claimed + claimed: bool, addresses: Vec, + eth_pubkey: String, + }, + ClaimDecay { + status: ResponseStatus, }, - // ClaimDecay { - // status: ResponseStatus, - // }, } #[cw_serde] @@ -181,14 +150,14 @@ pub enum QueryMsg { current_date: Option, }, TotalClaimed {}, - // Account { - // permit: AccountPermit, - // current_date: Option, - // }, + Account { + permit: AccountPermit, + eth_pubkey: String, + }, AccountWithKey { account: Addr, key: String, - current_date: Option, + eth_pubkey: String, }, } @@ -204,27 +173,20 @@ pub enum QueryAnswer { Dates { start: u64, end: Option, - decay_start: Option, - decay_factor: Option, }, TotalClaimed { claimed: Uint128, }, Account { - // Total eligible - total: Uint128, - // Total claimed - claimed: Uint128, - // Total unclaimed but available - unclaimed: Uint128, - finished_tasks: Vec, - // Addresses claimed + claimed: bool, addresses: Vec, + eth_pubkey: String, }, } #[cw_serde] pub struct AccountVerification { + pub eth_pubkey: String, pub account: Addr, pub claimed: bool, } diff --git a/packages/shade_protocol/src/schemas.rs b/packages/shade_protocol/src/schemas.rs index 5a50a2d..fce0c51 100644 --- a/packages/shade_protocol/src/schemas.rs +++ b/packages/shade_protocol/src/schemas.rs @@ -45,20 +45,11 @@ macro_rules! generate_nested_schemas { pub fn main() { generate_schemas!( - airdrop, - bonds, - governance, - peg_stability, - query_auth, - sky, - snip20 + airdrop ); // generate_nested_schemas!(mint, liability_mint, mint, mint_router); - generate_nested_schemas!(oracles, oracle); - - generate_nested_schemas!(dao, treasury_manager, treasury, scrt_staking); // generate_nested_schemas!(staking, snip20_staking); diff --git a/temp-contracts/liability_mint/.cargo/config b/temp-contracts/liability_mint/.cargo/config deleted file mode 100644 index 882fe08..0000000 --- a/temp-contracts/liability_mint/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[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/temp-contracts/liability_mint/.circleci/config.yml b/temp-contracts/liability_mint/.circleci/config.yml deleted file mode 100644 index 127e1ae..0000000 --- a/temp-contracts/liability_mint/.circleci/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.43.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Build - command: cargo wasm --locked - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Integration tests - command: cargo integration-test --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/temp-contracts/liability_mint/Cargo.toml b/temp-contracts/liability_mint/Cargo.toml deleted file mode 100644 index 0a4f1e0..0000000 --- a/temp-contracts/liability_mint/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -name = "liability_mint" -version = "0.1.0" -authors = [ - "Guy Garcia ", - "Jackson Swenson ", -] -edition = "2018" - -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"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["shade-protocol/backtraces"] -debug-print = ["shade-protocol/debug-print"] - -[dependencies] -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = [ "mint", "liability_mint", "snip20", "storage_plus", "chrono"] } -schemars = "0.7" -cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" } -cosmwasm-schema = "1.1.5" -shade-oracles = { git = "https://github.com/securesecrets/shade-oracle" } - -[dev-dependencies] -shade-multi-test = { path = "../../packages/multi_test", features = [ - "liability_mint", - "snip20" -] } diff --git a/temp-contracts/liability_mint/Makefile b/temp-contracts/liability_mint/Makefile deleted file mode 100644 index 2493c22..0000000 --- a/temp-contracts/liability_mint/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.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/*.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 - -# 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/temp-contracts/liability_mint/README.md b/temp-contracts/liability_mint/README.md deleted file mode 100644 index e7d2c66..0000000 --- a/temp-contracts/liability_mint/README.md +++ /dev/null @@ -1,215 +0,0 @@ - -# Mint Contract -* [Introduction](#Introduction) -* [Sections](#Sections) - * [Init](#Init) - * [Admin](#Admin) - * Messages - * [UpdateConfig](#UpdateConfig) - * [UpdateMintLimit](#UpdateMintLimit) - * [RegisterAsset](#RegisterAsset) - * [RemoveAsset](#RemoveAsset) - * [User](#User) - * Messages - * [Receive](#Receive) - * Queries - * [GetNativeAsset](#GetNativeAsset) - * [GetConfig](#GetConfig) - * [GetMintLimit](#GetMintLimit) - * [GetSupportedAssets](#GetSupportedAssets) - * [GetAsset](#GetAsset) -# Introduction -Contract responsible to mint a paired snip20 asset - -# Sections - -## Init -##### Request -|Name |Type |Description | optional | -|-----------------|------------|-------------------------------------------------------------------------------|----------| -|admin | string | New contract owner; SHOULD be a valid bech32 address | yes | -|native_asset | Contract | Asset to mint | no | -|oracle | Contract | Oracle contract | no | -|peg | String | Symbol to peg to when querying oracle (defaults to native_asset symbol) | yes | -|treasury | Contract | Treasury contract | yes | -|secondary_burn | Addrr | Where non-burnable assets will go | yes | -|start_epoch | String | The starting epoch | yes | -|epoch_frequency | String | The frequency in which the mint limit resets, if 0 then no limit is enforced | yes | -|epoch_mint_limit | String | The limit of uTokens to mint per epoch | yes | -## Admin - -### Messages -#### UpdateConfig -Updates the given values -##### Request -|Name |Type |Description | optional | -|---------------|------------|-------------------------------------------------------|----------| -|admin | string | New contract admin; SHOULD be a valid bech32 address | yes | -|oracle | Contract | Oracle contract | yes | -|treasury | Contract | Treasury contract | yes | -|secondary_burn | Addrr | Where non-burnable assets will go | yes | -##### Response -```json -{ - "update_config": { - "status": "success" - } -} -``` - -#### UpdateMintLimit -Updates the mint limit and epoch time -##### Request -|Name |Type |Description | optional | -|-----------------|----------|--------------------------------------------------------------------------------|----------| -|start_epoch | String | The starting epoch | yes | -|epoch_frequency | String | The frequency in which the mint limit resets, if 0 then no limit is enforced | yes | -|epoch_mint_limit | String | The limit of uTokens to mint per epoch | yes | -##### Response -```json -{ - "update_mint_limit": { - "status": "success" - } -} -``` - -#### RegisterAsset -Registers a supported asset. The asset must be SNIP-20 compliant since [RegisterReceive](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md#RegisterReceive) is called. - -##### Request -|Name |Type |Description | optional | -|------------|--------|-------------------------------------|----------| -|contract | Contract | Type explained [here](#Contract) | no | -##### Response -```json -{ - "register_asset": { - "status": "success" - } -} -``` - -#### RemoveAsset -Remove a registered asset. -##### Request -|Name |Type |Description | optional | -|------------|--------|--------------------------------|----------| -|address | String | The asset to remove's address | no | -##### Response -```json -{ - "remove_asset": { - "status": "success" - } -} -``` - -##User - -### Messages - -#### Receive -To mint the user must use a supported asset's send function and send the amount over to the contract's address. The contract will take care of the rest. - -In the msg field of a snip20 send command you must send a base64 encoded json like this one -```json -{"minimum_expected_amount": "Uint128" } -``` - -### Queries - -#### GetNativeAsset -Gets the contract's minted asset -#### Response -```json -{ - "native_asset": { - "asset": "Snip20Asset Object", - "peg": "Pegged symbol" - } -} -``` - -#### GetConfig -Gets the contract's configuration variables -##### Response -```json -{ - "config": { - "config": { - "admin": "Owner address", - "oracle": { - "address": "Asset contract address", - "code_hash": "Asset callback code hash" - }, - "treasury": { - "address": "Asset contract address", - "code_hash": "Asset callback code hash" - }, - "secondary_burn": "Optional burn address", - "activated": "Boolean of contract's actviation status" - } - } -} -``` - -#### GetMintLimit -Gets the contract's configuration variables -##### Response -```json -{ - "limit": { - "mint_limit": { - "frequency": "Frequency per epoch reset", - "mint_capacity": "Mint capacity per epoch", - "total_minted": "Total minted in current epoch", - "next_epoch": "Timestamp for the next epoch" - } - } -} -``` - -#### GetSupportedAssets -Get all the contract's supported assets. -##### Response -```json -{ - "supported_assets": { - "assets": ["asset address"] - } -} -``` - -#### GetAsset -Get specific information on a supported asset. -##### Request -|Name |Type |Description | optional | -|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| -|contract | string | Snip20 contract address; SHOULD be a valid bech32 address, but contracts may use a different naming scheme as well | no | -##### Response -```json -{ - "asset": { - "asset": { - "Snip20Asset": { - "contract": "Asset contract", - "token_info": "Token info as per Snip20", - "token_config": "Optional information about the config if the Snip20 supports it" - }, - "burned": "Total burned on this contract" - } - } -} -``` - -## Contract -Type used in many of the admin commands -```json -{ - "config": { - "address": "Asset contract address", - "code_hash": "Asset callback code hash" - } -} -``` \ No newline at end of file diff --git a/temp-contracts/liability_mint/src/contract.rs b/temp-contracts/liability_mint/src/contract.rs deleted file mode 100644 index 5910fab..0000000 --- a/temp-contracts/liability_mint/src/contract.rs +++ /dev/null @@ -1,71 +0,0 @@ -use shade_protocol::c_std::{ - entry_point, to_binary, Api, Binary, Deps, DepsMut, Env, MessageInfo, Querier, Response, - StdResult, Storage, Uint128, -}; -use shade_protocol::snip20::helpers::{fetch_snip20, register_receive, token_config, token_info}; - -use shade_protocol::contract_interfaces::{ - mint::liability_mint::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}, - snip20::helpers::Snip20Asset, -}; - -use crate::{execute, query, storage::*}; - -#[entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - let config = Config { - admin: match msg.admin { - None => info.sender.clone(), - Some(admin) => admin, - }, - token: msg.token, - debt_ratio: msg.debt_ratio, - oracle: msg.oracle, - treasury: msg.treasury, - }; - - CONFIG.save(deps.storage, &config)?; - TOKEN.save(deps.storage, &fetch_snip20(&msg.token, &deps.querier)?)?; - LIABILITIES.save(deps.storage, &Uint128::zero())?; - WHITELIST.save(deps.storage, &Vec::new())?; - COLLATERAL.save(deps.storage, &Vec::new())?; - - deps.api - .debug(&format!("Contract was initialized by {}", info.sender)); - - Ok(Response::new().add_message(register_receive(env.contract.code_hash, None, &msg.token)?)) -} - -#[entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - ExecuteMsg::UpdateConfig { config } => execute::try_update_config(deps, env, info, config), - ExecuteMsg::Receive { - sender, - from, - amount, - msg, - .. - } => execute::receive(deps, env, info, sender, from, amount, msg), - ExecuteMsg::AddWhitelist { address } => execute::add_whitelist(deps, env, info, address), - ExecuteMsg::RemoveWhitelist { address } => execute::rm_whitelist(deps, env, info, address), - ExecuteMsg::AddCollateral { asset } => execute::add_collateral(deps, env, info, asset), - ExecuteMsg::RemoveCollateral { asset } => execute::rm_collateral(deps, env, info, asset), - ExecuteMsg::Mint { amount } => execute::mint(deps, env, info, amount), - } -} - -#[entry_point] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query::config(deps)?), - QueryMsg::Token {} => to_binary(&query::token(deps)?), - QueryMsg::Liabilities {} => to_binary(&query::liabilities(deps)?), - QueryMsg::Whitelist {} => to_binary(&query::whitelist(deps)?), - } -} diff --git a/temp-contracts/liability_mint/src/execute.rs b/temp-contracts/liability_mint/src/execute.rs deleted file mode 100644 index 9859be5..0000000 --- a/temp-contracts/liability_mint/src/execute.rs +++ /dev/null @@ -1,311 +0,0 @@ -use shade_protocol::{ - c_std::{ - from_binary, - to_binary, - Addr, - Api, - Binary, - CosmosMsg, - Deps, - DepsMut, - Env, - MessageInfo, - Querier, - QuerierWrapper, - Response, - StdError, - StdResult, - Storage, - Uint128, - }, - chrono::prelude::*, - dao::adapter, - mint::liability_mint::{Config, HandleAnswer}, - snip20::helpers::{ - self, - burn_msg, - fetch_snip20, - mint_msg, - send_msg, - token_config, - token_info, - Snip20Asset, - TokenConfig, - }, - utils::{asset::Contract, callback::Query, generic_response::ResponseStatus}, -}; - -use crate::storage::*; -use shade_oracles::{ - common::{querier::query_prices, OraclePrice}, - interfaces::router, -}; - -pub fn receive( - deps: DepsMut, - env: Env, - info: MessageInfo, - _sender: Addr, - from: Addr, - amount: Uint128, - msg: Option, -) -> StdResult { - let token = TOKEN.load(deps.storage)?; - - if info.sender == token.contract.address { - //TODO Burn tokens - let liab = LIABILITIES.load(deps.storage)?; - - let mut messages = vec![]; - let mut burn_amount = amount; - - // Handle excess tokens - if liab < amount { - burn_amount = liab; - - //TODO to treasury? - messages.push(send_msg( - from, - liab - amount, - None, - None, - None, - &token.contract, - )?); - } - - messages.push(burn_msg(burn_amount, None, None, &token.contract)?); - - LIABILITIES.save(deps.storage, &(liab - burn_amount))?; - - Ok(Response::new() - .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::Mint { - status: ResponseStatus::Success, - amount, - })?)) - } else { - return Err(StdError::generic_err(format!( - "Unrecognized token {}", - info.sender - ))); - } -} - -pub fn try_update_config( - deps: DepsMut, - env: Env, - info: MessageInfo, - config: Config, -) -> StdResult { - let cur_config = CONFIG.load(deps.storage)?; - - // Admin-only - if info.sender != cur_config.admin { - return Err(StdError::generic_err("unauthorized")); - } - - CONFIG.save(deps.storage, &config)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { - status: ResponseStatus::Success, - })?), - ) -} - -/* Queries the treasury for 'collateral_assets' balances - * Queries oracle for 'collateral_assets' + 'debt_asset' USD prices - * Returns the debt limit of 'debt_asset' - * - * NOTE - * This is an N time operation, treasury should - * implement batch queries - * maybe manager & adapter as well? - */ -pub fn debt_limit( - deps: Deps, - debt_asset: Snip20Asset, - collateral_assets: Vec, - debt_ratio: Uint128, - oracle: Contract, - treasury: Contract, -) -> StdResult { - let mut balances: Vec = vec![]; - let mut symbols: Vec = vec![]; - for asset in COLLATERAL.load(deps.storage)? { - balances.push(adapter::balance_query( - deps.querier, - &asset.contract.address, - treasury, - )?); - symbols.push(asset.token_info.symbol); - } - - symbols.push(debt_asset.token_info.symbol); - - let mut prices = query_prices(&oracle, &deps.querier, symbols.iter())? - .iter() - .map(|p| p.data.rate) - .collect(); - - let debt_price = prices.pop().data.rate; - let asset_value = prices - .iter() - .zip(balances.iter()) - .map(|(b, p)| b * p.data.rate) - .sum::(); - - Ok(asset_value.multiply_ratio(debt_ratio, 10u128.pow(18))) -} - -pub fn mint(deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - // Check if admin - if !WHITELIST.load(deps.storage)?.contains(&info.sender) { - return Err(StdError::generic_err("Unauthorized")); - } - - let limit = debt_limit( - deps.as_ref(), - TOKEN.load(deps.storage)?, - COLLATERAL.load(deps.storage)?, - config.debt_ratio, - config.oracle, - config.treasury, - )?; - // change to 'debt' nomenclature everywhere? - let debt = LIABILITIES.load(deps.storage)?; - - if debt + amount > limit { - return Err(StdError::generic_err(format!( - "Additional debt would exceed limit, current: {} / {}", - debt, limit, - ))); - } - - LIABILITIES.save(deps.storage, &(debt + amount))?; - - Ok(Response::new() - .add_message(mint_msg( - info.sender, - amount, - None, - None, - &TOKEN.load(deps.storage)?.contract, - )?) - .set_data(to_binary(&ExecuteAnswer::Mint { - status: ResponseStatus::Success, - amount, - })?)) -} - -pub fn add_whitelist( - deps: DepsMut, - env: Env, - info: MessageInfo, - address: Addr, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - // Check if admin - if info.sender != config.admin { - return Err(StdError::generic_err("Unauthorized")); - } - - let mut ws = WHITELIST.load(deps.storage)?; - ws.push(address); - WHITELIST.save(deps.storage, &ws)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::AddWhitelist { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn rm_whitelist( - deps: DepsMut, - env: Env, - info: MessageInfo, - address: Addr, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - // Check if admin - if info.sender != config.admin { - return Err(StdError::generic_err("Unauthorized")); - } - - let mut ws = WHITELIST.load(deps.storage)?; - - if let Some(i) = ws.iter().position(|a| *a == address.clone()) { - ws.remove(i); - } else { - return Err(StdError::generic_err("Not on whitelist")); - } - - WHITELIST.save(deps.storage, &ws)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::RemoveWhitelist { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn add_collateral( - deps: DepsMut, - env: Env, - info: MessageInfo, - asset: Contract, -) -> StdResult { - let mut config = CONFIG.load(deps.storage)?; - - // Check if admin - if info.sender != config.admin { - return Err(StdError::generic_err("Unauthorized")); - } - - //TODO verify snip20 with msg - let mut collateral = COLLATERAL.load(deps.storage)?; - collateral.push(fetch_snip20(&asset, &deps.querier)?); - COLLATERAL.save(deps.storage, &collateral)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::AddCollateral { - status: ResponseStatus::Success, - })?), - ) -} - -pub fn rm_collateral( - deps: DepsMut, - env: Env, - info: MessageInfo, - asset: Contract, -) -> StdResult { - let mut config = CONFIG.load(deps.storage)?; - - // Check if admin - if info.sender != config.admin { - return Err(StdError::generic_err("Unauthorized")); - } - - //TODO verify snip20 with msg - let mut collateral = COLLATERAL.load(deps.storage)?; - if let Some(pos) = collateral.iter().position(|a| a.contract == asset) { - collateral.swap_remove(pos); - } else { - return Err(StdError::generic_err("Not valid collateral")); - } - - COLLATERAL.save(deps.storage, &collateral)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::RemoveCollateral { - status: ResponseStatus::Success, - })?), - ) -} diff --git a/temp-contracts/liability_mint/src/lib.rs b/temp-contracts/liability_mint/src/lib.rs deleted file mode 100644 index 25ee0d9..0000000 --- a/temp-contracts/liability_mint/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod contract; -pub mod execute; -pub mod query; -pub mod storage; diff --git a/temp-contracts/liability_mint/src/query.rs b/temp-contracts/liability_mint/src/query.rs deleted file mode 100644 index 6c64042..0000000 --- a/temp-contracts/liability_mint/src/query.rs +++ /dev/null @@ -1,39 +0,0 @@ -use super::execute::debt_limit; -use crate::storage::*; -use shade_protocol::{ - c_std::{Deps, StdResult}, - contract_interfaces::mint::liability_mint::QueryAnswer, -}; - -pub fn config(deps: Deps) -> StdResult { - Ok(QueryAnswer::Config { - config: CONFIG.load(deps.storage)?, - }) -} -pub fn token(deps: Deps) -> StdResult { - Ok(QueryAnswer::Token { - token: TOKEN.load(deps.storage)?, - }) -} - -pub fn liabilities(deps: Deps) -> StdResult { - let config = CONFIG.load(deps.storage)?; - let limit = debt_limit( - deps, - TOKEN.load(deps.storage)?, - COLLATERAL.load(deps.storage)?, - config.debt_ratio, - config.oracle, - config.treasury, - )?; - Ok(QueryAnswer::Liabilities { - outstanding: LIABILITIES.load(deps.storage)?, - limit, - }) -} - -pub fn whitelist(deps: Deps) -> StdResult { - Ok(QueryAnswer::Whitelist { - whitelist: WHITELIST.load(deps.storage)?, - }) -} diff --git a/temp-contracts/liability_mint/src/storage.rs b/temp-contracts/liability_mint/src/storage.rs deleted file mode 100644 index 993ab4e..0000000 --- a/temp-contracts/liability_mint/src/storage.rs +++ /dev/null @@ -1,12 +0,0 @@ -use shade_protocol::c_std::{Addr, Storage, Uint128}; -use shade_protocol::contract_interfaces::mint::liability_mint::Config; -use shade_protocol::secret_storage_plus::Item; -use shade_protocol::snip20::helpers::Snip20Asset; - -pub const CONFIG: Item = Item::new("config"); -pub const LIABILITIES: Item = Item::new("liabilities"); -pub const TOKEN: Item = Item::new("token"); -pub const WHITELIST: Item> = Item::new("whitelist"); - -// iter item? -pub const COLLATERAL: Item> = Item::new("collateral"); diff --git a/temp-contracts/liability_mint/tests/integration.rs b/temp-contracts/liability_mint/tests/integration.rs deleted file mode 100644 index 78171b3..0000000 --- a/temp-contracts/liability_mint/tests/integration.rs +++ /dev/null @@ -1,195 +0,0 @@ -use shade_protocol::c_std::{ - coins, - from_binary, - to_binary, - Binary, - Env, - DepsMut, - Addr, - Response, - StdError, - StdResult, -}; - -use shade_protocol::c_std::Uint128; -use shade_protocol::{ - contract_interfaces::{ - snip20, - mint::liability_mint, - }, - utils::{ - MultiTestable, - InstantiateCallback, - ExecuteCallback, - Query, - asset::Contract, - price::{normalize_price, translate_price}, - }, -}; - -use shade_protocol::multi_test::{ App }; - -use shade_multi_test::multi::{ - snip20::Snip20, - liability_mint::LiabilityMint, -}; - -fn test_liabilities( - mint_amount: Uint128, - limit: Uint128, - payback: Uint128, - expected_balance: Uint128, - expected_liabilities: Uint128, -) { - let mut app = App::default(); - - let admin = Addr::unchecked("admin"); - let viewing_key = "viewing_key".to_string(); - - let token = snip20::InstantiateMsg { - name: "token".into(), - admin: Some(admin.clone().into()), - symbol: "TKN".into(), - decimals: 6, - initial_balances: None, - prng_seed: to_binary("").ok().unwrap(), - config: Some(snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: None, - enable_redeem: None, - enable_mint: Some(true), - enable_burn: Some(true), - enable_transfer: Some(true), - }), - query_auth: None, - }.test_init(Snip20::default(), &mut app, admin.clone(), "token", &[]).unwrap(); - - let liab_mint = liability_mint::InstantiateMsg { - admin: Some(admin.clone()), - token: Contract { - address: token.address.clone(), - code_hash: token.code_hash.clone(), - }, - limit, - }.test_init(LiabilityMint::default(), &mut app, admin.clone(), "liability_mint", &[]).unwrap(); - - // Setup liability minting - &snip20::ExecuteMsg::AddMinters { - minters: vec![liab_mint.address.to_string().clone()], - padding: None, - }.test_exec(&token, &mut app, admin.clone(), &[]).unwrap(); - - // add user to whitelist - &liability_mint::ExecuteMsg::AddWhitelist { - address: admin.clone(), - }.test_exec(&liab_mint, &mut app, admin.clone(), &[]).unwrap(); - - // Mint funds - &liability_mint::ExecuteMsg::Mint { - amount: mint_amount, - }.test_exec(&liab_mint, &mut app, admin.clone(), &[]).unwrap(); - - snip20::ExecuteMsg::SetViewingKey { - key: viewing_key.clone(), - padding: None, - }.test_exec(&token, &mut app, admin.clone(), &[]).unwrap(); - - // Check total supply - match (snip20::QueryMsg::TokenInfo { }).test_query(&token, &app).unwrap() { - snip20::QueryAnswer::TokenInfo { name, symbol, decimals, total_supply } => { - assert_eq!(total_supply.unwrap(), mint_amount, "total supply {} less than mint amount {}", total_supply.unwrap(), mint_amount); - }, - _ => { panic!("Query failed"); }, - } - // Check user balance - match (snip20::QueryMsg::Balance { - address: admin.to_string().clone(), - key: viewing_key.clone(), - }).test_query(&token, &app).unwrap() { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, mint_amount, "amount minted") - }, - _ => { panic!("Query failed"); }, - } - - // Check liabilities - match (liability_mint::QueryMsg::Liabilities { - }).test_query(&liab_mint, &app).unwrap() { - liability_mint::QueryAnswer::Liabilities { outstanding, limit } => { - assert_eq!(outstanding, mint_amount, "liabilities before payback") - }, - _ => { panic!("Query failed"); }, - } - - // Payback - snip20::ExecuteMsg::Send { - recipient: liab_mint.address.to_string().clone(), - recipient_code_hash: None, - amount: payback, - msg: None, - memo: None, - padding: None, - }.test_exec(&token, &mut app, admin.clone(), &[]).unwrap(); - - // Check total supply - match (snip20::QueryMsg::TokenInfo { }).test_query(&token, &app).unwrap() { - snip20::QueryAnswer::TokenInfo { name, symbol, decimals, total_supply } => { - assert_eq!(total_supply.unwrap(), mint_amount - payback, "total supply {} should be mint amount - payback {}", total_supply.unwrap(), mint_amount - payback); - }, - _ => { panic!("Query failed"); }, - } - // Check user balance - match (snip20::QueryMsg::Balance { - address: admin.to_string().clone(), - key: viewing_key.clone(), - }).test_query(&token, &app).unwrap() { - snip20::QueryAnswer::Balance { amount } => { - assert_eq!(amount, mint_amount - payback, "user balance after payback") - }, - _ => { panic!("Query failed"); }, - } - - // Check liabilities - match (liability_mint::QueryMsg::Liabilities { - }).test_query(&liab_mint, &app).unwrap() { - liability_mint::QueryAnswer::Liabilities { outstanding, limit } => { - assert_eq!(outstanding, mint_amount - payback, "liabilities after payback") - }, - _ => { panic!("Query failed"); }, - } -} - -macro_rules! liability_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let (mint_amount, limit, payback, expected_balance, expected_liabilities) = $value; - test_liabilities(mint_amount, limit, payback, expected_balance, expected_liabilities); - } - )* - } -} -liability_tests! { - liability_half_payback: ( - Uint128::new(1_000_000), // mint amount - Uint128::new(1_000_000), // limit - Uint128::new( 500_000), // payback - Uint128::new( 500_000), // end balance - Uint128::new( 500_000), // end liabilities - ), - liability_full_payback: ( - Uint128::new(1_000_000), // mint amount - Uint128::new(1_000_000), // limit - Uint128::new(1_000_000), // payback - Uint128::new(0), // end balance - Uint128::new(0), // end liabilities - ), - liability_no_payback: ( - Uint128::new(1_000_000), // mint amount - Uint128::new(1_000_000), // limit - Uint128::new(0), // payback - Uint128::new(1_000_000), // end balance - Uint128::new(1_000_000), // end liabilities - ), -} diff --git a/tools/doc2book/Cargo.toml b/tools/doc2book/Cargo.toml deleted file mode 100644 index 8d02b99..0000000 --- a/tools/doc2book/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "doc2book" -version = "0.1.0" -edition = "2018" - -[[bin]] -name = "doc2book" -path = "src/doc2book.rs" - -[dev-dependencies] -pretty_assertions = "1.0.0" diff --git a/tools/doc2book/README.md b/tools/doc2book/README.md deleted file mode 100644 index 3da4ab3..0000000 --- a/tools/doc2book/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# `doc2book` -A simple tool that scrapes rustdoc comments, `doc2book`-only comments and specified Rust code from a crate's source files -and outputs them as a single file (e.g. a Markdown book chapter). - -## Usage -The usage is simple, specify the crate and the output file path -``` -USAGE: doc2book CRATE_DIR OUT_FILE_PATH -``` - -Example from Shade workspace root directory: -``` -cargo r -p doc2book --release -- packages/shade_protocol doc/book/src/smart_contracts.md -``` - -### Comments -The following comments are scraped: -- `//! `: [rustdoc][1] crate-level (inner) doc comments -- `/// `: [rustdoc][1] code-level (outer) doc comments -- `//# `: `doc2book` comments, these will be ignored by rustdoc but picked up by `doc2book` - -[1]: https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html - -### Code -The comments `// book_include_code` and `// end_book_include_code` mark a section of code to be copied verbatim and place in a Rust code block (Markdown syntax). -This code: - -```rust -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -// book_include_code -pub struct Contract { - /// The address of the contract - pub address: Addr, - /// The hex encoded hash of the contract code - pub code_hash: String, -} -// end_book_include_code -``` - -Would result in this Markdown section: - -~~~ -```rust -pub struct Contract { - /// The address of the contract - pub address: Addr, - /// The hex encoded hash of the contract code - pub code_hash: String, -} -``` -~~~ - -## Limitations -As the tool is currently very simple it has a few limitations: -- The crate structure is expected to be one file per module at the same directory level as the lib.rs file. -- The tool outputs everything as a single file, it could be made to create a 'sub-chapter' file for each module. -- Due to it creating a single file, the ordering of comments, modules declarations and the code is the order it will appear output file. -- Adding whitespace between scraped sections needs to be explicit, i.e. an empty comment line needs to be inserted where you want a blank line `//! `. diff --git a/tools/doc2book/src/doc2book.rs b/tools/doc2book/src/doc2book.rs deleted file mode 100644 index 8a00192..0000000 --- a/tools/doc2book/src/doc2book.rs +++ /dev/null @@ -1,326 +0,0 @@ -use std::{ - fs::File, - io::{BufRead, BufReader, Error as IoError, Lines, Write}, -}; - -const USAGE: &str = "USAGE: doc2book CRATE_DIR OUT_FILE_PATH"; - -type Result = std::result::Result; - -#[derive(Debug)] -enum Error { - CrateSourceDirNotFound(String), - LibRsNotFound(String), - ModuleSourceNotFound(String), - Io(IoError), -} - -impl From for Error { - fn from(err: IoError) -> Self { - Self::Io(err) - } -} - -// source reading interface -trait ReadSource { - type Src: BufRead; - - fn lib_rs_path(&self) -> &str; - - fn module_path_from_name(&self, module: &str) -> String; - - fn lines_from_path(&self, src_path: &str) -> Result>; -} - -// source reading implementor for crates -struct CrateSource { - crate_src_dir: String, - lib_rs_path: String, -} - -impl CrateSource { - // ensure the crate is valid, - // i.e. it's src directory exists and contains a mod file - fn new(crate_dir: &str) -> Result { - let crate_src_dir = format!("{}/src", crate_dir); - let lib_rs_path = format!("{}/mod", crate_src_dir); - - if std::fs::metadata(&crate_src_dir).is_err() { - return Err(Error::CrateSourceDirNotFound(crate_src_dir)); - } - - if std::fs::metadata(&lib_rs_path).is_err() { - return Err(Error::LibRsNotFound(lib_rs_path)); - } - - Ok(CrateSource { - crate_src_dir, - lib_rs_path, - }) - } -} - -impl ReadSource for CrateSource { - type Src = BufReader; - - fn lib_rs_path(&self) -> &str { - &self.lib_rs_path - } - - fn module_path_from_name(&self, module: &str) -> String { - format!("{}/{}.rs", self.crate_src_dir, module) - } - - fn lines_from_path(&self, src_path: &str) -> Result> { - if std::fs::metadata(src_path).is_err() { - return Err(Error::ModuleSourceNotFound(src_path.to_string())); - } - - let file = File::open(src_path)?; - let lines = BufReader::new(file).lines(); - - Ok(lines) - } -} - -struct Output { - output: Vec, -} - -impl Output { - fn new() -> Output { - // allocate a decent chunk of memory at the start - let output = Vec::with_capacity(1_000_000_000); - Output { output } - } - - fn push_line(&mut self, line: &str) { - writeln!(&mut self.output, "{}", line).unwrap(); - } - - fn write_into(&self, w: &mut dyn Write) -> Result<()> { - w.write_all(&self.output)?; - Ok(()) - } -} - -fn parse_args() -> Option<(String, String)> { - let mut args = std::env::args().skip(1); - let crate_dir = args.next()?; - let out_path = args.next()?; - Some((crate_dir, out_path)) -} - -fn find_doc_comment(line: &str) -> Option<&str> { - line.strip_prefix("//! ") - .or_else(|| line.strip_prefix("//# ")) - .or_else(|| line.strip_prefix("/// ")) -} - -// scrape from code between pragmas and each type of doc comment -fn process_module(input: &I, output: &mut Output, module_name: &str) -> Result<()> -where - I: ReadSource, -{ - let module_path = input.module_path_from_name(module_name); - eprintln!("Processing module file: {}", module_path); - - let mut code_transcribe_enabled = false; - - for line in input.lines_from_path(&module_path)? { - let line = line?; - - if line.starts_with("// book_include_code") { - output.push_line("```rust"); - code_transcribe_enabled = true; - continue; - } - - if line.starts_with("// end_book_include_code") { - output.push_line("```"); - code_transcribe_enabled = false; - continue; - } - - if code_transcribe_enabled { - output.push_line(&line); - continue; - } - - if let Some(line) = find_doc_comment(&line) { - output.push_line(line); - } - } - - Ok(()) -} - -// start with the crate root, mod -// scrape code comments and with each public module inserted in the order they appear -fn process_crate(input: I, output: &mut Output) -> Result<()> -where - I: ReadSource, -{ - let lib_rs_path = input.lib_rs_path(); - eprintln!("Processing mod file: {}", lib_rs_path); - - for line in input.lines_from_path(lib_rs_path)? { - let line = line?; - - if let Some(doc) = find_doc_comment(&line) { - output.push_line(doc); - continue; - } - - if line.starts_with("pub mod ") && line.ends_with(';') { - let module = line - .strip_prefix("pub mod ") - .unwrap() - .strip_suffix(';') - .unwrap(); - - process_module(&input, output, module)?; - } - } - - Ok(()) -} - -fn main() { - let (crate_root_dir, out_path) = match parse_args() { - Some(args) => args, - _ => return eprintln!("{}", USAGE), - }; - - let input = match CrateSource::new(&crate_root_dir) { - Ok(input) => input, - Err(Error::CrateSourceDirNotFound(path)) => { - eprintln!( - "{} does not exist. Make sure the specified directory is a Rust project.", - path - ); - return eprintln!("{}", USAGE); - } - Err(Error::LibRsNotFound(path)) => { - eprintln!("{} not does not exist. The crate must be a library.", path); - return eprintln!("{}", USAGE); - } - Err(err) => panic!("unexpected err: {:?}", err), - }; - - let mut output = Output::new(); - - let result = process_crate(input, &mut output).and_then(|_| { - let mut out_file = File::create(out_path)?; - output.write_into(&mut out_file) - }); - - if let Err(err) = result { - match err { - Error::ModuleSourceNotFound(path) => { - eprintln!( - "Processing module file {} failed: file does not exist", - path - ) - } - Error::Io(err) => eprintln!("Unexpected I/O Error: {}", err), - err => panic!("unexpected err: {:?}", err), - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - const LIB_RS: &str = r#"//! # Main Title -//! Some text -//! - -pub mod helper_mod; - -//# ## Common Types -//# Some more text -//# - -pub mod common_types; -"#; - - const COMMON_TYPES_RS: &str = r#"use some_dep::module; -use some_other_dep::module; - -//# ### `Contract` -/// Represents another contract on the network -/// - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -// book_include_code -pub struct Contract { - /// The address of the contract - pub address: Addr, - /// The hex encoded hash of the contract code - pub code_hash: String, -} -// end_book_include_code"#; - - const EXPECTED_OUTPUT: &str = r#"# Main Title -Some text - -## Common Types -Some more text - -### `Contract` -Represents another contract on the network - -```rust -pub struct Contract { - /// The address of the contract - pub address: Addr, - /// The hex encoded hash of the contract code - pub code_hash: String, -} -``` -"#; - - struct TestInput; - - impl ReadSource for TestInput { - type Src = BufReader<&'static [u8]>; - - fn lib_rs_path(&self) -> &str { - "src/mod" - } - - fn module_path_from_name(&self, module: &str) -> String { - format!("src/{}.rs", module) - } - - fn lines_from_path(&self, src_path: &str) -> Result> { - let s: &'static str = match src_path { - "src/mod" => LIB_RS, - "src/common_types.rs" => COMMON_TYPES_RS, - "src/helper_mod.rs" => "", - _ => panic!("Unexpected path: {}", src_path), - }; - - Ok(BufReader::new(s.as_bytes()).lines()) - } - } - - #[test] - fn it_works() { - let mut output = Output::new(); - - process_crate(TestInput, &mut output).unwrap(); - - let mut actual = Vec::new(); - - output.write_into(&mut actual).unwrap(); - - let actual = String::from_utf8(actual).unwrap(); - - assert_eq!(actual, EXPECTED_OUTPUT.to_owned()) - } -} diff --git a/tools/multisig/broadcast_multi.sh b/tools/multisig/broadcast_multi.sh deleted file mode 100755 index 97b2ecd..0000000 --- a/tools/multisig/broadcast_multi.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -# GUIDE: -# Organize all the signatures and the tx to be signed in one directory -# Pass that directory as $1 -# Pass the name of the file to be signed as $2 - - - -cd $1 -res=`ls` -signatures="" -for files in $res -do - if [ $files == signedMultiTx.json ] - then - rm signedMultiTx.json - fi - if [ $files == $2 ] - then - continue - else - signatures=$signatures" "$files - fi -done - -secretd tx multisign $2 ss_multisig $signatures --chain-id secret-4 > signedMultiTx.json -secretd tx broadcast signedMultiTx.json diff --git a/tools/multisig/sign_mutli.sh b/tools/multisig/sign_mutli.sh deleted file mode 100755 index f0b059c..0000000 --- a/tools/multisig/sign_mutli.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -# example: ./multisig.sh secret1y277c499f44nxe7geeaqw8t6gpge68rcpla9lf ~/json/output.json jsledger - -secretd config node https://rpc.scrt.network:443 -secretd config chain-id secret-4 -res=`secretd q account $1` -eval sequence=`echo $res | jq ".sequence"` -eval acc_num=`echo $res | jq ".account_number"` -outputdoc="signature_$3.json" -secretd tx sign $2 --multisig ss_multisig --from $3 --output-document $outputdoc --chain-id secret-4 --offline --sequence $sequence --account-number $acc_num --sign-mode amino-json diff --git a/tools/multisig/sign_permit.py b/tools/multisig/sign_permit.py deleted file mode 100644 index 6cd8c57..0000000 --- a/tools/multisig/sign_permit.py +++ /dev/null @@ -1,36 +0,0 @@ -import argparse -import os - -parser = argparse.ArgumentParser(description="Create a cosmwasm msg for offline signing") - -parser.add_argument("msg", type=str, help="Permit data") -parser.add_argument("account", type=str, help="Permit signer") -parser.add_argument("--account_number", type=str, help="Account number", default="0") -parser.add_argument("--chain_id", type=str, help="Chain id to which this permit is written for", default="secret-4") -parser.add_argument("--memo", type=str, help="Memo for the permit", default="") -parser.add_argument("--msg_type", type=str, help="Msg type used on the signed msg", default="signature_proof") -parser.add_argument("--sequence", type=str, help="Signature sequence number", default="0") - -parser.add_argument("-o", "--output", type=str, help="Output message") -parser.add_argument("--use_old", action="store_true", help="Uses secretcli instead of secretd") -args = parser.parse_args() - -bin = "secretd" - -if args.use_old: - bin = "secretcli" - -output = "signed.json" - -if args.output: - output = args.output - -unsigned_permit = f'echo \' {{ "account_number": "{args.account_number}", ' \ - f'"chain_id": "{args.chain_id}", ' \ - f'"fee": {{ "amount": [{{ "amount": "0", "denom": "uscrt"}}], "gas": "1" }}, ' \ - f'"memo": "{args.memo}", "msgs": [{{ "type": "{args.msg_type}", "value": {args.msg} }}], ' \ - f'"sequence": "{args.sequence}"}} \'> unsigned.json' -os.system(unsigned_permit) - -command = f'{bin} tx sign-doc unsigned.json --from {args.account} > {output}' -os.system(command) \ No newline at end of file diff --git a/tools/multisig/wasm_msg.py b/tools/multisig/wasm_msg.py deleted file mode 100644 index 197461a..0000000 --- a/tools/multisig/wasm_msg.py +++ /dev/null @@ -1,25 +0,0 @@ -import argparse -import os - -parser = argparse.ArgumentParser(description="Create a cosmwasm msg for offline signing") -parser.add_argument("contract_address", type=str, help="Smart contract's address") -parser.add_argument("contract_codehash", type=str, help="Smart contract's code hash") -parser.add_argument("msg", type=str, help="Smart contract's msg to execute") -parser.add_argument("sender", type=str, help="The msg sender") -parser.add_argument("key", type=str, help="Enclave key certificate") -parser.add_argument("-o", "--output", type=str, help="Output message") -parser.add_argument("--use_old", action="store_true", help="Uses secretcli instead of secretd") -args = parser.parse_args() - -bin = "secretd" - -if args.use_old: - bin = "secretcli" - -output = "output.json" - -if args.output: - output = args.output - -command = f"{bin} tx compute execute {args.contract_address} '{args.msg}' --from {args.sender} --generate-only --enclave-key {args.key} --code-hash {args.contract_codehash} --offline --sign-mode amino-json > {output}" -os.system(command) \ No newline at end of file From 45a74de534d1fa8c81e1d7c145c0eddee501311a Mon Sep 17 00:00:00 2001 From: hard-nett Date: Thu, 8 Feb 2024 16:22:58 -0500 Subject: [PATCH 03/23] add create viewing key function --- contracts/airdrop/src/contract.rs | 11 +- contracts/airdrop/src/handle.rs | 117 +++++++++++------- contracts/airdrop/src/query.rs | 9 -- contracts/airdrop/src/test.rs | 14 --- packages/multi_test/src/multi.rs | 40 +++--- .../src/contract_interfaces/airdrop/mod.rs | 11 ++ 6 files changed, 106 insertions(+), 96 deletions(-) diff --git a/contracts/airdrop/src/contract.rs b/contracts/airdrop/src/contract.rs index cb74e9b..1632077 100644 --- a/contracts/airdrop/src/contract.rs +++ b/contracts/airdrop/src/contract.rs @@ -1,10 +1,6 @@ use crate::{ handle::{ - try_account, - try_claim, - try_disable_permit_key, - try_set_viewing_key, - try_update_config, + try_account, try_claim, try_create_viewing_key, try_disable_permit_key, try_set_viewing_key, try_update_config // try_claim_decay, }, query, @@ -122,7 +118,6 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S ), ExecuteMsg::Account { eth_pubkey, - amount, addresses, .. } => try_account( @@ -130,9 +125,11 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S &env, &info, eth_pubkey, - amount.unwrap_or_default(), addresses, ), + ExecuteMsg::CreateViewingKey { entropy, .. } => { + try_create_viewing_key(deps, env, info, entropy) + } ExecuteMsg::DisablePermitKey { key, .. } => { try_disable_permit_key(deps, &env, &info, key) } diff --git a/contracts/airdrop/src/handle.rs b/contracts/airdrop/src/handle.rs index cf2cad8..2780856 100644 --- a/contracts/airdrop/src/handle.rs +++ b/contracts/airdrop/src/handle.rs @@ -5,7 +5,6 @@ use crate::state::{ account_viewkey_w, account_w, address_in_account_w, - claim_status_r, claim_status_w, config_r, config_w, @@ -25,9 +24,9 @@ use shade_protocol::{ airdrop::{ account::{Account, AccountKey, AddressProofMsg, AddressProofPermit}, errors::{ - account_does_not_exist, address_already_in_account, airdrop_ended, airdrop_not_started, + address_already_in_account, airdrop_ended, airdrop_not_started, already_claimed, decay_claimed, decay_not_set, expected_memo, failed_verification, - invalid_dates, not_admin, nothing_to_claim, unexpected_error, wrong_length, + invalid_dates, not_admin, nothing_to_claim,wrong_length, }, Config, ExecuteAnswer, }, @@ -35,10 +34,21 @@ use shade_protocol::{ ensure_eq, from_binary, to_binary, Addr, Api, Binary, Decimal, DepsMut, Env, MessageInfo, Response, StdResult, Storage, Uint128, }, - query_authentication::viewing_keys::ViewingKey, snip20::helpers::send_msg, - utils::generic_response::{ResponseStatus, ResponseStatus::Success}, + utils::generic_response::ResponseStatus::{self}, +}; +use shade_protocol::{ + contract_interfaces::query_auth::{ + auth::{HashedKey, Key, PermitKey}, + RngSeed, + }, + query_authentication::viewing_keys::ViewingKey, + utils::{ + generic_response::ResponseStatus::Success, + storage::plus::{ItemStorage, MapStorage}, + }, }; + use std::{convert::TryInto, fmt::Write}; #[allow(clippy::too_many_arguments)] @@ -54,9 +64,10 @@ pub fn try_update_config( ) -> StdResult { let config = config_r(deps.storage).load()?; // Check if admin - if info.sender != config.admin { - return Err(not_admin(config.admin.as_str())); - } + assert!( + info.sender == config.admin, + not_admin(config.admin.as_str()) + ); // Save new info let mut config = config_w(deps.storage); @@ -111,19 +122,15 @@ pub fn try_account( env: &Env, info: &MessageInfo, eth_pubkey: String, - amount: Uint128, addresses: Vec, ) -> StdResult { - // Check if airdrop active + // 1. Check if airdrop active + // 2. Check that airdrop hasn't ended + // 3. Setup account by cosmos signer + // 4. These variables are setup to facilitate updating let config = config_r(deps.storage).load()?; - - // Check that airdrop hasn't ended available(&config, env)?; - - // Setup account by cosmos signer let sender = info.sender.to_string(); - - // These variables are setup to facilitate updating let updating_account: bool; // define the msg senders account @@ -147,10 +154,10 @@ pub fn try_account( // we setup an unchecked eth_pubkey for now. We will verify this eth_pubkey during // the claim msg, and will update to verified eth_pubkey. - // sets the accounts eth_pubkey claim status to false. note we always check claim function when checking + // sets the accounts eth_pubkey claim status to false. note we always check claim function when checking // the signed msg with the stored address, never both or isolated; - // (in order for this function to be called, sender.clone was required for reading the account details, - // preventing the ability to determine if a eth_pubkey not yours has claimed)* + // (to avoid contract panic, sender.clone is required for reading the account details, + // prevents ability to determine if a eth_pubkey not yours has claimed)* claim_status_w(deps.storage, 0).save(account.eth_pubkey.as_bytes(), &false)?; account @@ -161,16 +168,6 @@ pub fn try_account( } }; - // Claim airdrop - let mut messages = vec![]; - let mut redeem_amount = Uint128::zero(); - - if account.claimed == false { - redeem_amount = claim_tokens(deps.storage, ð_pubkey, &amount)?; - } else { - return Err(nothing_to_claim()); - } - // Update account after claim to calculate difference, // and to save eth_pubkey as saved to the contract state. if updating_account { @@ -186,21 +183,8 @@ pub fn try_account( )?; } - total_claimed_w(deps.storage) - .update(|claimed| -> StdResult { Ok(claimed + redeem_amount) })?; - - messages.push(send_msg( - info.sender.clone(), - redeem_amount.into(), - None, - None, - None, - &config.airdrop_snip20, - )?); - // Save account account_w(deps.storage).save(sender.as_bytes(), &account)?; - claim_status_w(deps.storage, 0).save(account.eth_pubkey.as_bytes(), &true)?; Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Account { status: ResponseStatus::Success, @@ -257,10 +241,11 @@ pub fn try_claim( // Check that airdrop hasn't ended available(&config, env)?; - // Get account from the msg sender, restricting access to query account via eth_pubkey - // as well as verify eth sig was generated with account.eth_pubkey + // Get account from the msg sender, restricting access to query account + // via eth_pubkey, as well as verify eth_sig was generated with account.eth_pubkey let sender = info.sender.clone(); let account = account_r(deps.storage).load(sender.clone().as_bytes())?; + let updating_account: bool; // validate eth_signature validation::validate_claim( @@ -293,14 +278,34 @@ pub fn try_claim( let mut root_buf: [u8; 32] = [0; 32]; decode_to_slice(merkle, &mut root_buf).unwrap(); ensure_eq!(root_buf, hash, failed_verification()); - + + + // Claim airdrop + let mut messages = vec![]; + let mut redeem_amount = Uint128::zero(); + // check if eth_pubkey has already claimed - let redeem_amount = claim_tokens(deps.storage, ð_pubkey, &amount)?; + if account.claimed == false { + redeem_amount = claim_tokens(deps.storage, ð_pubkey, &amount)?; + } else { + return Err(nothing_to_claim()); + } // update global claimed amount total_claimed_w(deps.storage) .update(|claimed| -> StdResult { Ok(claimed + redeem_amount) })?; + messages.push(send_msg( + info.sender.clone(), + redeem_amount.into(), + None, + None, + None, + &config.airdrop_snip20, + )?); + + claim_status_w(deps.storage, 0).save(account.eth_pubkey.as_bytes(), &true)?; + Ok(Response::new() .set_data(to_binary(&ExecuteAnswer::Claim { status: ResponseStatus::Success, @@ -393,6 +398,7 @@ pub fn try_add_account_addresses( // Iterate addresses for permit in addresses.iter() { if let Some(memo) = permit.memo.clone() { + // unwrap the permits, coming from the message memo let params: AddressProofMsg = from_binary(&Binary::from_base64(&memo)?)?; // Avoid verifying sender @@ -414,6 +420,9 @@ pub fn try_add_account_addresses( )?; // Update eth_pubkey if its not in an account + // remember, this is unverified and checked when a eth_sig + // is provided, preventing unauthorized claim status queries + // eth_pubkey_in_account_w(storage).update( eth_pubkey.to_string().as_bytes(), |state| -> StdResult { @@ -536,3 +545,19 @@ mod validation { } } } + +// create viewing keys +pub fn try_create_viewing_key( + deps: DepsMut, + env: Env, + info: MessageInfo, + entropy: String, +) -> StdResult { + let seed = RngSeed::load(deps.storage)?.0; + + let key = Key::generate(&info, &env, seed.as_slice(), &entropy.as_ref()); + + HashedKey(key.hash()).save(deps.storage, info.sender)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::CreateViewingKey { key: key.0 })?)) +} diff --git a/contracts/airdrop/src/query.rs b/contracts/airdrop/src/query.rs index 1e93216..a867b07 100644 --- a/contracts/airdrop/src/query.rs +++ b/contracts/airdrop/src/query.rs @@ -49,18 +49,9 @@ fn account_information( ) -> StdResult { let account = account_r(deps.storage).load(account_address.to_string().as_bytes())?; - // Calculate eligible tasks - // let config = config_r(deps.storage).load()?; - // Check if eth address has claimed let claim_state = eth_pubkey_claim_r(deps.storage).may_load(account.eth_pubkey.as_bytes())?; - // match claim_state { - // // Ignore if none - // None => {} - // Some(claimed) => {} - // } - Ok(QueryAnswer::Account { claimed: claim_state.unwrap(), addresses: account.addresses, diff --git a/contracts/airdrop/src/test.rs b/contracts/airdrop/src/test.rs index af4835c..451676a 100644 --- a/contracts/airdrop/src/test.rs +++ b/contracts/airdrop/src/test.rs @@ -1,6 +1,5 @@ #[cfg(test)] pub mod tests { - use crate::handle::inverse_normalizer; use shade_protocol::{ airdrop::account::{AddressProofMsg, AddressProofPermit, FillerMsg}, c_std::{from_binary, testing::mock_dependencies, Addr, Binary, Uint128}, @@ -10,19 +9,6 @@ pub mod tests { }, }; - #[test] - fn decay_factor() { - assert_eq!( - Uint128::new(50u128), - Uint128::new(100u128) * inverse_normalizer(100, 200, 300) - ); - - assert_eq!( - Uint128::new(25u128), - Uint128::new(100u128) * inverse_normalizer(0, 75, 100) - ); - } - const MSGTYPE: &str = "wasm/MsgExecuteContract"; #[test] diff --git a/packages/multi_test/src/multi.rs b/packages/multi_test/src/multi.rs index f227745..60f033a 100644 --- a/packages/multi_test/src/multi.rs +++ b/packages/multi_test/src/multi.rs @@ -1,18 +1,18 @@ -// #[cfg(feature = "admin")] -// pub mod admin { -// pub use admin; -// use shade_protocol::{admin::InstantiateMsg, multi_test::App, utils::InstantiateCallback}; -// multi_derive::implement_multi!(Admin, admin); - -// // Multitest helper -// pub fn init_admin_auth(app: &mut App, superadmin: &Addr) -> ContractInfo { -// InstantiateMsg { -// super_admin: Some(superadmin.clone().to_string()), -// } -// .test_init(Admin::default(), app, superadmin.clone(), "admin_auth", &[]) -// .unwrap() -// } -// } +#[cfg(feature = "admin")] +pub mod admin { + pub use admin; + use shade_protocol::{admin::InstantiateMsg, multi_test::App, utils::InstantiateCallback}; + multi_derive::implement_multi!(Admin, admin); + + // Multitest helper + pub fn init_admin_auth(app: &mut App, superadmin: &Addr) -> ContractInfo { + InstantiateMsg { + super_admin: Some(superadmin.clone().to_string()), + } + .test_init(Admin::default(), app, superadmin.clone(), "admin_auth", &[]) + .unwrap() + } +} // #[cfg(feature = "snip20")] // pub mod snip20 { @@ -76,12 +76,12 @@ // // multi_derive::implement_multi!(Bonds, bonds); // // } -// #[cfg(feature = "query_auth")] -// pub mod query_auth { -// use query_auth; +#[cfg(feature = "query_auth")] +pub mod query_auth { + use query_auth; -// multi_derive::implement_multi!(QueryAuth, query_auth); -// } + multi_derive::implement_multi!(QueryAuth, query_auth); +} // #[cfg(feature = "treasury_manager")] // pub mod treasury_manager { diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs index a3bf497..12a580b 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs @@ -84,6 +84,12 @@ pub enum ExecuteMsg { decay_start: Option, padding: Option, }, + /// * Creates or Updates an Account. + /// * Uses the msg sender address as key to + /// search if an account exist when a owner interacts. + /// * Stores an unverified eth_pubkey that will be used to verify + /// ownership of eth_sig provided when claiming headstash. + /// * Account { eth_pubkey: String, amount: Option, @@ -91,6 +97,10 @@ pub enum ExecuteMsg { partial_tree: Vec, padding: Option, }, + CreateViewingKey { + entropy: String, + padding: Option, + }, DisablePermitKey { key: String, padding: Option, @@ -141,6 +151,7 @@ pub enum ExecuteAnswer { ClaimDecay { status: ResponseStatus, }, + CreateViewingKey { key: String }, } #[cw_serde] From ac339f8de4f5370af431688684491ce3e7d66b80 Mon Sep 17 00:00:00 2001 From: hard-nett Date: Thu, 8 Feb 2024 16:49:25 -0500 Subject: [PATCH 04/23] add claim decay function --- contracts/airdrop/src/contract.rs | 3 +- contracts/airdrop/src/handle.rs | 44 ++++++++++++------- .../src/contract_interfaces/airdrop/mod.rs | 6 +-- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/contracts/airdrop/src/contract.rs b/contracts/airdrop/src/contract.rs index 1632077..d1494d2 100644 --- a/contracts/airdrop/src/contract.rs +++ b/contracts/airdrop/src/contract.rs @@ -1,7 +1,6 @@ use crate::{ handle::{ try_account, try_claim, try_create_viewing_key, try_disable_permit_key, try_set_viewing_key, try_update_config - // try_claim_decay, }, query, state::{config_w, decay_claimed_w, total_claimed_w}, @@ -141,7 +140,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S proof, .. } => try_claim(deps, &env, &info, amount, eth_pubkey, eth_sig, proof), - // ExecuteMsg::ClaimDecay { .. } => try_claim_decay(deps, &env, &info), + ExecuteMsg::ClaimDecay { .. } => crate::handle::try_claim_decay(deps, &env, &info), }, RESPONSE_BLOCK_SIZE, ) diff --git a/contracts/airdrop/src/handle.rs b/contracts/airdrop/src/handle.rs index 2780856..a63a187 100644 --- a/contracts/airdrop/src/handle.rs +++ b/contracts/airdrop/src/handle.rs @@ -24,9 +24,9 @@ use shade_protocol::{ airdrop::{ account::{Account, AccountKey, AddressProofMsg, AddressProofPermit}, errors::{ - address_already_in_account, airdrop_ended, airdrop_not_started, - already_claimed, decay_claimed, decay_not_set, expected_memo, failed_verification, - invalid_dates, not_admin, nothing_to_claim,wrong_length, + address_already_in_account, airdrop_ended, airdrop_not_started, already_claimed, + decay_claimed, decay_not_set, expected_memo, failed_verification, invalid_dates, + not_admin, nothing_to_claim, wrong_length, }, Config, ExecuteAnswer, }, @@ -241,11 +241,10 @@ pub fn try_claim( // Check that airdrop hasn't ended available(&config, env)?; - // Get account from the msg sender, restricting access to query account + // Get account from the msg sender, restricting access to query account // via eth_pubkey, as well as verify eth_sig was generated with account.eth_pubkey let sender = info.sender.clone(); let account = account_r(deps.storage).load(sender.clone().as_bytes())?; - let updating_account: bool; // validate eth_signature validation::validate_claim( @@ -278,12 +277,9 @@ pub fn try_claim( let mut root_buf: [u8; 32] = [0; 32]; decode_to_slice(merkle, &mut root_buf).unwrap(); ensure_eq!(root_buf, hash, failed_verification()); - - // Claim airdrop let mut messages = vec![]; let mut redeem_amount = Uint128::zero(); - // check if eth_pubkey has already claimed if account.claimed == false { redeem_amount = claim_tokens(deps.storage, ð_pubkey, &amount)?; @@ -348,14 +344,28 @@ pub fn try_claim_decay(deps: DepsMut, env: &Env, _info: &MessageInfo) -> StdResu let total_claimed = total_claimed_r(deps.storage).load()?; let send_total = config.airdrop_amount.checked_sub(total_claimed)?; - let messages = vec![send_msg( - dump_address.clone(), - send_total.into(), - None, - None, - None, - &config.airdrop_snip20, - )?]; + let messages = vec![ + { + send_msg( + dump_address.clone(), + send_total.into(), + None, + None, + None, + &config.airdrop_snip20, + ) + }, + { + send_msg( + dump_address.clone(), + send_total.into(), + None, + None, + None, + &config.airdrop_snip20_optional, + ) + }, + ]; return Ok(Response::new() .set_data(to_binary(&ExecuteAnswer::ClaimDecay { status: Success })?)); @@ -422,7 +432,7 @@ pub fn try_add_account_addresses( // Update eth_pubkey if its not in an account // remember, this is unverified and checked when a eth_sig // is provided, preventing unauthorized claim status queries - // + // eth_pubkey_in_account_w(storage).update( eth_pubkey.to_string().as_bytes(), |state| -> StdResult { diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs index 12a580b..d864cea 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs @@ -116,9 +116,9 @@ pub enum ExecuteMsg { proof: Vec, padding: Option, }, - // ClaimDecay { - // padding: Option, - // }, + ClaimDecay { + padding: Option, + }, } impl ExecuteCallback for ExecuteMsg { From 58e1adfaf5d34dcd0d1ba8cd578bed7df54f2a29 Mon Sep 17 00:00:00 2001 From: Hard-Nett Date: Sun, 18 Feb 2024 04:34:45 +0000 Subject: [PATCH 05/23] start addition of tests --- contracts/airdrop/Cargo.toml | 2 +- contracts/airdrop/README.md | 2 - contracts/airdrop/src/contract.rs | 6 - contracts/airdrop/src/handle.rs | 4 - contracts/airdrop/src/test.rs | 408 +++++------------- .../src/contract_interfaces/airdrop/mod.rs | 7 - .../src/contract_interfaces/query_auth/mod.rs | 1 - 7 files changed, 120 insertions(+), 310 deletions(-) diff --git a/contracts/airdrop/Cargo.toml b/contracts/airdrop/Cargo.toml index d05f744..74cf04c 100644 --- a/contracts/airdrop/Cargo.toml +++ b/contracts/airdrop/Cargo.toml @@ -23,7 +23,7 @@ backtraces = ["shade-protocol/backtraces"] debug-print = ["shade-protocol/debug-print"] [dependencies] -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["airdrop"] } +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["airdrop", "query_auth_impl",] } ethereum-verify = { version = "0.1.0", path = "../../packages/ethereum_verify"} hex = "0.4" sha2 = { version = "0.10.2", default-features = false } diff --git a/contracts/airdrop/README.md b/contracts/airdrop/README.md index ed33134..01d2d19 100644 --- a/contracts/airdrop/README.md +++ b/contracts/airdrop/README.md @@ -45,7 +45,6 @@ Contract responsible to handle snip20 airdrop | max_amount | String | Used to limit the user permit amounts (lowers exploit possibility) | no | | default_claim | String | The default amount to be gifted regardless of tasks | no | | task_claim | RequiredTasks | The amounts per tasks to gift | no | -| query_rounding | string | To prevent leaking information, total claimed is rounded off to this value | no | ##Admin @@ -58,7 +57,6 @@ Updates the given values |----------------|--------|------------------------------------------------------|----------| | admin | string | New contract admin; SHOULD be a valid bech32 address | yes | | dump_address | string | Sets the dump address if there isnt any | yes | -| query_rounding | String | To prevent leaking information | yes | | start_date | u64 | When the airdrop starts in UNIX time | yes | | end_date | u64 | When the airdrop ends in UNIX time | yes | | decay_start | u64 | When the airdrop decay starts in UNIX time | yes | diff --git a/contracts/airdrop/src/contract.rs b/contracts/airdrop/src/contract.rs index d1494d2..496666a 100644 --- a/contracts/airdrop/src/contract.rs +++ b/contracts/airdrop/src/contract.rs @@ -80,7 +80,6 @@ pub fn instantiate( merkle_root: msg.merkle_root, total_accounts: msg.total_accounts, claim_msg_plaintext: msg.claim_msg_plaintext, - query_rounding: msg.query_rounding, }; config_w(deps.storage).save(&config)?; @@ -101,7 +100,6 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S ExecuteMsg::UpdateConfig { admin, dump_address, - query_rounding: redeem_step_size, start_date, end_date, .. @@ -111,7 +109,6 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S &info, admin, dump_address, - redeem_step_size, start_date, end_date, ), @@ -126,9 +123,6 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S eth_pubkey, addresses, ), - ExecuteMsg::CreateViewingKey { entropy, .. } => { - try_create_viewing_key(deps, env, info, entropy) - } ExecuteMsg::DisablePermitKey { key, .. } => { try_disable_permit_key(deps, &env, &info, key) } diff --git a/contracts/airdrop/src/handle.rs b/contracts/airdrop/src/handle.rs index a63a187..c03ebc9 100644 --- a/contracts/airdrop/src/handle.rs +++ b/contracts/airdrop/src/handle.rs @@ -58,7 +58,6 @@ pub fn try_update_config( info: &MessageInfo, admin: Option, dump_address: Option, - query_rounding: Option, start_date: Option, end_date: Option, ) -> StdResult { @@ -78,9 +77,6 @@ pub fn try_update_config( if let Some(dump_address) = dump_address { state.dump_address = Some(dump_address); } - if let Some(query_rounding) = query_rounding { - state.query_rounding = query_rounding; - } if let Some(start_date) = start_date { // Avoid date collisions if let Some(end_date) = end_date { diff --git a/contracts/airdrop/src/test.rs b/contracts/airdrop/src/test.rs index 451676a..9d1b685 100644 --- a/contracts/airdrop/src/test.rs +++ b/contracts/airdrop/src/test.rs @@ -1,291 +1,136 @@ #[cfg(test)] pub mod tests { + use crate::contract::{execute, query}; + use crate::{contract::instantiate, handle::inverse_normalizer}; use shade_protocol::{ - airdrop::account::{AddressProofMsg, AddressProofPermit, FillerMsg}, - c_std::{from_binary, testing::mock_dependencies, Addr, Binary, Uint128}, + airdrop::{ExecuteMsg, QueryMsg}, + c_std::testing::mock_dependencies_with_balance, + }; + + use shade_protocol::{ + airdrop::{ + account::{AddressProofMsg, AddressProofPermit, FillerMsg}, + InstantiateMsg, + }, + c_std::{ + from_binary, + testing::{mock_dependencies, mock_env, mock_info}, + Addr, Binary, Coin, Uint128, + }, query_authentication::{ permit::bech32_to_canonical, transaction::{PermitSignature, PubKey}, }, + Contract, }; - const MSGTYPE: &str = "wasm/MsgExecuteContract"; - - #[test] - fn terra_station_ledger() { - let mut permit = AddressProofPermit { - params: FillerMsg::default(), - chain_id: Some("columbus-5".to_string()), - sequence: None, - signature: PermitSignature { - pub_key: PubKey::new(Binary::from_base64( - "Ar7aIv8k6Rm7ugLBAHShtRWmZ/CDgvwXYOc8Ffycwggc").unwrap()), - signature: Binary::from_base64( - "MM1UOheGCYX0Cb3r8zVhyZyWk/qIY61yqiDP53//31cjkd7G5FfEki+JC91kBRYCnt9NlI7gjnY8ZcJauDH3FA==").unwrap(), - }, - account_number: Some(Uint128::new(3441602u128).into()), - memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) - }; - - let deps = mock_dependencies(); - let addr = permit - .validate(&deps.api, Some(MSGTYPE.to_string())) - .expect("Signature validation failed"); - assert_eq!( - addr.as_canonical(), - bech32_to_canonical("terra17dhvxnwzazszgtuc498qsudh7zq945qh29gj4e") - ); - assert_ne!( - addr.as_canonical(), - bech32_to_canonical("terra19m2zgdyuq0crpww00jc2a9k70ut944dum53p7x") - ); - - permit.memo = Some("OtherMemo".to_string()); - - // NOTE: New SN broke unit testing - // assert!( - // permit - // .validate(&deps.api, Some("wasm/MsgExecuteContract".to_string())) - // .is_err() - // ) - } - - #[test] - fn terra_station_non_ledger() { - let mut permit = AddressProofPermit { - params: FillerMsg::default(), - chain_id: Some("columbus-5".to_string()), - sequence: None, - signature: PermitSignature { - pub_key: PubKey::new(Binary::from_base64( - "A8r22cTiywZYSoWR5DnmAeP1jPDF3CLVKJe1QGorv9cM").unwrap()), - signature: Binary::from_base64( - "xhU2JkJDWO/eZEeJVp8vo1rNAK7H7G2uDucZAjAhfVRjLHHX7C+16dwQzr0Jmd2DdZHAJZNkGhGb5nucicN1TA==").unwrap(), - }, - account_number: None, - memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) + const VIEWING_KEY: &str = "jUsTfOrTeStInG"; + + fn proper_initialization() { + let mut deps = mock_dependencies(); + let info = mock_info( + "creator", + &[Coin { + denom: "earth".to_string(), + amount: Uint128::new(1000), + }], + ); + + let init_msg = InstantiateMsg { + admin: Some(Addr::unchecked("creator")), + dump_address: Some(Addr::unchecked("creator")), + airdrop_token: Contract::new( + &Addr::unchecked("secret-terps"), + &"xyx-code-hash".to_string(), + ), + airdrop_snip20_optional: Contract::new( + &Addr::unchecked("secret-thiol"), + &"xyz-code-hash".to_string(), + ), + airdrop_amount: Uint128::new(420), + start_date: None, + end_date: None, + decay_start: None, + merkle_root: "77fb25152b72ac67f5a155461e396b0788dd0567ec32a96f8201b899ad516b02" + .to_string(), + total_accounts: 12580u32, + claim_msg_plaintext: "{wallet}".to_string(), }; - let deps = mock_dependencies(); - let addr = permit - .validate(&deps.api, Some(MSGTYPE.to_string())) - .expect("Signature validation failed"); - assert_eq!( - addr.as_canonical(), - bech32_to_canonical("terra1j8wupj3kpclp98dgg4j5am44kjykx6uztjttyr") - ); - assert_ne!( - addr.as_canonical(), - bech32_to_canonical("terra1ns69jhkjg5wmcgf8w8ecewnpca7sezyhvg0a29") - ); - - permit.memo = Some("OtherMemo".to_string()); + let res = instantiate(deps.as_mut(), mock_env(), info, init_msg).unwrap(); - // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) + assert_eq!(0, res.messages.len()); } - #[test] - fn keplr_terra_non_ledger() { - let mut permit = AddressProofPermit { - params: FillerMsg::default(), - chain_id: Some("columbus-5".to_string()), - sequence: None, - signature: PermitSignature { - pub_key: PubKey::new(Binary::from_base64( - "AyGKvc3OCs/pg7unFCJgKjtqiLYRACeR4ZU0f8UVDFbM").unwrap()), - signature: Binary::from_base64( - "fbgFeYUsAjI2CB2dwaqttolFE1wx/3MXbNWYKicJj20mV3marS4zz+k5aCKsYlv4HSd9NYxl4deuhasMKndB2w==").unwrap(), - }, - account_number: None, - memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) - }; - - let deps = mock_dependencies(); - let addr = permit - .validate(&deps.api, Some(MSGTYPE.to_string())) - .expect("Signature validation failed"); - assert_eq!( - addr.as_canonical(), - bech32_to_canonical("terra18xg6g5yfzflnt8v45r2yndnydhg2vndvzsv3rn") - ); - assert_ne!( - addr.as_canonical(), - bech32_to_canonical("terra1ns69jhkjg5wmcgf8w8ecewnpca7sezyhvg0a29") - ); - - permit.memo = Some("OtherMemo".to_string()); - - // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) + mod account { + + use super::*; + + #[test] + fn set_viewing_key() { + let mut deps = mock_dependencies_with_balance(&[Coin { + denom: "earth".to_string(), + amount: Uint128::new(1000), + }]); + let info = mock_info( + "creator", + &[Coin { + denom: "earth".to_string(), + amount: Uint128::new(1000), + }], + ); + + let init_msg = InstantiateMsg { + admin: Some(Addr::unchecked("creator")), + dump_address: Some(Addr::unchecked("creator")), + airdrop_token: Contract::new( + &Addr::unchecked("secret-terps"), + &"xyx-code-hash".to_string(), + ), + airdrop_snip20_optional: Contract::new( + &Addr::unchecked("secret-thiol"), + &"xyz-code-hash".to_string(), + ), + airdrop_amount: Uint128::new(420), + start_date: None, + end_date: None, + decay_start: None, + merkle_root: "77fb25152b72ac67f5a155461e396b0788dd0567ec32a96f8201b899ad516b02" + .to_string(), + total_accounts: 12580u32, + claim_msg_plaintext: "{wallet}".to_string(), + }; + + let _res = instantiate(deps.as_mut(), mock_env(), info, init_msg).unwrap(); + + // anyone can setup an account + let info = mock_info( + "anyone", + &[Coin { + denom: "token".to_string(), + amount: Uint128::new(420), + }], + ); + // test setting a viewing key + let exec_msg = ExecuteMsg::SetViewingKey { + key: VIEWING_KEY.into(), + padding: None, + }; + let res = execute(deps.as_mut(), mock_env(), info.clone(), exec_msg).unwrap(); + println!("{res:?}"); + + // test disabling the permit key + let exec_msg = ExecuteMsg::DisablePermitKey { + key: VIEWING_KEY.into(), + padding: None, + }; + let res = execute(deps.as_mut(), mock_env(), info, exec_msg).unwrap(); + assert_eq!(0, res.messages.len()); + } } - #[test] - fn keplr_terra_ledger() { - let mut permit = AddressProofPermit { - params: FillerMsg::default(), - chain_id: Some("columbus-5".to_string()), - sequence: None, - signature: PermitSignature { - pub_key: PubKey::new(Binary::from_base64( - "AqjDFVbY+znM1F5XCDuaca0JT0uAdd3QyuHt04j9k0DB").unwrap()), - signature: Binary::from_base64( - "1kwDnlsltqgj8fqbohs0MMgEWiRMUmznM98ofOranBAe5f8Ja1tZCKmh5miPkgC6KoUdOam7BvBjuFhM1q0rBA==").unwrap(), - }, - account_number: None, - memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) - }; - + fn test_s_i_e_q() { let deps = mock_dependencies(); - let addr = permit - .validate(&deps.api, Some(MSGTYPE.to_string())) - .expect("Signature validation failed"); - assert_eq!( - addr.as_canonical(), - bech32_to_canonical("terra1ns69jhkjg5wmcgf8w8ecewnpca7sezyhvg0a29") - ); - assert_ne!( - addr.as_canonical(), - bech32_to_canonical("terra18xg6g5yfzflnt8v45r2yndnydhg2vndvzsv3rn") - ); - - permit.memo = Some("OtherMemo".to_string()); - - // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) - } - - #[test] - fn keplr_sn_non_ledger() { - let mut permit = AddressProofPermit { - params: FillerMsg::default(), - chain_id: Some("secret-4".to_string()), - sequence: None, - signature: PermitSignature { - pub_key: PubKey::new(Binary::from_base64( - "A2uZZ02iy/QhPZ0s6WO8HTEfNZEnt5o5PsQ34WHmQFPK").unwrap()), - signature: Binary::from_base64( - "s80mH5OuZCudS20d0k73evWx5xGrC2l3uubQjIkukT4L5mcgsepDIq9d1YpAJwiUEitaHFOGy42MfHZJVY1LdA==").unwrap(), - }, - account_number: None, - memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) - }; - - let deps = mock_dependencies(); - let addr = permit - .validate(&deps.api, Some(MSGTYPE.to_string())) - .expect("Signature validation failed"); - assert_eq!( - addr.as_canonical(), - bech32_to_canonical("secret19q7h2zy8mgesy3r39el5fcm986nxqjd7cgylrz") - ); - assert_ne!( - addr.as_canonical(), - bech32_to_canonical("secret1ns69jhkjg5wmcgf8w8ecewnpca7sezyhgfp54e") - ); - - permit.memo = Some("OtherMemo".to_string()); - - // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) - } - - #[test] - fn keplr_sn_ledger() { - let mut permit = AddressProofPermit { - params: FillerMsg::default(), - chain_id: Some("secret-4".to_string()), - sequence: None, - signature: PermitSignature { - pub_key: PubKey::new(Binary::from_base64( - "AqjDFVbY+znM1F5XCDuaca0JT0uAdd3QyuHt04j9k0DB").unwrap()), - signature: Binary::from_base64( - "snd8k5nWAAVoUytxKZt1FCUNQNXLAQpBlF7h4YGbTmx3S5+rqaZnM2bKq1ifCvErz/pdeE7B/s+WsGLdQRpzoA==").unwrap(), - }, - account_number: None, - memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) - }; - - let deps = mock_dependencies(); - let addr = permit - .validate(&deps.api, Some(MSGTYPE.to_string())) - .expect("Signature validation failed"); - assert_eq!( - addr.as_canonical(), - bech32_to_canonical("secret1ns69jhkjg5wmcgf8w8ecewnpca7sezyhgfp54e") - ); - assert_ne!( - addr.as_canonical(), - bech32_to_canonical("secret19q7h2zy8mgesy3r39el5fcm986nxqjd7cgylrz") - ); - - permit.memo = Some("OtherMemo".to_string()); - - // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) - } - - #[test] - fn keplr_cosmos_non_ledger() { - let mut permit = AddressProofPermit { - params: FillerMsg::default(), - chain_id: Some("cosmoshub-4".to_string()), - sequence: None, - signature: PermitSignature { - pub_key: PubKey::new(Binary::from_base64( - "AqcyBLqPn7QnOctkK9i9KhnhD0aHA03+LppvNTCdZ1wK").unwrap()), - signature: Binary::from_base64( - "KLwotev7wnbj2VGBxbyTfIrRn/1vQY3x3I7BAUhu4FIC6OHVXqxIl/lclgdBWksnr32ULVfz8u78OqEbaePRZQ==").unwrap(), - }, - account_number: None, - memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) - }; - - let deps = mock_dependencies(); - let addr = permit - .validate(&deps.api, Some(MSGTYPE.to_string())) - .expect("Signature validation failed"); - assert_eq!( - addr.as_canonical(), - bech32_to_canonical("cosmos1lj5vh5y8yp4a97jmfwpd98lsg0tf5lsqgnnhq3") - ); - assert_ne!( - addr.as_canonical(), - bech32_to_canonical("cosmos1ns69jhkjg5wmcgf8w8ecewnpca7sezyh2v4ag9") - ); - - permit.memo = Some("OtherMemo".to_string()); - - // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) - } - - #[test] - fn keplr_cosmos_ledger() { - let mut permit = AddressProofPermit { - params: FillerMsg::default(), - chain_id: Some("cosmoshub-4".to_string()), - sequence: None, - signature: PermitSignature { - pub_key: PubKey::new(Binary::from_base64( - "AqjDFVbY+znM1F5XCDuaca0JT0uAdd3QyuHt04j9k0DB").unwrap()), - signature: Binary::from_base64( - "h/RpG1eKzN03oId0GvN7TSxoHOUibjmqPEQ1E+ZWh+BvghPL99lBj4L3BKpjjsaRtXX3lexO7ztafLKBVtq4xA==").unwrap(), - }, - account_number: None, - memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) - }; - - let deps = mock_dependencies(); - let addr = permit - .validate(&deps.api, Some(MSGTYPE.to_string())) - .expect("Signature validation failed"); - assert_eq!( - addr.as_canonical(), - bech32_to_canonical("cosmos1ns69jhkjg5wmcgf8w8ecewnpca7sezyh2v4ag9") - ); - assert_ne!( - addr.as_canonical(), - bech32_to_canonical("cosmos1lj5vh5y8yp4a97jmfwpd98lsg0tf5lsqgnnhq3") - ); - - permit.memo = Some("OtherMemo".to_string()); - - // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) } #[test] @@ -307,26 +152,11 @@ pub mod tests { } #[test] - fn claim_query() { - assert_eq!( - Uint128::new(300u128), - (Uint128::new(345u128) / Uint128::new(100u128)) * Uint128::new(100u128) - ) - } + fn claim_query() {} #[test] - fn claim_query_odd_multiple() { - assert_eq!( - Uint128::new(13475u128), - (Uint128::new(13480u128) / Uint128::new(7u128)) * Uint128::new(7u128) - ) - } + fn claim_query_odd_multiple() {} #[test] - fn claim_query_under_step() { - assert_eq!( - Uint128::zero(), - (Uint128::new(200u128) / Uint128::new(1000u128)) * Uint128::new(1000u128) - ) - } + fn claim_query_under_step() {} } diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs index d864cea..e65ab8d 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs @@ -40,7 +40,6 @@ pub struct Config { // {wallet} pub claim_msg_plaintext: String, // Protects from leaking user information by limiting amount detail - pub query_rounding: Uint128, } #[cw_serde] @@ -66,7 +65,6 @@ pub struct InstantiateMsg { /// {wallet} pub claim_msg_plaintext: String, // Protects from leaking user information by limiting amount detail - pub query_rounding: Uint128, } impl InstantiateCallback for InstantiateMsg { @@ -78,7 +76,6 @@ pub enum ExecuteMsg { UpdateConfig { admin: Option, dump_address: Option, - query_rounding: Option, start_date: Option, end_date: Option, decay_start: Option, @@ -97,10 +94,6 @@ pub enum ExecuteMsg { partial_tree: Vec, padding: Option, }, - CreateViewingKey { - entropy: String, - padding: Option, - }, DisablePermitKey { key: String, padding: Option, diff --git a/packages/shade_protocol/src/contract_interfaces/query_auth/mod.rs b/packages/shade_protocol/src/contract_interfaces/query_auth/mod.rs index 29413af..95c9e6e 100644 --- a/packages/shade_protocol/src/contract_interfaces/query_auth/mod.rs +++ b/packages/shade_protocol/src/contract_interfaces/query_auth/mod.rs @@ -30,7 +30,6 @@ impl ItemStorage for Admin { const ITEM: Item<'static, Self> = Item::new("admin-"); } -#[cfg(feature = "query_auth_impl")] #[cw_serde] pub struct RngSeed(pub Vec); From 7fad363f52064e10c454c261d993781fc077f740 Mon Sep 17 00:00:00 2001 From: Hard-Nett Date: Sun, 18 Feb 2024 05:11:10 +0000 Subject: [PATCH 06/23] add code-id --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index b3bc8f7..7984c0e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # Private Headstash Airdrop + +### pulsar-3 +**code-id: `4461`** \ +**contract-address: ` `** + ### InstantiateMsg ### ExecuteMsg From 76eb9c0892e444f9a6e944eb9ff060e72f30e476 Mon Sep 17 00:00:00 2001 From: hard-nett Date: Thu, 28 Mar 2024 16:28:27 -0400 Subject: [PATCH 07/23] prep tests --- Cargo.toml | 4 +- README.md | 7 - contracts/airdrop/README.md | 307 +----------------- contracts/airdrop/src/contract.rs | 25 +- contracts/airdrop/src/handle.rs | 33 +- contracts/airdrop/src/test.rs | 11 +- .../src/contract_interfaces/airdrop/errors.rs | 5 +- .../src/contract_interfaces/airdrop/mod.rs | 15 +- .../src/contract_interfaces/snip20/helpers.rs | 6 +- 9 files changed, 71 insertions(+), 342 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a1c05ac..54c6ad1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,16 +3,14 @@ resolver = "2" members = [ # Packages "packages/shade_protocol", - # "packages/secretcli", "packages/multi_test", "packages/multi_derive", "packages/contract_derive", "packages/ethereum_verify", - # Network setups + # Secret Headstash Contract "contracts/airdrop", - # Tools # "tools/doc2book", # "launch" diff --git a/README.md b/README.md index 7984c0e..8dbf49d 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,7 @@ # Private Headstash Airdrop - ### pulsar-3 **code-id: `4461`** \ **contract-address: ` `** -### InstantiateMsg - -### ExecuteMsg - -### QueryMsg - ## Unaudited fork of [Shade Protocol](https://shadeprotocol.io/) contracts. \ No newline at end of file diff --git a/contracts/airdrop/README.md b/contracts/airdrop/README.md index 01d2d19..e13fe92 100644 --- a/contracts/airdrop/README.md +++ b/contracts/airdrop/README.md @@ -1,34 +1,6 @@ +# Private Headstash Airdrop -# Airdrop Contract -* [Introduction](#Introduction) -* [Sections](#Sections) - * [Init](#Init) - * [Admin](#Admin) - * Messages - * [UpdateConfig](#UpdateConfig) - * [AddTasks](#AddTasks) - * [ClaimDecay](#ClaimDecay) - * [Task_Admin](#Task_Admin) - * Messages - * [CompleteTask](#CompleteTask) - * [User](#User) - * Messages - * [Account](#Account) - * [DisablePermitKey](#DisablePermitKey) - * [SetViewingKey](#SetViewingKey) - * [Claim](#Claim) - * Queries - * [Config](#Config) - * [Dates](#Dates) - * [TotalClaimed](#TotalClaimed) - * [Account](#Account) - * [AccountWithKey](#AccountWithKey) - -# Introduction -Contract responsible to handle snip20 airdrop - -# Sections - +### InstantiateMsg ## Init ##### Request | Name | Type | Description | optional | @@ -36,6 +8,7 @@ Contract responsible to handle snip20 airdrop | admin | String | New contract owner; SHOULD be a valid bech32 address | yes | | dump_address | String | Where the decay amount will be sent | yes | | airdrop_token | Contract | The token that will be airdropped | no | +| airdrop_2 | Contract | The 2nd token that will be airdropped | yes | | airdrop_amount | String | Total airdrop amount to be claimed | no | | start_date | u64 | When the airdrop starts in UNIX time | yes | | end_date | u64 | When the airdrop ends in UNIX time | yes | @@ -44,83 +17,10 @@ Contract responsible to handle snip20 airdrop | total_accounts | u32 | Total accounts in airdrop (needed for merkle proof) | no | | max_amount | String | Used to limit the user permit amounts (lowers exploit possibility) | no | | default_claim | String | The default amount to be gifted regardless of tasks | no | -| task_claim | RequiredTasks | The amounts per tasks to gift | no | - -##Admin - -### Messages - -#### UpdateConfig -Updates the given values -##### Request -| Name | Type | Description | optional | -|----------------|--------|------------------------------------------------------|----------| -| admin | string | New contract admin; SHOULD be a valid bech32 address | yes | -| dump_address | string | Sets the dump address if there isnt any | yes | -| start_date | u64 | When the airdrop starts in UNIX time | yes | -| end_date | u64 | When the airdrop ends in UNIX time | yes | -| decay_start | u64 | When the airdrop decay starts in UNIX time | yes | -| padding | string | Allows for enforcing constant length messages | yes | - -#### AddTasks -Adds another task that can unlock the users claim percentage, total task percentage cannot exceed 100% -##### Task -| Name | Type | Description | optional | -|---------|--------|--------------------------------------------------|----------| -| address | String | The address that will grant the task to accounts | no | -| percent | string | The percent to be unlocked when completed | no | - -##### Request -| Name | Type | Description | optional | -|---------|--------|-----------------------------------------------|----------| -| tasks | Tasks | The new tasks to be added | no | -| padding | string | Allows for enforcing constant length messages | yes | - -##### Response -```json -{ - "add_tasks": { - "status": "success" - } -} -``` - -#### ClaimDecay -Drains the decayed amount of airdrop into the specified dump_address - -##### Response -```json -{ - "claim_decay": { - "status": "success" - } -} -``` +| claim_msg_plaintext | String | {wallet} | no | +| query_rounding | string | To prevent leaking information, total claimed is rounded off to this value | no | -##Task Admin - -### Messages - -#### CompleteTask -Complete that address' tasks for a given user -##### Request -| Name | Type | Description | optional | -|---------|--------|-----------------------------------------------|----------| -| address | String | The address that completed the task | no | -| padding | string | Allows for enforcing constant length messages | yes | - -##### Response -```json -{ - "complete_task": { - "status": "success" - } -} -``` - -##User - -### Messages +### ExecuteMsg ### Account (Creates / Updates) an account from which the user will claim all of his given addresses' rewards @@ -128,219 +28,46 @@ Complete that address' tasks for a given user | Name | Type | Description | optional | |--------------|----------------------------------------------------|-----------------------------------------------------------|----------| | addresses | Array of [AddressProofPermit](#AddressProofPermit) | Proof that the user owns those addresses | no | -| partial_tree | Array of string | An array of nodes that serve as a proof for the addresses | no | +| eth_pubkey | string | Key included in headstash distribution | no | | padding | string | Allows for enforcing constant length messages | yes | ##### Response ```json -{ - "account": { - "status": "success", - "total": "Total airdrop amount", - "claimed": "Claimed amount", - "finished_tasks": "All of the finished tasks", - "addresses": ["claimed addresses"] - } -} +{} ``` -### DisablePermitKey -Disables that permit's key. Any permit that has that key for that address will be declined. +### SetViewingKey +Sets a viewing key for the account, useful for when the network is congested because of permits. ##### Request | Name | Type | Description | optional | |---------|--------|-----------------------------------------------|----------| -| key | string | Permit key | no | +| key | string | Viewing key | no | | padding | string | Allows for enforcing constant length messages | yes | ##### Response ```json { - "disable_permit_key": { + "set_viewing_key": { "status": "success" } } ``` -### SetViewingKey -Sets a viewing key for the account, useful for when the network is congested because of permits. +### DisablePermitKey +Disables that permit's key. Any permit that has that key for that address will be declined. ##### Request | Name | Type | Description | optional | |---------|--------|-----------------------------------------------|----------| -| key | string | Viewing key | no | +| key | string | Permit key | no | | padding | string | Allows for enforcing constant length messages | yes | ##### Response ```json { - "set_viewing_key": { + "disable_permit_key": { "status": "success" } } ``` -#### Claim -Claim the user's available claimable amount - -##### Response -```json -{ - "claim": { - "status": "success", - "total": "Total airdrop amount", - "claimed": "Claimed amount", - "finished_tasks": "All of the finished tasks", - "addresses": ["claimed addresses"] - } -} -``` - -### Queries - -#### GetConfig -Gets the contract's config -#### Response -```json -{ - "config": { - "config": "Contract's config" - } -} -``` - -## Dates -Get the contracts airdrop timeframe, can calculate the decay factor if a time is given -##### Request -| Name | Type | Description | optional | -|--------------|------|---------------------------------|----------| -| current_date | u64 | The current time in UNIX format | yes | -```json -{ - "dates": { - "start": "Airdrop start", - "end": "Airdrop end", - "decay_start": "Airdrop start of decay", - "decay_factor": "Decay percentage" - } -} -``` - -## TotalClaimed -Shows the total amount of the token that has been claimed. If airdrop hasn't ended then it'll just show an estimation. -##### Request -```json -{ - "total_claimed": { - "claimed": "Claimed amount" - } -} -``` - -## Account -Get the account's information -##### Request -| Name | Type | Description | optional | -|--------------|----------------------------------------|-----------------------------|----------| -| permit | [AccountProofPermit](#AccountProofMsg) | Address's permit | no | -| current_date | u64 | Current time in UNIT format | yes | -```json -{ - "account": { - "total": "Total airdrop amount", - "claimed": "Claimed amount", - "unclaimed": "Amount available to claim", - "finished_tasks": "All of the finished tasks", - "addresses": ["claimed addresses"] - } -} -``` - -## AccountWithKey -Get the account's information using a viewing key -##### Request -| Name | Type | Description | optional | -|--------------|--------|-----------------------------|----------| -| account | String | Accounts address | yes | -| key | String | Address's viewing key | no | -| current_date | u64 | Current time in UNIT format | yes | -```json -{ - "account_with_key": { - "total": "Total airdrop amount", - "claimed": "Claimed amount", - "unclaimed": "Amount available to claim", - "finished_tasks": "All of the finished tasks", - "addresses": ["claimed addresses"] - } -} -``` - -## AddressProofPermit -This is a structure used to prove that the user has permission to query that address's information (when querying account info). -This is also used to prove that the user owns that address (when creating/updating accounts) and the given amount is in the airdrop. -This permit is written differently from the rest since its made taking into consideration many of Terra's limitations compared to Keplr's flexibility. - -NOTE: The parameters must be in order - -[How to sign](https://github.com/securesecrets/shade/blob/77abdc70bc645d97aee7de5eb9a2347d22da425f/packages/shade_protocol/src/signature/mod.rs#L100) -#### Structure -| Name | Type | Description | optional | -|------------|-----------------|--------------------------------------------------------|----------| -| params | FillerMsg | Filler params accounting for Terra Ledgers limitations | no | -| memo | String | Base64Encoded AddressProofMsg | no | -| chain_id | String | Chain ID of the network this proof will be used in | no | -| signature | PermitSignature | Signature of the permit | no | - -## FillerMsg - -```json -{ - "coins": [], - "contract": "", - "execute_msg": "", - "sender": "" -} -``` - -## AddressProofMsg -The information inside permits that validate the airdrop eligibility and validate the account holder's key. - -NOTE: The parameters must be in order -### Structure -| Name | Type | Description | optional | -|----------|---------|---------------------------------------------------------|----------| -| address | String | Address of the signer (might be redundant) | no | -| amount | String | Airdrop amount | no | -| contract | String | Airdrop contract | no | -| index | Integer | Index of airdrop data in reference to the original tree | no | -| key | String | Some permit key | no | - -## AccountProofMsg -The information inside permits that validate account ownership - -NOTE: The parameters must be in order -### Structure -| Name | Type | Description | optional | -|----------|---------|---------------------------------------------------------|----------| -| contract | String | Airdrop contract | no | -| key | String | Some permit key | no | - - -## PermitSignature -The signature that proves the validity of the data - -NOTE: The parameters must be in order -### Structure -| Name | Type | Description | optional | -|-----------|--------|---------------------------|----------| -| pub_key | pubkey | Signer's public key | no | -| signature | String | Base 64 encoded signature | no | - -## Pubkey -Public key - -NOTE: The parameters must be in order -### Structure -| Name | Type | Description | optional | -|-------|--------|------------------------------------|----------| -| type | String | Must be tendermint/PubKeySecp256k1 | no | -| value | String | The base 64 key | no | \ No newline at end of file +### QueryMsg \ No newline at end of file diff --git a/contracts/airdrop/src/contract.rs b/contracts/airdrop/src/contract.rs index 496666a..ecb2cc0 100644 --- a/contracts/airdrop/src/contract.rs +++ b/contracts/airdrop/src/contract.rs @@ -1,6 +1,7 @@ use crate::{ handle::{ - try_account, try_claim, try_create_viewing_key, try_disable_permit_key, try_set_viewing_key, try_update_config + try_account, try_claim, try_create_viewing_key, try_disable_permit_key, + try_set_viewing_key, try_update_config, validation::validate_instantiation_params, }, query, state::{config_w, decay_claimed_w, total_claimed_w}, @@ -28,7 +29,7 @@ pub fn instantiate( None => env.block.time.seconds(), Some(date) => date, }; - + validate_instantiation_params(info.clone(), msg.clone())?; if let Some(end_date) = msg.end_date { if end_date < start_date { return Err(invalid_dates( @@ -72,7 +73,7 @@ pub fn instantiate( contract: env.contract.address, dump_address: msg.dump_address, airdrop_snip20: msg.airdrop_token.clone(), - airdrop_snip20_optional: msg.airdrop_snip20_optional.clone(), + airdrop_snip20_optional: msg.airdrop_2.clone(), airdrop_amount: msg.airdrop_amount, start_date, end_date: msg.end_date, @@ -103,26 +104,12 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S start_date, end_date, .. - } => try_update_config( - deps, - env, - &info, - admin, - dump_address, - start_date, - end_date, - ), + } => try_update_config(deps, env, &info, admin, dump_address, start_date, end_date), ExecuteMsg::Account { eth_pubkey, addresses, .. - } => try_account( - deps, - &env, - &info, - eth_pubkey, - addresses, - ), + } => try_account(deps, &env, &info, eth_pubkey, addresses), ExecuteMsg::DisablePermitKey { key, .. } => { try_disable_permit_key(deps, &env, &info, key) } diff --git a/contracts/airdrop/src/handle.rs b/contracts/airdrop/src/handle.rs index c03ebc9..6422f6e 100644 --- a/contracts/airdrop/src/handle.rs +++ b/contracts/airdrop/src/handle.rs @@ -136,7 +136,7 @@ pub fn try_account( // setup a new account with addresses & eth_pubkey let mut account = Account::default(); - // Validate permits + // this function will validate permits try_add_account_addresses( deps.storage, deps.api, @@ -174,7 +174,7 @@ pub fn try_account( &config, &info.sender, &mut account, - addresses.clone(), + addresses, eth_pubkey, )?; } @@ -406,8 +406,7 @@ pub fn try_add_account_addresses( if let Some(memo) = permit.memo.clone() { // unwrap the permits, coming from the message memo let params: AddressProofMsg = from_binary(&Binary::from_base64(&memo)?)?; - - // Avoid verifying sender + // avoid verifying sender if ¶ms.address != sender { // Check permit legitimacy validate_address_permit(storage, api, permit, ¶ms, config.contract.clone())?; @@ -494,10 +493,20 @@ pub fn inverse_normalizer(min: u64, x: u64, max: u64) -> Decimal { } // src: https://github.com/public-awesome/launchpad/blob/main/contracts/sg-eth-airdrop/src/claim_airdrop.rs#L85 -mod validation { +pub mod validation { use super::*; use ethereum_verify::verify_ethereum_text; - use shade_protocol::c_std::StdError; + use shade_protocol::{airdrop::InstantiateMsg, c_std::StdError}; + + pub fn validate_instantiation_params( + info: MessageInfo, + msg: InstantiateMsg, + ) -> Result<(), StdError> { + // validate_airdrop_amount(msg.airdrop_amount)?; + validate_plaintext_msg(msg.claim_msg_plaintext)?; + // validate_instantiate_funds(info)?; + Ok(()) + } pub fn compute_plaintext_msg(config: &Config, info: MessageInfo) -> String { str::replace( @@ -550,6 +559,18 @@ mod validation { }), } } + + pub fn validate_plaintext_msg(plaintext_msg: String) -> Result<(), StdError> { + if !plaintext_msg.contains("{wallet}") { + return Err(StdError::generic_err( + "Plaintext message must contain `{{wallet}}` string", + )); + } + if plaintext_msg.len() > 1000 { + return Err(StdError::generic_err("Plaintext message is too long")); + } + Ok(()) + } } // create viewing keys diff --git a/contracts/airdrop/src/test.rs b/contracts/airdrop/src/test.rs index 9d1b685..da0fc69 100644 --- a/contracts/airdrop/src/test.rs +++ b/contracts/airdrop/src/test.rs @@ -2,6 +2,7 @@ pub mod tests { use crate::contract::{execute, query}; use crate::{contract::instantiate, handle::inverse_normalizer}; + use shade_protocol::query_authentication::permit::Permit; use shade_protocol::{ airdrop::{ExecuteMsg, QueryMsg}, c_std::testing::mock_dependencies_with_balance, @@ -43,7 +44,7 @@ pub mod tests { &Addr::unchecked("secret-terps"), &"xyx-code-hash".to_string(), ), - airdrop_snip20_optional: Contract::new( + airdrop_2: Contract::new( &Addr::unchecked("secret-thiol"), &"xyz-code-hash".to_string(), ), @@ -63,6 +64,7 @@ pub mod tests { } mod account { + use shade_protocol::{airdrop::account::EmptyMsg, c_std::to_binary}; use super::*; @@ -87,7 +89,7 @@ pub mod tests { &Addr::unchecked("secret-terps"), &"xyx-code-hash".to_string(), ), - airdrop_snip20_optional: Contract::new( + airdrop_2: Contract::new( &Addr::unchecked("secret-thiol"), &"xyz-code-hash".to_string(), ), @@ -127,6 +129,11 @@ pub mod tests { let res = execute(deps.as_mut(), mock_env(), info, exec_msg).unwrap(); assert_eq!(0, res.messages.len()); } + + #[test] + fn claim_headstash() {} + #[test] + fn create_account() {} } #[test] fn test_s_i_e_q() { diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/errors.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/errors.rs index 4d91022..4ec6133 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/errors.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/errors.rs @@ -43,7 +43,7 @@ impl CodeType for Error { } Error::InvalidDates => build_string("{} ({}) cannot happen {} {} ({})", context), Error::PermitContractMismatch => { - build_string("Permit is valid for {}, expected {}", context) + build_string("Permit is not valid for {}, expected {}", context) } Error::PermitKeyRevoked => build_string("Permit key {} revoked", context), Error::PermitRejected => build_string("Permit was rejected", context), @@ -151,9 +151,6 @@ pub fn expected_memo() -> StdError { DetailedError::from_code(AIRDROP_TARGET, Error::ExpectedMemo, vec![]).to_error() } -pub fn invalid_partial_tree() -> StdError { - DetailedError::from_code(AIRDROP_TARGET, Error::InvalidPartialTree, vec![]).to_error() -} pub fn airdrop_not_started(start: &str, current: &str) -> StdError { DetailedError::from_code( diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs index e65ab8d..907d6ba 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs @@ -47,10 +47,11 @@ pub struct InstantiateMsg { pub admin: Option, // Where the decayed tokens will be dumped, if none then nothing happens pub dump_address: Option, + // primary scrt-20 contract being distributed pub airdrop_token: Contract, - // An optional, second snip20 to be minted - pub airdrop_snip20_optional: Contract, - // Airdrop amount + // an optional, second snip20 to be minted + pub airdrop_2: Contract, + // total amount of airdrop pub airdrop_amount: Uint128, // The airdrop time limit pub start_date: Option, @@ -81,17 +82,15 @@ pub enum ExecuteMsg { decay_start: Option, padding: Option, }, - /// * Creates or Updates an Account. - /// * Uses the msg sender address as key to - /// search if an account exist when a owner interacts. + /// * creates or updates an account. + /// * use msg.sender addr as key to search if an account exist. /// * Stores an unverified eth_pubkey that will be used to verify - /// ownership of eth_sig provided when claiming headstash. + /// ownership of eth_sig provided when claiming headstash. /// * Account { eth_pubkey: String, amount: Option, addresses: Vec, - partial_tree: Vec, padding: Option, }, DisablePermitKey { diff --git a/packages/shade_protocol/src/contract_interfaces/snip20/helpers.rs b/packages/shade_protocol/src/contract_interfaces/snip20/helpers.rs index 89d19e8..1bf7f72 100644 --- a/packages/shade_protocol/src/contract_interfaces/snip20/helpers.rs +++ b/packages/shade_protocol/src/contract_interfaces/snip20/helpers.rs @@ -242,7 +242,7 @@ pub fn increase_allowance_msg( amount: Uint128, expiration: Option, padding: Option, - block_size: usize, + _block_size: usize, contract: &Contract, funds: Vec, ) -> StdResult { @@ -271,7 +271,7 @@ pub fn decrease_allowance_msg( amount: Uint128, expiration: Option, padding: Option, - block_size: usize, + _block_size: usize, contract: &Contract, funds: Vec, ) -> StdResult { @@ -301,7 +301,7 @@ pub fn allowance_query( owner: Addr, spender: Addr, key: String, - block_size: usize, + _block_size: usize, contract: &Contract, ) -> StdResult { let answer: QueryAnswer = QueryMsg::Allowance { From a924bfd1408963c134782335faaba0d4dba293a3 Mon Sep 17 00:00:00 2001 From: hard-nett Date: Fri, 29 Mar 2024 14:56:35 -0400 Subject: [PATCH 08/23] remove verifying merkle leaf in account for now --- contracts/airdrop/helpers/.eslintignore | 1 + contracts/airdrop/helpers/.eslintrc | 6 + contracts/airdrop/helpers/.gitignore | 8 ++ contracts/airdrop/helpers/README.md | 47 +++++++ contracts/airdrop/helpers/bin/run | 5 + contracts/airdrop/helpers/bin/run.cmd | 3 + contracts/airdrop/helpers/src/airdrop.ts | 44 ++++++ .../helpers/src/commands/generateProofs.ts | 48 +++++++ .../helpers/src/commands/generateRoot.ts | 37 +++++ .../helpers/src/commands/verifyProofs.ts | 55 ++++++++ contracts/airdrop/helpers/src/index.ts | 1 + contracts/airdrop/src/handle.rs | 22 --- contracts/airdrop/src/test.rs | 73 +++++++++- packages/multi_test/src/lib.rs | 1 + packages/multi_test/src/multi.rs | 127 ++---------------- .../src/contract_interfaces/mod.rs | 31 ----- 16 files changed, 333 insertions(+), 176 deletions(-) create mode 100644 contracts/airdrop/helpers/.eslintignore create mode 100644 contracts/airdrop/helpers/.eslintrc create mode 100644 contracts/airdrop/helpers/.gitignore create mode 100644 contracts/airdrop/helpers/README.md create mode 100755 contracts/airdrop/helpers/bin/run create mode 100644 contracts/airdrop/helpers/bin/run.cmd create mode 100644 contracts/airdrop/helpers/src/airdrop.ts create mode 100644 contracts/airdrop/helpers/src/commands/generateProofs.ts create mode 100644 contracts/airdrop/helpers/src/commands/generateRoot.ts create mode 100644 contracts/airdrop/helpers/src/commands/verifyProofs.ts create mode 100644 contracts/airdrop/helpers/src/index.ts diff --git a/contracts/airdrop/helpers/.eslintignore b/contracts/airdrop/helpers/.eslintignore new file mode 100644 index 0000000..dc55552 --- /dev/null +++ b/contracts/airdrop/helpers/.eslintignore @@ -0,0 +1 @@ +/lib \ No newline at end of file diff --git a/contracts/airdrop/helpers/.eslintrc b/contracts/airdrop/helpers/.eslintrc new file mode 100644 index 0000000..d15adc8 --- /dev/null +++ b/contracts/airdrop/helpers/.eslintrc @@ -0,0 +1,6 @@ +{ + "extends": [ + "oclif", + "oclif-typescript" + ] +} \ No newline at end of file diff --git a/contracts/airdrop/helpers/.gitignore b/contracts/airdrop/helpers/.gitignore new file mode 100644 index 0000000..7caeba7 --- /dev/null +++ b/contracts/airdrop/helpers/.gitignore @@ -0,0 +1,8 @@ +*-debug.log +*-error.log +/.nyc_output +/dist +/lib +/package-lock.json +/tmp +node_modules \ No newline at end of file diff --git a/contracts/airdrop/helpers/README.md b/contracts/airdrop/helpers/README.md new file mode 100644 index 0000000..e8d91e5 --- /dev/null +++ b/contracts/airdrop/helpers/README.md @@ -0,0 +1,47 @@ +merkle-airdrop-cli +================== + +This is a helper client shipped along contract. +Use this to generate root, generate proofs and verify proofs + +## Installation + +```shell +yarn install +yarn link +``` + +Binary will be placed to path. + +## Airdrop file format + +```json +[ + { "address": "wasm1k9hwzxs889jpvd7env8z49gad3a3633vg350tq", "amount": "100"}, + { "address": "wasm1uy9ucvgerneekxpnfwyfnpxvlsx5dzdpf0mzjd", "amount": "1010"} +] +``` + +## Commands + +**Generate Root:** +```shell +merkle-airdrop-cli generateRoot --file ../testdata/airdrop_stage_2_list.json +``` + +**Generate proof:** +```shell +merkle-airdrop-cli generateProofs --file ../testdata/airdrop_stage_2_list.json \ + --address wasm1ylna88nach9sn5n7qe7u5l6lh7dmt6lp2y63xx \ + --amount 1000000000 +``` + +**Verify proof:** +```shell +PROOFS='[ "27e9b1ec8cb64709d0a8d3702344561674199fe81b885f1f9c9b2fb268795962","280777995d054081cbf208bccb70f8d736c1766b81d90a1fd21cd97d2d83a5cc","3946ea1758a5a2bf55bae1186168ad35aa0329805bc8bff1ca3d51345faec04a" +]' +merkle-airdrop-cli verifyProofs --file ../testdata/airdrop.json \ + --address wasm1k9hwzxs889jpvd7env8z49gad3a3633vg350tq \ + --amount 100 \ + --proofs $PROOFS +``` \ No newline at end of file diff --git a/contracts/airdrop/helpers/bin/run b/contracts/airdrop/helpers/bin/run new file mode 100755 index 0000000..9ff6bec --- /dev/null +++ b/contracts/airdrop/helpers/bin/run @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +require('@oclif/command').run() +.then(require('@oclif/command/flush')) +.catch(require('@oclif/errors/handle')) \ No newline at end of file diff --git a/contracts/airdrop/helpers/bin/run.cmd b/contracts/airdrop/helpers/bin/run.cmd new file mode 100644 index 0000000..cf40b54 --- /dev/null +++ b/contracts/airdrop/helpers/bin/run.cmd @@ -0,0 +1,3 @@ +@echo off + +node "%~dp0\run" %* \ No newline at end of file diff --git a/contracts/airdrop/helpers/src/airdrop.ts b/contracts/airdrop/helpers/src/airdrop.ts new file mode 100644 index 0000000..c798e40 --- /dev/null +++ b/contracts/airdrop/helpers/src/airdrop.ts @@ -0,0 +1,44 @@ +import sha256 from 'crypto-js/sha256' +import { MerkleTree } from 'merkletreejs'; + +class Airdrop { + private tree: MerkleTree; + + constructor(accounts: Array<{ address: string; amount: string }>) { + const leaves = accounts.map((a) => sha256(a.address + a.amount)); + this.tree = new MerkleTree(leaves, sha256, { sort: true }); + } + + public getMerkleRoot(): string { + return this.tree.getHexRoot().replace('0x', ''); + } + + public getMerkleProof(account: { + address: string; + amount: string; + }): string[] { + return this.tree + .getHexProof(sha256(account.address + account.amount).toString()) + .map((v) => v.replace('0x', '')); + } + + public verify( + proof: string[], + account: { address: string; amount: string } + ): boolean { + let hashBuf = Buffer.from(sha256(account.address + account.amount).toString()) + + proof.forEach((proofElem) => { + const proofBuf = Buffer.from(proofElem, 'hex'); + if (hashBuf < proofBuf) { + hashBuf = Buffer.from(sha256(Buffer.concat([hashBuf, proofBuf]).toString())); + } else { + hashBuf = Buffer.from(sha256(Buffer.concat([proofBuf, hashBuf]).toString())); + } + }); + + return this.getMerkleRoot() === hashBuf.toString('hex'); + } +} + +export {Airdrop} \ No newline at end of file diff --git a/contracts/airdrop/helpers/src/commands/generateProofs.ts b/contracts/airdrop/helpers/src/commands/generateProofs.ts new file mode 100644 index 0000000..35bcf1b --- /dev/null +++ b/contracts/airdrop/helpers/src/commands/generateProofs.ts @@ -0,0 +1,48 @@ +import {Command, flags} from '@oclif/command' +import { readFileSync } from 'fs'; +import {Airdrop} from '../airdrop'; + +export default class GenerateProof extends Command { + static description = 'Generates merkle proofs for given address' + + static examples = [ + `$ merkle-airdrop-cli generateProofs --file ../testdata/airdrop_stage_2.json \ + --address wasm1ylna88nach9sn5n7qe7u5l6lh7dmt6lp2y63xx \ + --amount 1000000000 +`, + ] + + static flags = { + help: flags.help({char: 'h'}), + file: flags.string({char: 'f', description: 'airdrop file location'}), + address: flags.string({char: 'a', description: 'address'}), + amount: flags.string({char: 'b', description: 'amount'}), + } + + async run() { + const {flags} = this.parse(GenerateProof) + + if (!flags.file) { + this.error(new Error('Airdrop file location not defined')) + } + if (!flags.address) { + this.error(new Error('Address not defined')) + } + if (!flags.amount) { + this.error(new Error('Amount not defined')) + } + + let file; + try { + file = readFileSync(flags.file, 'utf-8'); + } catch (e) { + this.error(e) + } + + let receivers: Array<{ address: string; amount: string }> = JSON.parse(file); + + let airdrop = new Airdrop(receivers) + let proof = airdrop.getMerkleProof({address: flags.address, amount: flags.amount}) + console.log(proof) + } +} \ No newline at end of file diff --git a/contracts/airdrop/helpers/src/commands/generateRoot.ts b/contracts/airdrop/helpers/src/commands/generateRoot.ts new file mode 100644 index 0000000..311868a --- /dev/null +++ b/contracts/airdrop/helpers/src/commands/generateRoot.ts @@ -0,0 +1,37 @@ +import {Command, flags} from '@oclif/command' +import { readFileSync } from 'fs'; +import {Airdrop} from '../airdrop'; + +export default class GenerateRoot extends Command { + static description = 'Generates merkle root' + + static examples = [ + `$ merkle-airdrop-cli generateRoot --file ../testdata/airdrop_stage_2.json +`, + ] + + static flags = { + help: flags.help({char: 'h'}), + file: flags.string({char: 'f', description: 'Airdrop file location'}), + } + + async run() { + const {flags} = this.parse(GenerateRoot) + + if (!flags.file) { + this.error(new Error('Airdrop file location not defined')) + } + + let file; + try { + file = readFileSync(flags.file, 'utf-8'); + } catch (e) { + this.error(e) + } + + let receivers: Array<{ address: string; amount: string }> = JSON.parse(file); + + let airdrop = new Airdrop(receivers) + console.log(airdrop.getMerkleRoot()) + } +} \ No newline at end of file diff --git a/contracts/airdrop/helpers/src/commands/verifyProofs.ts b/contracts/airdrop/helpers/src/commands/verifyProofs.ts new file mode 100644 index 0000000..d52fbab --- /dev/null +++ b/contracts/airdrop/helpers/src/commands/verifyProofs.ts @@ -0,0 +1,55 @@ +import {Command, flags} from '@oclif/command' +import { readFileSync } from 'fs'; +import {Airdrop} from '../airdrop'; + +export default class VerifyProof extends Command { + static description = 'Verifies merkle proofs for given address' + + static examples = [ + `$ PROOFS='[ "27e9b1ec8cb64709d0a8d3702344561674199fe81b885f1f9c9b2fb268795962","280777995d054081cbf208bccb70f8d736c1766b81d90a1fd21cd97d2d83a5cc","3946ea1758a5a2bf55bae1186168ad35aa0329805bc8bff1ca3d51345faec04a"]' + $ merkle-airdrop-cli verifyProofs --file ../testdata/airdrop.json \ + --address wasm1k9hwzxs889jpvd7env8z49gad3a3633vg350tq \ + --amount 100 + --proofs $PROOFS +`, + ] + + static flags = { + help: flags.help({char: 'h'}), + file: flags.string({char: 'f', description: 'airdrop file location'}), + proofs: flags.string({char: 'p', description: 'proofs in json format'}), + address: flags.string({char: 'a', description: 'address'}), + amount: flags.string({char: 'b', description: 'amount'}), + } + + async run() { + const {flags} = this.parse(VerifyProof) + + if (!flags.file) { + this.error(new Error('Airdrop file location not defined')) + } + if (!flags.proofs) { + this.error(new Error('Proofs not defined')) + } + if (!flags.address) { + this.error(new Error('Address not defined')) + } + if (!flags.amount) { + this.error(new Error('Amount not defined')) + } + + let file; + try { + file = readFileSync(flags.file, 'utf-8'); + } catch (e) { + this.error(e) + } + + let receivers: Array<{ address: string; amount: string }> = JSON.parse(file); + + let airdrop = new Airdrop(receivers) + let proofs: string[] = JSON.parse(flags.proofs) + + console.log(airdrop.verify(proofs, {address: flags.address, amount: flags.amount})) + } +} \ No newline at end of file diff --git a/contracts/airdrop/helpers/src/index.ts b/contracts/airdrop/helpers/src/index.ts new file mode 100644 index 0000000..639cad3 --- /dev/null +++ b/contracts/airdrop/helpers/src/index.ts @@ -0,0 +1 @@ +export {run} from '@oclif/command' \ No newline at end of file diff --git a/contracts/airdrop/src/handle.rs b/contracts/airdrop/src/handle.rs index 6422f6e..87a51ee 100644 --- a/contracts/airdrop/src/handle.rs +++ b/contracts/airdrop/src/handle.rs @@ -1,7 +1,5 @@ use crate::state::{ account_r, - // account_total_claimed_r, - // account_total_claimed_w, account_viewkey_w, account_w, address_in_account_w, @@ -398,8 +396,6 @@ pub fn try_add_account_addresses( addresses: Vec, eth_pubkey: String, ) -> StdResult<()> { - // Setup the items to validate - let mut leaves_to_validate: Vec<(usize, [u8; 32])> = vec![]; // Iterate addresses for permit in addresses.iter() { @@ -437,12 +433,6 @@ pub fn try_add_account_addresses( Ok(true) }, )?; - - // Add account as a leaf - let leaf_hash = - Sha256::hash((params.address.to_string() + ¶ms.amount.to_string()).as_bytes()); - leaves_to_validate.push((params.index as usize, leaf_hash)); - // If valid then add to account array and sum total amount account.addresses.push(params.address); account.eth_pubkey = eth_pubkey.clone(); @@ -450,18 +440,6 @@ pub fn try_add_account_addresses( return Err(expected_memo()); } } - - // Need to sort by index in order for the proof to work - leaves_to_validate.sort_by_key(|item| item.0); - - let mut indices: Vec = vec![]; - let mut leaves: Vec<[u8; 32]> = vec![]; - - for leaf in leaves_to_validate.iter() { - indices.push(leaf.0); - leaves.push(leaf.1); - } - Ok(()) } diff --git a/contracts/airdrop/src/test.rs b/contracts/airdrop/src/test.rs index da0fc69..7e62ff6 100644 --- a/contracts/airdrop/src/test.rs +++ b/contracts/airdrop/src/test.rs @@ -1,3 +1,9 @@ +// +// testing-mnemonic-1: indoor lend bachelor survey capital long venue name report fortune soldier course bring tiger unlock tree release swift van immune sketch attend upon rent +// testing-mnemonic-2: cement worth weekend silk aspect game balance speak warfare social swing permit engage expand ring soft fiscal exit race invest fragile brain drift economy +// +// +// #[cfg(test)] pub mod tests { use crate::contract::{execute, query}; @@ -27,6 +33,7 @@ pub mod tests { const VIEWING_KEY: &str = "jUsTfOrTeStInG"; + #[test] fn proper_initialization() { let mut deps = mock_dependencies(); let info = mock_info( @@ -48,7 +55,7 @@ pub mod tests { &Addr::unchecked("secret-thiol"), &"xyz-code-hash".to_string(), ), - airdrop_amount: Uint128::new(420), + airdrop_amount: Uint128::new(43476089028199), start_date: None, end_date: None, decay_start: None, @@ -59,14 +66,12 @@ pub mod tests { }; let res = instantiate(deps.as_mut(), mock_env(), info, init_msg).unwrap(); - assert_eq!(0, res.messages.len()); } mod account { - use shade_protocol::{airdrop::account::EmptyMsg, c_std::to_binary}; - use super::*; + use shade_protocol::{airdrop::account::EmptyMsg, c_std::to_binary}; #[test] fn set_viewing_key() { @@ -103,7 +108,8 @@ pub mod tests { claim_msg_plaintext: "{wallet}".to_string(), }; - let _res = instantiate(deps.as_mut(), mock_env(), info, init_msg).unwrap(); + let res = instantiate(deps.as_mut(), mock_env(), info, init_msg).unwrap(); + assert_eq!(0, res.messages.len()); // anyone can setup an account let info = mock_info( @@ -119,7 +125,7 @@ pub mod tests { padding: None, }; let res = execute(deps.as_mut(), mock_env(), info.clone(), exec_msg).unwrap(); - println!("{res:?}"); + assert_eq!(0, res.messages.len()); // test disabling the permit key let exec_msg = ExecuteMsg::DisablePermitKey { @@ -131,7 +137,60 @@ pub mod tests { } #[test] - fn claim_headstash() {} + fn claim_headstash() { + let mut deps = mock_dependencies_with_balance(&[Coin { + denom: "earth".to_string(), + amount: Uint128::new(1000), + }]); + let info = mock_info( + "creator", + &[Coin { + denom: "earth".to_string(), + amount: Uint128::new(1000), + }], + ); + let init_msg = InstantiateMsg { + admin: Some(Addr::unchecked("creator")), + dump_address: Some(Addr::unchecked("creator")), + airdrop_token: Contract::new( + &Addr::unchecked("secret-terps"), + &"xyx-code-hash".to_string(), + ), + airdrop_2: Contract::new( + &Addr::unchecked("secret-thiol"), + &"xyz-code-hash".to_string(), + ), + airdrop_amount: Uint128::from(840u128), + start_date: None, + end_date: None, + decay_start: None, + merkle_root: "d599867bdb2ade1e470d9ec9456490adcd9da6e0cfd8f515e2b95d345a5cd92f" + .to_string(), + total_accounts: 2u32, + claim_msg_plaintext: "{wallet}".to_string(), + }; + let res = instantiate(deps.as_mut(), mock_env(), info, init_msg).unwrap(); + assert_eq!(0, res.messages.len()); + + // anyone can setup an account + let info = mock_info( + "anyone", + &[Coin { + denom: "token".to_string(), + amount: Uint128::new(420), + }], + ); + // test setting a viewing key + let exec_msg = ExecuteMsg::SetViewingKey { + key: VIEWING_KEY.into(), + padding: None, + }; + let res = execute(deps.as_mut(), mock_env(), info.clone(), exec_msg).unwrap(); + assert_eq!(0, res.messages.len()); + + + } + #[test] fn create_account() {} } diff --git a/packages/multi_test/src/lib.rs b/packages/multi_test/src/lib.rs index 51a504c..c5ccbf5 100644 --- a/packages/multi_test/src/lib.rs +++ b/packages/multi_test/src/lib.rs @@ -1,4 +1,5 @@ #[cfg(not(target_arch = "wasm32"))] pub mod multi; +// pub mod headstash; pub mod interfaces; diff --git a/packages/multi_test/src/multi.rs b/packages/multi_test/src/multi.rs index 60f033a..edb0bc6 100644 --- a/packages/multi_test/src/multi.rs +++ b/packages/multi_test/src/multi.rs @@ -14,67 +14,18 @@ pub mod admin { } } -// #[cfg(feature = "snip20")] -// pub mod snip20 { -// use snip20; -// multi_derive::implement_multi!(Snip20, snip20); -// } - -// #[cfg(feature = "liability_mint")] -// pub mod liability_mint { -// use liability_mint; -// multi_derive::implement_multi!(LiabilityMint, liability_mint); -// } - -// #[cfg(feature = "stkd_scrt")] -// pub mod stkd_scrt { -// use stkd_scrt; -// multi_derive::implement_multi!(StkdScrt, stkd_scrt); -// } - -// // #[cfg(feature = "mint")] -// // pub mod mint { -// // use mint; -// // multi_derive::implement_multi!(Mint, mint); -// // } - -// // #[cfg(feature = "oracle")] -// // pub mod oracle { -// // use oracle; -// // multi_derive::implement_multi!(Oracle, oracle); -// // } - -// // #[cfg(feature = "mock_band")] -// // pub mod mock_band { -// // use crate::multi_derive; -// // use mock_band; - -// // pub struct MockBand; -// // multi_derive::implement_multi!(MockBand, mock_band); -// // } - -// #[cfg(feature = "governance")] -// pub mod governance { -// use governance; - -// multi_derive::implement_multi_with_reply!(Governance, governance); -// } - -// // #[cfg(feature = "snip20_staking")] -// // pub mod snip20_staking { -// // use spip_stkd_0; -// // -// // multi_derive::implement_multi!(Snip20Staking, spip_stkd_0); -// // } - -// // #[cfg(feature = "bonds")] -// // pub mod bonds { -// // use crate::multi_derive; -// // use bonds; +#[cfg(feature = "snip20")] +pub mod snip20 { + use snip20; + multi_derive::implement_multi!(Snip20, snip20); +} -// // pub struct Bonds; -// // multi_derive::implement_multi!(Bonds, bonds); -// // } +#[cfg(feature = "airdrop")] +pub mod airdrop { + pub use airdrop; + use shade_protocol::{airdrop::InstantiateMsg, multi_test::App, utils::InstantiateCallback}; + multi_derive::implement_multi!(Airdrop, airdrop); +} #[cfg(feature = "query_auth")] pub mod query_auth { @@ -82,59 +33,3 @@ pub mod query_auth { multi_derive::implement_multi!(QueryAuth, query_auth); } - -// #[cfg(feature = "treasury_manager")] -// pub mod treasury_manager { -// use treasury_manager; -// multi_derive::implement_multi!(TreasuryManager, treasury_manager); -// } - -// #[cfg(feature = "treasury")] -// pub mod treasury { -// use treasury; -// multi_derive::implement_multi!(Treasury, treasury); -// } - -// #[cfg(feature = "mock_adapter")] -// pub mod mock_adapter { -// use mock_adapter; -// multi_derive::implement_multi!(MockAdapter, mock_adapter); -// } - -// #[cfg(feature = "scrt_staking")] -// pub mod scrt_staking { -// use scrt_staking; -// multi_derive::implement_multi!(ScrtStaking, scrt_staking); -// } - -// #[cfg(feature = "basic_staking")] -// pub mod basic_staking { -// use basic_staking; -// multi_derive::implement_multi!(BasicStaking, basic_staking); -// } - -// #[cfg(feature = "peg_stability")] -// pub mod peg_stability { -// use peg_stability; - -// multi_derive::implement_multi!(PegStability, peg_stability); -// } - -// #[cfg(feature = "mock_stkd")] -// pub mod mock_stkd { -// pub use mock_stkd; -// multi_derive::implement_multi!(MockStkd, mock_stkd); -// } - -// #[cfg(feature = "mock_sienna")] -// pub mod mock_sienna { -// pub use mock_sienna; -// multi_derive::implement_multi!(MockSienna, mock_sienna); -// } - -// #[cfg(feature = "snip20_migration")] -// pub mod snip20_migration { -// use snip20_migration; - -// multi_derive::implement_multi!(Snip20Migration, snip20_migration); -// } diff --git a/packages/shade_protocol/src/contract_interfaces/mod.rs b/packages/shade_protocol/src/contract_interfaces/mod.rs index afd066f..317cb3c 100644 --- a/packages/shade_protocol/src/contract_interfaces/mod.rs +++ b/packages/shade_protocol/src/contract_interfaces/mod.rs @@ -1,17 +1,6 @@ -#[cfg(feature = "dex")] -pub mod dex; - -#[cfg(feature = "dao")] -pub mod dao; pub mod oracles; -#[cfg(feature = "mint")] -pub mod mint; - -#[cfg(feature = "sky")] -pub mod sky; - #[cfg(feature = "snip20")] pub mod snip20; @@ -19,28 +8,8 @@ pub mod snip20; #[cfg(feature = "airdrop")] pub mod airdrop; -// Protocol libraries -#[cfg(feature = "governance")] -pub mod governance; - -// Bonds -#[cfg(feature = "bonds")] -pub mod bonds; - #[cfg(feature = "query_auth")] pub mod query_auth; #[cfg(feature = "admin")] pub mod admin; - -#[cfg(feature = "peg_stability")] -pub mod peg_stability; - -#[cfg(feature = "stkd")] -pub mod stkd; - -#[cfg(feature = "basic_staking")] -pub mod basic_staking; - -#[cfg(feature = "snip20_migration")] -pub mod snip20_migration; From e223d59e90a9af589a37c621e0ea5953730c4d83 Mon Sep 17 00:00:00 2001 From: hard-nett Date: Sun, 31 Mar 2024 14:08:49 -0400 Subject: [PATCH 09/23] update repo layout --- contracts/airdrop/Cargo.toml | 4 +- contracts/airdrop/README.md | 261 +- contracts/airdrop/helpers/bin/run | 5 - contracts/airdrop/helpers/bin/run.cmd | 3 - contracts/airdrop/src/contract.rs | 41 +- contracts/airdrop/src/handle.rs | 273 +- contracts/airdrop/src/query.rs | 43 +- contracts/airdrop/src/state.rs | 64 +- contracts/airdrop/src/test.rs | 486 ++- packages/multi_test/Cargo.toml | 1 - packages/multi_test/src/interfaces/mod.rs | 19 +- packages/multi_test/src/interfaces/snip20.rs | 186 + packages/multi_test/src/interfaces/utils.rs | 2 +- packages/multi_test/src/lib.rs | 1 - packages/multi_test/src/multi.rs | 115 +- packages/shade_protocol/Cargo.toml | 3 +- .../contract_interfaces/airdrop/account.rs | 14 +- .../contract_interfaces/airdrop/claim_info.rs | 14 +- .../src/contract_interfaces/airdrop/errors.rs | 61 +- .../src/contract_interfaces/airdrop/mod.rs | 37 +- .../contract_interfaces/basic_staking/mod.rs | 299 -- .../src/contract_interfaces/bonds/errors.rs | 331 -- .../src/contract_interfaces/bonds/mod.rs | 283 -- .../src/contract_interfaces/bonds/rand.rs | 74 - .../src/contract_interfaces/bonds/utils.rs | 17 - .../contract_interfaces/dao/DAO_ADAPTER.md | 153 - .../src/contract_interfaces/dao/adapter.rs | 187 - .../src/contract_interfaces/dao/lp_shdswap.rs | 151 - .../src/contract_interfaces/dao/manager.rs | 240 -- .../src/contract_interfaces/dao/mod.rs | 23 - .../dao/rewards_emission.rs | 102 - .../contract_interfaces/dao/scrt_staking.rs | 100 - .../src/contract_interfaces/dao/stkd_scrt.rs | 192 - .../src/contract_interfaces/dao/treasury.rs | 250 -- .../dao/treasury_manager.rs | 247 -- .../src/contract_interfaces/dex/dex.rs | 173 - .../src/contract_interfaces/dex/mod.rs | 11 - .../src/contract_interfaces/dex/secretswap.rs | 136 - .../src/contract_interfaces/dex/shadeswap.rs | 229 -- .../src/contract_interfaces/dex/sienna.rs | 170 - .../governance/assembly.rs | 184 - .../governance/contract.rs | 105 - .../contract_interfaces/governance/errors.rs | 44 - .../src/contract_interfaces/governance/mod.rs | 455 --- .../contract_interfaces/governance/profile.rs | 308 -- .../governance/proposal.rs | 399 -- .../governance/stored_id.rs | 261 -- .../contract_interfaces/governance/vote.rs | 79 - .../src/contract_interfaces/mod.rs | 3 - .../src/contract_interfaces/oracles/band.rs | 62 - .../src/contract_interfaces/oracles/mod.rs | 4 - .../src/contract_interfaces/oracles/oracle.rs | 97 - .../contract_interfaces/peg_stability/mod.rs | 146 - .../src/contract_interfaces/query_auth/mod.rs | 1 + .../src/contract_interfaces/shade_oracles.rs | 106 - .../src/contract_interfaces/sky/cycles.rs | 339 -- .../src/contract_interfaces/sky/mod.rs | 182 - .../src/contract_interfaces/snip20/helpers.rs | 6 +- .../src/contract_interfaces/stkd/mod.rs | 170 - packages/shade_protocol/src/schemas.rs | 11 +- tools/headstash/account.js | 103 + tools/headstash/deploy-cw20.sh | 58 + tools/headstash/main.js | 148 + tools/headstash/merkle-gen.sh | 0 .../merkleTree}/.eslintignore | 0 .../helpers => tools/merkleTree}/.eslintrc | 0 .../helpers => tools/merkleTree}/.gitignore | 0 .../helpers => tools/merkleTree}/README.md | 0 .../merkleTree}/src/airdrop.ts | 0 .../src/commands/generateProofs.ts | 0 .../merkleTree}/src/commands/generateRoot.ts | 0 .../merkleTree}/src/commands/verifyProofs.ts | 0 .../helpers => tools/merkleTree}/src/index.ts | 0 tools/merkleTree/yarn.lock | 3228 +++++++++++++++++ tools/multisig/broadcast_multi.sh | 28 + tools/multisig/sign_mutli.sh | 11 + tools/multisig/sign_permit.py | 36 + tools/multisig/wasm_msg.py | 25 + 78 files changed, 4762 insertions(+), 6838 deletions(-) delete mode 100755 contracts/airdrop/helpers/bin/run delete mode 100644 contracts/airdrop/helpers/bin/run.cmd create mode 100644 packages/multi_test/src/interfaces/snip20.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/basic_staking/mod.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/bonds/errors.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/bonds/mod.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/bonds/rand.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/bonds/utils.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/dao/DAO_ADAPTER.md delete mode 100644 packages/shade_protocol/src/contract_interfaces/dao/adapter.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/dao/lp_shdswap.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/dao/manager.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/dao/mod.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/dao/rewards_emission.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/dao/scrt_staking.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/dao/stkd_scrt.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/dao/treasury.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/dao/treasury_manager.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/dex/dex.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/dex/mod.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/dex/secretswap.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/dex/shadeswap.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/dex/sienna.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/governance/assembly.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/governance/contract.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/governance/errors.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/governance/mod.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/governance/profile.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/governance/proposal.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/governance/stored_id.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/governance/vote.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/oracles/band.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/oracles/mod.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/oracles/oracle.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/peg_stability/mod.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/shade_oracles.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/sky/cycles.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/sky/mod.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/stkd/mod.rs create mode 100644 tools/headstash/account.js create mode 100644 tools/headstash/deploy-cw20.sh create mode 100644 tools/headstash/main.js create mode 100644 tools/headstash/merkle-gen.sh rename {contracts/airdrop/helpers => tools/merkleTree}/.eslintignore (100%) rename {contracts/airdrop/helpers => tools/merkleTree}/.eslintrc (100%) rename {contracts/airdrop/helpers => tools/merkleTree}/.gitignore (100%) rename {contracts/airdrop/helpers => tools/merkleTree}/README.md (100%) rename {contracts/airdrop/helpers => tools/merkleTree}/src/airdrop.ts (100%) rename {contracts/airdrop/helpers => tools/merkleTree}/src/commands/generateProofs.ts (100%) rename {contracts/airdrop/helpers => tools/merkleTree}/src/commands/generateRoot.ts (100%) rename {contracts/airdrop/helpers => tools/merkleTree}/src/commands/verifyProofs.ts (100%) rename {contracts/airdrop/helpers => tools/merkleTree}/src/index.ts (100%) create mode 100644 tools/merkleTree/yarn.lock create mode 100755 tools/multisig/broadcast_multi.sh create mode 100755 tools/multisig/sign_mutli.sh create mode 100644 tools/multisig/sign_permit.py create mode 100644 tools/multisig/wasm_msg.py diff --git a/contracts/airdrop/Cargo.toml b/contracts/airdrop/Cargo.toml index 74cf04c..d9ff0bc 100644 --- a/contracts/airdrop/Cargo.toml +++ b/contracts/airdrop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "airdrop" -version = "0.1.1" +version = "0.1.0" authors = ["Guy Garcia "] edition = "2018" @@ -23,7 +23,7 @@ backtraces = ["shade-protocol/backtraces"] debug-print = ["shade-protocol/debug-print"] [dependencies] -shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["airdrop", "query_auth_impl",] } +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol", features = ["airdrop"] } ethereum-verify = { version = "0.1.0", path = "../../packages/ethereum_verify"} hex = "0.4" sha2 = { version = "0.10.2", default-features = false } diff --git a/contracts/airdrop/README.md b/contracts/airdrop/README.md index e13fe92..cfc20ec 100644 --- a/contracts/airdrop/README.md +++ b/contracts/airdrop/README.md @@ -1,6 +1,34 @@ -# Private Headstash Airdrop -### InstantiateMsg +# Airdrop Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Init](#Init) + * [Admin](#Admin) + * Messages + * [UpdateConfig](#UpdateConfig) + * [AddTasks](#AddTasks) + * [ClaimDecay](#ClaimDecay) + * [Admin](#Admin) + * Messages + * [CompleteTask](#CompleteTask) + * [User](#User) + * Messages + * [Account](#Account) + * [DisablePermitKey](#DisablePermitKey) + * [SetViewingKey](#SetViewingKey) + * [Claim](#Claim) + * Queries + * [Config](#Config) + * [Dates](#Dates) + * [TotalClaimed](#TotalClaimed) + * [Account](#Account) + * [AccountWithKey](#AccountWithKey) + +# Introduction +Contract responsible to handle snip20 airdrop + +# Sections + ## Init ##### Request | Name | Type | Description | optional | @@ -8,7 +36,7 @@ | admin | String | New contract owner; SHOULD be a valid bech32 address | yes | | dump_address | String | Where the decay amount will be sent | yes | | airdrop_token | Contract | The token that will be airdropped | no | -| airdrop_2 | Contract | The 2nd token that will be airdropped | yes | +| airdrop_2 | Contract | The token that will be airdropped | yes | | airdrop_amount | String | Total airdrop amount to be claimed | no | | start_date | u64 | When the airdrop starts in UNIX time | yes | | end_date | u64 | When the airdrop ends in UNIX time | yes | @@ -16,11 +44,43 @@ | merkle_root | String | Base 64 encoded merkle root of the airdrop data tree | no | | total_accounts | u32 | Total accounts in airdrop (needed for merkle proof) | no | | max_amount | String | Used to limit the user permit amounts (lowers exploit possibility) | no | -| default_claim | String | The default amount to be gifted regardless of tasks | no | -| claim_msg_plaintext | String | {wallet} | no | +| claim_msg_plaintext | String | {wallet} | no | | query_rounding | string | To prevent leaking information, total claimed is rounded off to this value | no | -### ExecuteMsg +##Admin + +### Messages + +#### UpdateConfig +Updates the given values +##### Request +| Name | Type | Description | optional | +|----------------|--------|------------------------------------------------------|----------| +| admin | string | New contract admin; SHOULD be a valid bech32 address | yes | +| dump_address | string | Sets the dump address if there isnt any | yes | +| query_rounding | String | To prevent leaking information | yes | +| start_date | u64 | When the airdrop starts in UNIX time | yes | +| end_date | u64 | When the airdrop ends in UNIX time | yes | +| decay_start | u64 | When the airdrop decay starts in UNIX time | yes | +| padding | string | Allows for enforcing constant length messages | yes | + +#### ClaimDecay +Drains the decayed amount of airdrop into the specified dump_address + +##### Response +```json +{ + "claim_decay": { + "status": "success" + } +} +``` + +### Messages + +##User + +### Messages ### Account (Creates / Updates) an account from which the user will claim all of his given addresses' rewards @@ -28,46 +88,213 @@ | Name | Type | Description | optional | |--------------|----------------------------------------------------|-----------------------------------------------------------|----------| | addresses | Array of [AddressProofPermit](#AddressProofPermit) | Proof that the user owns those addresses | no | -| eth_pubkey | string | Key included in headstash distribution | no | +| eth_pubkey | string | Key included in headstash distribution | no | | padding | string | Allows for enforcing constant length messages | yes | ##### Response ```json -{} +{ + "account": { + "status": "success", + "claimed": "boolean", + "addresses": ["claimed addresses"], + "eth_pubkey": "eth Pubkey" + } +} ``` -### SetViewingKey -Sets a viewing key for the account, useful for when the network is congested because of permits. +### DisablePermitKey +Disables that permit's key. Any permit that has that key for that address will be declined. ##### Request | Name | Type | Description | optional | |---------|--------|-----------------------------------------------|----------| -| key | string | Viewing key | no | +| key | string | Permit key | no | | padding | string | Allows for enforcing constant length messages | yes | ##### Response ```json { - "set_viewing_key": { + "disable_permit_key": { "status": "success" } } ``` -### DisablePermitKey -Disables that permit's key. Any permit that has that key for that address will be declined. +### SetViewingKey +Sets a viewing key for the account, useful for when the network is congested because of permits. ##### Request | Name | Type | Description | optional | |---------|--------|-----------------------------------------------|----------| -| key | string | Permit key | no | +| key | string | Viewing key | no | | padding | string | Allows for enforcing constant length messages | yes | ##### Response ```json { - "disable_permit_key": { + "set_viewing_key": { "status": "success" } } ``` -### QueryMsg \ No newline at end of file +#### Claim +Claim the user's available claimable amount + +##### Response +```json +{ + "claim": { + "status": "success", + "total": "Total airdrop amount", + "claimed": "Claimed amount", + "addresses": ["claimed addresses"] + } +} +``` + +### Queries + +#### GetConfig +Gets the contract's config +#### Response +```json +{ + "config": { + "config": "Contract's config" + } +} +``` + +## Dates +Get the contracts airdrop timeframe, can calculate the decay factor if a time is given +##### Request +| Name | Type | Description | optional | +|--------------|------|---------------------------------|----------| +| current_date | u64 | The current time in UNIX format | yes | +```json +{ + "dates": { + "start": "Airdrop start", + "end": "Airdrop end", + "decay_start": "Airdrop start of decay", + "decay_factor": "Decay percentage" + } +} +``` + +## TotalClaimed +Shows the total amount of the token that has been claimed. If airdrop hasn't ended then it'll just show an estimation. +##### Request +```json +{ + "total_claimed": { + "claimed": "Claimed amount" + } +} +``` + +## Account +Get the account's information +##### Request +| Name | Type | Description | optional | +|--------------|----------------------------------------|-----------------------------|----------| +| permit | [AccountProofPermit](#AccountProofMsg) | Address's permit | no | +| current_date | u64 | Current time in UNIT format | yes | +```json +{ + "account": { + "claimed": "boolean", + "addresses": ["claimed addresses"], + "eth_pubkey": "" + } +} +``` + +## AccountWithKey +Get the account's information using a viewing key +##### Request +| Name | Type | Description | optional | +|--------------|--------|-----------------------------|----------| +| account | String | Accounts address | yes | +| key | String | Address's viewing key | no | +| current_date | u64 | Current time in UNIT format | yes | +```json +{ + "account_with_key": { + "claimed": "boolean", + "addresses": ["claimed addresses"], + "eth_pubkey": "" + } +} +``` + +## AddressProofPermit +This is a structure used to prove that the user has permission to query that address's information (when querying account info). +This is also used to prove that the user owns that address (when creating/updating accounts) and the given amount is in the airdrop. +This permit is written differently from the rest since its made taking into consideration many of Terra's limitations compared to Keplr's flexibility. + +NOTE: The parameters must be in order + +[How to sign](https://github.com/securesecrets/shade/blob/77abdc70bc645d97aee7de5eb9a2347d22da425f/packages/shade_protocol/src/signature/mod.rs#L100) +#### Structure +| Name | Type | Description | optional | +|------------|-----------------|--------------------------------------------------------|----------| +| params | FillerMsg | Filler params accounting for Terra Ledgers limitations | no | +| memo | String | Base64Encoded AddressProofMsg | no | +| chain_id | String | Chain ID of the network this proof will be used in | no | +| signature | PermitSignature | Signature of the permit | no | + +## FillerMsg + +```json +{ + "coins": [], + "contract": "", + "execute_msg": "", + "sender": "" +} +``` + +## AddressProofMsg +The information inside permits that validate the airdrop eligibility and validate the account holder's key. + +NOTE: The parameters must be in order +### Structure +| Name | Type | Description | optional | +|----------|---------|---------------------------------------------------------|----------| +| address | String | Address of the signer (might be redundant) | no | +| amount | String | Airdrop amount | no | +| contract | String | Airdrop contract | no | +| index | Integer | Index of airdrop data in reference to the original tree | no | +| key | String | Some permit key | no | + +## AccountProofMsg +The information inside permits that validate account ownership + +NOTE: The parameters must be in order +### Structure +| Name | Type | Description | optional | +|----------|---------|---------------------------------------------------------|----------| +| contract | String | Airdrop contract | no | +| key | String | Some permit key | no | + + +## PermitSignature +The signature that proves the validity of the data + +NOTE: The parameters must be in order +### Structure +| Name | Type | Description | optional | +|-----------|--------|---------------------------|----------| +| pub_key | pubkey | Signer's public key | no | +| signature | String | Base 64 encoded signature | no | + +## Pubkey +Public key + +NOTE: The parameters must be in order +### Structure +| Name | Type | Description | optional | +|-------|--------|------------------------------------|----------| +| type | String | Must be tendermint/PubKeySecp256k1 | no | +| value | String | The base 64 key | no | \ No newline at end of file diff --git a/contracts/airdrop/helpers/bin/run b/contracts/airdrop/helpers/bin/run deleted file mode 100755 index 9ff6bec..0000000 --- a/contracts/airdrop/helpers/bin/run +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node - -require('@oclif/command').run() -.then(require('@oclif/command/flush')) -.catch(require('@oclif/errors/handle')) \ No newline at end of file diff --git a/contracts/airdrop/helpers/bin/run.cmd b/contracts/airdrop/helpers/bin/run.cmd deleted file mode 100644 index cf40b54..0000000 --- a/contracts/airdrop/helpers/bin/run.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@echo off - -node "%~dp0\run" %* \ No newline at end of file diff --git a/contracts/airdrop/src/contract.rs b/contracts/airdrop/src/contract.rs index ecb2cc0..940b0a1 100644 --- a/contracts/airdrop/src/contract.rs +++ b/contracts/airdrop/src/contract.rs @@ -1,7 +1,7 @@ use crate::{ handle::{ - try_account, try_claim, try_create_viewing_key, try_disable_permit_key, - try_set_viewing_key, try_update_config, validation::validate_instantiation_params, + try_account, try_claim, try_claim_decay, try_disable_permit_key, try_set_viewing_key, + try_update_config, }, query, state::{config_w, decay_claimed_w, total_claimed_w}, @@ -29,7 +29,7 @@ pub fn instantiate( None => env.block.time.seconds(), Some(date) => date, }; - validate_instantiation_params(info.clone(), msg.clone())?; + if let Some(end_date) = msg.end_date { if end_date < start_date { return Err(invalid_dates( @@ -67,7 +67,6 @@ pub fn instantiate( return Err(StdError::generic_err("Decay must have an end date")); } } - let config = Config { admin: msg.admin.unwrap_or(info.sender), contract: env.contract.address, @@ -81,6 +80,8 @@ pub fn instantiate( merkle_root: msg.merkle_root, total_accounts: msg.total_accounts, claim_msg_plaintext: msg.claim_msg_plaintext, + max_amount: msg.max_amount, + query_rounding: msg.query_rounding, }; config_w(deps.storage).save(&config)?; @@ -88,7 +89,6 @@ pub fn instantiate( // Initialize claim amount total_claimed_w(deps.storage).save(&Uint128::zero())?; - // clawback function?? decay_claimed_w(deps.storage).save(&false)?; Ok(Response::new()) @@ -101,15 +101,27 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S ExecuteMsg::UpdateConfig { admin, dump_address, + query_rounding: redeem_step_size, start_date, end_date, + decay_start: start_decay, .. - } => try_update_config(deps, env, &info, admin, dump_address, start_date, end_date), + } => try_update_config( + deps, + env, + &info, + admin, + dump_address, + redeem_step_size, + start_date, + end_date, + start_decay, + ), ExecuteMsg::Account { - eth_pubkey, addresses, + eth_pubkey, .. - } => try_account(deps, &env, &info, eth_pubkey, addresses), + } => try_account(deps, &env, &info, addresses, eth_pubkey), ExecuteMsg::DisablePermitKey { key, .. } => { try_disable_permit_key(deps, &env, &info, key) } @@ -121,7 +133,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S proof, .. } => try_claim(deps, &env, &info, amount, eth_pubkey, eth_sig, proof), - ExecuteMsg::ClaimDecay { .. } => crate::handle::try_claim_decay(deps, &env, &info), + ExecuteMsg::ClaimDecay { .. } => try_claim_decay(deps, &env, &info), }, RESPONSE_BLOCK_SIZE, ) @@ -134,14 +146,15 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::Config {} => to_binary(&query::config(deps)?), QueryMsg::Dates { current_date } => to_binary(&query::dates(deps, current_date)?), QueryMsg::TotalClaimed {} => to_binary(&query::total_claimed(deps)?), - QueryMsg::Account { permit, eth_pubkey } => { - to_binary(&query::account(deps, permit, eth_pubkey)?) - } + QueryMsg::Account { + permit, + current_date, + } => to_binary(&query::account(deps, permit, current_date)?), QueryMsg::AccountWithKey { account, key, - eth_pubkey, - } => to_binary(&query::account_with_key(deps, account, key, eth_pubkey)?), + current_date, + } => to_binary(&query::account_with_key(deps, account, key, current_date)?), }, RESPONSE_BLOCK_SIZE, ) diff --git a/contracts/airdrop/src/handle.rs b/contracts/airdrop/src/handle.rs index 87a51ee..026137a 100644 --- a/contracts/airdrop/src/handle.rs +++ b/contracts/airdrop/src/handle.rs @@ -1,30 +1,18 @@ use crate::state::{ - account_r, - account_viewkey_w, - account_w, - address_in_account_w, - claim_status_w, - config_r, - config_w, - decay_claimed_w, - eth_pubkey_claim_r, - eth_pubkey_claim_w, - eth_pubkey_in_account_w, - revoke_permit, - total_claimed_r, - total_claimed_w, - validate_address_permit, + account_r, account_viewkey_w, account_w, address_in_account_w, claim_status_w, config_r, + config_w, decay_claimed_w, eth_pubkey_claim_r, eth_pubkey_claim_w, revoke_permit, + total_claimed_r, total_claimed_w, validate_address_permit, }; use hex::decode_to_slice; -use rs_merkle::{algorithms::Sha256, Hasher}; +// use rs_merkle::{algorithms::Sha256, Hasher, MerkleProof}; use sha2::Digest; use shade_protocol::{ airdrop::{ account::{Account, AccountKey, AddressProofMsg, AddressProofPermit}, errors::{ address_already_in_account, airdrop_ended, airdrop_not_started, already_claimed, - decay_claimed, decay_not_set, expected_memo, failed_verification, invalid_dates, - not_admin, nothing_to_claim, wrong_length, + claim_too_high, decay_claimed, decay_not_set, expected_memo, failed_verification, + invalid_dates, not_admin, nothing_to_claim, wrong_length, }, Config, ExecuteAnswer, }, @@ -32,21 +20,10 @@ use shade_protocol::{ ensure_eq, from_binary, to_binary, Addr, Api, Binary, Decimal, DepsMut, Env, MessageInfo, Response, StdResult, Storage, Uint128, }, - snip20::helpers::send_msg, - utils::generic_response::ResponseStatus::{self}, -}; -use shade_protocol::{ - contract_interfaces::query_auth::{ - auth::{HashedKey, Key, PermitKey}, - RngSeed, - }, query_authentication::viewing_keys::ViewingKey, - utils::{ - generic_response::ResponseStatus::Success, - storage::plus::{ItemStorage, MapStorage}, - }, + snip20::helpers::send_msg, + utils::generic_response::ResponseStatus::{self, Success}, }; - use std::{convert::TryInto, fmt::Write}; #[allow(clippy::too_many_arguments)] @@ -56,15 +33,16 @@ pub fn try_update_config( info: &MessageInfo, admin: Option, dump_address: Option, + query_rounding: Option, start_date: Option, end_date: Option, + decay_start: Option, ) -> StdResult { let config = config_r(deps.storage).load()?; // Check if admin - assert!( - info.sender == config.admin, - not_admin(config.admin.as_str()) - ); + if info.sender != config.admin { + return Err(not_admin(config.admin.as_str())); + } // Save new info let mut config = config_w(deps.storage); @@ -75,6 +53,9 @@ pub fn try_update_config( if let Some(dump_address) = dump_address { state.dump_address = Some(dump_address); } + if let Some(query_rounding) = query_rounding { + state.query_rounding = query_rounding; + } if let Some(start_date) = start_date { // Avoid date collisions if let Some(end_date) = end_date { @@ -98,13 +79,59 @@ pub fn try_update_config( )); } } + if let Some(start_decay) = decay_start { + if start_date > start_decay { + return Err(invalid_dates( + "Decay", + start_decay.to_string().as_str(), + "before", + "StartDate", + start_date.to_string().as_str(), + )); + } + } else if let Some(start_decay) = state.decay_start { + if start_date > start_decay { + return Err(invalid_dates( + "Decay", + start_decay.to_string().as_str(), + "before", + "StartDate", + start_date.to_string().as_str(), + )); + } + } state.start_date = start_date; } if let Some(end_date) = end_date { // Avoid date collisions + if let Some(decay_start) = decay_start { + if decay_start > end_date { + return Err(invalid_dates( + "EndDate", + end_date.to_string().as_str(), + "before", + "Decay", + decay_start.to_string().as_str(), + )); + } + } else if let Some(decay_start) = state.decay_start { + if decay_start > end_date { + return Err(invalid_dates( + "EndDate", + end_date.to_string().as_str(), + "before", + "Decay", + decay_start.to_string().as_str(), + )); + } + } + state.end_date = Some(end_date); } + if decay_start.is_some() { + state.decay_start = decay_start + } Ok(state) })?; @@ -115,26 +142,27 @@ pub fn try_account( deps: DepsMut, env: &Env, info: &MessageInfo, - eth_pubkey: String, addresses: Vec, + eth_pubkey: String, ) -> StdResult { - // 1. Check if airdrop active - // 2. Check that airdrop hasn't ended - // 3. Setup account by cosmos signer - // 4. These variables are setup to facilitate updating + // Check if airdrop active let config = config_r(deps.storage).load()?; + + // Check that airdrop hasn't ended available(&config, env)?; + + // Setup account let sender = info.sender.to_string(); + + // These variables are setup to facilitate updating let updating_account: bool; - // define the msg senders account - let mut account: Account = match account_r(deps.storage).may_load(sender.clone().as_bytes())? { + let mut account = match account_r(deps.storage).may_load(sender.as_bytes())? { None => { updating_account = false; - // setup a new account with addresses & eth_pubkey let mut account = Account::default(); - // this function will validate permits + // Validate permits try_add_account_addresses( deps.storage, deps.api, @@ -147,12 +175,11 @@ pub fn try_account( // we setup an unchecked eth_pubkey for now. We will verify this eth_pubkey during // the claim msg, and will update to verified eth_pubkey. - // sets the accounts eth_pubkey claim status to false. note we always check claim function when checking // the signed msg with the stored address, never both or isolated; // (to avoid contract panic, sender.clone is required for reading the account details, // prevents ability to determine if a eth_pubkey not yours has claimed)* - claim_status_w(deps.storage, 0).save(account.eth_pubkey.as_bytes(), &false)?; + claim_status_w(deps.storage, 0).save(sender.as_bytes(), &false)?; account } @@ -162,8 +189,7 @@ pub fn try_account( } }; - // Update account after claim to calculate difference, - // and to save eth_pubkey as saved to the contract state. + // Update account after claim to calculate difference if updating_account { // Validate permits try_add_account_addresses( @@ -172,8 +198,8 @@ pub fn try_account( &config, &info.sender, &mut account, - addresses, - eth_pubkey, + addresses.clone(), + eth_pubkey.clone(), )?; } @@ -183,7 +209,7 @@ pub fn try_account( Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Account { status: ResponseStatus::Success, claimed: eth_pubkey_claim_r(deps.storage) - .load(account.eth_pubkey.to_string().as_bytes())?, + .load(account.eth_pubkey.to_string().as_bytes())?, // Will always be 0 since rewards are automatically claimed here addresses: account.addresses, eth_pubkey: account.eth_pubkey, })?)) @@ -220,7 +246,6 @@ pub fn try_set_viewing_key( ) } -// claim an airdrop, privately. Must have created an account & signing keys prior pub fn try_claim( deps: DepsMut, env: &Env, @@ -230,15 +255,14 @@ pub fn try_claim( eth_sig: String, proof: Vec, ) -> StdResult { - let config: Config = config_r(deps.storage).load()?; + let config = config_r(deps.storage).load()?; // Check that airdrop hasn't ended available(&config, env)?; - // Get account from the msg sender, restricting access to query account - // via eth_pubkey, as well as verify eth_sig was generated with account.eth_pubkey + // Get account let sender = info.sender.clone(); - let account = account_r(deps.storage).load(sender.clone().as_bytes())?; + let account = account_r(deps.storage).load(sender.to_string().as_bytes())?; // validate eth_signature validation::validate_claim( @@ -271,37 +295,22 @@ pub fn try_claim( let mut root_buf: [u8; 32] = [0; 32]; decode_to_slice(merkle, &mut root_buf).unwrap(); ensure_eq!(root_buf, hash, failed_verification()); - // Claim airdrop - let mut messages = vec![]; - let mut redeem_amount = Uint128::zero(); - // check if eth_pubkey has already claimed - if account.claimed == false { - redeem_amount = claim_tokens(deps.storage, ð_pubkey, &amount)?; - } else { + + if account.claimed == true { return Err(nothing_to_claim()); } - // update global claimed amount + let redeem_amount = claim_tokens(deps.storage, env, info, &config, &account, amount)?; + total_claimed_w(deps.storage) .update(|claimed| -> StdResult { Ok(claimed + redeem_amount) })?; - messages.push(send_msg( - info.sender.clone(), - redeem_amount.into(), - None, - None, - None, - &config.airdrop_snip20, - )?); - - claim_status_w(deps.storage, 0).save(account.eth_pubkey.as_bytes(), &true)?; - Ok(Response::new() .set_data(to_binary(&ExecuteAnswer::Claim { status: ResponseStatus::Success, claimed: eth_pubkey_claim_r(deps.storage).load(eth_pubkey.as_bytes())?, addresses: account.addresses, - eth_pubkey, + eth_pubkey: account.eth_pubkey, })?) .add_message(send_msg( sender.clone(), @@ -310,14 +319,6 @@ pub fn try_claim( None, None, &config.airdrop_snip20, - )?) - .add_message(send_msg( - sender.clone(), - redeem_amount.into(), - None, - None, - None, - &config.airdrop_snip20_optional, )?)) } @@ -338,28 +339,14 @@ pub fn try_claim_decay(deps: DepsMut, env: &Env, _info: &MessageInfo) -> StdResu let total_claimed = total_claimed_r(deps.storage).load()?; let send_total = config.airdrop_amount.checked_sub(total_claimed)?; - let messages = vec![ - { - send_msg( - dump_address.clone(), - send_total.into(), - None, - None, - None, - &config.airdrop_snip20, - ) - }, - { - send_msg( - dump_address.clone(), - send_total.into(), - None, - None, - None, - &config.airdrop_snip20_optional, - ) - }, - ]; + let messages = vec![send_msg( + dump_address.clone(), + send_total.into(), + None, + None, + None, + &config.airdrop_snip20, + )?]; return Ok(Response::new() .set_data(to_binary(&ExecuteAnswer::ClaimDecay { status: Success })?)); @@ -372,18 +359,27 @@ pub fn try_claim_decay(deps: DepsMut, env: &Env, _info: &MessageInfo) -> StdResu pub fn claim_tokens( storage: &mut dyn Storage, - eth_pubkey: &String, - amount: &Uint128, + env: &Env, + info: &MessageInfo, + config: &Config, + account: &Account, + amount: Uint128, ) -> StdResult { - // Save the eth_pubkey to state, if eth_pubkey already exist, error on claim - eth_pubkey_claim_w(storage).update(eth_pubkey.as_bytes(), |claimed| { + // send_amount + let sender = account.eth_pubkey.to_string(); + + // Amount to be redeemed + + // Update total claimed and calculate claimable + eth_pubkey_claim_w(storage).update(sender.as_bytes(), |claimed| { if let Some(_claimed) = claimed { Err(already_claimed()) } else { Ok(true) } })?; - Ok(*amount) + + Ok(amount) } /// Validates all of the information and updates relevant states @@ -396,18 +392,28 @@ pub fn try_add_account_addresses( addresses: Vec, eth_pubkey: String, ) -> StdResult<()> { + // Setup the items to validate + let mut leaves_to_validate: Vec<(usize, [u8; 32])> = vec![]; // Iterate addresses for permit in addresses.iter() { if let Some(memo) = permit.memo.clone() { - // unwrap the permits, coming from the message memo let params: AddressProofMsg = from_binary(&Binary::from_base64(&memo)?)?; - // avoid verifying sender + + // Avoid verifying sender if ¶ms.address != sender { // Check permit legitimacy validate_address_permit(storage, api, permit, ¶ms, config.contract.clone())?; } + // Check that airdrop amount does not exceed maximum + if params.amount > config.max_amount { + return Err(claim_too_high( + params.amount.to_string().as_str(), + config.max_amount.to_string().as_str(), + )); + } + // Update address if its not in an account address_in_account_w(storage).update( params.address.to_string().as_bytes(), @@ -420,22 +426,8 @@ pub fn try_add_account_addresses( }, )?; - // Update eth_pubkey if its not in an account - // remember, this is unverified and checked when a eth_sig - // is provided, preventing unauthorized claim status queries - // - eth_pubkey_in_account_w(storage).update( - eth_pubkey.to_string().as_bytes(), - |state| -> StdResult { - if state.is_some() { - return Err(address_already_in_account(eth_pubkey.as_str())); - } - Ok(true) - }, - )?; // If valid then add to account array and sum total amount account.addresses.push(params.address); - account.eth_pubkey = eth_pubkey.clone(); } else { return Err(expected_memo()); } @@ -465,6 +457,17 @@ pub fn available(config: &Config, env: &Env) -> StdResult<()> { Ok(()) } +/// Get the multiplier for decay, will return 1 when decay isnt in effect. +pub fn decay_factor(current_time: u64, config: &Config) -> Decimal { + // Calculate redeem amount after applying decay + if let Some(decay_start) = config.decay_start { + if current_time >= decay_start { + return inverse_normalizer(decay_start, current_time, config.end_date.unwrap()); + } + } + Decimal::one() +} + /// Get the inverse normalized value [0,1] of x between [min, max] pub fn inverse_normalizer(min: u64, x: u64, max: u64) -> Decimal { Decimal::from_ratio(max - x, max - min) @@ -550,19 +553,3 @@ pub mod validation { Ok(()) } } - -// create viewing keys -pub fn try_create_viewing_key( - deps: DepsMut, - env: Env, - info: MessageInfo, - entropy: String, -) -> StdResult { - let seed = RngSeed::load(deps.storage)?.0; - - let key = Key::generate(&info, &env, seed.as_slice(), &entropy.as_ref()); - - HashedKey(key.hash()).save(deps.storage, info.sender)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::CreateViewingKey { key: key.0 })?)) -} diff --git a/contracts/airdrop/src/query.rs b/contracts/airdrop/src/query.rs index a867b07..58753c4 100644 --- a/contracts/airdrop/src/query.rs +++ b/contracts/airdrop/src/query.rs @@ -1,13 +1,8 @@ -use crate::state::{ - account_r, - // account_total_claimed_r, - account_viewkey_r, - claim_status_r, - config_r, - decay_claimed_r, - eth_pubkey_claim_r, - total_claimed_r, - validate_account_permit, +use crate::{ + handle::decay_factor, + state::{ + account_r, account_viewkey_r, config_r, decay_claimed_r, eth_pubkey_claim_r, total_claimed_r, validate_account_permit + }, }; use shade_protocol::{ airdrop::{ @@ -30,25 +25,33 @@ pub fn dates(deps: Deps, current_date: Option) -> StdResult { Ok(QueryAnswer::Dates { start: config.start_date, end: config.end_date, + decay_start: config.decay_start, + decay_factor: current_date.map(|date| Uint128::new(100u128) * decay_factor(date, &config)), }) } pub fn total_claimed(deps: Deps) -> StdResult { let claimed: Uint128; let total_claimed = total_claimed_r(deps.storage).load()?; - - claimed = total_claimed; + if decay_claimed_r(deps.storage).load()? { + claimed = total_claimed; + } else { + let config = config_r(deps.storage).load()?; + claimed = total_claimed.checked_div(config.query_rounding)? * config.query_rounding; + } Ok(QueryAnswer::TotalClaimed { claimed }) } -// returns account information for an eth_pubkey fn account_information( deps: Deps, account_address: Addr, - eth_pubkey: String, + current_date: Option, ) -> StdResult { let account = account_r(deps.storage).load(account_address.to_string().as_bytes())?; + // Calculate eligible tasks + let config = config_r(deps.storage).load()?; + // Check if eth address has claimed let claim_state = eth_pubkey_claim_r(deps.storage).may_load(account.eth_pubkey.as_bytes())?; @@ -59,12 +62,16 @@ fn account_information( }) } -pub fn account(deps: Deps, permit: AccountPermit, eth_pubkey: String) -> StdResult { +pub fn account( + deps: Deps, + permit: AccountPermit, + current_date: Option, +) -> StdResult { let config = config_r(deps.storage).load()?; account_information( deps, validate_account_permit(deps, &permit, config.contract)?, - eth_pubkey, + current_date, ) } @@ -72,7 +79,7 @@ pub fn account_with_key( deps: Deps, account: Addr, key: String, - eth_pubkey: String, + current_date: Option, ) -> StdResult { // Validate address let stored_hash = account_viewkey_r(deps.storage).load(account.to_string().as_bytes())?; @@ -81,5 +88,5 @@ pub fn account_with_key( return Err(invalid_viewing_key()); } - account_information(deps, account, eth_pubkey) + account_information(deps, account, current_date) } diff --git a/contracts/airdrop/src/state.rs b/contracts/airdrop/src/state.rs index 2c8177c..87ff062 100644 --- a/contracts/airdrop/src/state.rs +++ b/contracts/airdrop/src/state.rs @@ -1,42 +1,25 @@ +use shade_protocol::c_std::{Addr, Api, StdResult, Storage}; use shade_protocol::c_std::{Deps, Uint128}; -use shade_protocol::c_std::{ - Api, - Addr, - StdResult, - Storage, -}; -use shade_protocol::storage::{ - bucket, - bucket_read, - singleton, - singleton_read, - Bucket, - ReadonlyBucket, - ReadonlySingleton, - Singleton, -}; use shade_protocol::contract_interfaces::airdrop::{ account::{ - authenticate_ownership, - Account, - AccountPermit, - AddressProofMsg, - AddressProofPermit, + authenticate_ownership, Account, AccountPermit, AddressProofMsg, AddressProofPermit, }, errors::{permit_contract_mismatch, permit_key_revoked}, Config, }; +use shade_protocol::storage::{ + bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton, + Singleton, +}; pub static CONFIG_KEY: &[u8] = b"config"; pub static DECAY_CLAIMED_KEY: &[u8] = b"decay_claimed"; pub static CLAIM_STATUS_KEY: &[u8] = b"claim_status_"; pub static REWARD_IN_ACCOUNT_KEY: &[u8] = b"reward_in_account"; -// Save a list of eth_pubkeys to prevent double claims pub static ETH_PUBKEY_IN_ACCOUNT_KEY: &[u8] = b"eth_pubkey_in_account"; -pub static ETH_PUBKEY: &str = "eth_pubkeys"; +pub static ETH_PUBKEY: &str = "eth_pubkey"; pub static ACCOUNTS_KEY: &[u8] = b"accounts"; pub static TOTAL_CLAIMED_KEY: &[u8] = b"total_claimed"; -// pub static USER_TOTAL_CLAIMED_KEY: &[u8] = b"user_total_claimed"; pub static ACCOUNT_PERMIT_KEY: &str = "account_permit_key"; pub static ACCOUNT_VIEWING_KEY: &[u8] = b"account_viewing_key"; @@ -65,15 +48,6 @@ pub fn address_in_account_w(storage: &mut dyn Storage) -> Bucket { bucket(storage, REWARD_IN_ACCOUNT_KEY) } -// Is address added to an account -pub fn eth_pubkey_in_account_r(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, ETH_PUBKEY_IN_ACCOUNT_KEY) -} - -pub fn eth_pubkey_in_account_w(storage: &mut dyn Storage) -> Bucket { - bucket(storage, ETH_PUBKEY_IN_ACCOUNT_KEY) -} - // airdrop account pub fn account_r(storage: &dyn Storage) -> ReadonlyBucket { bucket_read(storage, ACCOUNTS_KEY) @@ -105,6 +79,15 @@ pub fn total_claimed_w(storage: &mut dyn Storage) -> Singleton { singleton(storage, TOTAL_CLAIMED_KEY) } +// Is address added to an account +pub fn eth_pubkey_in_account_r(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, ETH_PUBKEY_IN_ACCOUNT_KEY) +} + +pub fn eth_pubkey_in_account_w(storage: &mut dyn Storage) -> Bucket { + bucket(storage, ETH_PUBKEY_IN_ACCOUNT_KEY) +} + // Eth pubkey claimed pub fn eth_pubkey_claim_r(storage: &dyn Storage) -> ReadonlyBucket { let key = ETH_PUBKEY.to_string(); @@ -116,15 +99,6 @@ pub fn eth_pubkey_claim_w(storage: &mut dyn Storage) -> Bucket { bucket(storage, key.as_bytes()) } -// Total account claimed -// pub fn account_total_claimed_r(storage: &dyn Storage) -> ReadonlyBucket { -// bucket_read(storage, USER_TOTAL_CLAIMED_KEY) -// } - -// pub fn account_total_claimed_w(storage: &mut dyn Storage) -> Bucket { -// bucket(storage, USER_TOTAL_CLAIMED_KEY) -// } - // Account viewing key pub fn account_viewkey_r(storage: &dyn Storage) -> ReadonlyBucket<[u8; 32]> { bucket_read(storage, ACCOUNT_VIEWING_KEY) @@ -207,11 +181,7 @@ pub fn validate_account_permit( let address = permit.validate(deps.api, None)?.as_addr(None)?; // Check that permit is not revoked - if is_permit_revoked( - deps.storage, - address.to_string(), - permit.params.key.clone(), - )? { + if is_permit_revoked(deps.storage, address.to_string(), permit.params.key.clone())? { return Err(permit_key_revoked(permit.params.key.as_str())); } diff --git a/contracts/airdrop/src/test.rs b/contracts/airdrop/src/test.rs index 7e62ff6..af4835c 100644 --- a/contracts/airdrop/src/test.rs +++ b/contracts/airdrop/src/test.rs @@ -1,202 +1,305 @@ -// -// testing-mnemonic-1: indoor lend bachelor survey capital long venue name report fortune soldier course bring tiger unlock tree release swift van immune sketch attend upon rent -// testing-mnemonic-2: cement worth weekend silk aspect game balance speak warfare social swing permit engage expand ring soft fiscal exit race invest fragile brain drift economy -// -// -// #[cfg(test)] pub mod tests { - use crate::contract::{execute, query}; - use crate::{contract::instantiate, handle::inverse_normalizer}; - use shade_protocol::query_authentication::permit::Permit; + use crate::handle::inverse_normalizer; use shade_protocol::{ - airdrop::{ExecuteMsg, QueryMsg}, - c_std::testing::mock_dependencies_with_balance, - }; - - use shade_protocol::{ - airdrop::{ - account::{AddressProofMsg, AddressProofPermit, FillerMsg}, - InstantiateMsg, - }, - c_std::{ - from_binary, - testing::{mock_dependencies, mock_env, mock_info}, - Addr, Binary, Coin, Uint128, - }, + airdrop::account::{AddressProofMsg, AddressProofPermit, FillerMsg}, + c_std::{from_binary, testing::mock_dependencies, Addr, Binary, Uint128}, query_authentication::{ permit::bech32_to_canonical, transaction::{PermitSignature, PubKey}, }, - Contract, }; - const VIEWING_KEY: &str = "jUsTfOrTeStInG"; + #[test] + fn decay_factor() { + assert_eq!( + Uint128::new(50u128), + Uint128::new(100u128) * inverse_normalizer(100, 200, 300) + ); + + assert_eq!( + Uint128::new(25u128), + Uint128::new(100u128) * inverse_normalizer(0, 75, 100) + ); + } + + const MSGTYPE: &str = "wasm/MsgExecuteContract"; + + #[test] + fn terra_station_ledger() { + let mut permit = AddressProofPermit { + params: FillerMsg::default(), + chain_id: Some("columbus-5".to_string()), + sequence: None, + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64( + "Ar7aIv8k6Rm7ugLBAHShtRWmZ/CDgvwXYOc8Ffycwggc").unwrap()), + signature: Binary::from_base64( + "MM1UOheGCYX0Cb3r8zVhyZyWk/qIY61yqiDP53//31cjkd7G5FfEki+JC91kBRYCnt9NlI7gjnY8ZcJauDH3FA==").unwrap(), + }, + account_number: Some(Uint128::new(3441602u128).into()), + memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) + }; + + let deps = mock_dependencies(); + let addr = permit + .validate(&deps.api, Some(MSGTYPE.to_string())) + .expect("Signature validation failed"); + assert_eq!( + addr.as_canonical(), + bech32_to_canonical("terra17dhvxnwzazszgtuc498qsudh7zq945qh29gj4e") + ); + assert_ne!( + addr.as_canonical(), + bech32_to_canonical("terra19m2zgdyuq0crpww00jc2a9k70ut944dum53p7x") + ); + + permit.memo = Some("OtherMemo".to_string()); + + // NOTE: New SN broke unit testing + // assert!( + // permit + // .validate(&deps.api, Some("wasm/MsgExecuteContract".to_string())) + // .is_err() + // ) + } + + #[test] + fn terra_station_non_ledger() { + let mut permit = AddressProofPermit { + params: FillerMsg::default(), + chain_id: Some("columbus-5".to_string()), + sequence: None, + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64( + "A8r22cTiywZYSoWR5DnmAeP1jPDF3CLVKJe1QGorv9cM").unwrap()), + signature: Binary::from_base64( + "xhU2JkJDWO/eZEeJVp8vo1rNAK7H7G2uDucZAjAhfVRjLHHX7C+16dwQzr0Jmd2DdZHAJZNkGhGb5nucicN1TA==").unwrap(), + }, + account_number: None, + memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) + }; + + let deps = mock_dependencies(); + let addr = permit + .validate(&deps.api, Some(MSGTYPE.to_string())) + .expect("Signature validation failed"); + assert_eq!( + addr.as_canonical(), + bech32_to_canonical("terra1j8wupj3kpclp98dgg4j5am44kjykx6uztjttyr") + ); + assert_ne!( + addr.as_canonical(), + bech32_to_canonical("terra1ns69jhkjg5wmcgf8w8ecewnpca7sezyhvg0a29") + ); + + permit.memo = Some("OtherMemo".to_string()); + + // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) + } + + #[test] + fn keplr_terra_non_ledger() { + let mut permit = AddressProofPermit { + params: FillerMsg::default(), + chain_id: Some("columbus-5".to_string()), + sequence: None, + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64( + "AyGKvc3OCs/pg7unFCJgKjtqiLYRACeR4ZU0f8UVDFbM").unwrap()), + signature: Binary::from_base64( + "fbgFeYUsAjI2CB2dwaqttolFE1wx/3MXbNWYKicJj20mV3marS4zz+k5aCKsYlv4HSd9NYxl4deuhasMKndB2w==").unwrap(), + }, + account_number: None, + memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) + }; + + let deps = mock_dependencies(); + let addr = permit + .validate(&deps.api, Some(MSGTYPE.to_string())) + .expect("Signature validation failed"); + assert_eq!( + addr.as_canonical(), + bech32_to_canonical("terra18xg6g5yfzflnt8v45r2yndnydhg2vndvzsv3rn") + ); + assert_ne!( + addr.as_canonical(), + bech32_to_canonical("terra1ns69jhkjg5wmcgf8w8ecewnpca7sezyhvg0a29") + ); + + permit.memo = Some("OtherMemo".to_string()); + + // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) + } + + #[test] + fn keplr_terra_ledger() { + let mut permit = AddressProofPermit { + params: FillerMsg::default(), + chain_id: Some("columbus-5".to_string()), + sequence: None, + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64( + "AqjDFVbY+znM1F5XCDuaca0JT0uAdd3QyuHt04j9k0DB").unwrap()), + signature: Binary::from_base64( + "1kwDnlsltqgj8fqbohs0MMgEWiRMUmznM98ofOranBAe5f8Ja1tZCKmh5miPkgC6KoUdOam7BvBjuFhM1q0rBA==").unwrap(), + }, + account_number: None, + memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) + }; + + let deps = mock_dependencies(); + let addr = permit + .validate(&deps.api, Some(MSGTYPE.to_string())) + .expect("Signature validation failed"); + assert_eq!( + addr.as_canonical(), + bech32_to_canonical("terra1ns69jhkjg5wmcgf8w8ecewnpca7sezyhvg0a29") + ); + assert_ne!( + addr.as_canonical(), + bech32_to_canonical("terra18xg6g5yfzflnt8v45r2yndnydhg2vndvzsv3rn") + ); + + permit.memo = Some("OtherMemo".to_string()); + + // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) + } #[test] - fn proper_initialization() { - let mut deps = mock_dependencies(); - let info = mock_info( - "creator", - &[Coin { - denom: "earth".to_string(), - amount: Uint128::new(1000), - }], - ); - - let init_msg = InstantiateMsg { - admin: Some(Addr::unchecked("creator")), - dump_address: Some(Addr::unchecked("creator")), - airdrop_token: Contract::new( - &Addr::unchecked("secret-terps"), - &"xyx-code-hash".to_string(), - ), - airdrop_2: Contract::new( - &Addr::unchecked("secret-thiol"), - &"xyz-code-hash".to_string(), - ), - airdrop_amount: Uint128::new(43476089028199), - start_date: None, - end_date: None, - decay_start: None, - merkle_root: "77fb25152b72ac67f5a155461e396b0788dd0567ec32a96f8201b899ad516b02" - .to_string(), - total_accounts: 12580u32, - claim_msg_plaintext: "{wallet}".to_string(), + fn keplr_sn_non_ledger() { + let mut permit = AddressProofPermit { + params: FillerMsg::default(), + chain_id: Some("secret-4".to_string()), + sequence: None, + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64( + "A2uZZ02iy/QhPZ0s6WO8HTEfNZEnt5o5PsQ34WHmQFPK").unwrap()), + signature: Binary::from_base64( + "s80mH5OuZCudS20d0k73evWx5xGrC2l3uubQjIkukT4L5mcgsepDIq9d1YpAJwiUEitaHFOGy42MfHZJVY1LdA==").unwrap(), + }, + account_number: None, + memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) }; - let res = instantiate(deps.as_mut(), mock_env(), info, init_msg).unwrap(); - assert_eq!(0, res.messages.len()); + let deps = mock_dependencies(); + let addr = permit + .validate(&deps.api, Some(MSGTYPE.to_string())) + .expect("Signature validation failed"); + assert_eq!( + addr.as_canonical(), + bech32_to_canonical("secret19q7h2zy8mgesy3r39el5fcm986nxqjd7cgylrz") + ); + assert_ne!( + addr.as_canonical(), + bech32_to_canonical("secret1ns69jhkjg5wmcgf8w8ecewnpca7sezyhgfp54e") + ); + + permit.memo = Some("OtherMemo".to_string()); + + // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) + } + + #[test] + fn keplr_sn_ledger() { + let mut permit = AddressProofPermit { + params: FillerMsg::default(), + chain_id: Some("secret-4".to_string()), + sequence: None, + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64( + "AqjDFVbY+znM1F5XCDuaca0JT0uAdd3QyuHt04j9k0DB").unwrap()), + signature: Binary::from_base64( + "snd8k5nWAAVoUytxKZt1FCUNQNXLAQpBlF7h4YGbTmx3S5+rqaZnM2bKq1ifCvErz/pdeE7B/s+WsGLdQRpzoA==").unwrap(), + }, + account_number: None, + memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) + }; + + let deps = mock_dependencies(); + let addr = permit + .validate(&deps.api, Some(MSGTYPE.to_string())) + .expect("Signature validation failed"); + assert_eq!( + addr.as_canonical(), + bech32_to_canonical("secret1ns69jhkjg5wmcgf8w8ecewnpca7sezyhgfp54e") + ); + assert_ne!( + addr.as_canonical(), + bech32_to_canonical("secret19q7h2zy8mgesy3r39el5fcm986nxqjd7cgylrz") + ); + + permit.memo = Some("OtherMemo".to_string()); + + // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) } - mod account { - use super::*; - use shade_protocol::{airdrop::account::EmptyMsg, c_std::to_binary}; - - #[test] - fn set_viewing_key() { - let mut deps = mock_dependencies_with_balance(&[Coin { - denom: "earth".to_string(), - amount: Uint128::new(1000), - }]); - let info = mock_info( - "creator", - &[Coin { - denom: "earth".to_string(), - amount: Uint128::new(1000), - }], - ); - - let init_msg = InstantiateMsg { - admin: Some(Addr::unchecked("creator")), - dump_address: Some(Addr::unchecked("creator")), - airdrop_token: Contract::new( - &Addr::unchecked("secret-terps"), - &"xyx-code-hash".to_string(), - ), - airdrop_2: Contract::new( - &Addr::unchecked("secret-thiol"), - &"xyz-code-hash".to_string(), - ), - airdrop_amount: Uint128::new(420), - start_date: None, - end_date: None, - decay_start: None, - merkle_root: "77fb25152b72ac67f5a155461e396b0788dd0567ec32a96f8201b899ad516b02" - .to_string(), - total_accounts: 12580u32, - claim_msg_plaintext: "{wallet}".to_string(), - }; - - let res = instantiate(deps.as_mut(), mock_env(), info, init_msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // anyone can setup an account - let info = mock_info( - "anyone", - &[Coin { - denom: "token".to_string(), - amount: Uint128::new(420), - }], - ); - // test setting a viewing key - let exec_msg = ExecuteMsg::SetViewingKey { - key: VIEWING_KEY.into(), - padding: None, - }; - let res = execute(deps.as_mut(), mock_env(), info.clone(), exec_msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // test disabling the permit key - let exec_msg = ExecuteMsg::DisablePermitKey { - key: VIEWING_KEY.into(), - padding: None, - }; - let res = execute(deps.as_mut(), mock_env(), info, exec_msg).unwrap(); - assert_eq!(0, res.messages.len()); - } - - #[test] - fn claim_headstash() { - let mut deps = mock_dependencies_with_balance(&[Coin { - denom: "earth".to_string(), - amount: Uint128::new(1000), - }]); - let info = mock_info( - "creator", - &[Coin { - denom: "earth".to_string(), - amount: Uint128::new(1000), - }], - ); - let init_msg = InstantiateMsg { - admin: Some(Addr::unchecked("creator")), - dump_address: Some(Addr::unchecked("creator")), - airdrop_token: Contract::new( - &Addr::unchecked("secret-terps"), - &"xyx-code-hash".to_string(), - ), - airdrop_2: Contract::new( - &Addr::unchecked("secret-thiol"), - &"xyz-code-hash".to_string(), - ), - airdrop_amount: Uint128::from(840u128), - start_date: None, - end_date: None, - decay_start: None, - merkle_root: "d599867bdb2ade1e470d9ec9456490adcd9da6e0cfd8f515e2b95d345a5cd92f" - .to_string(), - total_accounts: 2u32, - claim_msg_plaintext: "{wallet}".to_string(), - }; - let res = instantiate(deps.as_mut(), mock_env(), info, init_msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // anyone can setup an account - let info = mock_info( - "anyone", - &[Coin { - denom: "token".to_string(), - amount: Uint128::new(420), - }], - ); - // test setting a viewing key - let exec_msg = ExecuteMsg::SetViewingKey { - key: VIEWING_KEY.into(), - padding: None, - }; - let res = execute(deps.as_mut(), mock_env(), info.clone(), exec_msg).unwrap(); - assert_eq!(0, res.messages.len()); - - - } - - #[test] - fn create_account() {} + #[test] + fn keplr_cosmos_non_ledger() { + let mut permit = AddressProofPermit { + params: FillerMsg::default(), + chain_id: Some("cosmoshub-4".to_string()), + sequence: None, + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64( + "AqcyBLqPn7QnOctkK9i9KhnhD0aHA03+LppvNTCdZ1wK").unwrap()), + signature: Binary::from_base64( + "KLwotev7wnbj2VGBxbyTfIrRn/1vQY3x3I7BAUhu4FIC6OHVXqxIl/lclgdBWksnr32ULVfz8u78OqEbaePRZQ==").unwrap(), + }, + account_number: None, + memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) + }; + + let deps = mock_dependencies(); + let addr = permit + .validate(&deps.api, Some(MSGTYPE.to_string())) + .expect("Signature validation failed"); + assert_eq!( + addr.as_canonical(), + bech32_to_canonical("cosmos1lj5vh5y8yp4a97jmfwpd98lsg0tf5lsqgnnhq3") + ); + assert_ne!( + addr.as_canonical(), + bech32_to_canonical("cosmos1ns69jhkjg5wmcgf8w8ecewnpca7sezyh2v4ag9") + ); + + permit.memo = Some("OtherMemo".to_string()); + + // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) } + #[test] - fn test_s_i_e_q() { + fn keplr_cosmos_ledger() { + let mut permit = AddressProofPermit { + params: FillerMsg::default(), + chain_id: Some("cosmoshub-4".to_string()), + sequence: None, + signature: PermitSignature { + pub_key: PubKey::new(Binary::from_base64( + "AqjDFVbY+znM1F5XCDuaca0JT0uAdd3QyuHt04j9k0DB").unwrap()), + signature: Binary::from_base64( + "h/RpG1eKzN03oId0GvN7TSxoHOUibjmqPEQ1E+ZWh+BvghPL99lBj4L3BKpjjsaRtXX3lexO7ztafLKBVtq4xA==").unwrap(), + }, + account_number: None, + memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) + }; + let deps = mock_dependencies(); + let addr = permit + .validate(&deps.api, Some(MSGTYPE.to_string())) + .expect("Signature validation failed"); + assert_eq!( + addr.as_canonical(), + bech32_to_canonical("cosmos1ns69jhkjg5wmcgf8w8ecewnpca7sezyh2v4ag9") + ); + assert_ne!( + addr.as_canonical(), + bech32_to_canonical("cosmos1lj5vh5y8yp4a97jmfwpd98lsg0tf5lsqgnnhq3") + ); + + permit.memo = Some("OtherMemo".to_string()); + + // assert!(permit.validate(&deps.api, Some(MSGTYPE.to_string())).is_err()) } #[test] @@ -218,11 +321,26 @@ pub mod tests { } #[test] - fn claim_query() {} + fn claim_query() { + assert_eq!( + Uint128::new(300u128), + (Uint128::new(345u128) / Uint128::new(100u128)) * Uint128::new(100u128) + ) + } #[test] - fn claim_query_odd_multiple() {} + fn claim_query_odd_multiple() { + assert_eq!( + Uint128::new(13475u128), + (Uint128::new(13480u128) / Uint128::new(7u128)) * Uint128::new(7u128) + ) + } #[test] - fn claim_query_under_step() {} + fn claim_query_under_step() { + assert_eq!( + Uint128::zero(), + (Uint128::new(200u128) / Uint128::new(1000u128)) * Uint128::new(1000u128) + ) + } } diff --git a/packages/multi_test/Cargo.toml b/packages/multi_test/Cargo.toml index 17f9ced..c16b591 100644 --- a/packages/multi_test/Cargo.toml +++ b/packages/multi_test/Cargo.toml @@ -15,7 +15,6 @@ airdrop = ["dep:airdrop"] [dependencies] airdrop = { path = "../../contracts/airdrop", optional = true } - shade-protocol = { path = "../shade_protocol", features = ["multi-test"] } [target.'cfg(not(target_arch="wasm32"))'.dependencies] diff --git a/packages/multi_test/src/interfaces/mod.rs b/packages/multi_test/src/interfaces/mod.rs index 1558805..098e0e4 100644 --- a/packages/multi_test/src/interfaces/mod.rs +++ b/packages/multi_test/src/interfaces/mod.rs @@ -1,19 +1,4 @@ -// #[cfg(feature = "dao")] -// pub mod dao; -// /* -// #[cfg(feature = "dao")] -// pub mod manager; -// #[cfg(feature = "dao")] -// pub mod adapter; -// */ -// #[cfg(feature = "snip20")] -// pub mod snip20; -// #[cfg(feature = "treasury")] -// pub mod treasury; -// #[cfg(feature = "treasury_manager")] -// pub mod treasury_manager; - -// #[cfg(feature = "scrt_staking")] -// pub mod scrt_staking; +#[cfg(feature = "snip20")] +pub mod snip20; pub mod utils; diff --git a/packages/multi_test/src/interfaces/snip20.rs b/packages/multi_test/src/interfaces/snip20.rs new file mode 100644 index 0000000..63bf6bd --- /dev/null +++ b/packages/multi_test/src/interfaces/snip20.rs @@ -0,0 +1,186 @@ +use crate::{ + interfaces::utils::{DeployedContracts, SupportedContracts}, + multi::snip20::Snip20, +}; +use shade_protocol::{ + c_std::{Addr, Binary, Coin, StdError, StdResult, Uint128}, + contract_interfaces::snip20, + multi_test::App, + utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +pub fn init( + chain: &mut App, + sender: &str, + contracts: &mut DeployedContracts, + name: &str, + snip20_symbol: &str, + decimals: u8, + config: Option, +) -> StdResult<()> { + let snip20 = Contract::from( + match (snip20::InstantiateMsg { + name: name.to_string(), + admin: Some(sender.into()), + symbol: snip20_symbol.to_string(), + decimals, + initial_balances: Some(vec![snip20::InitialBalance { + address: sender.into(), + amount: Uint128::from(1_000_000_000 * 10 ^ decimals as u128), + }]), + prng_seed: Binary::default(), + query_auth: None, + config, + } + .test_init( + Snip20::default(), + chain, + Addr::unchecked(sender), + "snip20", + &[], + )) { + Ok(contract_info) => contract_info, + Err(e) => return Err(StdError::generic_err(e.to_string())), + }, + ); + contracts.insert( + SupportedContracts::Snip20(snip20_symbol.to_string()), + snip20, + ); + Ok(()) +} + +pub fn deposit_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + coins: &Vec, +) -> StdResult<()> { + match (snip20::ExecuteMsg::Deposit { padding: None }.test_exec( + &contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + coins, + )) { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(e.to_string())), + } +} + +pub fn set_viewing_key_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + key: String, +) -> StdResult<()> { + match (snip20::ExecuteMsg::SetViewingKey { key, padding: None }.test_exec( + &contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(e.to_string())), + } +} + +pub fn send_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + recipient: String, + amount: Uint128, + msg: Option, +) -> StdResult<()> { + match (snip20::ExecuteMsg::Send { + recipient, + amount, + msg, + memo: None, + padding: None, + recipient_code_hash: None, + } + .test_exec( + &contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(_) => Err(StdError::generic_err("snip20 send failed")), + } +} + +pub fn send_from_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + owner: String, + recipient: String, + amount: Uint128, + msg: Option, +) -> StdResult<()> { + match (snip20::ExecuteMsg::SendFrom { + owner, + recipient, + amount, + msg, + memo: None, + padding: None, + recipient_code_hash: None, + } + .test_exec( + &contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(_) => Err(StdError::generic_err("snip20 send failed")), + } +} + +pub fn balance_query( + chain: &App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + key: String, +) -> StdResult { + let res = snip20::QueryMsg::Balance { + address: sender.to_string(), + key, + } + .test_query( + &contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .into(), + chain, + )?; + match res { + snip20::QueryAnswer::Balance { amount } => Ok(amount), + _ => Err(StdError::generic_err("SetViewingKey failed")), + } +} diff --git a/packages/multi_test/src/interfaces/utils.rs b/packages/multi_test/src/interfaces/utils.rs index 0c40210..e1b9946 100644 --- a/packages/multi_test/src/interfaces/utils.rs +++ b/packages/multi_test/src/interfaces/utils.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; #[derive(Clone, Eq, PartialEq, Hash)] pub enum SupportedContracts { - MockAdapter(usize), + Snip20(String), } pub type DeployedContracts = HashMap; diff --git a/packages/multi_test/src/lib.rs b/packages/multi_test/src/lib.rs index c5ccbf5..51a504c 100644 --- a/packages/multi_test/src/lib.rs +++ b/packages/multi_test/src/lib.rs @@ -1,5 +1,4 @@ #[cfg(not(target_arch = "wasm32"))] pub mod multi; -// pub mod headstash; pub mod interfaces; diff --git a/packages/multi_test/src/multi.rs b/packages/multi_test/src/multi.rs index edb0bc6..9296174 100644 --- a/packages/multi_test/src/multi.rs +++ b/packages/multi_test/src/multi.rs @@ -20,16 +20,121 @@ pub mod snip20 { multi_derive::implement_multi!(Snip20, snip20); } -#[cfg(feature = "airdrop")] -pub mod airdrop { - pub use airdrop; - use shade_protocol::{airdrop::InstantiateMsg, multi_test::App, utils::InstantiateCallback}; - multi_derive::implement_multi!(Airdrop, airdrop); +#[cfg(feature = "liability_mint")] +pub mod liability_mint { + use liability_mint; + multi_derive::implement_multi!(LiabilityMint, liability_mint); } +#[cfg(feature = "stkd_scrt")] +pub mod stkd_scrt { + use stkd_scrt; + multi_derive::implement_multi!(StkdScrt, stkd_scrt); +} + +// #[cfg(feature = "mint")] +// pub mod mint { +// use mint; +// multi_derive::implement_multi!(Mint, mint); +// } + +// #[cfg(feature = "oracle")] +// pub mod oracle { +// use oracle; +// multi_derive::implement_multi!(Oracle, oracle); +// } + +// #[cfg(feature = "mock_band")] +// pub mod mock_band { +// use crate::multi_derive; +// use mock_band; + +// pub struct MockBand; +// multi_derive::implement_multi!(MockBand, mock_band); +// } + +#[cfg(feature = "governance")] +pub mod governance { + use governance; + + multi_derive::implement_multi_with_reply!(Governance, governance); +} + +// #[cfg(feature = "snip20_staking")] +// pub mod snip20_staking { +// use spip_stkd_0; +// +// multi_derive::implement_multi!(Snip20Staking, spip_stkd_0); +// } + +// #[cfg(feature = "bonds")] +// pub mod bonds { +// use crate::multi_derive; +// use bonds; + +// pub struct Bonds; +// multi_derive::implement_multi!(Bonds, bonds); +// } + #[cfg(feature = "query_auth")] pub mod query_auth { use query_auth; multi_derive::implement_multi!(QueryAuth, query_auth); } + +#[cfg(feature = "treasury_manager")] +pub mod treasury_manager { + use treasury_manager; + multi_derive::implement_multi!(TreasuryManager, treasury_manager); +} + +#[cfg(feature = "treasury")] +pub mod treasury { + use treasury; + multi_derive::implement_multi!(Treasury, treasury); +} + +#[cfg(feature = "mock_adapter")] +pub mod mock_adapter { + use mock_adapter; + multi_derive::implement_multi!(MockAdapter, mock_adapter); +} + +#[cfg(feature = "scrt_staking")] +pub mod scrt_staking { + use scrt_staking; + multi_derive::implement_multi!(ScrtStaking, scrt_staking); +} + +#[cfg(feature = "basic_staking")] +pub mod basic_staking { + use basic_staking; + multi_derive::implement_multi!(BasicStaking, basic_staking); +} + +#[cfg(feature = "peg_stability")] +pub mod peg_stability { + use peg_stability; + + multi_derive::implement_multi!(PegStability, peg_stability); +} + +#[cfg(feature = "mock_stkd")] +pub mod mock_stkd { + pub use mock_stkd; + multi_derive::implement_multi!(MockStkd, mock_stkd); +} + +#[cfg(feature = "mock_sienna")] +pub mod mock_sienna { + pub use mock_sienna; + multi_derive::implement_multi!(MockSienna, mock_sienna); +} + +#[cfg(feature = "snip20_migration")] +pub mod snip20_migration { + use snip20_migration; + + multi_derive::implement_multi!(Snip20Migration, snip20_migration); +} diff --git a/packages/shade_protocol/Cargo.toml b/packages/shade_protocol/Cargo.toml index dfec136..9e307df 100644 --- a/packages/shade_protocol/Cargo.toml +++ b/packages/shade_protocol/Cargo.toml @@ -14,7 +14,8 @@ name = "schemas" path = "src/schemas.rs" # Must have all of the contract_interfaces required-features = [ - "airdrop", + "admin", "airdrop", "bonds", "dao", "dex", "governance-impl", "mint", "oracles", + "peg_stability", "query_auth", "sky", "snip20", "staking", "mint_router", "snip20_migration", ] [lib] diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs index 88680e1..f08e294 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs @@ -1,12 +1,12 @@ -use crate::c_std::Uint128; -use crate::c_std::{Addr, Api, StdResult}; use crate::contract_interfaces::airdrop::errors::permit_rejected; +use crate::c_std::Uint128; +use crate::c_std::{Addr, StdResult, Api}; use crate::query_authentication::{ permit::{bech32_to_canonical, Permit}, viewing_keys::ViewingKey, }; -use cosmwasm_schema::cw_serde; +use cosmwasm_schema::{cw_serde}; #[cw_serde] pub struct Account { @@ -40,7 +40,6 @@ pub struct AccountPermitMsg { pub struct FillerMsg { pub coins: Vec, pub contract: String, - pub eth_pubkey: String, pub execute_msg: EmptyMsg, pub sender: String, } @@ -52,7 +51,6 @@ impl Default for FillerMsg { contract: "".to_string(), sender: "".to_string(), execute_msg: EmptyMsg {}, - eth_pubkey: "".to_string(), } } } @@ -64,11 +62,7 @@ pub struct EmptyMsg {} // Used to prove ownership over IBC addresses pub type AddressProofPermit = Permit; -pub fn authenticate_ownership( - api: &dyn Api, - permit: &AddressProofPermit, - permit_address: &str, -) -> StdResult<()> { +pub fn authenticate_ownership(api: &dyn Api, permit: &AddressProofPermit, permit_address: &str) -> StdResult<()> { let signer_address = permit .validate(api, Some("wasm/MsgExecuteContract".to_string()))? .as_canonical(); diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/claim_info.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/claim_info.rs index 30becec..64e05a1 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/claim_info.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/claim_info.rs @@ -1,8 +1,8 @@ -// use crate::c_std::{Uint128, Addr}; -// use cosmwasm_schema::{cw_serde}; +use crate::c_std::{Uint128, Addr}; +use cosmwasm_schema::{cw_serde}; -// #[cw_serde] -// pub struct RequiredTask { -// pub address: Addr, -// pub percent: Uint128, -// } +#[cw_serde] +pub struct RequiredTask { + pub address: Addr, + pub percent: Uint128, +} diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/errors.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/errors.rs index 4ec6133..1e2f423 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/errors.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/errors.rs @@ -15,7 +15,6 @@ pub enum Error { PermitKeyRevoked, PermitRejected, NotAdmin, - AlreadyClaimed, AccountAlreadyCreated, AccountDoesntExist, NothingToClaim, @@ -31,6 +30,7 @@ pub enum Error { UnexpectedError, WrongLength, FailedVerification, + AlreadyClaimed, } impl_into_u8!(Error); @@ -43,14 +43,13 @@ impl CodeType for Error { } Error::InvalidDates => build_string("{} ({}) cannot happen {} {} ({})", context), Error::PermitContractMismatch => { - build_string("Permit is not valid for {}, expected {}", context) + build_string("Permit is valid for {}, expected {}", context) } Error::PermitKeyRevoked => build_string("Permit key {} revoked", context), Error::PermitRejected => build_string("Permit was rejected", context), Error::NotAdmin => build_string("Can only be accessed by {}", context), Error::AccountAlreadyCreated => build_string("Account already exists", context), Error::AccountDoesntExist => build_string("Account does not exist", context), - Error::AlreadyClaimed => build_string("Already claimed", context), Error::NothingToClaim => build_string("Amount to claim is 0", context), Error::DecayClaimed => build_string("Decay already claimed", context), Error::NoDecaySet => build_string("Decay has not been set", context), @@ -66,8 +65,9 @@ impl CodeType for Error { Error::AirdropEnded => build_string("Airdrop ended on {}, its currently {}", context), Error::InvalidViewingKey => build_string("Provided viewing key is invalid", context), Error::UnexpectedError => build_string("Something unexpected happened", context), - Error::WrongLength => build_string("Wrong Length", context), - Error::FailedVerification => build_string("Verification Failed", context), + Error::WrongLength => build_string("Wrong length", context), + Error::FailedVerification => build_string("Failed to verify merkle proofs", context), + Error::AlreadyClaimed => build_string("Already claimed", context), } } } @@ -75,11 +75,9 @@ impl CodeType for Error { const AIRDROP_TARGET: &str = "airdrop"; pub fn invalid_task_percentage(percentage: &str) -> StdError { - DetailedError::from_code( - AIRDROP_TARGET, - Error::InvalidTaskPercentage, - vec![percentage], - ) + DetailedError::from_code(AIRDROP_TARGET, Error::InvalidTaskPercentage, vec![ + percentage, + ]) .to_error() } @@ -90,20 +88,20 @@ pub fn invalid_dates( item_b: &str, item_b_amount: &str, ) -> StdError { - DetailedError::from_code( - AIRDROP_TARGET, - Error::InvalidDates, - vec![item_a, item_a_amount, precedence, item_b, item_b_amount], - ) + DetailedError::from_code(AIRDROP_TARGET, Error::InvalidDates, vec![ + item_a, + item_a_amount, + precedence, + item_b, + item_b_amount, + ]) .to_error() } pub fn permit_contract_mismatch(contract: &str, expected: &str) -> StdError { - DetailedError::from_code( - AIRDROP_TARGET, - Error::PermitContractMismatch, - vec![contract, expected], - ) + DetailedError::from_code(AIRDROP_TARGET, Error::PermitContractMismatch, vec![ + contract, expected, + ]) .to_error() } @@ -151,13 +149,14 @@ pub fn expected_memo() -> StdError { DetailedError::from_code(AIRDROP_TARGET, Error::ExpectedMemo, vec![]).to_error() } +pub fn invalid_partial_tree() -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::InvalidPartialTree, vec![]).to_error() +} pub fn airdrop_not_started(start: &str, current: &str) -> StdError { - DetailedError::from_code( - AIRDROP_TARGET, - Error::AirdropNotStarted, - vec![start, current], - ) + DetailedError::from_code(AIRDROP_TARGET, Error::AirdropNotStarted, vec![ + start, current, + ]) .to_error() } @@ -172,14 +171,12 @@ pub fn invalid_viewing_key() -> StdError { pub fn unexpected_error() -> StdError { DetailedError::from_code(AIRDROP_TARGET, Error::UnexpectedError, vec![]).to_error() } - -pub fn already_claimed() -> StdError { - DetailedError::from_code(AIRDROP_TARGET, Error::AlreadyClaimed, vec![]).to_error() -} - pub fn wrong_length() -> StdError { - DetailedError::from_code(AIRDROP_TARGET, Error::ExpectedMemo, vec![]).to_error() + DetailedError::from_code(AIRDROP_TARGET, Error::WrongLength, vec![]).to_error() } pub fn failed_verification() -> StdError { - DetailedError::from_code(AIRDROP_TARGET, Error::ExpectedMemo, vec![]).to_error() + DetailedError::from_code(AIRDROP_TARGET, Error::FailedVerification, vec![]).to_error() } +pub fn already_claimed() -> StdError { + DetailedError::from_code(AIRDROP_TARGET, Error::AlreadyClaimed, vec![]).to_error() +} \ No newline at end of file diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs index 907d6ba..71b86ce 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs @@ -4,7 +4,10 @@ pub mod errors; use crate::{ c_std::{Addr, Binary, Uint128}, - contract_interfaces::airdrop::account::{AccountPermit, AddressProofPermit}, + contract_interfaces::airdrop::{ + account::{AccountPermit, AddressProofPermit}, + claim_info::RequiredTask, + }, utils::{asset::Contract, generic_response::ResponseStatus}, }; @@ -18,10 +21,10 @@ pub struct Config { pub contract: Addr, // Where the decayed tokens will be dumped, if none then nothing happens pub dump_address: Option, - // The snip20 to be minted + // The snip20 to be mint, pub airdrop_snip20: Contract, // An optional, second snip20 to be minted - pub airdrop_snip20_optional: Contract, + pub airdrop_snip20_optional: Option, // Airdrop amount pub airdrop_amount: Uint128, // Required tasks @@ -39,7 +42,10 @@ pub struct Config { pub total_accounts: u32, // {wallet} pub claim_msg_plaintext: String, + // max possible reward amount; used to prevent collision possibility + pub max_amount: Uint128, // Protects from leaking user information by limiting amount detail + pub query_rounding: Uint128, } #[cw_serde] @@ -50,7 +56,7 @@ pub struct InstantiateMsg { // primary scrt-20 contract being distributed pub airdrop_token: Contract, // an optional, second snip20 to be minted - pub airdrop_2: Contract, + pub airdrop_2: Option, // total amount of airdrop pub airdrop_amount: Uint128, // The airdrop time limit @@ -59,6 +65,8 @@ pub struct InstantiateMsg { pub end_date: Option, // Starts to decay at this date pub decay_start: Option, + // max possible reward amount; used to prevent collision possibility + pub max_amount: Uint128, // Base64 encoded version of the tree root pub merkle_root: String, // Root height @@ -66,6 +74,7 @@ pub struct InstantiateMsg { /// {wallet} pub claim_msg_plaintext: String, // Protects from leaking user information by limiting amount detail + pub query_rounding: Uint128, } impl InstantiateCallback for InstantiateMsg { @@ -77,20 +86,15 @@ pub enum ExecuteMsg { UpdateConfig { admin: Option, dump_address: Option, + query_rounding: Option, start_date: Option, end_date: Option, decay_start: Option, padding: Option, }, - /// * creates or updates an account. - /// * use msg.sender addr as key to search if an account exist. - /// * Stores an unverified eth_pubkey that will be used to verify - /// ownership of eth_sig provided when claiming headstash. - /// * Account { - eth_pubkey: String, - amount: Option, addresses: Vec, + eth_pubkey: String, padding: Option, }, DisablePermitKey { @@ -111,6 +115,9 @@ pub enum ExecuteMsg { ClaimDecay { padding: Option, }, + // CreateViewingKey { + // key: String, + // }, } impl ExecuteCallback for ExecuteMsg { @@ -134,6 +141,7 @@ pub enum ExecuteAnswer { SetViewingKey { status: ResponseStatus, }, + Claim { status: ResponseStatus, claimed: bool, @@ -143,7 +151,6 @@ pub enum ExecuteAnswer { ClaimDecay { status: ResponseStatus, }, - CreateViewingKey { key: String }, } #[cw_serde] @@ -155,12 +162,12 @@ pub enum QueryMsg { TotalClaimed {}, Account { permit: AccountPermit, - eth_pubkey: String, + current_date: Option, }, AccountWithKey { account: Addr, key: String, - eth_pubkey: String, + current_date: Option, }, } @@ -176,6 +183,8 @@ pub enum QueryAnswer { Dates { start: u64, end: Option, + decay_start: Option, + decay_factor: Option, }, TotalClaimed { claimed: Uint128, diff --git a/packages/shade_protocol/src/contract_interfaces/basic_staking/mod.rs b/packages/shade_protocol/src/contract_interfaces/basic_staking/mod.rs deleted file mode 100644 index 0a0b363..0000000 --- a/packages/shade_protocol/src/contract_interfaces/basic_staking/mod.rs +++ /dev/null @@ -1,299 +0,0 @@ -use crate::{ - c_std::{Addr, Binary, Decimal, Uint128}, - query_auth::{ - helpers::{authenticate_permit, authenticate_vk, PermitAuthentication}, - QueryPermit, - }, - utils::{ - asset::{Contract, RawContract}, - generic_response::ResponseStatus, - }, -}; - -use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; -use cosmwasm_schema::cw_serde; - -#[cw_serde] -pub struct Config { - pub admin_auth: Contract, - pub query_auth: Contract, - pub airdrop: Option, - pub unbond_period: Uint128, - // Number of non-admin pools allowed - pub max_user_pools: Uint128, -} - -#[cw_serde] -pub struct StakingInfo { - pub stake_token: Addr, - pub total_staked: Uint128, - pub unbond_period: Uint128, - pub reward_pools: Vec, -} - -// For the Snip20 msg field -#[cw_serde] -pub enum Action { - // Deposit rewards to be distributed - Stake { - compound: Option, - airdrop_task: Option, - }, - Rewards { - start: Uint128, - end: Uint128, - }, -} - -#[cw_serde] -pub struct Unbonding { - pub id: Uint128, - pub amount: Uint128, - pub complete: Uint128, -} - -#[cw_serde] -pub struct Reward { - pub token: Contract, - pub amount: Uint128, -} - -// Internal storage -#[cw_serde] -pub struct RewardPoolInternal { - pub id: Uint128, - pub amount: Uint128, - pub start: Uint128, - pub end: Uint128, - pub token: Contract, - pub rate: Uint128, - pub reward_per_token: Uint128, - pub claimed: Uint128, - pub last_update: Uint128, - pub creator: Addr, - pub official: bool, -} - -// Query returned data -#[cw_serde] -pub struct RewardPool { - pub id: Uint128, - pub amount: Uint128, - pub start: Uint128, - pub end: Uint128, - pub token: Contract, - pub rate: Uint128, - pub official: bool, -} - -#[cw_serde] -pub struct InstantiateMsg { - pub admin_auth: RawContract, - pub query_auth: RawContract, - pub airdrop: Option, - pub stake_token: RawContract, - pub unbond_period: Uint128, - pub max_user_pools: Uint128, - pub viewing_key: String, -} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteMsg { - UpdateConfig { - admin_auth: Option, - query_auth: Option, - airdrop: Option, - unbond_period: Option, - max_user_pools: Option, - padding: Option, - }, - RegisterRewards { - token: RawContract, - padding: Option, - }, - Receive { - sender: Addr, - from: Addr, - amount: Uint128, - memo: Option, - msg: Option, - }, - Unbond { - amount: Uint128, - compound: Option, - padding: Option, - }, - Withdraw { - ids: Option>, - padding: Option, - }, - Claim { - padding: Option, - }, - Compound { - padding: Option, - }, - EndRewardPool { - id: Uint128, - force: Option, - padding: Option, - }, - AddTransferWhitelist { - user: String, - padding: Option, - }, - RemoveTransferWhitelist { - user: String, - padding: Option, - }, - TransferStake { - amount: Uint128, - recipient: String, - compound: Option, - padding: Option, - }, -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteAnswer { - Init { - status: ResponseStatus, - address: Addr, - }, - UpdateConfig { - status: ResponseStatus, - }, - // Receive Response - Stake { - staked: Uint128, - status: ResponseStatus, - }, - // Receive Response - Rewards { - status: ResponseStatus, - }, - Claim { - //TODO multiple denoms? - // claimed: Uint128, - status: ResponseStatus, - }, - Unbond { - id: Uint128, - unbonded: Uint128, - status: ResponseStatus, - }, - Withdraw { - withdrawn: Uint128, - status: ResponseStatus, - }, - Compound { - compounded: Uint128, - status: ResponseStatus, - }, - RegisterRewards { - status: ResponseStatus, - }, - EndRewardPool { - deleted: bool, - extracted: Uint128, - status: ResponseStatus, - }, - RemoveTransferWhitelist { - status: ResponseStatus, - }, - AddTransferWhitelist { - status: ResponseStatus, - }, - TransferStake { - transferred: Uint128, - status: ResponseStatus, - }, -} - -#[cw_serde] -pub struct AuthPermit {} - -#[cw_serde] -pub enum Auth { - ViewingKey { key: String, address: String }, - Permit(QueryPermit), -} - -#[cw_serde] -pub enum QueryMsg { - Config {}, - // TotalShares {}, - StakeToken {}, - StakingInfo {}, - TotalStaked {}, - RewardTokens {}, - // All reward pools in progress - RewardPools {}, - - Balance { - auth: Auth, - unbonding_ids: Option>, - }, - Staked { - auth: Auth, - }, - Rewards { - auth: Auth, - }, - Unbonding { - auth: Auth, - ids: Option>, - }, - TransferWhitelist {}, -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryAnswer { - Config { - config: Config, - }, - StakeToken { - token: Addr, - }, - StakingInfo { - info: StakingInfo, - }, - TotalStaked { - amount: Uint128, - }, - RewardTokens { - tokens: Vec, - }, - RewardPools { - rewards: Vec, - }, - Balance { - staked: Uint128, - rewards: Vec, - unbondings: Vec, - }, - Staked { - amount: Uint128, - }, - Rewards { - rewards: Vec, - }, - Unbonding { - unbondings: Vec, - }, - TransferWhitelist { - whitelist: Vec, - }, -} diff --git a/packages/shade_protocol/src/contract_interfaces/bonds/errors.rs b/packages/shade_protocol/src/contract_interfaces/bonds/errors.rs deleted file mode 100644 index 8966d2b..0000000 --- a/packages/shade_protocol/src/contract_interfaces/bonds/errors.rs +++ /dev/null @@ -1,331 +0,0 @@ -use crate::impl_into_u8; -use crate::utils::errors::{build_string, CodeType, DetailedError}; -use crate::c_std::Uint128; -use crate::c_std::{Addr, StdError}; - -use cosmwasm_schema::{cw_serde}; - -#[cw_serde] -#[repr(u8)] -pub enum Error { - BondEnded, - BondNotStarted, - BondLimitReached, - GlobalLimitReached, - MintExceedsLimit, - ContractNotActive, - NoBondFound, - NoPendingBonds, - PermitContractMismatch, - PermitRevoked, - BondLimitExceedsGlobalLimit, - BondingPeriodBelowMinimumTime, - BondDiscountAboveMaximumRate, - BondIssuanceExceedsAllowance, - NotLimitAdmin, - DepositPriceExceedsLimit, - IssuedPriceBelowMinimum, - SlippageToleranceExceeded, - Blacklisted, - IssuedAssetDeposit, - NotTreasuryBond, - NoBondsClaimable, - NotAdmin, - QueryAuthBadResponse, -} - -impl_into_u8!(Error); - -impl CodeType for Error { - fn to_verbose(&self, context: &Vec<&str>) -> String { - match self{ - Error::BondEnded => { - build_string("Bond ended on {}, it is currently {}", context) - } - Error::BondNotStarted => { - build_string("Bond begins on {}, it is currently {}", context) - } - Error::BondLimitReached => { - build_string("Bond opportunity is not available due to issuance limit of {} being reached", context) - } - Error::GlobalLimitReached => { - build_string("Bond issuance limit of {} has been reached", context) - } - Error::MintExceedsLimit => { - build_string("Mint amount of {} exceeds available mint of {}", context) - } - Error::ContractNotActive => { - build_string("Bonds contract is currently not active. Governance must activate the contract before functionality can resume.", context) - } - Error::NoBondFound => { - build_string("No bond opportunity found for deposit contract {}", context) - } - Error::NoPendingBonds => { - build_string("No pending bonds for user address {}", context) - } - Error::BondLimitExceedsGlobalLimit => { - build_string("Proposed bond issuance limit of {} exceeds available bond limit of {}", context) - } - Error::BondingPeriodBelowMinimumTime => { - build_string("Bonding period of {} is below minimum limit of {}", context) - } - Error::BondDiscountAboveMaximumRate => { - build_string("Bond discount of {} is above maximum limit of {}", context) - } - Error::BondIssuanceExceedsAllowance => { - build_string("Bond issuance limit of {} exceeds available allowance of {}", context) - } - Error::NotLimitAdmin => { - build_string("Global limit parameters can only be changed by the limit admin", context) - } - Error::DepositPriceExceedsLimit => { - build_string("Deposit asset price of {} exceeds limit price of {}, cannot enter bond opportunity", context) - } - Error::IssuedPriceBelowMinimum => { - build_string("Issued asset price of {} is below minimum value of {}, cannot enter opportunity", context) - } - Error::SlippageToleranceExceeded => { - build_string("Calculated issuance amount of {} is below minimum accepted value of {}", context) - } - Error::PermitContractMismatch => { - build_string("Permit isn't valid for {}", context) - } - Error::PermitRevoked => { - build_string("Permit is revoked for user {}", context) - } - Error::Blacklisted => { - build_string("Cannot enter bond opportunity, sender address of {} is blacklisted", context) - } - Error::IssuedAssetDeposit => { - build_string("Cannot deposit using this contract's issued asset", context) - } - Error::NotTreasuryBond => { - build_string("Cannot perform function since this is not a treasury bond", context) - } - Error::NoBondsClaimable => { - build_string("Pending bonds not redeemable, nothing claimed", context) - } - Error::NotAdmin => { - build_string("Not registered as admin address via Shade-Admin", context) - } - Error::QueryAuthBadResponse => { - build_string("Query Authentication returned unrecognized response, cannot access information", context) - } - } - } -} - -const BOND_TARGET: &str = "bond"; - -pub fn bond_not_started(start: u64, current: u64) -> StdError { - DetailedError::from_code( - BOND_TARGET, - Error::BondNotStarted, - vec![&start.to_string(), ¤t.to_string()], - ) - .to_error() -} - -pub fn bond_ended(end: u64, current: u64) -> StdError { - DetailedError::from_code( - BOND_TARGET, - Error::BondEnded, - vec![&end.to_string(), ¤t.to_string()], - ) - .to_error() -} - -pub fn bond_limit_reached(limit: Uint128) -> StdError { - let limit_string: String = limit.into(); - let limit_str: &str = limit_string.as_str(); - DetailedError::from_code(BOND_TARGET, Error::BondLimitReached, vec![limit_str]).to_error() -} - -pub fn global_limit_reached(limit: Uint128) -> StdError { - let limit_string: String = limit.into(); - let limit_str: &str = limit_string.as_str(); - DetailedError::from_code(BOND_TARGET, Error::GlobalLimitReached, vec![limit_str]).to_error() -} - -pub fn mint_exceeds_limit(mint_amount: Uint128, available: Uint128) -> StdError { - let mint_string: String = mint_amount.into(); - let mint_str = mint_string.as_str(); - let available_string: String = available.into(); - let available_str: &str = available_string.as_str(); - DetailedError::from_code( - BOND_TARGET, - Error::MintExceedsLimit, - vec![mint_str, available_str], - ) - .to_error() -} - -pub fn contract_not_active() -> StdError { - DetailedError::from_code(BOND_TARGET, Error::ContractNotActive, vec![""]).to_error() -} - -pub fn no_bond_found(deposit_asset_address: &str) -> StdError { - DetailedError::from_code( - BOND_TARGET, - Error::NoBondFound, - vec![deposit_asset_address], - ) - .to_error() -} - -pub fn no_pending_bonds(account_address: &str) -> StdError { - DetailedError::from_code(BOND_TARGET, Error::NoPendingBonds, vec![account_address]).to_error() -} - -pub fn bond_limit_exceeds_global_limit( - global_issuance_limit: Uint128, - global_total_issued: Uint128, - bond_issuance_limit: Uint128, -) -> StdError { - let available = global_issuance_limit - .checked_sub(global_total_issued) - .unwrap(); - let available_string = available.to_string(); - let available_str = available_string.as_str(); - let bond_limit_string = bond_issuance_limit.to_string(); - let bond_limit_str = bond_limit_string.as_str(); - DetailedError::from_code( - BOND_TARGET, - Error::BondLimitExceedsGlobalLimit, - vec![bond_limit_str, available_str], - ) - .to_error() -} - -pub fn bonding_period_below_minimum_time( - bond_period: u64, - global_minimum_bonding_period: u64, -) -> StdError { - let bond_period_string = bond_period.to_string(); - let bond_period_str = bond_period_string.as_str(); - let global_minimum_bonding_period_string = global_minimum_bonding_period.to_string(); - let global_minimum_bonding_period_str = global_minimum_bonding_period_string.as_str(); - DetailedError::from_code( - BOND_TARGET, - Error::BondingPeriodBelowMinimumTime, - vec![bond_period_str, global_minimum_bonding_period_str], - ) - .to_error() -} - -pub fn bond_discount_above_maximum_rate( - bond_discount: Uint128, - global_maximum_discount: Uint128, -) -> StdError { - let bond_discount_string = bond_discount.to_string(); - let bond_discount_str = bond_discount_string.as_str(); - let global_maximum_discount_string = global_maximum_discount.to_string(); - let global_maximum_discount_str = global_maximum_discount_string.as_str(); - DetailedError::from_code( - BOND_TARGET, - Error::BondDiscountAboveMaximumRate, - vec![bond_discount_str, global_maximum_discount_str], - ) - .to_error() -} - -pub fn bond_issuance_exceeds_allowance( - snip20_allowance: Uint128, - allocated_allowance: Uint128, - bond_limit: Uint128, -) -> StdError { - let available = snip20_allowance.checked_sub(allocated_allowance).unwrap(); - let available_string = available.to_string(); - let available_str = available_string.as_str(); - let bond_limit_string = bond_limit.to_string(); - let bond_limit_str = bond_limit_string.as_str(); - DetailedError::from_code( - BOND_TARGET, - Error::BondIssuanceExceedsAllowance, - vec![bond_limit_str, available_str], - ) - .to_error() -} - -pub fn not_limit_admin() -> StdError { - DetailedError::from_code(BOND_TARGET, Error::NotLimitAdmin, vec![]).to_error() -} - -pub fn deposit_price_exceeds_limit(deposit_price: Uint128, limit: Uint128) -> StdError { - let deposit_string = deposit_price.to_string(); - let deposit_str = deposit_string.as_str(); - let limit_string = limit.to_string(); - let limit_str = limit_string.as_str(); - DetailedError::from_code( - BOND_TARGET, - Error::DepositPriceExceedsLimit, - vec![deposit_str, limit_str], - ) - .to_error() -} - -pub fn issued_price_below_minimum(issued_price: Uint128, limit: Uint128) -> StdError { - let issued_string = issued_price.to_string(); - let issued_str = issued_string.as_str(); - let limit_string = limit.to_string(); - let limit_str = limit_string.as_str(); - DetailedError::from_code( - BOND_TARGET, - Error::IssuedPriceBelowMinimum, - vec![issued_str, limit_str], - ) - .to_error() -} - -pub fn slippage_tolerance_exceeded( - amount_to_issue: Uint128, - min_expected_amount: Uint128, -) -> StdError { - let issue_string = amount_to_issue.to_string(); - let issue_str = issue_string.as_str(); - let min_amount_string = min_expected_amount.to_string(); - let min_amount_str = min_amount_string.as_str(); - DetailedError::from_code( - BOND_TARGET, - Error::SlippageToleranceExceeded, - vec![issue_str, min_amount_str], - ) - .to_error() -} - -pub fn permit_contract_mismatch(expected: &str) -> StdError { - DetailedError::from_code( - BOND_TARGET, - Error::PermitContractMismatch, - vec![expected], - ) - .to_error() -} - -pub fn permit_revoked(user: &str) -> StdError { - DetailedError::from_code(BOND_TARGET, Error::PermitRevoked, vec![user]).to_error() -} - -pub fn blacklisted(address: Addr) -> StdError { - DetailedError::from_code(BOND_TARGET, Error::Blacklisted, vec![address.as_str()]).to_error() -} - -pub fn issued_asset_deposit() -> StdError { - DetailedError::from_code(BOND_TARGET, Error::IssuedAssetDeposit, vec![]).to_error() -} - -pub fn not_treasury_bond() -> StdError { - DetailedError::from_code(BOND_TARGET, Error::NotTreasuryBond, vec![]).to_error() -} - -pub fn no_bonds_claimable() -> StdError { - DetailedError::from_code(BOND_TARGET, Error::NoBondsClaimable, vec![]).to_error() -} - -pub fn not_admin() -> StdError { - DetailedError::from_code(BOND_TARGET, Error::NotAdmin, vec![]).to_error() -} - -pub fn query_auth_bad_response() -> StdError { - DetailedError::from_code(BOND_TARGET, Error::QueryAuthBadResponse, vec![]).to_error() -} \ No newline at end of file diff --git a/packages/shade_protocol/src/contract_interfaces/bonds/mod.rs b/packages/shade_protocol/src/contract_interfaces/bonds/mod.rs deleted file mode 100644 index 18a6042..0000000 --- a/packages/shade_protocol/src/contract_interfaces/bonds/mod.rs +++ /dev/null @@ -1,283 +0,0 @@ -pub mod errors; -pub mod rand; -pub mod utils; - -use crate::c_std::Env; -use cosmwasm_std::MessageInfo; - -use crate::{ - c_std::{Addr, Binary, Uint128}, - contract_interfaces::{ - bonds::{ - rand::{sha_256, Prng}, - utils::{ - create_hashed_password, - ct_slice_compare, - VIEWING_KEY_PREFIX, - VIEWING_KEY_SIZE, - }, - }, - query_auth::QueryPermit, - snip20::helpers::Snip20Asset, - }, - utils::{asset::Contract, generic_response::ResponseStatus}, -}; - -use crate::utils::ExecuteCallback; -use cosmwasm_schema::cw_serde; - -#[cw_serde] -pub struct Config { - pub limit_admin: Addr, - pub shade_admin: Contract, - pub oracle: Contract, - pub treasury: Addr, - pub issued_asset: Contract, - pub activated: bool, - pub bond_issuance_limit: Uint128, - pub bonding_period: u64, - pub discount: Uint128, - pub global_issuance_limit: Uint128, - pub global_minimum_bonding_period: u64, - pub global_maximum_discount: Uint128, - pub global_min_accepted_issued_price: Uint128, - pub global_err_issued_price: Uint128, - pub contract: Addr, - pub airdrop: Option, - pub query_auth: Contract, -} - -#[cw_serde] -pub struct InstantiateMsg { - pub limit_admin: Addr, - pub global_issuance_limit: Uint128, - pub global_minimum_bonding_period: u64, - pub global_maximum_discount: Uint128, - pub shade_admin: Contract, - pub oracle: Contract, - pub treasury: Addr, - pub issued_asset: Contract, - pub activated: bool, - pub bond_issuance_limit: Uint128, - pub bonding_period: u64, - pub discount: Uint128, - pub global_min_accepted_issued_price: Uint128, - pub global_err_issued_price: Uint128, - pub allowance_key_entropy: String, - pub airdrop: Option, - pub query_auth: Contract, -} - -#[cw_serde] -pub enum ExecuteMsg { - UpdateLimitConfig { - limit_admin: Option, - shade_admin: Option, - global_issuance_limit: Option, - global_minimum_bonding_period: Option, - global_maximum_discount: Option, - reset_total_issued: Option, - reset_total_claimed: Option, - padding: Option, - }, - UpdateConfig { - oracle: Option, - treasury: Option, - issued_asset: Option, - activated: Option, - bond_issuance_limit: Option, - bonding_period: Option, - discount: Option, - global_min_accepted_issued_price: Option, - global_err_issued_price: Option, - allowance_key: Option, - airdrop: Option, - query_auth: Option, - padding: Option, - }, - OpenBond { - deposit_asset: Contract, - start_time: u64, - end_time: u64, - bond_issuance_limit: Option, - bonding_period: Option, - discount: Option, - max_accepted_deposit_price: Uint128, - err_deposit_price: Uint128, - minting_bond: bool, - padding: Option, - }, - CloseBond { - deposit_asset: Contract, - padding: Option, - }, - Receive { - sender: Addr, - from: Addr, - amount: Uint128, - msg: Option, - padding: Option, - }, - Claim { - padding: Option, - }, -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteAnswer { - UpdateLimitConfig { - status: ResponseStatus, - }, - UpdateConfig { - status: ResponseStatus, - }, - Deposit { - status: ResponseStatus, - deposit_amount: Uint128, - pending_claim_amount: Uint128, - end_date: u64, - }, - Claim { - status: ResponseStatus, - amount: Uint128, - }, - OpenBond { - status: ResponseStatus, - deposit_contract: Contract, - start_time: u64, - end_time: u64, - bond_issuance_limit: Uint128, - bonding_period: u64, - discount: Uint128, - max_accepted_deposit_price: Uint128, - err_deposit_price: Uint128, - minting_bond: bool, - }, - ClosedBond { - status: ResponseStatus, - deposit_asset: Contract, - }, -} - -#[cw_serde] -pub enum QueryMsg { - Config {}, - BondOpportunities {}, - Account { permit: QueryPermit }, - DepositAddresses {}, - PriceCheck { asset: String }, - BondInfo {}, - CheckAllowance {}, - CheckBalance {}, -} - -#[cw_serde] -pub enum QueryAnswer { - Config { - config: Config, - }, - BondOpportunities { - bond_opportunities: Vec, - }, - Account { - pending_bonds: Vec, - }, - DepositAddresses { - deposit_addresses: Vec, - }, - PriceCheck { - price: Uint128, - }, - BondInfo { - global_total_issued: Uint128, - global_total_claimed: Uint128, - issued_asset: Snip20Asset, - global_min_accepted_issued_price: Uint128, - global_err_issued_price: Uint128, - }, - CheckAllowance { - allowance: Uint128, - }, - CheckBalance { - balance: Uint128, - }, -} - -#[cw_serde] -pub struct Account { - pub address: Addr, - pub pending_bonds: Vec, -} - -#[cw_serde] -pub struct SnipViewingKey(pub String); - -impl SnipViewingKey { - 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(info: &MessageInfo, env: &Env, 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 key = sha_256(&rand_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() - } -} - -#[cw_serde] -pub struct PendingBond { - pub deposit_denom: Snip20Asset, - pub end_time: u64, // Will be turned into a time via block time calculations - pub deposit_amount: Uint128, - pub deposit_price: Uint128, - pub claim_amount: Uint128, - pub claim_price: Uint128, - pub discount: Uint128, - pub discount_price: Uint128, -} - -// When users deposit and try to use the bond, a Bond Opportunity is selected via deposit denom -#[cw_serde] -pub struct BondOpportunity { - pub issuance_limit: Uint128, - pub amount_issued: Uint128, - pub deposit_denom: Snip20Asset, - pub start_time: u64, - pub end_time: u64, - pub bonding_period: u64, - pub discount: Uint128, - pub max_accepted_deposit_price: Uint128, - pub err_deposit_price: Uint128, - pub minting_bond: bool, -} - -#[cw_serde] -pub struct SlipMsg { - pub minimum_expected_amount: Uint128, -} diff --git a/packages/shade_protocol/src/contract_interfaces/bonds/rand.rs b/packages/shade_protocol/src/contract_interfaces/bonds/rand.rs deleted file mode 100644 index 11da813..0000000 --- a/packages/shade_protocol/src/contract_interfaces/bonds/rand.rs +++ /dev/null @@ -1,74 +0,0 @@ -use rand_chacha::ChaChaRng; -use rand_core::{RngCore, SeedableRng}; -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/packages/shade_protocol/src/contract_interfaces/bonds/utils.rs b/packages/shade_protocol/src/contract_interfaces/bonds/utils.rs deleted file mode 100644 index fd534df..0000000 --- a/packages/shade_protocol/src/contract_interfaces/bonds/utils.rs +++ /dev/null @@ -1,17 +0,0 @@ -use sha2::{Digest, Sha256}; -use std::convert::TryInto; -use subtle::ConstantTimeEq; - -pub const VIEWING_KEY_SIZE: usize = 32; -pub const VIEWING_KEY_PREFIX: &str = "api_key_"; - -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/packages/shade_protocol/src/contract_interfaces/dao/DAO_ADAPTER.md b/packages/shade_protocol/src/contract_interfaces/dao/DAO_ADAPTER.md deleted file mode 100644 index c34c135..0000000 --- a/packages/shade_protocol/src/contract_interfaces/dao/DAO_ADAPTER.md +++ /dev/null @@ -1,153 +0,0 @@ -# DAO Adapter Interface -* [Introduction](#Introduction) -* [Sections](#Sections) - * [Interface](#Interface) - * Messages - * [Unbond](#Unbond) - * [Claim](#Claim) - * [Update](#Update) - * Queries - * [Balance](#Balance) - * [Unbonding](#Unbonding) - * [Claimable](#Claimable) - * [Unbondable](#Unbondable) - -# Introduction -This is an interface for dapps to follow to integrate with the DAO, to receive funding fromthe treasury and later unbond those funds back to treasury when needed. -NOTE: Because of how the contract implements this, all messages will be enclosed as: -``` -{ - "adapter": { - - } -} -``` - -# Sections - -### Messages -#### Unbond -Begin unbonding of a given amount from a given asset - -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|asset | Addr | SNIP-20 asset to unbond - -##### Response -```json -{ - "unbond": { - "amount": "100" - "status": "success" - } -} -``` - -#### Claim -Claim a given amount from completed unbonding of a given asset - -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|asset | Addr | SNIP-20 asset to unbond - -##### Response -```json -{ - "claim": { - "amount": "100" - "status": "success" - } -} -``` - -#### Update -Update a given asset on the adapter, to perform regular maintenance tasks if needed -Examples: - - `scrt_staking` - Claim rewards and restake - - `treasury` - Rebalance funds - -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|asset | Addr | SNIP-20 asset to unbond - -##### Response -```json -{ - "update": { - "status": "success" - } -} -``` - -### Queries - -#### Balance -Get the balance of a given asset, Error if unrecognized - -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|asset | Addr | SNIP-20 asset to query - -##### Response -```json -{ - "balance": { - "amount": "100000", - } -} -``` - -#### Unbonding -Get the current unbonding amount of a given asset, Error if unrecognized - -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|asset | Addr | SNIP-20 asset to query - -##### Response -```json -{ - "unbonding": { - "amount": "100000", - } -} -``` - -#### Claimable -Get the current claimable amount of a given asset, Error if unrecognized - -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|asset | Addr | SNIP-20 asset to query - -##### Response -```json -{ - "claimable": { - "amount": "100000", - } -} -``` - -#### Unbondable -Get the current unbondable amount of a given asset, Error if unrecognized - -##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|asset | Addr | SNIP-20 asset to query - -##### Response -```json -{ - "unbondable": { - "amount": "100000", - } -} -``` diff --git a/packages/shade_protocol/src/contract_interfaces/dao/adapter.rs b/packages/shade_protocol/src/contract_interfaces/dao/adapter.rs deleted file mode 100644 index 349b617..0000000 --- a/packages/shade_protocol/src/contract_interfaces/dao/adapter.rs +++ /dev/null @@ -1,187 +0,0 @@ -use crate::{ - c_std::{Addr, CosmosMsg, QuerierWrapper, StdError, StdResult, Uint128}, - utils::{asset::Contract, generic_response::ResponseStatus}, -}; - -use crate::utils::{ExecuteCallback, Query}; -use cosmwasm_schema::cw_serde; - -#[cw_serde] -pub enum SubExecuteMsg { - // Begin unbonding amount - Unbond { asset: String, amount: Uint128 }, - Claim { asset: String }, - // Maintenance trigger e.g. claim rewards and restake - Update { asset: String }, -} - -impl ExecuteCallback for SubExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteMsg { - Adapter(SubExecuteMsg), -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteAnswer { - Init { - status: ResponseStatus, - address: Addr, - }, - Unbond { - status: ResponseStatus, - amount: Uint128, - }, - Claim { - status: ResponseStatus, - amount: Uint128, - }, - Update { - status: ResponseStatus, - }, -} - -#[cw_serde] -pub enum SubQueryMsg { - Balance { asset: String }, - Unbonding { asset: String }, - Claimable { asset: String }, - Unbondable { asset: String }, - Reserves { asset: String }, -} - -#[cw_serde] -pub enum QueryMsg { - Adapter(SubQueryMsg), -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryAnswer { - Balance { amount: Uint128 }, - Unbonding { amount: Uint128 }, - Claimable { amount: Uint128 }, - Unbondable { amount: Uint128 }, - Reserves { amount: Uint128 }, -} - -pub fn claimable_query( - querier: QuerierWrapper, - asset: &Addr, - adapter: Contract, -) -> StdResult { - match QueryMsg::Adapter(SubQueryMsg::Claimable { - asset: asset.to_string().clone(), - }) - .query(&querier, &adapter)? - { - QueryAnswer::Claimable { amount } => Ok(amount), - _ => Err(StdError::generic_err(format!( - "Failed to query adapter claimable from {}", - adapter.address - ))), - } -} - -pub fn unbonding_query( - querier: QuerierWrapper, - asset: &Addr, - adapter: Contract, -) -> StdResult { - match QueryMsg::Adapter(SubQueryMsg::Unbonding { - asset: asset.to_string().clone(), - }) - .query(&querier, &adapter)? - { - QueryAnswer::Unbonding { amount } => Ok(amount), - _ => Err(StdError::generic_err(format!( - "Failed to query adapter unbonding from {}", - adapter.address - ))), - } -} - -pub fn unbondable_query( - querier: QuerierWrapper, - asset: &Addr, - adapter: Contract, -) -> StdResult { - match QueryMsg::Adapter(SubQueryMsg::Unbondable { - asset: asset.to_string().clone(), - }) - .query(&querier, &adapter)? - { - QueryAnswer::Unbondable { amount } => Ok(amount), - _ => Err(StdError::generic_err(format!( - "Failed to query adapter unbondable from {}", - adapter.address - ))), - } -} - -pub fn reserves_query( - querier: QuerierWrapper, - asset: &Addr, - adapter: Contract, -) -> StdResult { - match QueryMsg::Adapter(SubQueryMsg::Reserves { - asset: asset.to_string().clone(), - }) - .query(&querier, &adapter)? - { - QueryAnswer::Reserves { amount } => Ok(amount), - _ => Err(StdError::generic_err(format!( - "Failed to query adapter unbondable from {}", - adapter.address - ))), - } -} - -pub fn balance_query( - querier: QuerierWrapper, - asset: &Addr, - adapter: Contract, -) -> StdResult { - match QueryMsg::Adapter(SubQueryMsg::Balance { - asset: asset.to_string().clone(), - }) - .query(&querier, &adapter)? - { - QueryAnswer::Balance { amount } => Ok(amount), - _ => Err(StdError::generic_err(format!( - "Failed to query adapter balance from {}", - adapter.address - ))), - } -} - -pub fn claim_msg(asset: &Addr, adapter: Contract) -> StdResult { - ExecuteMsg::Adapter(SubExecuteMsg::Claim { - asset: asset.to_string().clone(), - }) - .to_cosmos_msg(&adapter, vec![]) -} - -pub fn unbond_msg(asset: &Addr, amount: Uint128, adapter: Contract) -> StdResult { - ExecuteMsg::Adapter(SubExecuteMsg::Unbond { - asset: asset.to_string().clone(), - amount, - }) - .to_cosmos_msg(&adapter, vec![]) -} - -pub fn update_msg(asset: &Addr, adapter: Contract) -> StdResult { - ExecuteMsg::Adapter(SubExecuteMsg::Update { - asset: asset.to_string().clone(), - }) - .to_cosmos_msg(&adapter, vec![]) -} diff --git a/packages/shade_protocol/src/contract_interfaces/dao/lp_shdswap.rs b/packages/shade_protocol/src/contract_interfaces/dao/lp_shdswap.rs deleted file mode 100644 index 079b580..0000000 --- a/packages/shade_protocol/src/contract_interfaces/dao/lp_shdswap.rs +++ /dev/null @@ -1,151 +0,0 @@ -use crate::{ - c_std::{Addr, Binary, Uint128}, - contract_interfaces::dao::adapter, - utils::{ - asset::Contract, - generic_response::ResponseStatus, - ExecuteCallback, - InstantiateCallback, - Query, - }, -}; -use cosmwasm_schema::cw_serde; - -#[cw_serde] -pub enum SplitMethod { - Conversion { contract: Contract }, - //TODO implement - /* - Market { - // "market_buy" contract - contract: Contract, - }, - Lend { - overseer: Contract, - }, - */ -} - -#[cw_serde] -pub struct Config { - pub admin: Addr, - pub treasury: Addr, - pub pair: Contract, - pub token_a: Contract, - pub token_b: Contract, - pub liquidity_token: Contract, - pub staking_contract: Option, - pub reward_token: Option, - pub split: Option, -} - -#[cw_serde] -pub struct InstantiateMsg { - pub admin: Option, - pub treasury: Addr, - pub viewing_key: String, - pub pair: Contract, - pub token_a: Contract, - pub token_b: Contract, - pub staking_contract: Option, -} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteMsg { - /* token_a || token_b - * - check and provide as much as you can based on balances - * - * LP share token - * - Bond the share token, to be used when unbonding - */ - Receive { - sender: Addr, - from: Addr, - amount: Uint128, - memo: Option, - msg: Option, - }, - // TODO Refresh approvals to max - // admin only - RefreshApprovals, - UpdateConfig { - config: Config, - }, - Adapter(adapter::SubExecuteMsg), -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteAnswer { - Init { - status: ResponseStatus, - address: Addr, - }, - UpdateConfig { - status: ResponseStatus, - config: Config, - }, - RefreshApprovals { - status: ResponseStatus, - }, - Receive { - status: ResponseStatus, - }, -} - -#[cw_serde] -pub enum QueryMsg { - Config {}, - //Ratio {}, - Adapter(adapter::SubQueryMsg), -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryAnswer { - Config { config: Config }, - // Should add to %100 - //Ratio { token_a: Uint128, token_b: Uint128 }, -} - -/* NOTE - * 'reward_token' isn't technically supported - * if it collides with one of the pair tokens - * it will be treated as such - * Otherwise it will be sent straight to treasury on claim - */ -pub fn is_supported_asset(config: &Config, asset: &Addr) -> bool { - if let Some(reward_token) = &config.reward_token { - if reward_token.address == *asset { - return true; - } - } - - vec![ - config.token_a.address.clone(), - config.token_b.address.clone(), - config.liquidity_token.address.clone(), - ] - .contains(asset) -} - -pub fn get_supported_asset(config: &Config, asset: &Addr) -> Contract { - vec![ - config.token_a.clone(), - config.token_b.clone(), - config.liquidity_token.clone(), - ] - .into_iter() - .find(|a| a.address == *asset) - .unwrap() -} diff --git a/packages/shade_protocol/src/contract_interfaces/dao/manager.rs b/packages/shade_protocol/src/contract_interfaces/dao/manager.rs deleted file mode 100644 index 5da264a..0000000 --- a/packages/shade_protocol/src/contract_interfaces/dao/manager.rs +++ /dev/null @@ -1,240 +0,0 @@ -use cosmwasm_std::{ - Addr, - CosmosMsg, - QuerierWrapper, - StdError, - StdResult, - Uint128, -}; - -use crate::utils::{asset::Contract, generic_response::ResponseStatus, ExecuteCallback, Query}; -use cosmwasm_schema::cw_serde; - -#[cw_serde] -pub enum SubExecuteMsg { - // Begin unbonding amount - Unbond { asset: String, amount: Uint128 }, - Claim { asset: String }, - // Maintenance trigger e.g. claim rewards and restake - Update { asset: String }, -} - -impl ExecuteCallback for SubExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteMsg { - Manager(SubExecuteMsg), -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteAnswer { - Init { - status: ResponseStatus, - address: String, - }, - Unbond { - status: ResponseStatus, - amount: Uint128, - }, - Claim { - status: ResponseStatus, - amount: Uint128, - }, - Update { - status: ResponseStatus, - }, -} - -#[cw_serde] -pub enum SubQueryMsg { - BatchBalance { assets: Vec, holder: String }, - Balance { asset: String, holder: String }, - Unbonding { asset: String, holder: String }, - Claimable { asset: String, holder: String }, - Unbondable { asset: String, holder: String }, - Reserves { asset: String, holder: String }, -} - -#[cw_serde] -pub enum QueryMsg { - Manager(SubQueryMsg), -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryAnswer { - BatchBalance { amounts: Vec }, - Balance { amount: Uint128 }, - Unbonding { amount: Uint128 }, - Claimable { amount: Uint128 }, - Unbondable { amount: Uint128 }, - Reserves { amount: Uint128 }, -} - -pub fn claimable_query( - querier: QuerierWrapper, - asset: &Addr, - holder: Addr, - manager: Contract, -) -> StdResult { - match QueryMsg::Manager(SubQueryMsg::Claimable { - asset: asset.to_string().clone(), - holder: holder.to_string().clone(), - }) - .query(&querier, &manager)? - { - QueryAnswer::Claimable { amount } => Ok(amount), - _ => Err(StdError::generic_err(format!( - "Failed to query manager claimable from {}", - manager.address - ))), - } -} - -pub fn unbonding_query( - querier: QuerierWrapper, - asset: &Addr, - holder: Addr, - manager: Contract, -) -> StdResult { - match QueryMsg::Manager(SubQueryMsg::Unbonding { - asset: asset.to_string().clone(), - holder: holder.to_string().clone(), - }) - .query(&querier, &manager)? - { - QueryAnswer::Unbonding { amount } => Ok(amount), - _ => Err(StdError::generic_err(format!( - "Failed to query manager unbonding from {}", - manager.address - ))), - } -} - -pub fn unbondable_query( - querier: QuerierWrapper, - asset: &Addr, - holder: Addr, - manager: Contract, -) -> StdResult { - match QueryMsg::Manager(SubQueryMsg::Unbondable { - asset: asset.to_string().clone(), - holder: holder.to_string().clone(), - }) - .query(&querier, &manager)? - { - QueryAnswer::Unbondable { amount } => Ok(amount), - _ => Err(StdError::generic_err(format!( - "Failed to query manager unbondable from {}", - manager.address - ))), - } -} - -pub fn reserves_query( - querier: QuerierWrapper, - asset: &Addr, - holder: Addr, - manager: Contract, -) -> StdResult { - match QueryMsg::Manager(SubQueryMsg::Reserves { - asset: asset.to_string().clone(), - holder: holder.to_string().clone(), - }) - .query(&querier, &manager)? - { - QueryAnswer::Reserves { amount } => Ok(amount), - _ => Err(StdError::generic_err(format!( - "Failed to query manager unbondable from {}", - manager.address - ))), - } -} - -pub fn balance_query( - querier: QuerierWrapper, - asset: &Addr, - holder: Addr, - manager: Contract, -) -> StdResult { - match QueryMsg::Manager(SubQueryMsg::Balance { - asset: asset.to_string().clone(), - holder: holder.to_string().clone(), - }) - .query(&querier, &manager) - { - Ok(resp) => match resp { - QueryAnswer::Balance { amount } => Ok(amount), - _ => Err(StdError::generic_err(format!( - "Unexpected response from {} manager balance", - manager.address - ))), - }, - Err(e) => { - println!("HERERERER"); - return Err(StdError::generic_err(format!( - "Failed to query manager balance: {}", - e.to_string() - ))); - } - } -} - -pub fn batch_balance_query( - querier: QuerierWrapper, - assets: &Vec, - holder: Addr, - manager: Contract, -) -> StdResult> { - match QueryMsg::Manager(SubQueryMsg::BatchBalance { - assets: assets.iter().map(|a| a.to_string()).collect(), - holder: holder.to_string().clone(), - }) - .query(&querier, &manager) - { - Ok(resp) => match resp { - QueryAnswer::BatchBalance { amounts } => Ok(amounts), - _ => Err(StdError::generic_err(format!( - "Unexpected response from {} manager batch balance", - manager.address - ))), - }, - Err(e) => { - return Err(StdError::generic_err(format!( - "Failed to query manager batch balance: {}", - e.to_string() - ))); - } - } -} - -pub fn claim_msg(asset: &Addr, manager: Contract) -> StdResult { - ExecuteMsg::Manager(SubExecuteMsg::Claim { - asset: asset.to_string().clone(), - }) - .to_cosmos_msg(&manager, vec![]) -} - -pub fn unbond_msg(asset: &Addr, amount: Uint128, manager: Contract) -> StdResult { - ExecuteMsg::Manager(SubExecuteMsg::Unbond { - asset: asset.to_string().clone(), - amount, - }) - .to_cosmos_msg(&manager, vec![]) -} - -pub fn update_msg(asset: &Addr, manager: Contract) -> StdResult { - ExecuteMsg::Manager(SubExecuteMsg::Update { - asset: asset.to_string().clone(), - }) - .to_cosmos_msg(&manager, vec![]) -} diff --git a/packages/shade_protocol/src/contract_interfaces/dao/mod.rs b/packages/shade_protocol/src/contract_interfaces/dao/mod.rs deleted file mode 100644 index 9469417..0000000 --- a/packages/shade_protocol/src/contract_interfaces/dao/mod.rs +++ /dev/null @@ -1,23 +0,0 @@ -#[cfg(feature = "adapter")] -pub mod adapter; - -#[cfg(feature = "manager")] -pub mod manager; - -#[cfg(feature = "treasury_manager")] -pub mod treasury_manager; - -#[cfg(feature = "rewards_emission")] -pub mod rewards_emission; - -#[cfg(feature = "treasury")] -pub mod treasury; - -#[cfg(feature = "scrt_staking")] -pub mod scrt_staking; - -#[cfg(feature = "lp_shdswap")] -pub mod lp_shdswap; - -#[cfg(feature = "stkd_scrt")] -pub mod stkd_scrt; diff --git a/packages/shade_protocol/src/contract_interfaces/dao/rewards_emission.rs b/packages/shade_protocol/src/contract_interfaces/dao/rewards_emission.rs deleted file mode 100644 index 9b5822e..0000000 --- a/packages/shade_protocol/src/contract_interfaces/dao/rewards_emission.rs +++ /dev/null @@ -1,102 +0,0 @@ -use crate::{ - c_std::{Addr, Binary, Uint128}, - utils::{ - asset::{Contract, RawContract}, - cycle::Cycle, - generic_response::ResponseStatus, - }, -}; - -use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; -use cosmwasm_schema::cw_serde; - -#[cw_serde] -pub struct Reward { - //pub token: Addr, - pub distributor: Contract, - pub amount: Uint128, - pub cycle: Cycle, - pub last_refresh: String, - // datetime string - pub expiration: Option, -} - -#[cw_serde] -pub struct Config { - pub admins: Vec, - pub treasury: Addr, -} - -#[cw_serde] -pub struct InstantiateMsg { - pub admins: Vec, - pub viewing_key: String, - pub treasury: String, - pub token: RawContract, -} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteMsg { - UpdateConfig { - config: Config, - }, - Receive { - sender: Addr, - from: Addr, - amount: Uint128, - memo: Option, - msg: Option, - }, - RefillRewards {}, - RegisterRewards { - token: Addr, // Just for verification - distributor: Contract, - amount: Uint128, - cycle: Cycle, - expiration: Option, - }, -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteAnswer { - Init { - status: ResponseStatus, - address: Addr, - }, - UpdateConfig { - status: ResponseStatus, - }, - Receive { - status: ResponseStatus, - }, - RegisterReward { - status: ResponseStatus, - }, - RefillRewards { - status: ResponseStatus, - }, -} - -#[cw_serde] -pub enum QueryMsg { - Config {}, - //PendingAllowance { asset: Addr }, -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryAnswer { - Config { config: Config }, - //PendingAllowance { amount: Uint128 }, -} diff --git a/packages/shade_protocol/src/contract_interfaces/dao/scrt_staking.rs b/packages/shade_protocol/src/contract_interfaces/dao/scrt_staking.rs deleted file mode 100644 index 5716365..0000000 --- a/packages/shade_protocol/src/contract_interfaces/dao/scrt_staking.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::utils::asset::RawContract; -use crate::utils::{asset::Contract, generic_response::ResponseStatus}; -use crate::c_std::{Binary, Decimal, Addr, Uint128, Validator}; - -use crate::contract_interfaces::dao::adapter; - -use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; -use cosmwasm_schema::{cw_serde}; - -#[cw_serde] -pub struct Config { - pub admin_auth: Contract, - //pub treasury: Addr, - // This is the contract that will "unbond" funds - pub owner: Addr, - pub sscrt: Contract, - pub validator_bounds: Option, -} - -#[cw_serde] -pub struct ValidatorBounds { - pub min_commission: Decimal, - pub max_commission: Decimal, - pub top_position: Uint128, - pub bottom_position: Uint128, -} - -#[cw_serde] -pub struct InstantiateMsg { - pub admin_auth: RawContract, - pub owner: String, - pub sscrt: RawContract, - pub validator_bounds: Option, - pub viewing_key: String, -} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteMsg { - Receive { - sender: String, - from: String, - amount: Uint128, - memo: Option, - msg: Option, - }, - UpdateConfig { - config: Config, - }, - Adapter(adapter::SubExecuteMsg), -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteAnswer { - Init { - status: ResponseStatus, - address: String, - }, - UpdateConfig { - status: ResponseStatus, - }, - Receive { - status: ResponseStatus, - validator: Validator, - }, - /* - Claim { - status: ResponseStatus, - }, - Unbond { - status: ResponseStatus, - delegations: Vec, - }, - */ -} - -#[cw_serde] -pub enum QueryMsg { - Config {}, - Delegations {}, - Rewards {}, - Adapter(adapter::SubQueryMsg), -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryAnswer { - Config { config: Config }, - //Balance { amount: Uint128 }, -} diff --git a/packages/shade_protocol/src/contract_interfaces/dao/stkd_scrt.rs b/packages/shade_protocol/src/contract_interfaces/dao/stkd_scrt.rs deleted file mode 100644 index 16958ac..0000000 --- a/packages/shade_protocol/src/contract_interfaces/dao/stkd_scrt.rs +++ /dev/null @@ -1,192 +0,0 @@ -use crate::{ - c_std::{Addr, Binary, Uint128}, - cosmwasm_schema::cw_serde, - utils::{ - asset::{Contract, RawContract}, - generic_response::ResponseStatus, - }, -}; - -use crate::contract_interfaces::dao::adapter; - -use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; - -#[cw_serde] -pub struct Config { - pub admin_auth: Contract, - //pub treasury: Addr, - // This is the contract that will "unbond" funds - pub owner: Addr, - pub sscrt: Contract, - pub staking_derivatives: Contract, -} - -#[cw_serde] -pub struct InstantiateMsg { - pub admin_auth: RawContract, - pub owner: String, - pub sscrt: RawContract, - pub viewing_key: String, - pub staking_derivatives: RawContract, -} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteMsg { - Receive { - sender: String, - from: String, - amount: Uint128, - memo: Option, - msg: Option, - }, - UpdateConfig { - config: Config, - }, - Adapter(adapter::SubExecuteMsg), -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteAnswer { - Init { - status: ResponseStatus, - address: String, - }, - UpdateConfig { - status: ResponseStatus, - }, - Receive { - status: ResponseStatus, - }, -} - -#[cw_serde] -pub enum QueryMsg { - Config {}, - Adapter(adapter::SubQueryMsg), -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryAnswer { - Config { config: Config }, -} - -// STAKING DERIVATIVES INTERFACE -// TODO move to common location -pub mod staking_derivatives { - use crate::{ - c_std::{Addr, Coin, CosmosMsg, QuerierWrapper, StdResult, Uint128}, - cosmwasm_schema::cw_serde, - utils::asset::Contract, - }; - - use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; - - #[cw_serde] - pub enum ExecuteMsg { - Stake {}, - Unbond { redeem_amount: Uint128 }, - Claim {}, - } - - impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; - } - - #[cw_serde] - pub enum QueryMsg { - Unbonding { - address: Addr, - key: String, - page: Option, - page_size: Option, - time: Option, - }, - Holdings { - address: Addr, - key: String, - time: u64, - }, - } - - #[cw_serde] - pub struct Unbond { - pub amount: Uint128, - pub unbonds_at: u64, - pub is_mature: Option, - } - - #[cw_serde] - pub struct WeightedValidator { - pub validator: Addr, - pub weight: u8, - } - - #[cw_serde] - pub enum QueryAnswer { - Unbonding { - count: u64, - claimable_scrt: Option, - unbondings: Vec, - unbond_amount_in_next_batch: Uint128, - estimated_time_of_maturity_for_next_batch: Option, - }, - Holdings { - claimable_scrt: Uint128, - unbonding_scrt: Uint128, - token_balance: Uint128, - token_balance_value_in_scrt: Uint128, - }, - } - - #[cw_serde] - pub struct Holdings { - pub claimable_scrt: Uint128, - pub unbonding_scrt: Uint128, - pub token_balance: Uint128, - pub token_balance_value_in_scrt: Uint128, - } - - impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; - } - - pub fn stake_msg(amount: Uint128, contract: &Contract) -> StdResult { - ExecuteMsg::Stake {}.to_cosmos_msg(contract, vec![Coin { - amount, - denom: "uscrt".to_string(), - }]) - } - - pub fn unbond_msg(amount: Uint128, contract: &Contract) -> StdResult { - ExecuteMsg::Unbond { - redeem_amount: amount, - } - .to_cosmos_msg(contract, vec![]) - } - - pub fn claim_msg(contract: &Contract) -> StdResult { - ExecuteMsg::Claim {}.to_cosmos_msg(contract, vec![]) - } - - pub fn holdings_query( - querier: &QuerierWrapper, - address: Addr, - key: String, - time: u64, - contract: &Contract, - ) -> StdResult { - QueryMsg::Holdings { address, key, time }.query(querier, contract) - } -} diff --git a/packages/shade_protocol/src/contract_interfaces/dao/treasury.rs b/packages/shade_protocol/src/contract_interfaces/dao/treasury.rs deleted file mode 100644 index 00ddcdb..0000000 --- a/packages/shade_protocol/src/contract_interfaces/dao/treasury.rs +++ /dev/null @@ -1,250 +0,0 @@ -use crate::utils::{ - asset::{Contract, RawContract}, - cycle::Cycle, - generic_response::ResponseStatus, -}; - -use crate::c_std::{Addr, Api, Binary, Coin, StdResult, Uint128}; - -use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; -use cosmwasm_schema::cw_serde; - -use crate::utils::storage::plus::period_storage::Period; - -/// The permission referenced in the Admin Auth contract to give a user -/// admin permissions for the Shade Treasury -//pub const SHADE_TREASURY_ADMIN: &str = "SHADE_TREASURY_ADMIN"; - -#[cw_serde] -pub struct Config { - pub admin_auth: Contract, - pub multisig: Addr, -} - -#[cw_serde] -pub enum RunLevel { - Normal, - Deactivated, - Migrating, -} - -#[cw_serde] -pub enum Context { - Receive, - Rebalance, - Migration, - Unbond, - Wrap, -} - -#[cw_serde] -pub enum Action { - IncreaseAllowance, - DecreaseAllowance, - Unbond, - Claim, - FundsReceived, - SendFunds, - Wrap, -} - -#[cw_serde] -pub struct Metric { - pub action: Action, - pub context: Context, - pub timestamp: u64, - pub token: Addr, - pub amount: Uint128, - pub user: Addr, -} - -#[cw_serde] -pub enum AllowanceType { - Amount, - Portion, -} - -#[cw_serde] -pub struct RawAllowance { - pub spender: String, - pub allowance_type: AllowanceType, - pub cycle: Cycle, - pub amount: Uint128, - pub tolerance: Uint128, -} - -impl RawAllowance { - pub fn valid(self, api: &dyn Api) -> StdResult { - Ok(Allowance { - spender: api.addr_validate(self.spender.as_str())?, - allowance_type: self.allowance_type, - cycle: self.cycle, - amount: self.amount, - tolerance: self.tolerance, - }) - } -} - -#[cw_serde] -pub struct Allowance { - pub spender: Addr, - pub allowance_type: AllowanceType, - pub cycle: Cycle, - pub amount: Uint128, - pub tolerance: Uint128, -} - -#[cw_serde] -pub struct AllowanceMeta { - pub spender: Addr, - pub allowance_type: AllowanceType, - pub cycle: Cycle, - pub amount: Uint128, - pub tolerance: Uint128, - pub last_refresh: String, -} - -#[cw_serde] -pub struct InstantiateMsg { - pub admin_auth: RawContract, - pub multisig: String, - pub viewing_key: String, -} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteMsg { - Receive { - sender: String, - from: String, - amount: Uint128, - memo: Option, - msg: Option, - }, - UpdateConfig { - admin_auth: Option, - multisig: Option, - }, - RegisterAsset { - contract: RawContract, - }, - RegisterManager { - contract: RawContract, - }, - RegisterWrap { - denom: String, - contract: RawContract, - }, - WrapCoins {}, - // Setup a new allowance - Allowance { - asset: String, - allowance: RawAllowance, - refresh_now: bool, - }, - Update { - asset: String, - }, - SetRunLevel { - run_level: RunLevel, - }, -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteAnswer { - Init { - status: ResponseStatus, - address: String, - }, - UpdateConfig { - config: Config, - status: ResponseStatus, - }, - Receive { - status: ResponseStatus, - }, - RegisterAsset { - status: ResponseStatus, - }, - RegisterManager { - status: ResponseStatus, - }, - RegisterWrap { - status: ResponseStatus, - }, - Allowance { - status: ResponseStatus, - }, - Rebalance { - status: ResponseStatus, - }, - Migration { - status: ResponseStatus, - }, - Unbond { - status: ResponseStatus, - }, - Update { - status: ResponseStatus, - }, - RunLevel { - run_level: RunLevel, - }, - WrapCoins { - success: Vec, - failed: Vec, - }, -} - -#[cw_serde] -pub enum QueryMsg { - Config {}, - Assets {}, - // List of recurring allowances configured - Allowances { - asset: String, - }, - // Current allowance to spender - Allowance { - asset: String, - spender: String, - }, - RunLevel, - Metrics { - date: Option, - epoch: Option, - period: Period, - }, - Balance { - asset: String, - }, - BatchBalance { - assets: Vec, - }, - Reserves { - asset: String, - }, -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryAnswer { - Config { config: Config }, - Assets { assets: Vec }, - Allowances { allowances: Vec }, - Allowance { amount: Uint128 }, - RunLevel { run_level: RunLevel }, - Metrics { metrics: Vec }, - Balance { amount: Uint128 }, - Reserves { amount: Uint128 }, -} diff --git a/packages/shade_protocol/src/contract_interfaces/dao/treasury_manager.rs b/packages/shade_protocol/src/contract_interfaces/dao/treasury_manager.rs deleted file mode 100644 index 78960dd..0000000 --- a/packages/shade_protocol/src/contract_interfaces/dao/treasury_manager.rs +++ /dev/null @@ -1,247 +0,0 @@ -use crate::{ - c_std::{Addr, Api, Binary, StdResult, Uint128}, - contract_interfaces::dao::manager, - utils::{ - asset::{Contract, RawContract}, - generic_response::ResponseStatus, - storage::plus::period_storage::Period, - }, -}; - -use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; -use cosmwasm_schema::cw_serde; - -#[cw_serde] -pub enum Context { - Receive, - Update, - Unbond, - Claim, - Holders, -} - -#[cw_serde] -pub enum Action { - Unbond, - Claim, - FundsReceived, - SendFunds, - SendFundsFrom, - RealizeGains, - RealizeLosses, - //TODO - AddHolder, - RemoveHolder, -} - -#[cw_serde] -pub struct Metric { - pub action: Action, - pub context: Context, - pub timestamp: u64, - pub token: Addr, - pub amount: Uint128, - pub user: Addr, -} - -#[cw_serde] -pub struct Config { - pub admin_auth: Contract, - pub treasury: Addr, -} - -#[cw_serde] -pub struct Balance { - pub token: Addr, - pub amount: Uint128, -} - -#[cw_serde] -pub enum Status { - Active, - Disabled, - Closed, - Transferred, -} - -//TODO: move accounts to treasury manager -#[cw_serde] -pub struct Holding { - pub balances: Vec, - pub unbondings: Vec, - //pub claimable: Vec, - pub status: Status, -} - -#[cw_serde] -pub struct Unbonding { - pub holder: Addr, - pub amount: Uint128, -} - -#[cw_serde] -pub struct RawAllocation { - pub nick: Option, - pub contract: RawContract, - pub alloc_type: AllocationType, - pub amount: Uint128, - pub tolerance: Uint128, -} - -impl RawAllocation { - pub fn valid(self, api: &dyn Api) -> StdResult { - Ok(Allocation { - nick: self.nick, - contract: self.contract.into_valid(api)?, - alloc_type: self.alloc_type, - amount: self.amount, - tolerance: self.tolerance, - }) - } -} - -#[cw_serde] -pub struct Allocation { - pub nick: Option, - pub contract: Contract, - pub alloc_type: AllocationType, - pub amount: Uint128, - pub tolerance: Uint128, -} - -#[cw_serde] -pub enum AllocationType { - // amount becomes percent * 10^18 - Portion, - Amount, -} - -//TODO remove - same as Allocation -#[cw_serde] -pub struct AllocationMeta { - pub nick: Option, - pub contract: Contract, - pub alloc_type: AllocationType, - pub amount: Uint128, - pub tolerance: Uint128, -} - -#[cw_serde] -pub struct AllocationTempData { - pub contract: Contract, - pub alloc_type: AllocationType, - pub amount: Uint128, - pub tolerance: Uint128, - pub balance: Uint128, - pub unbondable: Uint128, - pub unbonding: Uint128, -} - -#[cw_serde] -pub struct InstantiateMsg { - pub admin_auth: RawContract, - pub viewing_key: String, - pub treasury: String, -} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteMsg { - Receive { - sender: String, - from: String, - amount: Uint128, - memo: Option, - msg: Option, - }, - UpdateConfig { - admin_auth: Option, - treasury: Option, - }, - RegisterAsset { - contract: RawContract, - }, - Allocate { - asset: String, - allocation: RawAllocation, - }, - AddHolder { - holder: String, - }, - RemoveHolder { - holder: String, - }, - Manager(manager::SubExecuteMsg), -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteAnswer { - Init { - status: ResponseStatus, - address: String, - }, - Receive { - status: ResponseStatus, - }, - UpdateConfig { - config: Config, - status: ResponseStatus, - }, - RegisterAsset { - status: ResponseStatus, - }, - Allocate { - status: ResponseStatus, - }, - AddHolder { - status: ResponseStatus, - }, - RemoveHolder { - status: ResponseStatus, - }, - Manager(manager::ExecuteAnswer), -} - -#[cw_serde] -pub enum QueryMsg { - Config {}, - Assets {}, - Allocations { - asset: String, - }, - PendingAllowance { - asset: String, - }, - Holders {}, - Holding { - holder: String, - }, - Metrics { - date: Option, - epoch: Option, - period: Period, - }, - Manager(manager::SubQueryMsg), -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryAnswer { - Config { config: Config }, - Assets { assets: Vec }, - Allocations { allocations: Vec }, - PendingAllowance { amount: Uint128 }, - Holders { holders: Vec }, - Holding { holding: Holding }, - Metrics { metrics: Vec }, -} diff --git a/packages/shade_protocol/src/contract_interfaces/dex/dex.rs b/packages/shade_protocol/src/contract_interfaces/dex/dex.rs deleted file mode 100644 index 205d957..0000000 --- a/packages/shade_protocol/src/contract_interfaces/dex/dex.rs +++ /dev/null @@ -1,173 +0,0 @@ -use crate::{ - contract_interfaces::{ - dex::{secretswap, sienna}, - oracles::band, - snip20::helpers::Snip20Asset, - }, - utils::{ - asset::Contract, - price::{normalize_price, translate_price}, - }, -}; -use crate::c_std::{Deps, StdError, StdResult}; - -use cosmwasm_schema::{cw_serde}; - -use crate::c_std::{Uint128, Uint512}; -use std::convert::TryFrom; - -#[cw_serde] -pub enum Dex { - SecretSwap, - SiennaSwap, - ShadeSwap, - Mint, -} - -#[cw_serde] -pub struct TradingPair { - pub dex: Dex, - pub contract: Contract, - pub asset: Snip20Asset, -} - -/* give_amount into give_pool - * returns how much to be received from take_pool - */ - -pub fn pool_take_amount(give_amount: Uint128, give_pool: Uint128, take_pool: Uint128) -> Uint128 { - Uint128::new( - take_pool.u128() - give_pool.u128() * take_pool.u128() / (give_pool + give_amount).u128(), - ) -} - -pub fn aggregate_price( - deps: &Deps, - pairs: Vec, - sscrt: Contract, - band: Contract, -) -> StdResult { - // indices will align with - let mut amounts_per_scrt = vec![]; - let mut pool_sizes: Vec = vec![]; - - for pair in pairs.clone() { - match &pair.dex { - Dex::SecretSwap => { - amounts_per_scrt.push(Uint512::from( - normalize_price( - secretswap::amount_per_scrt(&deps, pair.clone(), sscrt.clone())?, - pair.asset.token_info.decimals, - ) - .u128(), - )); - pool_sizes.push(Uint512::from(secretswap::pool_cp(&deps, pair)?.u128())); - } - Dex::SiennaSwap => { - amounts_per_scrt.push(Uint512::from( - normalize_price( - sienna::amount_per_scrt(&deps, pair.clone(), sscrt.clone())?, - pair.asset.token_info.decimals, - ) - .u128(), - )); - pool_sizes.push(Uint512::from(sienna::pool_cp(&deps, pair)?.u128())); - } - _ => {} /* - ShadeSwap => { - prices.push(shadeswap::price(&deps, pair.clone(), sscrt.clone(), band.clone())?); - pool_sizes.push(shadeswap::pool_size(&deps, pair)?); - return Err(StdErr::generic_err("ShadeSwap Unavailable")); - }, - */ - } - } - - let combined_cp: Uint512 = pool_sizes.iter().sum(); - - let weighted_sum: Uint512 = amounts_per_scrt - .into_iter() - .zip(pool_sizes.into_iter()) - .map(|(a, s)| a * s / combined_cp) - .sum(); - - // Translate price from SHD/SCRT -> SHD/USD - // And normalize to * 10^18 - let price = translate_price( - band::reference_data(deps, "SCRT".to_string(), "USD".to_string(), band)?.rate, - Uint128::new(Uint128::try_from(weighted_sum)?.u128()), - ); - - Ok(price) -} - -pub fn best_price( - deps: &Deps, - pairs: Vec, - sscrt: Contract, - band: Contract, -) -> StdResult<(Uint128, TradingPair)> { - // indices will align with - let mut results = vec![]; - - for pair in &pairs { - match pair.clone().dex { - Dex::SecretSwap => { - results.push(secretswap::price( - &deps, - pair.clone(), - sscrt.clone(), - band.clone(), - )?); - } - Dex::SiennaSwap => { - results.push(sienna::price( - &deps, - pair.clone(), - sscrt.clone(), - band.clone(), - )?); - } - _ => {} /* - ShadeSwap => { - return Err(StdErr::generic_err("ShadeSwap Unavailable")); - }, - */ - } - } - let max_amount = results.iter().max().unwrap(); - let index = results.iter().position(|e| e == max_amount).unwrap(); - let scrt_result = band::reference_data(deps, "SCRT".to_string(), "USD".to_string(), band)?; - - Ok(( - translate_price(scrt_result.rate, *max_amount), - pairs[index].clone(), - )) -} - -pub fn price( - deps: &Deps, - pair: TradingPair, - sscrt: Contract, - band: Contract, -) -> StdResult { - match pair.clone().dex { - Dex::SecretSwap => Ok(secretswap::price( - &deps, - pair.clone(), - sscrt.clone(), - band.clone(), - )?), - Dex::SiennaSwap => Ok(sienna::price( - &deps, - pair.clone(), - sscrt.clone(), - band.clone(), - )?), - _ => return Err(StdError::generic_err("ShadeSwap not implemented")), /* - ShadeSwap => { - return Err(StdErr::generic_err("ShadeSwap Unavailable")); - }, - */ - } -} diff --git a/packages/shade_protocol/src/contract_interfaces/dex/mod.rs b/packages/shade_protocol/src/contract_interfaces/dex/mod.rs deleted file mode 100644 index 3480d4a..0000000 --- a/packages/shade_protocol/src/contract_interfaces/dex/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[cfg(feature = "dex")] -pub mod secretswap; - -#[cfg(feature = "dex")] -pub mod shadeswap; - -#[cfg(feature = "dex")] -pub mod sienna; - -#[cfg(feature = "dex")] -pub mod dex; diff --git a/packages/shade_protocol/src/contract_interfaces/dex/secretswap.rs b/packages/shade_protocol/src/contract_interfaces/dex/secretswap.rs deleted file mode 100644 index 37412a8..0000000 --- a/packages/shade_protocol/src/contract_interfaces/dex/secretswap.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::{ - c_std::{Addr, Deps, StdResult, Uint128}, - contract_interfaces::{dex::dex, oracles::band}, - utils::{ - asset::Contract, - price::{normalize_price, translate_price}, - }, -}; - -use crate::utils::Query; -use cosmwasm_schema::cw_serde; - -#[cw_serde] -pub struct Token { - pub contract_addr: Addr, - pub token_code_hash: String, - pub viewing_key: String, -} - -#[cw_serde] -pub struct AssetInfo { - pub token: Token, -} - -#[cw_serde] -pub struct Asset { - pub amount: Uint128, - pub info: AssetInfo, -} - -#[cw_serde] -pub struct Simulation { - pub offer_asset: Asset, -} - -#[cw_serde] -pub enum PairQuery { - Pair {}, - Pool {}, - Simulation { offer_asset: Asset }, - //ReverseSimulation {}, -} - -impl Query for PairQuery { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub struct SimulationResponse { - pub return_amount: Uint128, - pub spread_amount: Uint128, - pub commission_amount: Uint128, -} - -#[cw_serde] -pub struct PairResponse { - pub asset_infos: Vec, - pub contract_addr: Addr, - pub liquidity_token: Addr, - pub token_code_hash: String, - pub asset0_volume: Uint128, - pub asset1_volume: Uint128, - pub factory: Contract, -} - -#[cw_serde] -pub struct PoolResponse { - pub assets: Vec, - pub total_share: Uint128, -} - -#[cw_serde] -pub struct CallbackMsg { - pub swap: CallbackSwap, -} -#[cw_serde] -pub struct CallbackSwap { - pub expected_return: Uint128, -} - -/*pub fn is_pair( - deps: DepsMut, - pair: Contract, -) -> StdResult { - Ok( - match (PairQuery::Pair {}).query::(&deps.querier, &pair) { - Ok(_) => true, - Err(_) => false, - }, - ) -}*/ - -pub fn price( - deps: &Deps, - pair: dex::TradingPair, - sscrt: Contract, - band: Contract, -) -> StdResult { - let scrt_result = band::reference_data(deps, "SCRT".to_string(), "USD".to_string(), band)?; - - // SCRT-USD / SCRT-symbol - Ok(translate_price( - scrt_result.rate, - normalize_price( - amount_per_scrt(&deps, pair.clone(), sscrt)?, - pair.asset.token_info.decimals, - ), - )) -} - -pub fn amount_per_scrt(deps: &Deps, pair: dex::TradingPair, sscrt: Contract) -> StdResult { - let response: SimulationResponse = PairQuery::Simulation { - offer_asset: Asset { - amount: Uint128::new(1_000_000), // 1 sSCRT (6 decimals) - info: AssetInfo { - token: Token { - contract_addr: sscrt.address, - token_code_hash: sscrt.code_hash, - viewing_key: "SecretSwap".to_string(), - }, - }, - }, - } - .query(&deps.querier, &pair.contract)?; - - Ok(response.return_amount) -} - -pub fn pool_cp(deps: &Deps, pair: dex::TradingPair) -> StdResult { - let pool: PoolResponse = PairQuery::Pool {}.query(&deps.querier, &pair.contract)?; - - // Constant Product - Ok(Uint128::new( - pool.assets[0].amount.u128() * pool.assets[1].amount.u128(), - )) -} diff --git a/packages/shade_protocol/src/contract_interfaces/dex/shadeswap.rs b/packages/shade_protocol/src/contract_interfaces/dex/shadeswap.rs deleted file mode 100644 index feba1f5..0000000 --- a/packages/shade_protocol/src/contract_interfaces/dex/shadeswap.rs +++ /dev/null @@ -1,229 +0,0 @@ -use crate::{ - c_std::{Addr, Binary, Uint128}, - utils::{ - asset::Contract, - Query, - }, -}; -use cosmwasm_schema::cw_serde; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -/* -#[cw_serde] -pub struct Token { - pub contract_addr: Addr, - pub token_code_hash: String, - pub viewing_key: String, -} - -#[cw_serde] -pub struct AssetInfo { - pub token: Token, -} - -#[cw_serde] -pub struct Asset { - pub amount: Uint128, - pub info: AssetInfo, -} - -#[cw_serde] -pub struct Simulation { - pub offer_asset: Asset, -} -*/ -#[cw_serde] -pub struct ContractLink { - pub address: Addr, - pub code_hash: String, -} - -#[cw_serde] -pub enum PairQuery { - GetPairInfo {}, - GetEstimatedPrice { offer: TokenAmount }, -} - -impl Query for PairQuery { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum TokenType { - CustomToken { - contract_addr: Addr, - token_code_hash: String, - }, - NativeToken { - denom: String, - }, -} - -#[cw_serde] -pub struct TokenPair { - pub token_0: TokenType, - pub token_1: TokenType, -} - -/* -#[cw_serde] -pub struct SimulationResponse { - pub return_amount: Uint128, - pub spread_amount: Uint128, - pub commission_amount: Uint128, -} -*/ - -#[cw_serde] -pub struct PairInfoResponse { - pub liquidity_token: Contract, - pub factory: Contract, - pub pair: TokenPair, - pub amount_0: Uint128, - pub amount_1: Uint128, - pub total_liquidity: Uint128, - pub contract_version: u32, -} - -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsgResponse { - GetPairInfo { - liquidity_token: Contract, - factory: Contract, - pair: TokenPair, - amount_0: Uint128, - amount_1: Uint128, - total_liquidity: Uint128, - contract_version: u32, - }, - GetTradeHistory { - data: Vec, - }, - GetWhiteListAddress { - addresses: Vec, - }, - GetTradeCount { - count: u64, - }, - GetAdminAddress { - address: Addr, - }, - GetClaimReward { - amount: Uint128, - }, - StakingContractInfo { - staking_contract: Contract, - }, - EstimatedPrice { - estimated_price: Uint128, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct TokenAmount { - pub token: TokenType, - pub amount: Uint128, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct SwapTokens { - pub expected_return: Option, - pub to: Option, - pub router_link: Option, - pub callback_signature: Option, -} - -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] -pub struct TradeHistory { - pub price: Uint128, - pub amount: Uint128, - pub timestamp: u64, - pub direction: String, - pub total_fee_amount: Uint128, - pub lp_fee_amount: Uint128, - pub shade_dao_fee_amount: Uint128, -} - -/* -#[cw_serde] -pub struct PoolResponse { - pub assets: Vec, - pub total_share: Uint128, -} -*/ - -/*pub fn is_pair(deps: DepsMut, pair: Contract) -> StdResult { - Ok( - match (PairQuery::PairInfo).query::(&deps.querier, &pair) { - Ok(_) => true, - Err(_) => false, - }, - ) -}*/ - -/* -pub fn price( - deps: Deps, - pair: dex::TradingPair, - sscrt: Contract, - band: Contract, -) -> StdResult { - - let scrt_result = band::reference_data(deps, "SCRT".to_string(), "USD".to_string(), band)?; - - // SCRT-USD / SCRT-symbol - Ok(translate_price( - scrt_result.rate, - normalize_price( - amount_per_scrt(deps, pair.clone(), sscrt)?, - pair.asset.token_info.decimals, - ), - )) -} - -pub fn amount_per_scrt( - deps: Deps, - pair: dex::TradingPair, - sscrt: Contract, -) -> StdResult { - - let response: SimulationResponse = PairQuery::Simulation { - offer_asset: Asset { - amount: Uint128::new(1_000_000), // 1 sSCRT (6 decimals) - info: AssetInfo { - token: Token { - contract_addr: sscrt.address, - token_code_hash: sscrt.code_hash, - viewing_key: "SecretSwap".to_string(), - }, - }, - }, - } - .query( - &deps.querier, - pair.contract.code_hash, - pair.contract.address, - )?; - - Ok(response.return_amount) -} - -pub fn pool_cp( - deps: Deps, - pair: dex::TradingPair, -) -> StdResult { - let pool: PoolResponse = PairQuery::Pool {}.query( - &deps.querier, - pair.contract.code_hash, - pair.contract.address, - )?; - - // Constant Product - Ok(Uint128::new(pool.assets[0].amount.u128() * pool.assets[1].amount.u128())) -} -*/ diff --git a/packages/shade_protocol/src/contract_interfaces/dex/sienna.rs b/packages/shade_protocol/src/contract_interfaces/dex/sienna.rs deleted file mode 100644 index 4d86271..0000000 --- a/packages/shade_protocol/src/contract_interfaces/dex/sienna.rs +++ /dev/null @@ -1,170 +0,0 @@ -use crate::{ - c_std::{Addr, Deps, StdResult, Uint128}, - contract_interfaces::{dex::dex, oracles::band}, - utils::{ - asset::Contract, - price::{normalize_price, translate_price}, - Query, - }, -}; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::Binary; - -#[cw_serde] -pub enum TokenType { - CustomToken { - contract_addr: Addr, - token_code_hash: String, - }, - NativeToken { - denom: String, - }, -} - -#[cw_serde] -pub struct Pair { - pub token_0: TokenType, - pub token_1: TokenType, -} - -/* -#[cw_serde] -pub struct AssetInfo { - pub token: Token, -} -*/ - -#[cw_serde] -pub struct TokenTypeAmount { - pub amount: Uint128, - pub token: TokenType, -} - -#[cw_serde] -pub struct Swap { - pub send: SwapOffer, -} - -#[cw_serde] -pub struct SwapOffer { - pub recipient: Addr, - pub amount: Uint128, - pub msg: Binary, -} - -#[cw_serde] -pub enum ReceiverCallbackMsg { - Swap { - expected_return: Option, - to: Option, - }, -} - -#[cw_serde] -pub struct CallbackMsg { - pub swap: CallbackSwap, -} - -#[cw_serde] -pub struct CallbackSwap { - pub expected_return: Uint128, -} - -#[cw_serde] -pub struct SwapSimulation { - pub offer: TokenTypeAmount, -} - -#[cw_serde] -pub enum PairQuery { - /* - Pool {}, - */ - PairInfo, - SwapSimulation { offer: TokenTypeAmount }, -} - -impl Query for PairQuery { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub struct SimulationResponse { - pub return_amount: Uint128, - pub spread_amount: Uint128, - pub commission_amount: Uint128, -} - -#[cw_serde] -pub struct PairInfo { - pub liquidity_token: Contract, - pub factory: Contract, - pub pair: Pair, - pub amount_0: Uint128, - pub amount_1: Uint128, - pub total_liquidity: Uint128, - pub contract_version: u32, -} - -#[cw_serde] -pub struct PairInfoResponse { - pub pair_info: PairInfo, -} - -/*pub fn is_pair( - deps: DepsMut, - pair: Contract, -) -> StdResult { - Ok( - match (PairQuery::PairInfo).query::( - &deps.querier, - &pair - ) { - Ok(_) => true, - Err(_) => false, - }, - ) -}*/ - -pub fn price( - deps: &Deps, - pair: dex::TradingPair, - sscrt: Contract, - band: Contract, -) -> StdResult { - // TODO: This should be passed in to avoid multipl BAND SCRT queries in one query - let scrt_result = band::reference_data(deps, "SCRT".to_string(), "USD".to_string(), band)?; - - // SCRT-USD / SCRT-symbol - Ok(translate_price( - scrt_result.rate, - normalize_price( - amount_per_scrt(deps, pair.clone(), sscrt)?, - pair.asset.token_info.decimals, - ), - )) -} - -pub fn amount_per_scrt(deps: &Deps, pair: dex::TradingPair, sscrt: Contract) -> StdResult { - let response: SimulationResponse = PairQuery::SwapSimulation { - offer: TokenTypeAmount { - amount: Uint128::new(1_000_000), // 1 sSCRT (6 decimals) - token: TokenType::CustomToken { - contract_addr: sscrt.address, - token_code_hash: sscrt.code_hash, - }, - }, - } - .query(&deps.querier, &pair.contract)?; - - Ok(response.return_amount) -} - -pub fn pool_cp(deps: &Deps, pair: dex::TradingPair) -> StdResult { - let pair_info: PairInfoResponse = PairQuery::PairInfo.query(&deps.querier, &pair.contract)?; - - // Constant Product - Ok(Uint128::new( - pair_info.pair_info.amount_0.u128() * pair_info.pair_info.amount_1.u128(), - )) -} diff --git a/packages/shade_protocol/src/contract_interfaces/governance/assembly.rs b/packages/shade_protocol/src/contract_interfaces/governance/assembly.rs deleted file mode 100644 index 47bed68..0000000 --- a/packages/shade_protocol/src/contract_interfaces/governance/assembly.rs +++ /dev/null @@ -1,184 +0,0 @@ -use crate::{ - c_std::{Addr, StdResult, Storage}, - contract_interfaces::governance::stored_id::ID, - utils::flexible_msg::FlexibleMsg, -}; - -use cosmwasm_schema::cw_serde; -use secret_storage_plus::Map; - -#[cfg(feature = "governance-impl")] -use crate::utils::storage::plus::MapStorage; - -#[cw_serde] -pub struct Assembly { - // Readable name - pub name: String, - // Description of the assembly, preferably in base64 - pub metadata: String, - // List of members in assembly - pub members: Vec, - // Selected profile - pub profile: u16, -} - -#[cfg(feature = "governance-impl")] -impl Assembly { - pub fn load(storage: &dyn Storage, id: u16) -> StdResult { - let desc = Self::description(storage, id)?; - let data = Self::data(storage, id)?; - - Ok(Self { - name: desc.name, - metadata: desc.metadata, - members: data.members, - profile: data.profile, - }) - } - - pub fn may_load(storage: &dyn Storage, id: u16) -> StdResult> { - if id > ID::assembly(storage)? { - return Ok(None); - } - Ok(Some(Self::load(storage, id)?)) - } - - pub fn save(&self, storage: &mut dyn Storage, id: u16) -> StdResult<()> { - AssemblyData { - members: self.members.clone(), - profile: self.profile, - } - .save(storage, id)?; - - AssemblyDescription { - name: self.name.clone(), - metadata: self.metadata.clone(), - } - .save(storage, id)?; - - Ok(()) - } - - pub fn data(storage: &dyn Storage, id: u16) -> StdResult { - AssemblyData::load(storage, id) - } - - pub fn save_data(storage: &mut dyn Storage, id: u16, data: AssemblyData) -> StdResult<()> { - data.save(storage, id) - } - - pub fn description(storage: &dyn Storage, id: u16) -> StdResult { - AssemblyDescription::load(storage, id) - } - - pub fn save_description( - storage: &mut dyn Storage, - id: u16, - desc: AssemblyDescription, - ) -> StdResult<()> { - desc.save(storage, id) - } -} - -#[cfg(feature = "governance-impl")] -#[cw_serde] -pub struct AssemblyData { - pub members: Vec, - pub profile: u16, -} - -#[cfg(feature = "governance-impl")] -impl MapStorage<'static, u16> for AssemblyData { - const MAP: Map<'static, u16, Self> = Map::new("assembly_data-"); -} - -#[cfg(feature = "governance-impl")] -#[cw_serde] -pub struct AssemblyDescription { - pub name: String, - pub metadata: String, -} - -#[cfg(feature = "governance-impl")] -impl MapStorage<'static, u16> for AssemblyDescription { - const MAP: Map<'static, u16, Self> = Map::new("assembly_description-"); -} - -#[cw_serde] // A generic msg is created at init, its a black msg where the variable is the start -pub struct AssemblyMsg { - pub name: String, - // Assemblies allowed to call this msg - pub assemblies: Vec, - // ExecuteMsg template - pub msg: FlexibleMsg, -} - -#[cfg(feature = "governance-impl")] -impl AssemblyMsg { - pub fn load(storage: &dyn Storage, id: u16) -> StdResult { - let desc = Self::description(storage, id)?; - let data = Self::data(storage, id)?; - - Ok(Self { - name: desc, - assemblies: data.assemblies, - msg: data.msg, - }) - } - - pub fn may_load(storage: &dyn Storage, id: u16) -> StdResult> { - if id > ID::assembly_msg(storage)? { - return Ok(None); - } - Ok(Some(Self::load(storage, id)?)) - } - - pub fn save(&self, storage: &mut dyn Storage, id: u16) -> StdResult<()> { - AssemblyMsgData { - assemblies: self.assemblies.clone(), - msg: self.msg.clone(), - } - .save(storage, id)?; - - AssemblyMsgDescription(self.name.clone()).save(storage, id)?; - - Ok(()) - } - - pub fn data(storage: &dyn Storage, id: u16) -> StdResult { - AssemblyMsgData::load(storage, id) - } - - pub fn save_data(storage: &mut dyn Storage, id: u16, data: AssemblyMsgData) -> StdResult<()> { - data.save(storage, id) - } - - pub fn description(storage: &dyn Storage, id: u16) -> StdResult { - Ok(AssemblyMsgDescription::load(storage, id)?.0) - } - - pub fn save_description(storage: &mut dyn Storage, id: u16, desc: String) -> StdResult<()> { - AssemblyMsgDescription(desc).save(storage, id) - } -} - -#[cfg(feature = "governance-impl")] -#[cw_serde] -pub struct AssemblyMsgData { - pub assemblies: Vec, - pub msg: FlexibleMsg, -} - -#[cfg(feature = "governance-impl")] -impl MapStorage<'static, u16> for AssemblyMsgData { - const MAP: Map<'static, u16, Self> = Map::new("assembly_msg_data-"); -} - -#[cfg(feature = "governance-impl")] -#[cw_serde] -struct AssemblyMsgDescription(pub String); - -#[cfg(feature = "governance-impl")] -impl MapStorage<'static, u16> for AssemblyMsgDescription { - const MAP: Map<'static, u16, Self> = Map::new("assembly_msg_description-"); -} diff --git a/packages/shade_protocol/src/contract_interfaces/governance/contract.rs b/packages/shade_protocol/src/contract_interfaces/governance/contract.rs deleted file mode 100644 index b5e39a1..0000000 --- a/packages/shade_protocol/src/contract_interfaces/governance/contract.rs +++ /dev/null @@ -1,105 +0,0 @@ -use crate::{ - c_std::{StdResult, Storage}, - contract_interfaces::governance::stored_id::ID, - utils::asset::Contract, -}; - -use crate::utils::storage::plus::MapStorage; -use cosmwasm_schema::cw_serde; -use secret_storage_plus::Map; - -#[cw_serde] -pub struct AllowedContract { - pub name: String, - pub metadata: String, - // If none then anyone can use it - #[serde(skip_serializing_if = "Option::is_none")] - pub assemblies: Option>, - pub contract: Contract, -} - -#[cfg(feature = "governance-impl")] -impl AllowedContract { - pub fn load(storage: &dyn Storage, id: u16) -> StdResult { - let desc = Self::description(storage, id)?; - let data = Self::data(storage, id)?; - - Ok(Self { - name: desc.name, - metadata: desc.metadata, - contract: data.contract, - assemblies: data.assemblies, - }) - } - - pub fn may_load(storage: &dyn Storage, id: u16) -> StdResult> { - if id > ID::contract(storage)? { - return Ok(None); - } - Ok(Some(Self::load(storage, id)?)) - } - - pub fn save(&self, storage: &mut dyn Storage, id: u16) -> StdResult<()> { - AllowedContractData { - contract: self.contract.clone(), - assemblies: self.assemblies.clone(), - } - .save(storage, id)?; - - AllowedContractDescription { - name: self.name.clone(), - metadata: self.metadata.clone(), - } - .save(storage, id)?; - - Ok(()) - } - - pub fn data(storage: &dyn Storage, id: u16) -> StdResult { - AllowedContractData::load(storage, id) - } - - pub fn save_data( - storage: &mut dyn Storage, - id: u16, - data: AllowedContractData, - ) -> StdResult<()> { - data.save(storage, id) - } - - pub fn description(storage: &dyn Storage, id: u16) -> StdResult { - AllowedContractDescription::load(storage, id) - } - - pub fn save_description( - storage: &mut dyn Storage, - id: u16, - desc: AllowedContractDescription, - ) -> StdResult<()> { - desc.save(storage, id) - } -} - -#[cfg(feature = "governance-impl")] -#[cw_serde] -pub struct AllowedContractData { - pub contract: Contract, - pub assemblies: Option>, -} - -#[cfg(feature = "governance-impl")] -impl MapStorage<'static, u16> for AllowedContractData { - const MAP: Map<'static, u16, Self> = Map::new("allowed_contract_data-"); -} - -#[cfg(feature = "governance-impl")] -#[cw_serde] -pub struct AllowedContractDescription { - pub name: String, - pub metadata: String, -} - -#[cfg(feature = "governance-impl")] -impl MapStorage<'static, u16> for AllowedContractDescription { - const MAP: Map<'static, u16, Self> = Map::new("allowed_contract_description-"); -} diff --git a/packages/shade_protocol/src/contract_interfaces/governance/errors.rs b/packages/shade_protocol/src/contract_interfaces/governance/errors.rs deleted file mode 100644 index dd79c96..0000000 --- a/packages/shade_protocol/src/contract_interfaces/governance/errors.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::errors; - -errors!("governance"; - MissingFundingToken, "Funding token must be set", missing_funding_token, - MissingVotingToken, "Voting token must be set", missing_voting_token, - UnauthorizedVK, "Viewing key is invalid", bad_vk, - PermitRevoked, "Permit key was revoked", bad_pkey, - WasmEventNotFound, "Could not find \"wasm\" event", bad_event, - UnrecognizedReplyID, "Reply ID {} was not recognized", wrong_reply, - MissingMigrationEvents, "{} not found for instantiation", missing_migration_event, - ItemNotFound, "ID {} of type {} not found", item_not_found, - VotingEnded, "Voting ended at {}", voting_ended, - NotAssemblyVoting, "Proposal is not in assembly vote phase", not_assembly_voting, - AssemblyVoteQty, "Assembly vote can only be one", assembly_vote_qty, - MsgNotInAssembly, "Msg not allowed in assembly", msg_not_in_assembly, - MsgNotInContract, "Msg not allowed in contract", msg_not_in_contract, - ContractDisabled, "Contract is disabled", contract_disabled, - MigrationNotStarted, "Migration has not started", migration_not_started, - NoMigrationTarget, "No migration target found", migration_tartet, - NotMigrator, "Can only be called by old governance", no_migrator, - AssembliesLimited, "Runtime state only permits specific assemblies to operate", assemblies_limited, - ContractMigrated, "Contract has migrated and cannot be interacted with anymore", migrated, - ProfileDisabled, "Profile {} is disabled", profile_disabled, - NotInAssembly, "{} is not in Assembly {}", not_in_assembly, - NotSelf, "Message signer can only be Governance", not_self, - ProposalNotPassed, "Proposal has not passed", not_passed, - CannotCancel, "Proposal can be canceled at {}", cannot_cancel, - CannotUpdate, "Proposal in status {} cannot be updated until {}", cannot_update, - UnexpectedQueryResponse, "Unexpected query response", unexpected_query_response, - StateCannotUpdate, "Current proposal state cannot be updated through this message", state_update, - FundingMsgNotSet, "Funding message was not set", funding_msg_not_set, - FundingLimitReached, "Funding time limit has been reached", funding_limit_reached, - CompletelyFunded, "Proposal is completely funded", completely_funded, - NoFundingProfile, "Funding profile setting was removed", no_funding_profile, - NoFundingState, "Proposal not in funding state", no_funding_state, - FundingNotClaimable, "Cannot claim funding", funding_not_claimable, - FundingClaimed, "Funding already claimed", funding_claimed, - FundingNothing, "Noting to claim", funding_nothing, - SenderMustBeFunding, "Sender must be the funding token", sender_funding, - VotingMoreThanBalance, "Total vote is greater than available balance", voting_balance, - VotingMsgNotSet, "Msg missing voting information", voting_msg, - VotingTimeReached, "Voting time was reached on {}", voting_time, - VotingNotInState, "Not in public voting phase", voting_not_state -); diff --git a/packages/shade_protocol/src/contract_interfaces/governance/mod.rs b/packages/shade_protocol/src/contract_interfaces/governance/mod.rs deleted file mode 100644 index 7d0a99a..0000000 --- a/packages/shade_protocol/src/contract_interfaces/governance/mod.rs +++ /dev/null @@ -1,455 +0,0 @@ -pub mod assembly; -pub mod contract; -pub mod errors; -pub mod profile; -pub mod proposal; -#[cfg(feature = "governance-impl")] -pub mod stored_id; -pub mod vote; - -use crate::{ - c_std::{Addr, Binary, Uint128}, - contract_interfaces::governance::{ - assembly::{Assembly, AssemblyMsg}, - contract::AllowedContract, - profile::{Profile, UpdateProfile}, - proposal::{Proposal, ProposalMsg}, - vote::Vote, - }, - utils::{asset::Contract, generic_response::ResponseStatus}, -}; - -use crate::{ - governance::proposal::Funding, - query_auth::QueryPermit, - utils::{ExecuteCallback, InstantiateCallback, Query}, -}; -use cosmwasm_schema::cw_serde; -use secret_storage_plus::{Item, Json}; - -#[cfg(feature = "governance-impl")] -use crate::utils::storage::plus::ItemStorage; - -// TODO: add errors - -// Admin command variable spot -pub const MSG_VARIABLE: &str = "{~}"; - -#[cw_serde] -pub struct Config { - pub query: Contract, - pub treasury: Addr, - // When public voting is enabled, a voting token is expected - pub vote_token: Option, - // When funding is enabled, a funding token is expected - pub funding_token: Option, - - // Migration information - pub migrated_from: Option, - pub migrated_to: Option, -} - -#[cfg(feature = "governance-impl")] -impl ItemStorage for Config { - const ITEM: Item<'static, Self, Json> = Item::new("config-"); -} - -// Used for original instantiation -#[cw_serde] -pub struct AssemblyInit { - pub admin_members: Vec, - pub admin_profile: Profile, - pub public_profile: Profile, -} - -// Used for migration instantiation -#[cw_serde] -pub struct MigrationInit { - pub source: Contract, - pub assembly: u16, - pub assembly_msg: u16, - pub profile: u16, - pub contract: u16, -} - -#[cw_serde] -pub struct InstantiateMsg { - pub treasury: Addr, - pub query_auth: Contract, - - // Admin rules - pub assemblies: Option, - - // Token rules - pub funding_token: Option, - pub vote_token: Option, - - // Migration data - pub migrator: Option, -} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum RuntimeState { - // Run like normal - Normal, - // Allow only specific assemblies and admin - SpecificAssemblies { assemblies: Vec }, - // Migrated - points to the new version - Migrated, -} - -#[cfg(feature = "governance-impl")] -impl ItemStorage for RuntimeState { - const ITEM: Item<'static, Self, Json> = Item::new("runtime-state-"); -} - -#[cw_serde] -pub enum MigrationDataAsk { - Assembly, - AssemblyMsg, - Profile, - Contract, -} - -#[cw_serde] -pub enum MigrationData { - Assembly { data: Vec<(u16, Assembly)> }, - AssemblyMsg { data: Vec<(u16, AssemblyMsg)> }, - Profile { data: Vec<(u16, Profile)> }, - Contract { data: Vec<(u16, AllowedContract)> }, -} - -#[cw_serde] -pub enum ExecuteMsg { - // Internal config - SetConfig { - query_auth: Option, - treasury: Option, - funding_token: Option, - vote_token: Option, - padding: Option, - }, - SetRuntimeState { - state: RuntimeState, - padding: Option, - }, - - // Proposal interaction - /// Triggers the proposal when the MSG is approved - Trigger { - //TODO: Must be deprecated for v1 - proposal: u32, - padding: Option, - }, - /// Cancels the proposal if the msg keeps failing - Cancel { - //TODO: Must be deprecated for v1 - proposal: u32, - padding: Option, - }, - /// Forces a proposal update, - /// proposals automatically update on interaction - /// but this is a cheaper alternative - Update { - proposal: u32, - padding: Option, - }, - /// Funds a proposal, msg is a prop ID - Receive { - sender: Addr, - from: Addr, - amount: Uint128, - msg: Option, - memo: Option, - padding: Option, - }, - ClaimFunding { - id: u32, - }, - /// Votes on a assembly vote - AssemblyVote { - proposal: u32, - vote: Vote, - padding: Option, - }, - /// Votes on voting token - ReceiveBalance { - sender: Addr, - msg: Option, - balance: Uint128, - memo: Option, - }, - - // Assemblies - /// Creates a proposal under a assembly - AssemblyProposal { - assembly: u16, - title: String, - metadata: String, - - // Optionals, if none the proposal is assumed to be a text proposal - msgs: Option>, - padding: Option, - }, - - /// Creates a new assembly - AddAssembly { - name: String, - metadata: String, - members: Vec, - profile: u16, - padding: Option, - }, - /// Edits an existing assembly - SetAssembly { - id: u16, - name: Option, - metadata: Option, - members: Option>, - profile: Option, - padding: Option, - }, - - // AssemblyMsgs - /// Creates a new assembly message and its allowed users - AddAssemblyMsg { - name: String, - msg: String, - assemblies: Vec, - padding: Option, - }, - /// Edits an existing assembly msg - SetAssemblyMsg { - id: u16, - name: Option, - msg: Option, - assemblies: Option>, - padding: Option, - }, - AddAssemblyMsgAssemblies { - id: u16, - assemblies: Vec, - }, - - // Profiles - /// Creates a new profile that can be added to assemblys - AddProfile { - profile: Profile, - padding: Option, - }, - /// Edits an already existing profile and the assemblys using the profile - SetProfile { - id: u16, - profile: UpdateProfile, - padding: Option, - }, - - // Contracts - AddContract { - name: String, - metadata: String, - contract: Contract, - assemblies: Option>, - padding: Option, - }, - SetContract { - id: u16, - name: Option, - metadata: Option, - contract: Option, - disable_assemblies: bool, - assemblies: Option>, - padding: Option, - }, - AddContractAssemblies { - id: u16, - assemblies: Vec, - }, - // Migrations - // Export total numeric IDs - // Committee, msg, profile and contract keys must be exported - // Create a struct that stores the last migrated IDs - // Enum for migration targets - // migrate gives an array of items with their appropriate IDs - // Migrate Committee, Msg, Profile and Contract - // When receiving migration data, if given ID is greater then ignore - - // Add functions for exporting lists of data into the needed contracts - Migrate { - id: u64, - label: String, - code_hash: String, - }, - MigrateData { - data: MigrationDataAsk, - total: u16, - }, - ReceiveMigrationData { - data: MigrationData, - }, -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteAnswer { - SetConfig { status: ResponseStatus }, - SetRuntimeState { status: ResponseStatus }, - Proposal { status: ResponseStatus }, - ReceiveBalance { status: ResponseStatus }, - Trigger { status: ResponseStatus }, - Cancel { status: ResponseStatus }, - Update { status: ResponseStatus }, - Receive { status: ResponseStatus }, - ClaimFunding { status: ResponseStatus }, - AssemblyVote { status: ResponseStatus }, - AssemblyProposal { status: ResponseStatus }, - AddAssembly { status: ResponseStatus }, - SetAssembly { status: ResponseStatus }, - AddAssemblyMsg { status: ResponseStatus }, - SetAssemblyMsg { status: ResponseStatus }, - AddProfile { status: ResponseStatus }, - SetProfile { status: ResponseStatus }, - AddContract { status: ResponseStatus }, - SetContract { status: ResponseStatus }, - AddContractAssemblies { status: ResponseStatus }, - Migrate { status: ResponseStatus }, - MigrateData { status: ResponseStatus }, - ReceiveMigrationData { status: ResponseStatus }, -} - -#[cw_serde] -pub struct Pagination { - pub page: u16, - pub amount: u32, -} - -#[cw_serde] -pub enum AuthQuery { - Proposals { pagination: Pagination }, - AssemblyVotes { pagination: Pagination }, - Funding { pagination: Pagination }, - Votes { pagination: Pagination }, -} - -#[remain::sorted] -#[cw_serde] -pub struct QueryData {} - -#[cw_serde] -pub enum QueryMsg { - Config {}, - - TotalProposals {}, - - Proposals { - start: u32, - end: u32, - }, - - TotalAssemblies {}, - - Assemblies { - start: u16, - end: u16, - }, - - TotalAssemblyMsgs {}, - - AssemblyMsgs { - start: u16, - end: u16, - }, - - TotalProfiles {}, - - Profiles { - start: u16, - end: u16, - }, - - TotalContracts {}, - - Contracts { - start: u16, - end: u16, - }, - - WithVK { - user: Addr, - key: String, - query: AuthQuery, - }, - - WithPermit { - permit: QueryPermit, - query: AuthQuery, - }, -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub struct ResponseWithID { - pub prop_id: u32, - pub data: T, -} - -#[cw_serde] -pub enum QueryAnswer { - Config { - config: Config, - }, - - Proposals { - props: Vec, - }, - - Assemblies { - assemblies: Vec, - }, - - AssemblyMsgs { - msgs: Vec, - }, - - Profiles { - profiles: Vec, - }, - - Contracts { - contracts: Vec, - }, - - Total { - total: u32, - }, - - UserProposals { - props: Vec>, - total: u32, - }, - - UserAssemblyVotes { - votes: Vec>, - total: u32, - }, - - UserFunding { - funds: Vec>, - total: u32, - }, - - UserVotes { - votes: Vec>, - total: u32, - }, -} diff --git a/packages/shade_protocol/src/contract_interfaces/governance/profile.rs b/packages/shade_protocol/src/contract_interfaces/governance/profile.rs deleted file mode 100644 index e5bcdad..0000000 --- a/packages/shade_protocol/src/contract_interfaces/governance/profile.rs +++ /dev/null @@ -1,308 +0,0 @@ -use crate::{ - c_std::{StdError, StdResult, Storage, Uint128}, - contract_interfaces::governance::stored_id::ID, -}; - -use cosmwasm_schema::cw_serde; -use secret_storage_plus::Map; - -#[cfg(feature = "governance-impl")] -use crate::utils::storage::plus::{MapStorage, NaiveMapStorage}; - -/// Allow better control over the safety and privacy features that proposals will need if -/// Assemblys are implemented. If a profile is disabled then its assembly will also be disabled. -/// All percentages are taken as follows 100000 = 100% -#[cw_serde] -pub struct Profile { - pub name: String, - // State of the current profile and its subsequent assemblies - pub enabled: bool, - // Require assembly voting - #[serde(skip_serializing_if = "Option::is_none")] - pub assembly: Option, - // Require funding - #[serde(skip_serializing_if = "Option::is_none")] - pub funding: Option, - // Require token voting - #[serde(skip_serializing_if = "Option::is_none")] - pub token: Option, - // Once the contract is approved, theres a deadline for the tx to be executed and completed - // else it will just be canceled and assume that the tx failed - pub cancel_deadline: u64, -} - -const COMMITTEE_PROFILE_KEY: Map = Map::new("assembly_vote_profile-"); -const TOKEN_PROFILE_KEY: Map = Map::new("token_vote_profile-"); - -#[cfg(feature = "governance-impl")] -impl Profile { - pub fn load(storage: &dyn Storage, id: u16) -> StdResult { - let data = Self::data(storage, id)?; - - Ok(Self { - name: data.name, - enabled: data.enabled, - assembly: Self::assembly_voting(storage, id)?, - funding: Self::funding(storage, id)?, - token: Self::public_voting(storage, id)?, - cancel_deadline: data.cancel_deadline, - }) - } - - pub fn may_load(storage: &dyn Storage, id: u16) -> StdResult> { - if id > ID::profile(storage)? { - return Ok(None); - } - Ok(Some(Self::load(storage, id)?)) - } - - pub fn save(&self, storage: &mut dyn Storage, id: u16) -> StdResult<()> { - ProfileData { - name: self.name.clone(), - enabled: self.enabled, - cancel_deadline: self.cancel_deadline, - } - .save(storage, id)?; - - Self::save_assembly_voting(storage, id, self.assembly.clone())?; - - Self::save_public_voting(storage, id, self.token.clone())?; - - Self::save_funding(storage, id, self.funding.clone())?; - - Ok(()) - } - - pub fn data(storage: &dyn Storage, id: u16) -> StdResult { - ProfileData::load(storage, id) - } - - pub fn save_data(storage: &mut dyn Storage, id: u16, data: ProfileData) -> StdResult<()> { - data.save(storage, id) - } - - pub fn assembly_voting(storage: &dyn Storage, id: u16) -> StdResult> { - Ok(VoteProfileType::load(storage, COMMITTEE_PROFILE_KEY, id)?.0) - } - - pub fn save_assembly_voting( - storage: &mut dyn Storage, - id: u16, - assembly: Option, - ) -> StdResult<()> { - VoteProfileType(assembly).save(storage, COMMITTEE_PROFILE_KEY, id) - } - - pub fn public_voting(storage: &dyn Storage, id: u16) -> StdResult> { - Ok(VoteProfileType::load(storage, TOKEN_PROFILE_KEY, id)?.0) - } - - pub fn save_public_voting( - storage: &mut dyn Storage, - id: u16, - token: Option, - ) -> StdResult<()> { - VoteProfileType(token).save(storage, TOKEN_PROFILE_KEY, id) - } - - pub fn funding(storage: &dyn Storage, id: u16) -> StdResult> { - Ok(FundProfileType::load(storage, id)?.0) - } - - pub fn save_funding( - storage: &mut dyn Storage, - id: u16, - funding: Option, - ) -> StdResult<()> { - FundProfileType(funding).save(storage, id) - } -} - -#[cfg(feature = "governance-impl")] -#[cw_serde] -pub struct ProfileData { - pub name: String, - pub enabled: bool, - pub cancel_deadline: u64, -} - -#[cfg(feature = "governance-impl")] -impl MapStorage<'static, u16> for ProfileData { - const MAP: Map<'static, u16, Self> = Map::new("profile_data-"); -} - -#[cfg(feature = "governance-impl")] -#[cw_serde] // NOTE: 100% = Uint128::new(10000) -pub struct VoteProfile { - // Deadline for voting - pub deadline: u64, - // Expected participation threshold - pub threshold: Count, - // Expected yes votes - pub yes_threshold: Count, - // Expected veto votes - pub veto_threshold: Count, -} - -#[cfg(feature = "governance-impl")] -#[cw_serde] -struct VoteProfileType(pub Option); - -#[cfg(feature = "governance-impl")] -impl NaiveMapStorage<'static> for VoteProfileType {} - -#[cfg(feature = "governance-impl")] -#[cw_serde] -pub struct FundProfile { - // Deadline for funding - pub deadline: u64, - // Amount required to fund - pub required: Uint128, - // Display voter information - pub privacy: bool, - // Deposit loss on vetoed proposal - pub veto_deposit_loss: Uint128, -} - -#[cfg(feature = "governance-impl")] -#[cw_serde] -struct FundProfileType(pub Option); - -#[cfg(feature = "governance-impl")] -impl MapStorage<'static, u16> for FundProfileType { - const MAP: Map<'static, u16, Self> = Map::new("fund_profile-"); -} - -/// Helps simplify the given limits -#[cw_serde] -pub enum Count { - Percentage { percent: u16 }, - LiteralCount { count: Uint128 }, -} - -#[cw_serde] -pub struct UpdateProfile { - pub name: Option, - // State of the current profile and its subsequent assemblies - pub enabled: Option, - // Assembly status - pub disable_assembly: bool, - // Require assembly voting - pub assembly: Option, - // Funding status - pub disable_funding: bool, - // Require funding - pub funding: Option, - // Require token voting - pub disable_token: bool, - // Require token voting - pub token: Option, - // Once the contract is approved, theres a deadline for the tx to be executed and completed - // else it will just be canceled and assume that the tx failed - pub cancel_deadline: Option, -} - -#[cw_serde] -pub struct UpdateVoteProfile { - // Deadline for voting - pub deadline: Option, - // Expected participation threshold - pub threshold: Option, - // Expected yes votes - pub yes_threshold: Option, - // Expected veto votes - pub veto_threshold: Option, -} - -impl UpdateVoteProfile { - pub fn update_profile(&self, profile: &Option) -> StdResult { - let new_profile: VoteProfile; - - if let Some(profile) = profile { - new_profile = VoteProfile { - deadline: self.deadline.unwrap_or(profile.deadline), - threshold: self.threshold.clone().unwrap_or(profile.threshold.clone()), - yes_threshold: self - .yes_threshold - .clone() - .unwrap_or(profile.yes_threshold.clone()), - veto_threshold: self - .veto_threshold - .clone() - .unwrap_or(profile.veto_threshold.clone()), - }; - } else { - new_profile = VoteProfile { - deadline: match self.deadline { - None => Err(StdError::generic_err("Vote profile must be set")), - Some(ret) => Ok(ret), - }?, - threshold: match self.threshold.clone() { - None => Err(StdError::generic_err("Vote profile must be set")), - Some(ret) => Ok(ret), - }?, - yes_threshold: match self.yes_threshold.clone() { - None => Err(StdError::generic_err("Vote profile must be set")), - Some(ret) => Ok(ret), - }?, - veto_threshold: match self.veto_threshold.clone() { - None => Err(StdError::generic_err("Vote profile must be set")), - Some(ret) => Ok(ret), - }?, - }; - } - - Ok(new_profile) - } -} - -#[cw_serde] -pub struct UpdateFundProfile { - // Deadline for funding - pub deadline: Option, - // Amount required to fund - pub required: Option, - // Display voter information - pub privacy: Option, - // Deposit loss on vetoed proposal - pub veto_deposit_loss: Option, -} - -impl UpdateFundProfile { - pub fn update_profile(&self, profile: &Option) -> StdResult { - let new_profile: FundProfile; - - if let Some(profile) = profile { - new_profile = FundProfile { - deadline: self.deadline.unwrap_or(profile.deadline), - required: self.required.unwrap_or(profile.required), - privacy: self.privacy.unwrap_or(profile.privacy), - veto_deposit_loss: self - .veto_deposit_loss - .clone() - .unwrap_or(profile.veto_deposit_loss.clone()), - }; - } else { - new_profile = FundProfile { - deadline: match self.deadline { - None => Err(StdError::generic_err("Fund profile must be set")), - Some(ret) => Ok(ret), - }?, - required: match self.required { - None => Err(StdError::generic_err("Fund profile must be set")), - Some(ret) => Ok(ret), - }?, - privacy: match self.privacy { - None => Err(StdError::generic_err("Fund profile must be set")), - Some(ret) => Ok(ret), - }?, - veto_deposit_loss: match self.veto_deposit_loss.clone() { - None => Err(StdError::generic_err("Fund profile must be set")), - Some(ret) => Ok(ret), - }?, - }; - } - - Ok(new_profile) - } -} diff --git a/packages/shade_protocol/src/contract_interfaces/governance/proposal.rs b/packages/shade_protocol/src/contract_interfaces/governance/proposal.rs deleted file mode 100644 index 174652b..0000000 --- a/packages/shade_protocol/src/contract_interfaces/governance/proposal.rs +++ /dev/null @@ -1,399 +0,0 @@ -use crate::{ - c_std::{Addr, Binary, Coin, StdResult, Storage, Uint128}, - contract_interfaces::governance::{ - assembly::Assembly, - profile::Profile, - stored_id::{UserID, ID}, - vote::Vote, - }, -}; - -use cosmwasm_schema::cw_serde; -use cosmwasm_std::Timestamp; -use secret_storage_plus::Map; - -#[cfg(feature = "governance-impl")] -use crate::utils::storage::plus::{MapStorage, NaiveMapStorage}; - -#[cw_serde] -pub struct Proposal { - // Description - // Address of the proposal proposer - pub proposer: Addr, - // Proposal title - pub title: String, - // Description of proposal, can be in base64 - pub metadata: String, - - // Msg - #[serde(skip_serializing_if = "Option::is_none")] - pub msgs: Option>, - - // Assembly - // Assembly that called the proposal - pub assembly: u16, - - #[serde(skip_serializing_if = "Option::is_none")] - pub assembly_vote_tally: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub public_vote_tally: Option, - - // Status - pub status: Status, - - // Status History - pub status_history: Vec, - - // Funders - // Leave as an option so we can hide the data if None - #[serde(skip_serializing_if = "Option::is_none")] - pub funders: Option>, -} - -const ASSEMBLY_VOTE: Map<'static, (u32, Addr), Vote> = Map::new("user-assembly-vote-"); -const ASSEMBLY_VOTES: Map<'static, u32, Vote> = Map::new("total-assembly-votes-"); -const PUBLIC_VOTE: Map<'static, (u32, Addr), Vote> = Map::new("user-public-vote-"); -const PUBLIC_VOTES: Map<'static, u32, Vote> = Map::new("total-public-votes-"); - -#[cfg(feature = "governance-impl")] -impl Proposal { - pub fn save(&self, storage: &mut dyn Storage) -> StdResult<()> { - // Create new ID - let id = ID::add_proposal(storage)?; - - // Create proposers id - UserID::add_proposal(storage, self.proposer.clone(), &id)?; - - if let Some(msgs) = self.msgs.clone() { - Self::save_msg(storage, id, msgs)?; - } - - Self::save_description(storage, id, ProposalDescription { - proposer: self.proposer.clone(), - title: self.title.clone(), - metadata: self.metadata.clone(), - })?; - - Self::save_assembly(storage, id, self.assembly)?; - - Self::save_status(storage, id, self.status.clone())?; - - Self::save_status_history(storage, id, self.status_history.clone())?; - - if let Some(funder_list) = self.funders.clone() { - let mut funders = vec![]; - for (funder, funding) in funder_list.iter() { - funders.push(funder.clone()); - Self::save_funding(storage, id, &funder, Funding { - amount: *funding, - claimed: false, - })? - } - Self::save_funders(storage, id, funders)?; - } - - Ok(()) - } - - pub fn may_load(storage: &dyn Storage, id: u32) -> StdResult> { - if id > ID::proposal(storage)? { - return Ok(None); - } - Ok(Some(Self::load(storage, id)?)) - } - - pub fn load(storage: &dyn Storage, id: u32) -> StdResult { - let msgs = Self::msg(storage, id)?; - let description = Self::description(storage, id)?; - let assembly = Self::assembly(storage, id)?; - let status = Self::status(storage, id)?; - let status_history = Self::status_history(storage, id)?; - - let mut funders_arr = vec![]; - for funder in Self::funders(storage, id)?.iter() { - funders_arr.push((funder.clone(), Self::funding(storage, id, &funder)?.amount)) - } - - let mut funders: Option> = None; - if !funders_arr.is_empty() { - if let Some(prof) = - Profile::funding(storage, Assembly::data(storage, assembly)?.profile)? - { - if !prof.privacy { - funders = Some(funders_arr); - } - } - } - - let assembly_data = Assembly::data(storage, assembly)?; - - Ok(Self { - title: description.title, - proposer: description.proposer, - metadata: description.metadata, - msgs, - assembly, - assembly_vote_tally: match Profile::assembly_voting(storage, assembly_data.profile)? { - None => None, - Some(_) => Some(Self::assembly_votes(storage, id)?), - }, - public_vote_tally: match Profile::public_voting(storage, assembly_data.profile)? { - None => None, - Some(_) => Some(Self::public_votes(storage, id)?), - }, - status, - status_history, - funders, - }) - } - - pub fn msg(storage: &dyn Storage, id: u32) -> StdResult>> { - match ProposalMsgs::may_load(storage, id)? { - None => Ok(None), - Some(i) => Ok(Some(i.0)), - } - } - - pub fn save_msg(storage: &mut dyn Storage, id: u32, data: Vec) -> StdResult<()> { - ProposalMsgs(data).save(storage, id) - } - - pub fn description(storage: &dyn Storage, id: u32) -> StdResult { - ProposalDescription::load(storage, id) - } - - pub fn save_description( - storage: &mut dyn Storage, - id: u32, - data: ProposalDescription, - ) -> StdResult<()> { - data.save(storage, id) - } - - pub fn assembly(storage: &dyn Storage, id: u32) -> StdResult { - Ok(ProposalAssembly::load(storage, id)?.0) - } - - pub fn save_assembly(storage: &mut dyn Storage, id: u32, data: u16) -> StdResult<()> { - ProposalAssembly(data).save(storage, id) - } - - pub fn status(storage: &dyn Storage, id: u32) -> StdResult { - Status::load(storage, id) - } - - pub fn save_status(storage: &mut dyn Storage, id: u32, data: Status) -> StdResult<()> { - data.save(storage, id) - } - - pub fn status_history(storage: &dyn Storage, id: u32) -> StdResult> { - Ok(StatusHistory::load(storage, id)?.0) - } - - pub fn save_status_history( - storage: &mut dyn Storage, - id: u32, - data: Vec, - ) -> StdResult<()> { - StatusHistory(data).save(storage, id) - } - - pub fn funders(storage: &dyn Storage, id: u32) -> StdResult> { - let funders = match Funders::may_load(storage, id)? { - None => vec![], - Some(item) => item.0, - }; - Ok(funders) - } - - pub fn save_funders(storage: &mut dyn Storage, id: u32, data: Vec) -> StdResult<()> { - Funders(data).save(storage, id) - } - - pub fn funding(storage: &dyn Storage, id: u32, user: &Addr) -> StdResult { - Funding::load(storage, (id, user.clone())) - } - - pub fn save_funding( - storage: &mut dyn Storage, - id: u32, - user: &Addr, - data: Funding, - ) -> StdResult<()> { - data.save(storage, (id, user.clone())) - } - - // User assembly votes - pub fn assembly_vote(storage: &dyn Storage, id: u32, user: &Addr) -> StdResult> { - Ok(Vote::may_load(storage, ASSEMBLY_VOTE, (id, user.clone()))?) - } - - pub fn save_assembly_vote( - storage: &mut dyn Storage, - id: u32, - user: &Addr, - data: &Vote, - ) -> StdResult<()> { - data.save(storage, ASSEMBLY_VOTE, (id, user.clone())) - } - - // Total assembly votes - pub fn assembly_votes(storage: &dyn Storage, id: u32) -> StdResult { - match Vote::may_load(storage, ASSEMBLY_VOTES, id)? { - None => Ok(Vote::default()), - Some(vote) => Ok(vote), - } - } - - pub fn save_assembly_votes(storage: &mut dyn Storage, id: u32, data: &Vote) -> StdResult<()> { - data.save(storage, ASSEMBLY_VOTES, id) - } - - // User public votes - pub fn public_vote(storage: &dyn Storage, id: u32, user: &Addr) -> StdResult> { - Ok(Vote::may_load(storage, PUBLIC_VOTE, (id, user.clone()))?) - } - - pub fn save_public_vote( - storage: &mut dyn Storage, - id: u32, - user: &Addr, - data: &Vote, - ) -> StdResult<()> { - data.save(storage, PUBLIC_VOTE, (id, user.clone())) - } - - // Total public votes - pub fn public_votes(storage: &dyn Storage, id: u32) -> StdResult { - match Vote::may_load(storage, PUBLIC_VOTES, id)? { - None => Ok(Vote::default()), - Some(vote) => Ok(vote), - } - } - - pub fn save_public_votes(storage: &mut dyn Storage, id: u32, data: &Vote) -> StdResult<()> { - data.save(storage, PUBLIC_VOTES, id) - } -} - -#[cw_serde] -pub struct ProposalDescription { - pub proposer: Addr, - pub title: String, - pub metadata: String, -} - -#[cfg(feature = "governance-impl")] -impl MapStorage<'static, u32> for ProposalDescription { - const MAP: Map<'static, u32, Self> = Map::new("proposal_description-"); -} - -#[cw_serde] -pub struct ProposalMsg { - pub target: u16, - pub assembly_msg: u16, - // Used as both Vec when calling a handleMsg and Vec when saving the msg - pub msg: Binary, - pub send: Vec, -} - -#[cw_serde] -struct ProposalMsgs(pub Vec); - -#[cfg(feature = "governance-impl")] -impl MapStorage<'static, u32> for ProposalMsgs { - const MAP: Map<'static, u32, Self> = Map::new("proposal_msgs-"); -} - -#[cw_serde] -struct ProposalAssembly(pub u16); - -#[cfg(feature = "governance-impl")] -impl MapStorage<'static, u32> for ProposalAssembly { - const MAP: Map<'static, u32, Self> = Map::new("proposal_assembly-"); -} - -#[cw_serde] -pub enum Status { - // Assembly voting period - AssemblyVote { - start: u64, - end: u64, - }, - // In funding period - Funding { - amount: Uint128, - start: u64, - end: u64, - }, - // Voting in progress - Voting { - start: u64, - end: u64, - }, - // Total votes did not reach minimum total votes - Expired, - // Proposal was rejected - Rejected, - // Proposal was vetoed - // NOTE: percent it stored because proposal settings can change before claiming - Vetoed { - slash_percent: Uint128, - }, - // Proposal was approved, has a set timeline before it can be canceled - Passed { - start: u64, - end: u64, - }, - // If proposal is a msg then it was executed and was successful - Success, - // Proposal never got executed after a cancel deadline, - // assumed that tx failed everytime it got triggered - Canceled, -} - -impl Status { - pub fn passed(storage: &dyn Storage, profile: u16, time: &Timestamp) -> StdResult { - let seconds = time.seconds(); - Ok(Self::Passed { - start: seconds, - end: seconds + Profile::data(storage, profile)?.cancel_deadline, - }) - } -} - -#[cfg(feature = "governance-impl")] -impl MapStorage<'static, u32> for Status { - const MAP: Map<'static, u32, Self> = Map::new("proposal_status-"); -} - -#[cfg(feature = "governance-impl")] -#[cw_serde] -struct StatusHistory(pub Vec); - -#[cfg(feature = "governance-impl")] -impl MapStorage<'static, u32> for StatusHistory { - const MAP: Map<'static, u32, Self> = Map::new("proposal_status_history-"); -} - -#[cfg(feature = "governance-impl")] -#[cw_serde] -struct Funders(pub Vec); - -#[cfg(feature = "governance-impl")] -impl MapStorage<'static, u32> for Funders { - const MAP: Map<'static, u32, Self> = Map::new("proposal_funders-"); -} - -#[cfg(feature = "governance-impl")] -#[cw_serde] -pub struct Funding { - pub amount: Uint128, - pub claimed: bool, -} - -#[cfg(feature = "governance-impl")] -impl MapStorage<'static, (u32, Addr)> for Funding { - const MAP: Map<'static, (u32, Addr), Self> = Map::new("proposal_funding-"); -} diff --git a/packages/shade_protocol/src/contract_interfaces/governance/stored_id.rs b/packages/shade_protocol/src/contract_interfaces/governance/stored_id.rs deleted file mode 100644 index 39ef223..0000000 --- a/packages/shade_protocol/src/contract_interfaces/governance/stored_id.rs +++ /dev/null @@ -1,261 +0,0 @@ -use crate::{ - c_std::{StdResult, Storage}, - utils::storage::plus::{NaiveItemStorage, NaiveMapStorage}, -}; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::Addr; -use secret_storage_plus::{Item, Json, Map}; - -#[cw_serde] // Used to get total IDs -pub struct ID(u16); - -impl NaiveItemStorage for ID {} - -const PROP_KEY: Item<'static, u32, Json> = Item::new("proposal_id-"); -const ASSEMBLY_KEY: Item<'static, ID, Json> = Item::new("assembly_id-"); -const ASSEMBLY_MSG_KEY: Item<'static, ID, Json> = Item::new("assembly_msg_id-"); -const PROFILE_KEY: Item<'static, ID, Json> = Item::new("profile_id-"); -const CONTRACT_KEY: Item<'static, ID, Json> = Item::new("allowed_contract_id-"); - -// Migration specific data -// Used to determine the next ID to migrate over -const LAST_ASSEMBLY_KEY: Item<'static, ID, Json> = Item::new("last_assembly_id-"); -const LAST_ASSEMBLY_MSG_KEY: Item<'static, ID, Json> = Item::new("last_assembly_msg_id-"); -const LAST_PROFILE_KEY: Item<'static, ID, Json> = Item::new("last_profile_id-"); -const LAST_CONTRACT_KEY: Item<'static, ID, Json> = Item::new("last_allowed_contract_id-"); - -impl ID { - // Load current ID related proposals - pub fn set_proposal(storage: &mut dyn Storage, id: u32) -> StdResult<()> { - PROP_KEY.save(storage, &id) - } - - pub fn proposal(storage: &dyn Storage) -> StdResult { - Ok(PROP_KEY.load(storage)?) - } - - pub fn add_proposal(storage: &mut dyn Storage) -> StdResult { - let item = match PROP_KEY.may_load(storage)? { - None => 0, - Some(i) => i.checked_add(1).unwrap(), - }; - PROP_KEY.save(storage, &item)?; - Ok(item) - } - - // Assembly - pub fn set_assembly(storage: &mut dyn Storage, id: u16) -> StdResult<()> { - ID(id).save(storage, ASSEMBLY_KEY) - } - - pub fn assembly(storage: &dyn Storage) -> StdResult { - Ok(ID::load(storage, ASSEMBLY_KEY)?.0) - } - - pub fn add_assembly(storage: &mut dyn Storage) -> StdResult { - let mut item = ID::load(storage, ASSEMBLY_KEY)?; - item.0 = item.0.checked_add(1).unwrap(); - item.save(storage, ASSEMBLY_KEY)?; - Ok(item.0) - } - - // Committee Msg - pub fn set_assembly_msg(storage: &mut dyn Storage, id: u16) -> StdResult<()> { - ID(id).save(storage, ASSEMBLY_MSG_KEY) - } - - pub fn assembly_msg(storage: &dyn Storage) -> StdResult { - Ok(ID::load(storage, ASSEMBLY_MSG_KEY)?.0) - } - - pub fn add_assembly_msg(storage: &mut dyn Storage) -> StdResult { - let mut item = ID::load(storage, ASSEMBLY_MSG_KEY)?; - item.0 = item.0.checked_add(1).unwrap(); - item.save(storage, ASSEMBLY_MSG_KEY)?; - Ok(item.0) - } - - // Profile - pub fn set_profile(storage: &mut dyn Storage, id: u16) -> StdResult<()> { - ID(id).save(storage, PROFILE_KEY) - } - - pub fn profile(storage: &dyn Storage) -> StdResult { - Ok(ID::load(storage, PROFILE_KEY)?.0) - } - - pub fn add_profile(storage: &mut dyn Storage) -> StdResult { - let mut item = ID::load(storage, PROFILE_KEY)?; - item.0 = item.0.checked_add(1).unwrap(); - item.save(storage, PROFILE_KEY)?; - Ok(item.0) - } - - // Contract - // Profile - pub fn set_contract(storage: &mut dyn Storage, id: u16) -> StdResult<()> { - ID(id).save(storage, CONTRACT_KEY) - } - - pub fn contract(storage: &dyn Storage) -> StdResult { - Ok(ID::load(storage, CONTRACT_KEY)?.0) - } - - pub fn add_contract(storage: &mut dyn Storage) -> StdResult { - let mut item = ID::load(storage, CONTRACT_KEY)?; - item.0 = item.0.checked_add(1).unwrap(); - item.save(storage, CONTRACT_KEY)?; - Ok(item.0) - } - - // Migration - pub fn init_migration(storage: &mut dyn Storage) -> StdResult<()> { - let id = ID(0); - id.save(storage, LAST_ASSEMBLY_KEY)?; - id.save(storage, LAST_ASSEMBLY_MSG_KEY)?; - id.save(storage, LAST_PROFILE_KEY)?; - id.save(storage, LAST_CONTRACT_KEY)?; - Ok(()) - } - - pub fn set_assembly_migration(storage: &mut dyn Storage, id: u16) -> StdResult<()> { - ID(id).save(storage, LAST_ASSEMBLY_KEY) - } - - pub fn assembly_migration(storage: &dyn Storage) -> StdResult { - Ok(ID::load(storage, LAST_ASSEMBLY_KEY)?.0) - } - - pub fn set_assembly_msg_migration(storage: &mut dyn Storage, id: u16) -> StdResult<()> { - ID(id).save(storage, LAST_ASSEMBLY_MSG_KEY) - } - - pub fn assembly_msg_migration(storage: &dyn Storage) -> StdResult { - Ok(ID::load(storage, LAST_ASSEMBLY_MSG_KEY)?.0) - } - - pub fn set_profile_migration(storage: &mut dyn Storage, id: u16) -> StdResult<()> { - ID(id).save(storage, LAST_PROFILE_KEY) - } - - pub fn profile_migration(storage: &dyn Storage) -> StdResult { - Ok(ID::load(storage, LAST_PROFILE_KEY)?.0) - } - - pub fn set_contract_migration(storage: &mut dyn Storage, id: u16) -> StdResult<()> { - ID(id).save(storage, LAST_CONTRACT_KEY) - } - - pub fn contract_migration(storage: &dyn Storage) -> StdResult { - Ok(ID::load(storage, LAST_CONTRACT_KEY)?.0) - } -} - -#[cw_serde] -// Used for ease of querying -pub struct UserID(u32); - -impl NaiveMapStorage<'static> for UserID {} - -// Using user ID cause its practically the same type -const USER_PROP_ID: Map<'static, Addr, UserID> = Map::new("user_proposal_id-"); -const USER_PROP: Map<'static, (Addr, u32), UserID> = Map::new("user_proposal_list-"); - -const USER_ASSEMBLY_VOTE_ID: Map<'static, Addr, UserID> = Map::new("user_assembly_votes_id-"); -const USER_ASSEMBLY_VOTE: Map<'static, (Addr, u32), UserID> = Map::new("user_assembly_votes_list-"); - -const USER_FUNDING_ID: Map<'static, Addr, UserID> = Map::new("user_funding_id-"); -const USER_FUNDING: Map<'static, (Addr, u32), UserID> = Map::new("user_funding_list-"); - -const USER_VOTES_ID: Map<'static, Addr, UserID> = Map::new("user_votes_id-"); -const USER_VOTES: Map<'static, (Addr, u32), UserID> = Map::new("user_votes_list-"); - -impl UserID { - // Stores the proposal's id - pub fn total_proposals(storage: &dyn Storage, user: Addr) -> StdResult { - Ok(UserID::may_load(storage, USER_PROP_ID, user)? - .unwrap_or(UserID(0)) - .0) - } - - pub fn proposal(storage: &dyn Storage, user: Addr, id: u32) -> StdResult { - Ok(UserID::load(storage, USER_PROP, (user, id))?.0) - } - - pub fn add_proposal(storage: &mut dyn Storage, user: Addr, prop_id: &u32) -> StdResult { - let item = match UserID::may_load(storage, USER_PROP_ID, user.clone())? { - None => 0, - Some(i) => i.0.checked_add(1).unwrap(), - }; - UserID(item).save(storage, USER_PROP_ID, user.clone())?; - UserID(prop_id.clone()).save(storage, USER_PROP, (user, item))?; - Ok(item) - } - - // Stores the proposal's ID so it can be cross searched - pub fn total_assembly_votes(storage: &dyn Storage, user: Addr) -> StdResult { - Ok(UserID::may_load(storage, USER_ASSEMBLY_VOTE_ID, user)? - .unwrap_or(UserID(0)) - .0) - } - - pub fn assembly_vote(storage: &dyn Storage, user: Addr, id: u32) -> StdResult { - Ok(UserID::load(storage, USER_ASSEMBLY_VOTE, (user, id))?.0) - } - - pub fn add_assembly_vote( - storage: &mut dyn Storage, - user: Addr, - prop_id: u32, - ) -> StdResult { - let item = match UserID::may_load(storage, USER_ASSEMBLY_VOTE_ID, user.clone())? { - None => 0, - Some(i) => i.0.checked_add(1).unwrap(), - }; - UserID(item).save(storage, USER_ASSEMBLY_VOTE_ID, user.clone())?; - UserID(prop_id).save(storage, USER_ASSEMBLY_VOTE, (user, item))?; - Ok(item) - } - - // Stores the proposal's ID so it can be cross searched - pub fn total_funding(storage: &dyn Storage, user: Addr) -> StdResult { - Ok(UserID::may_load(storage, USER_FUNDING_ID, user)? - .unwrap_or(UserID(0)) - .0) - } - - pub fn funding(storage: &dyn Storage, user: Addr, id: u32) -> StdResult { - Ok(UserID::load(storage, USER_FUNDING, (user, id))?.0) - } - - pub fn add_funding(storage: &mut dyn Storage, user: Addr, prop_id: u32) -> StdResult { - let item = match UserID::may_load(storage, USER_FUNDING_ID, user.clone())? { - None => 0, - Some(i) => i.0.checked_add(1).unwrap(), - }; - UserID(item).save(storage, USER_FUNDING_ID, user.clone())?; - UserID(prop_id).save(storage, USER_FUNDING, (user, item))?; - Ok(item) - } - - // Stores the proposal's ID so it can be cross searched - pub fn total_votes(storage: &dyn Storage, user: Addr) -> StdResult { - Ok(UserID::may_load(storage, USER_VOTES_ID, user)? - .unwrap_or(UserID(0)) - .0) - } - - pub fn votes(storage: &dyn Storage, user: Addr, id: u32) -> StdResult { - Ok(UserID::load(storage, USER_VOTES, (user, id))?.0) - } - - pub fn add_vote(storage: &mut dyn Storage, user: Addr, prop_id: u32) -> StdResult { - let item = match UserID::may_load(storage, USER_VOTES_ID, user.clone())? { - None => 0, - Some(i) => i.0.checked_add(1).unwrap(), - }; - UserID(item).save(storage, USER_VOTES_ID, user.clone())?; - UserID(prop_id).save(storage, USER_VOTES, (user, item))?; - Ok(item) - } -} diff --git a/packages/shade_protocol/src/contract_interfaces/governance/vote.rs b/packages/shade_protocol/src/contract_interfaces/governance/vote.rs deleted file mode 100644 index 001b208..0000000 --- a/packages/shade_protocol/src/contract_interfaces/governance/vote.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::c_std::{StdResult, Uint128}; - -use cosmwasm_schema::cw_serde; - -#[cfg(feature = "governance-impl")] -use crate::utils::storage::plus::NaiveMapStorage; - -#[cw_serde] -pub struct ReceiveBalanceMsg { - pub vote: Vote, - pub proposal: u32, -} - -#[cw_serde] -pub struct Vote { - pub yes: Uint128, - pub no: Uint128, - pub no_with_veto: Uint128, - pub abstain: Uint128, -} - -#[cfg(feature = "governance-impl")] -impl NaiveMapStorage<'static> for Vote {} - -impl Default for Vote { - fn default() -> Self { - Self { - yes: Uint128::zero(), - no: Uint128::zero(), - no_with_veto: Uint128::zero(), - abstain: Uint128::zero(), - } - } -} - -impl Vote { - pub fn total_count(&self) -> StdResult { - Ok(self.yes.checked_add( - self.no - .checked_add(self.no_with_veto.checked_add(self.abstain)?)?, - )?) - } - - pub fn checked_sub(&self, vote: &Self) -> StdResult { - Ok(Self { - yes: self.yes.checked_sub(vote.yes)?, - no: self.no.checked_sub(vote.no)?, - no_with_veto: self.no_with_veto.checked_sub(vote.no_with_veto)?, - abstain: self.abstain.checked_sub(vote.abstain)?, - }) - } - - pub fn checked_add(&self, vote: &Self) -> StdResult { - Ok(Self { - yes: self.yes.checked_add(vote.yes)?, - no: self.no.checked_add(vote.no)?, - no_with_veto: self.no_with_veto.checked_add(vote.no_with_veto)?, - abstain: self.abstain.checked_add(vote.abstain)?, - }) - } -} - -pub struct TalliedVotes { - pub yes: Uint128, - pub no: Uint128, - pub veto: Uint128, - pub total: Uint128, -} - -impl TalliedVotes { - pub fn tally(votes: Vote) -> Self { - Self { - yes: votes.yes, - no: votes.no + votes.no_with_veto, - veto: votes.no_with_veto, - total: votes.yes + votes.no + votes.no_with_veto + votes.abstain, - } - } -} diff --git a/packages/shade_protocol/src/contract_interfaces/mod.rs b/packages/shade_protocol/src/contract_interfaces/mod.rs index 317cb3c..c29d272 100644 --- a/packages/shade_protocol/src/contract_interfaces/mod.rs +++ b/packages/shade_protocol/src/contract_interfaces/mod.rs @@ -1,6 +1,3 @@ - -pub mod oracles; - #[cfg(feature = "snip20")] pub mod snip20; diff --git a/packages/shade_protocol/src/contract_interfaces/oracles/band.rs b/packages/shade_protocol/src/contract_interfaces/oracles/band.rs deleted file mode 100644 index 3267c2e..0000000 --- a/packages/shade_protocol/src/contract_interfaces/oracles/band.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::utils::asset::Contract; -use crate::c_std::{Deps, StdResult}; -use crate::c_std::Uint128; - -use crate::utils::{InstantiateCallback, Query}; -use cosmwasm_schema::{cw_serde}; - -#[cw_serde] -pub struct InstantiateMsg {} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum BandQuery { - GetReferenceData { - base_symbol: String, - quote_symbol: String, - }, - GetReferenceDataBulk { - base_symbols: Vec, - quote_symbols: Vec, - }, -} - -impl Query for BandQuery { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub struct ReferenceData { - pub rate: Uint128, - pub last_updated_base: u64, - pub last_updated_quote: u64, -} - -pub fn reference_data( - deps: &Deps, - base_symbol: String, - quote_symbol: String, - band: Contract, -) -> StdResult { - BandQuery::GetReferenceData { - base_symbol, - quote_symbol, - } - .query(&deps.querier, &band) -} - -pub fn reference_data_bulk( - deps: &Deps, - base_symbols: Vec, - quote_symbols: Vec, - band: Contract, -) -> StdResult> { - BandQuery::GetReferenceDataBulk { - base_symbols, - quote_symbols, - } - .query(&deps.querier, &band) -} diff --git a/packages/shade_protocol/src/contract_interfaces/oracles/mod.rs b/packages/shade_protocol/src/contract_interfaces/oracles/mod.rs deleted file mode 100644 index 2be5080..0000000 --- a/packages/shade_protocol/src/contract_interfaces/oracles/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[cfg(feature = "band")] -pub mod band; -#[cfg(feature = "oracles")] -pub mod oracle; diff --git a/packages/shade_protocol/src/contract_interfaces/oracles/oracle.rs b/packages/shade_protocol/src/contract_interfaces/oracles/oracle.rs deleted file mode 100644 index 98296e1..0000000 --- a/packages/shade_protocol/src/contract_interfaces/oracles/oracle.rs +++ /dev/null @@ -1,97 +0,0 @@ -use crate::c_std::Uint128; -use crate::c_std::Addr; - -use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; -use cosmwasm_schema::{cw_serde}; - -use crate::{ - contract_interfaces::{ - dex::dex::TradingPair, - }, - utils::{asset::Contract, generic_response::ResponseStatus}, -}; - -#[cw_serde] -pub struct IndexElement { - pub symbol: String, - pub weight: Uint128, -} - -#[cw_serde] -pub struct OracleConfig { - pub admin: Addr, - pub band: Contract, - pub sscrt: Contract, -} - -#[cw_serde] -pub struct InstantiateMsg { - pub admin: Option, - pub band: Contract, - pub sscrt: Contract, -} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteMsg { - UpdateConfig { - admin: Option, - band: Option, - }, - // Register Secret Swap or Sienna Pair (should be */sSCRT or sSCRT/*) - RegisterPair { - pair: Contract, - }, - // Unregister Secret Swap Pair (opposite action to RegisterSswapPair) - UnregisterPair { - symbol: String, - pair: Contract, - }, - - RegisterIndex { - symbol: String, - basket: Vec, - }, -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteAnswer { - UpdateConfig { - status: ResponseStatus, - }, - - RegisterPair { - status: ResponseStatus, - symbol: String, - pair: TradingPair, - }, - UnregisterPair { - status: ResponseStatus, - }, - RegisterIndex { - status: ResponseStatus, - }, -} - -#[cw_serde] -pub enum QueryMsg { - Config {}, - Price { symbol: String }, - Prices { symbols: Vec }, -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryAnswer { - Config { config: OracleConfig }, -} diff --git a/packages/shade_protocol/src/contract_interfaces/peg_stability/mod.rs b/packages/shade_protocol/src/contract_interfaces/peg_stability/mod.rs deleted file mode 100644 index ce3a3ae..0000000 --- a/packages/shade_protocol/src/contract_interfaces/peg_stability/mod.rs +++ /dev/null @@ -1,146 +0,0 @@ -use crate::{ - contract_interfaces::sky::cycles::{ArbPair, Offer}, - utils::{ - asset::Contract, - generic_response::ResponseStatus, - storage::plus::{GenericItemStorage, ItemStorage}, - ExecuteCallback, - InstantiateCallback, - Query, - }, -}; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Decimal, Uint128}; -use secret_storage_plus::Item; - -#[cw_serde] -pub struct Config { - pub admin_auth: Contract, - pub snip20: Contract, - pub pairs: Vec, - pub oracle: Contract, - pub treasury: Contract, - pub symbols: Vec, - pub payback: Decimal, - pub self_addr: Addr, - pub dump_contract: Contract, -} - -impl ItemStorage for Config { - const ITEM: Item<'static, Config> = Item::new("item_config"); -} - -#[cw_serde] -pub struct ViewingKey; - -impl GenericItemStorage for ViewingKey { - const ITEM: Item<'static, String> = Item::new("item_view_key"); -} - -#[cw_serde] -pub struct InstantiateMsg { - pub admin_auth: Contract, - pub snip20: Contract, - pub oracle: Contract, - pub treasury: Contract, - pub payback: Decimal, - pub viewing_key: String, - pub dump_contract: Contract, -} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteMsg { - UpdateConfig { - admin_auth: Option, - snip20: Option, - oracle: Option, - treasury: Option, - symbols: Option>, - payback: Option, - dump_contract: Option, - padding: Option, - }, - SetPairs { - pairs: Vec, - symbol: Option, - padding: Option, - }, - AppendPairs { - pairs: Vec, - symbol: Option, - padding: Option, - }, - RemovePair { - pair_address: String, - padding: Option, - }, - Swap { - padding: Option, - }, -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteAnswer { - Init { - status: ResponseStatus, - }, - UpdateConfig { - config: Config, - status: ResponseStatus, - }, - SetPairs { - pairs: Vec, - status: ResponseStatus, - }, - AppendPairs { - pairs: Vec, - status: ResponseStatus, - }, - RemovePair { - pairs: Vec, - status: ResponseStatus, - }, - Swap { - profit: Uint128, - payback: Uint128, - status: ResponseStatus, - }, -} - -#[cw_serde] -pub enum QueryMsg { - GetConfig {}, - Balance {}, - GetPairs {}, - Profitable {}, -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryAnswer { - Config { config: Config }, - Balance { snip20_bal: Uint128 }, - GetPairs { pairs: Vec }, - Profitable { profit: Uint128, payback: Uint128 }, -} - -#[cw_serde] -pub struct CalculateRes { - pub profit: Uint128, - pub payback: Uint128, - pub index: usize, - pub config: Config, - pub offer: Offer, - pub min_expected: Uint128, -} diff --git a/packages/shade_protocol/src/contract_interfaces/query_auth/mod.rs b/packages/shade_protocol/src/contract_interfaces/query_auth/mod.rs index 95c9e6e..29413af 100644 --- a/packages/shade_protocol/src/contract_interfaces/query_auth/mod.rs +++ b/packages/shade_protocol/src/contract_interfaces/query_auth/mod.rs @@ -30,6 +30,7 @@ impl ItemStorage for Admin { const ITEM: Item<'static, Self> = Item::new("admin-"); } +#[cfg(feature = "query_auth_impl")] #[cw_serde] pub struct RngSeed(pub Vec); diff --git a/packages/shade_protocol/src/contract_interfaces/shade_oracles.rs b/packages/shade_protocol/src/contract_interfaces/shade_oracles.rs deleted file mode 100644 index bd088f0..0000000 --- a/packages/shade_protocol/src/contract_interfaces/shade_oracles.rs +++ /dev/null @@ -1,106 +0,0 @@ -//#! -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{StdResult, QuerierWrapper}; -use crate::{ - Contract, - BLOCK_SIZE, - utils::{Query}, -}; -use std::collections::HashMap; - -#[cw_serde] -#[derive(Default)] -pub struct OraclePrice { - pub key: String, - pub data: ReferenceData, -} - -pub type PriceResponse = OraclePrice; -pub type PricesResponse = Vec; - -#[cw_serde] -pub enum RouterQueryMsg { - GetOracle { key: String }, - GetOracles { keys: Vec }, -} - -impl Query for RouterQueryMsg { - const BLOCK_SIZE: usize = BLOCK_SIZE; -} - -#[cw_serde] -pub enum OracleQueryMsg { - GetPrice { key: String }, - GetPrices { keys: Vec }, -} - -impl Query for OracleQueryMsg { - const BLOCK_SIZE: usize = BLOCK_SIZE; -} - -#[cw_serde] -pub struct OracleResponse { - pub key: String, - pub oracle: Contract, -} - -/// Gets the oracle for the key from the router & calls GetPrice on it. -/// -/// Has a query depth of 1. -pub fn query_price( - router: &Contract, - querier: &QuerierWrapper, - key: String, -) -> StdResult { - let oracle_resp: OracleResponse = - RouterQueryMsg::GetOracle { key: key.clone() }.query(querier, router)?; - query_oracle_price(&oracle_resp.oracle, querier, key) -} - -/// Groups the keys by their respective oracles and sends bulk GetPrices queries to each of those oracles. -/// -/// Done to reduce impact on query depth. -pub fn query_prices( - router: &Contract, - querier: &QuerierWrapper, - keys: Vec, -) -> StdResult> { - let oracle_resps: Vec = - RouterQueryMsg::GetOracles { keys }.query(querier, router)?; - let mut map: HashMap> = HashMap::new(); - let mut prices: Vec = vec![]; - - for resp in oracle_resps { - // Get the current vector of symbols at that oracle and add the current key to it - map.entry(resp.oracle).or_insert(vec![]).push(resp.key); - } - - for (oracle, keys) in map { - if keys.len() == 1 { - let queried_price = query_oracle_price(&oracle, querier, keys[0].clone())?; - prices.push(queried_price); - } else { - let mut queried_prices = query_oracle_prices(&oracle, querier, keys)?; - prices.append(&mut queried_prices); - } - } - Ok(prices) -} - -pub fn query_oracle_price( - oracle: &Contract, - querier: &QuerierWrapper, - key: String, -) -> StdResult { - let resp: PriceResponse = OracleQueryMsg::GetPrice { key }.query(querier, oracle)?; - Ok(resp.price) -} - -pub fn query_oracle_prices( - oracle: &Contract, - querier: &QuerierWrapper, - keys: Vec, -) -> StdResult> { - let resp: PricesResponse = OracleQueryMsg::GetPrices { keys }.query(querier, oracle)?; - Ok(resp.prices) -} diff --git a/packages/shade_protocol/src/contract_interfaces/sky/cycles.rs b/packages/shade_protocol/src/contract_interfaces/sky/cycles.rs deleted file mode 100644 index ea23f0a..0000000 --- a/packages/shade_protocol/src/contract_interfaces/sky/cycles.rs +++ /dev/null @@ -1,339 +0,0 @@ -use crate::{ - contract_interfaces::{ - dex::{dex::Dex, secretswap, shadeswap, sienna}, - mint::mint, - snip20::helpers::send_msg, - }, - utils::{asset::Contract, Query}, -}; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{ - to_binary, - CosmosMsg, - Deps, - StdError, - StdResult, - Uint128, -}; - -#[cw_serde] -pub struct ArbPair { - pub pair_contract: Option, - pub mint_info: Option, - pub token0: Contract, - pub token0_decimals: Uint128, - pub token0_amount: Option, - pub token1: Contract, - pub token1_decimals: Uint128, - pub token1_amount: Option, - pub dex: Dex, -} - -impl ArbPair { - // Returns pool amounts in a tuple where 0 is the amount for token0 - pub fn pool_amounts(&mut self, deps: Deps) -> StdResult<(Uint128, Uint128)> { - self.validate_pair()?; - match self.dex { - Dex::SecretSwap => { - let res = secretswap::PairQuery::Pool {} - .query(&deps.querier, &self.pair_contract.clone().unwrap())?; - match res { - secretswap::PoolResponse { assets, .. } => { - if assets[0].info.token.contract_addr.clone() == self.token0.address.clone() - { - self.token0_amount = Some(assets[0].amount); - self.token1_amount = Some(assets[1].amount); - Ok((assets[0].amount, assets[1].amount)) - } else { - self.token0_amount = Some(assets[1].amount); - self.token1_amount = Some(assets[0].amount); - Ok((assets[1].amount, assets[0].amount)) - } - } - } - } - Dex::ShadeSwap => { - let res = shadeswap::PairQuery::GetPairInfo {} - .query(&deps.querier, &self.pair_contract.clone().unwrap())?; - match res { - shadeswap::PairInfoResponse { - pair, - amount_0, - amount_1, - .. - } => match pair.token_0 { - shadeswap::TokenType::CustomToken { contract_addr, .. } => { - if contract_addr == self.token0.address.clone() { - self.token0_amount = Some(amount_0); - self.token1_amount = Some(amount_1); - Ok((amount_0, amount_1)) - } else { - self.token0_amount = Some(amount_1); - self.token1_amount = Some(amount_0); - Ok((amount_1, amount_0)) - } - } - _ => Err(StdError::generic_err("Unexpected")), - }, - } - } - Dex::SiennaSwap => { - let res = sienna::PairQuery::PairInfo - .query(&deps.querier, &self.pair_contract.clone().unwrap())?; - - match res { - sienna::PairInfoResponse { pair_info } => match pair_info.pair.token_0 { - sienna::TokenType::CustomToken { contract_addr, .. } => { - if contract_addr == self.token0.address.clone() { - self.token0_amount = Some(pair_info.amount_0); - self.token1_amount = Some(pair_info.amount_1); - Ok((pair_info.amount_0, pair_info.amount_1)) - } else { - self.token0_amount = Some(pair_info.amount_1); - self.token1_amount = Some(pair_info.amount_0); - Ok((pair_info.amount_1, pair_info.amount_0)) - } - } - _ => Err(StdError::generic_err("Unexpected")), - }, - } - } - Dex::Mint => Err(StdError::generic_err("Not available")), - } - } - - // Returns the calculated swap result when passed an offer with respect to the dex enum option - pub fn simulate_swap(self, deps: Deps, offer: Offer) -> StdResult { - let mut swap_result = Uint128::zero(); - match self.dex { - Dex::SecretSwap => { - let res = secretswap::PairQuery::Simulation { - offer_asset: secretswap::Asset { - amount: offer.amount, - info: secretswap::AssetInfo { - token: secretswap::Token { - contract_addr: offer.asset.address, - token_code_hash: offer.asset.code_hash, - viewing_key: "".to_string(), //TODO will sky have to make viewing keys for every asset? - }, - }, - }, - } - .query(&deps.querier, &self.pair_contract.clone().unwrap())?; - match res { - secretswap::SimulationResponse { return_amount, .. } => { - swap_result = return_amount - } - } - } - Dex::SiennaSwap => { - let res = sienna::PairQuery::SwapSimulation { - offer: sienna::TokenTypeAmount { - token: sienna::TokenType::CustomToken { - token_code_hash: offer.asset.code_hash.clone(), - contract_addr: offer.asset.address.clone(), - }, - amount: offer.amount, - }, - } - .query(&deps.querier, &self.pair_contract.clone().unwrap())?; - match res { - sienna::SimulationResponse { return_amount, .. } => swap_result = return_amount, - } - } - Dex::ShadeSwap => { - let res = shadeswap::PairQuery::GetEstimatedPrice { - offer: shadeswap::TokenAmount { - token: shadeswap::TokenType::CustomToken { - token_code_hash: offer.asset.code_hash.clone(), - contract_addr: offer.asset.address.clone(), - }, - amount: offer.amount, - }, - } - .query(&deps.querier, &self.pair_contract.clone().unwrap())?; - match res { - shadeswap::QueryMsgResponse::EstimatedPrice { estimated_price } => { - swap_result = estimated_price - } - _ => {} - } - } - Dex::Mint => { - let mint_contract = self.get_mint_contract(offer.asset.clone())?; - let res = mint::QueryMsg::Mint { - offer_asset: offer.asset.address, - amount: offer.amount, - } - .query(&deps.querier, &mint_contract)?; - match res { - mint::QueryAnswer::Mint { amount, .. } => swap_result = amount, - _ => {} - } - } - } - Ok(swap_result) - } - - // Returns the snip20 send_msg that will execute a swap for each of the possible Dex enum - // options - pub fn to_cosmos_msg(&self, offer: Offer, expected_return: Uint128) -> StdResult { - match self.dex { - Dex::SiennaSwap => send_msg( - self.pair_contract.clone().unwrap().address, - Uint128::new(offer.amount.u128()), - Some(to_binary(&sienna::CallbackMsg { - swap: sienna::CallbackSwap { expected_return }, - })?), - None, - None, - &offer.asset, - ), - Dex::SecretSwap => send_msg( - self.pair_contract.clone().unwrap().address, - Uint128::new(offer.amount.u128()), - Some(to_binary(&secretswap::CallbackMsg { - swap: secretswap::CallbackSwap { expected_return }, - })?), - None, - None, - &offer.asset, - ), - Dex::ShadeSwap => send_msg( - self.pair_contract.clone().unwrap().address, - Uint128::new(offer.amount.u128()), - Some(to_binary(&shadeswap::SwapTokens { - expected_return: Some(expected_return), - to: None, - router_link: None, - callback_signature: None, - })?), - None, - None, - &offer.asset, - ), - Dex::Mint => { - let mint_contract = self.get_mint_contract(offer.asset.clone())?; - send_msg( - mint_contract.address.clone(), - Uint128::new(offer.amount.u128()), - Some(to_binary(&mint::MintMsgHook { - minimum_expected_amount: expected_return, - })?), - None, - None, - &offer.asset, - ) - } - } - } - - // Returns either the silk mint or the shade mint contract depending on what the input asset is - pub fn get_mint_contract(&self, offer_contract: Contract) -> StdResult { - if offer_contract.clone() == self.mint_info.clone().unwrap().shd_token { - Ok(self.mint_info.clone().unwrap().mint_contract_silk) - } else if offer_contract == self.mint_info.clone().unwrap().silk_token { - Ok(self.mint_info.clone().unwrap().mint_contract_shd) - } else { - Err(StdError::generic_err( - "Must be sending either silk or shd to mint contracts", - )) - } - } - - // Gatekeeper that validates the ArbPair for entry into contract storage - pub fn validate_pair(&self) -> StdResult { - match self.dex { - Dex::Mint => { - if self.mint_info == None { - return Err(StdError::generic_err("Dex mint must include mint_info")); - } - } - _ => { - if self.pair_contract == None { - return Err(StdError::generic_err( - "Dex pairs must include pair contract", - )); - } - } - } - Ok(true) - } -} - -#[cw_serde] -pub struct Cycle { - pub pair_addrs: Vec, - pub start_addr: Contract, -} - -impl Cycle { - // Gatekeeper that validates if the contract should accept the cycle into storage - pub fn validate_cycle(&self) -> StdResult { - // check if start address is in both the first arb pair and the last arb pair - let start_addr_in_first_pair = self.start_addr == self.pair_addrs[0].token0 - || self.start_addr == self.pair_addrs[0].token1; - let start_addr_in_last_pair = self.start_addr - == self.pair_addrs[self.pair_addrs.len() - 1].token0 - || self.start_addr == self.pair_addrs[self.pair_addrs.len() - 1].token1; - if !(start_addr_in_first_pair && start_addr_in_last_pair) { - return Err(StdError::generic_err( - "First and last pair in cycle must contain start addr", - )); - } - // check to see if each arb pair has the necessary information and if there is an actual - // path - - // initialize this for later use - let mut hash_vec = vec![]; - let mut cur_asset = self.start_addr.clone(); - for arb_pair in self.pair_addrs.clone() { - match arb_pair.dex { - Dex::Mint => { - arb_pair - .mint_info - .expect("Mint arb pairs must include mint info"); - } - _ => { - arb_pair - .pair_contract - .clone() - .expect("Dex pairs must include pair contract"); - hash_vec.push(arb_pair.pair_contract.unwrap().code_hash.clone()); - } - } - if arb_pair.token0 == cur_asset { - cur_asset = arb_pair.token1; - } else if arb_pair.token1 == cur_asset { - cur_asset = arb_pair.token0; - } else { - return Err(StdError::generic_err("cycle not complete")); - } - } - let initial_len = hash_vec.clone().len(); - // Sorting and dedup ing will remove any dublicates and tell us if there's 2 of the same - // pair contract included in the cycle - hash_vec.sort(); - hash_vec.dedup(); - if hash_vec.len() < initial_len { - return Err(StdError::generic_err( - "cycles should include one copy of each pair", - )); - } - Ok(true) - } -} - -#[cw_serde] -pub struct Offer { - pub asset: Contract, - pub amount: Uint128, -} - -#[cw_serde] -pub struct MintInfo { - pub mint_contract_shd: Contract, - pub mint_contract_silk: Contract, - pub shd_token: Contract, - pub silk_token: Contract, -} diff --git a/packages/shade_protocol/src/contract_interfaces/sky/mod.rs b/packages/shade_protocol/src/contract_interfaces/sky/mod.rs deleted file mode 100644 index 5446f5f..0000000 --- a/packages/shade_protocol/src/contract_interfaces/sky/mod.rs +++ /dev/null @@ -1,182 +0,0 @@ -#[cfg(feature = "sky-utils")] -pub mod cycles; - -use crate::{ - contract_interfaces::{dao::adapter, sky::cycles::Cycle}, - utils::{ - asset::Contract, - storage::plus::ItemStorage, - ExecuteCallback, - InstantiateCallback, - Query, - }, -}; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Decimal, Uint128}; -use secret_storage_plus::Item; - -#[cw_serde] -pub struct Config { - pub shade_admin: Contract, - pub shd_token: Contract, - pub silk_token: Contract, - pub sscrt_token: Contract, - pub treasury: Contract, - pub payback_rate: Decimal, -} - -impl ItemStorage for Config { - const ITEM: Item<'static, Config> = Item::new("item_config"); -} - -#[cw_serde] -pub struct ViewingKeys(pub String); - -impl ItemStorage for ViewingKeys { - const ITEM: Item<'static, ViewingKeys> = Item::new("item_view_keys"); -} - -#[cw_serde] -pub struct SelfAddr(pub Addr); - -impl ItemStorage for SelfAddr { - const ITEM: Item<'static, SelfAddr> = Item::new("item_self_addr"); -} - -#[cw_serde] -pub struct Cycles(pub Vec); - -impl ItemStorage for Cycles { - const ITEM: Item<'static, Cycles> = Item::new("item_cycles"); -} - -#[cw_serde] -pub struct InstantiateMsg { - pub shade_admin: Contract, - pub shd_token: Contract, - pub silk_token: Contract, - pub sscrt_token: Contract, - pub treasury: Contract, - pub viewing_key: String, - pub payback_rate: Decimal, -} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteMsg { - UpdateConfig { - shade_admin: Option, - shd_token: Option, - silk_token: Option, - sscrt_token: Option, - treasury: Option, - payback_rate: Option, - padding: Option, - }, - SetCycles { - cycles: Vec, - padding: Option, - }, - AppendCycles { - cycle: Vec, - padding: Option, - }, - UpdateCycle { - cycle: Cycle, - index: Uint128, - padding: Option, - }, - RemoveCycle { - index: Uint128, - padding: Option, - }, - ArbCycle { - amount: Uint128, - index: Uint128, - padding: Option, - }, - ArbAllCycles { - amount: Uint128, - padding: Option, - }, - Adapter(adapter::SubExecuteMsg), -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteAnswer { - Init { - status: bool, - }, - UpdateConfig { - status: bool, - }, - SetCycles { - status: bool, - }, - AppendCycles { - status: bool, - }, - UpdateCycle { - status: bool, - }, - RemoveCycle { - status: bool, - }, - ExecuteArbCycle { - status: bool, - swap_amounts: Vec, - payback_amount: Uint128, - }, - ArbAllCycles { - status: bool, - payback_amount: Uint128, - }, -} - -#[cw_serde] -pub enum QueryMsg { - GetConfig {}, - Balance {}, - GetCycles {}, - IsCycleProfitable { amount: Uint128, index: Uint128 }, - IsAnyCycleProfitable { amount: Uint128 }, - Adapter(adapter::SubQueryMsg), -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryAnswer { - Config { - config: Config, - }, - Balance { - shd_bal: Uint128, - silk_bal: Uint128, //should be zero or close to - sscrt_bal: Uint128, - }, - GetCycles { - cycles: Vec, - }, - IsCycleProfitable { - is_profitable: bool, - direction: Cycle, - swap_amounts: Vec, - profit: Uint128, - }, - IsAnyCycleProfitable { - is_profitable: Vec, - direction: Vec, - swap_amounts: Vec>, - profit: Vec, - }, -} diff --git a/packages/shade_protocol/src/contract_interfaces/snip20/helpers.rs b/packages/shade_protocol/src/contract_interfaces/snip20/helpers.rs index 1bf7f72..89d19e8 100644 --- a/packages/shade_protocol/src/contract_interfaces/snip20/helpers.rs +++ b/packages/shade_protocol/src/contract_interfaces/snip20/helpers.rs @@ -242,7 +242,7 @@ pub fn increase_allowance_msg( amount: Uint128, expiration: Option, padding: Option, - _block_size: usize, + block_size: usize, contract: &Contract, funds: Vec, ) -> StdResult { @@ -271,7 +271,7 @@ pub fn decrease_allowance_msg( amount: Uint128, expiration: Option, padding: Option, - _block_size: usize, + block_size: usize, contract: &Contract, funds: Vec, ) -> StdResult { @@ -301,7 +301,7 @@ pub fn allowance_query( owner: Addr, spender: Addr, key: String, - _block_size: usize, + block_size: usize, contract: &Contract, ) -> StdResult { let answer: QueryAnswer = QueryMsg::Allowance { diff --git a/packages/shade_protocol/src/contract_interfaces/stkd/mod.rs b/packages/shade_protocol/src/contract_interfaces/stkd/mod.rs deleted file mode 100644 index 73752c8..0000000 --- a/packages/shade_protocol/src/contract_interfaces/stkd/mod.rs +++ /dev/null @@ -1,170 +0,0 @@ -// Types imported from staking-derivatives private repo used for interfacing with stkd-SCRT -// and updated to v1. Types copied as needed, feel free to add. -// Types also included for mock_stkd contract - -use cosmwasm_std::{Addr, Binary}; -use cosmwasm_std::Uint128; - -use crate::utils::{ - ExecuteCallback, Query, -}; -use cosmwasm_schema::cw_serde; - -#[cw_serde] -pub enum HandleMsg { - /// stake the sent SCRT - Stake {}, - /// Unbond SCRT - Unbond { - /// amount of derivative tokens to redeem - redeem_amount: Uint128, - }, - /// claim matured unbondings - Claim {}, - - SetViewingKey { - key: String, - padding: Option, - }, - Send { - recipient: Addr, - recipient_code_hash: Option, - amount: Uint128, - msg: Option, - memo: Option, - padding: Option, - }, - - // For mock_stkd contract, to simulate passage of time for unbondings - MockFastForward { - steps: u32, - }, -} - -#[cw_serde] -pub enum HandleAnswer { - Stake { - /// amount of uSCRT staked - scrt_staked: Uint128, - /// amount of derivative token minted - tokens_returned: Uint128, - }, - Unbond { - /// amount of derivative tokens redeemed - tokens_redeemed: Uint128, - /// amount of scrt to be unbonded (available in 21 days after the batch processes) - scrt_to_be_received: Uint128, - /// estimated time of maturity - estimated_time_of_maturity: u64, - }, - Claim { - /// amount of SCRT claimed - withdrawn: Uint128, - /// fees collected - fees: Uint128, - }, -} - -impl ExecuteCallback for HandleMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryMsg { - /// display the validator addresses, amount of bonded SCRT, amount of available SCRT not - /// reserved for mature unbondings, amount of pending staking rewards not yet claimed, - /// the derivative token supply, and the price of the derivative token in SCRT to 6 decimals - StakingInfo { - /// time in seconds since 01/01/1970. - time: u64, - }, - Unbonding { - /// address whose unclaimed unbondings to display - address: Addr, - /// the address' viewing key - key: String, - /// optional page number to display - page: Option, - /// optional page size - page_size: Option, - /// optional time in seconds since 01/01/1970. If provided, response will - /// include the amount of SCRT that can be withdrawn at that time - time: Option, - }, - Balance { - address: Addr, - key: String, - }, -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryAnswer { - /// displays staking info - StakingInfo { - /// validator addresses and their weights - validators: Vec, - /// unbonding time - unbonding_time: u32, - /// minimum number of seconds between unbonding batches - unbonding_batch_interval: u32, - /// earliest time of next batch unbonding - next_unbonding_batch_time: u64, - /// amount of SCRT that will unbond in the next batch - unbond_amount_of_next_batch: Uint128, - /// true if a batch unbonding is in progress - batch_unbond_in_progress: bool, - /// amount of bonded SCRT - bonded_scrt: Uint128, - /// amount of SCRT reserved for mature unbondings - reserved_scrt: Uint128, - /// amount of available SCRT not reserved for mature unbondings - available_scrt: Uint128, - /// unclaimed staking rewards - rewards: Uint128, - /// total supply of derivative token - total_derivative_token_supply: Uint128, - /// price of derivative token in SCRT to 6 decimals - price: Uint128, - }, - /// displays user's unclaimed unbondings - Unbonding { - /// number of unclaimed unbondings - count: u64, - /// amount of claimable SCRT at the specified time (if given) - claimable_scrt: Option, - /// unclaimed unbondings - unbondings: Vec, - /// total amount of pending unbondings that will begin maturing in the next batch - unbond_amount_in_next_batch: Uint128, - /// optional estimated time the next batch of unbondings will mature. Only provided - /// if the user has SCRT waiting to be unbonded in the next batch - estimated_time_of_maturity_for_next_batch: Option, - }, - Balance { - amount: Uint128, - }, -} - -/// validators and their weights -#[cw_serde] -pub struct WeightedValidator { - /// the validator's address - pub validator: Addr, - /// the validator's weight in whole percents - pub weight: u8, -} - -/// Unbonding data -#[cw_serde] -pub struct Unbond { - /// amount of SCRT unbonding - pub amount: Uint128, - /// time of maturation in seconds since 01/01/1970 - pub unbonds_at: u64, - /// optional bool if time was supplied, which is true if the unbonding is mature - pub is_mature: Option, -} diff --git a/packages/shade_protocol/src/schemas.rs b/packages/shade_protocol/src/schemas.rs index fce0c51..5a50a2d 100644 --- a/packages/shade_protocol/src/schemas.rs +++ b/packages/shade_protocol/src/schemas.rs @@ -45,11 +45,20 @@ macro_rules! generate_nested_schemas { pub fn main() { generate_schemas!( - airdrop + airdrop, + bonds, + governance, + peg_stability, + query_auth, + sky, + snip20 ); // generate_nested_schemas!(mint, liability_mint, mint, mint_router); + generate_nested_schemas!(oracles, oracle); + + generate_nested_schemas!(dao, treasury_manager, treasury, scrt_staking); // generate_nested_schemas!(staking, snip20_staking); diff --git a/tools/headstash/account.js b/tools/headstash/account.js new file mode 100644 index 0000000..3d41f5a --- /dev/null +++ b/tools/headstash/account.js @@ -0,0 +1,103 @@ +import { Wallet, SecretNetworkClient, EncryptionUtilsImpl, fromUtf8, MsgExecuteContract, MsgExecuteContractResponse, fromBase64, toBase64 } from "secretjs"; +import * as fs from "fs"; + +const wallet = new Wallet( + "goat action fuel major strategy adult kind sand draw amazing pigeon inspire antenna forget six kiss loan script west jaguar again click review have" +); + +const txEncryptionSeed = EncryptionUtilsImpl.GenerateNewSeed(); + +const scrt20codeId = 5697; +const scrt20CodeHash = "c74bc4b0406507257ed033caa922272023ab013b0c74330efc16569528fa34fe"; +const secretTerpContractAddr = "secret1c3lj7dr9r2pe83j3yx8jt5v800zs9sq7we6wrc"; +const secretThiolContractAddr = "secret1umh28jgcp0g9jy3qc29xk42kq92xjrcdfgvwdz"; + +const scrtHeadstashCodeId = 6272; +const scrtHeadstashCodeHash = "f87c7817a43ca68c99fcf425eb1f255393df813c9955501d89a24d09b9967512"; +const scrtHeadstashContractAddr = "secret1l97vzuf8lsr0kdvepxqut9w4mf2v49vh35zqxp"; + +const viewingKey = "eretskeretjableret" + +const eth_pubkey = "0x254768D47Cf8958a68242ce5AA1aDB401E1feF2B"; + + +// client +const secretjs = new SecretNetworkClient({ + chainId: "pulsar-3", + url: "https://api.pulsar.scrttestnet.com", + wallet: wallet, + walletAddress: wallet.address, + txEncryptionSeed: txEncryptionSeed +}); + +// filler message of AddressProofPermit +const fillerMsg = { + coins: [], + contract: scrtHeadstashContractAddr, + eth_pubkey: eth_pubkey, + execute_msg: {}, + sender: wallet.address, +} + +// data to be encoded in memo of AddressProofPermit +const addrProofMsg = { + address: wallet.address, + amount: "420", + contract: scrtHeadstashContractAddr, + index: 0, + key: "eretskeretjableret" +} + +// Convert JSON object to JSON string +const addrProofMsgJson = JSON.stringify(addrProofMsg); + +// signature documentate as defined here: +// https://github.com/securesecrets/shade/blob/77abdc70bc645d97aee7de5eb9a2347d22da425f/packages/shade_protocol/src/signature/mod.rs#L100 + + +const encodedAddrProofMsg = toBase64(addrProofMsgJson); + +const addMinterMsg = new MsgExecuteContract({ + sender: "secret13uazul89dp0lypuxcz0upygpjy0ftdah4lnrs4", + contract_address: scrtHeadstashContractAddr, + code_hash: scrtHeadstashCodeHash, // optional but way faster + msg: { + account: { + eth_pubkey: eth_pubkey, + addresses: [{ + params: { + account_number: 0, + chain_id: "pulsar-3" + }, + memo: "eyJhZGRyZXNzIjoic2VjcmV0MTN1YXp1bDg5ZHAwbHlwdXhjejB1cHlncGp5MGZ0ZGFoNGxucnM0IiwiYW1vdW50IjoiNDIwIiwiY29udHJhY3QiOiJzZWNyZXQxbDk3dnp1Zjhsc3Iwa2R2ZXB4cXV0OXc0bWYydjQ5dmgzNXpxeHAiLCJpbmRleCI6MCwia2V5IjoiZXJldHNrZXJldGphYmxlcmV0In0=", + signature: { + pub_key: { + type: "tendermint/PubKeySecp256k1", + value: "AyZtxhLgis4Ec66OVlKDnuzEZqqV641sm46R3mbE2cpO", + }, + signature: fromBase64("tTjK3Mf4dpQrSQT2hsqXn+pgeXhVjQhw2EXP5N50uhBJ0kpV9IS5uyfo+PHvB20CVHMwux9leaByfXI3T6PD6A==") + }, + // account_number: Option, + // chain_id: Option, + // sequence: Option, + // memo: Option, + }], + } + }, + sent_funds: [], // optional +}); + +const tx = await secretjs.tx.broadcast([addMinterMsg], { + gasLimit: 200_000, +}); + +// console.log(addrProofMsgJson); +console.log(tx); + +// let create_account = async () => { + +// let tx = await secretjs.tx.broadcast([], { gasLimit: 400_000, } +// ); +// }; + + diff --git a/tools/headstash/deploy-cw20.sh b/tools/headstash/deploy-cw20.sh new file mode 100644 index 0000000..a429863 --- /dev/null +++ b/tools/headstash/deploy-cw20.sh @@ -0,0 +1,58 @@ + + +# install secret-cli + +if ! command -v scrt &> /dev/null +then + echo "The Secret Network CLI is not installed, downloading & installing." + wget https://github.com/scrtlabs/SecretNetwork/releases/download/v1.12.2/secretcli-Linux > /root/go/bin/scrt + sudo chmod +x /root/go/bin/scrt +else + # Command exists + echo "The Secret Network CLI is installed." +fi + +# define code-id or fetch cw-20 raw wasm file +CODE_ID=123 +CW20_GIT=https://github.com/scrtlabs/snip20-reference-impl +# store or instantiate +TYPE =-i + +if ! command -v docker &> /dev/null +then + echo "Docker is not installed, downloading & installing." + snap install docker +else + # Command exists + echo "Docker is now installed." +fi + + +git clone $CW20_GIT snip20 +cd snip20 + +docker run --rm -v "$(pwd)":/contract --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry enigmampc/secret-contract-optimizer +mv contract.wasm.gz ../ && cd ../ + + +## instantiate or store and instantitate contracts +# Check if the number of arguments is less than 2 (excluding the script name) +if [ "$#" -lt 2 ]; then + echo "Usage: $0 [-s|-i] name symbol [supported_denoms]" + exit 1 +fi + +# Extract the action type (instantiate or upload) and values +action=$1 +name=$2 +symbol=$3 +supported_denoms=$4 + +# Run the Node.js script with the chosen action and values +if [ "$action" == "-i" ]; then + node your_script.js "$action" "$name" "$symbol" "$supported_denoms" +elif [ "$action" == "-s" ]; then + node your_script.js "$action" +else + echo "Invalid action type. Please provide -s or -i." +fi diff --git a/tools/headstash/main.js b/tools/headstash/main.js new file mode 100644 index 0000000..065eca0 --- /dev/null +++ b/tools/headstash/main.js @@ -0,0 +1,148 @@ +import { Wallet, SecretNetworkClient, EncryptionUtilsImpl, fromUtf8, MsgExecuteContractResponse } from "secretjs"; +import * as fs from "fs"; + +const wallet = new Wallet( + "goat action fuel major strategy adult kind sand draw amazing pigeon inspire antenna forget six kiss loan script west jaguar again click review have" +); + +const txEncryptionSeed = EncryptionUtilsImpl.GenerateNewSeed(); +const contract_wasm = fs.readFileSync("./airdrop.wasm"); + +const scrt20codeId = 5697; +const scrt20CodeHash = "c74bc4b0406507257ed033caa922272023ab013b0c74330efc16569528fa34fe"; +const scrtHeadstashCodeId = 6282; +const scrtHeadstashCodeHash = "f87c7817a43ca68c99fcf425eb1f255393df813c9955501d89a24d09b9967512"; + +const secretTerpContractAddr = "secret1c3lj7dr9r2pe83j3yx8jt5v800zs9sq7we6wrc"; +const secretThiolContractAddr = "secret1umh28jgcp0g9jy3qc29xk42kq92xjrcdfgvwdz"; +const secretHeadstashContractAddr = "secret1l97vzuf8lsr0kdvepxqut9w4mf2v49vh35zqxp"; + +const secretjs = new SecretNetworkClient({ + chainId: "pulsar-3", + url: "https://api.pulsar.scrttestnet.com", + wallet: wallet, + walletAddress: wallet.address, + txEncryptionSeed: txEncryptionSeed +}); + +let upload_contract = async () => { + let tx = await secretjs.tx.compute.storeCode( + { + sender: wallet.address, + wasm_byte_code: contract_wasm, + source: "", + builder: "", + }, + { + gasLimit: 4_000_000, + } + ); + + const codeId = Number( + tx.arrayLog.find((log) => log.type === "message" && log.key === "code_id") + .value + ); + + console.log("codeId: ", codeId); + // contract hash, useful for contract composition + const contractCodeHash = (await secretjs.query.compute.codeHashByCodeId({ code_id: codeId })).code_hash; + console.log(`Contract hash: ${contractCodeHash}`); +} +let instantiate_headstash_contract = async () => { + let initMsg = { + admin: "secret13uazul89dp0lypuxcz0upygpjy0ftdah4lnrs4", + dump_address: "secret13uazul89dp0lypuxcz0upygpjy0ftdah4lnrs4", + airdrop_token: { + address: secretTerpContractAddr, + code_hash: scrt20CodeHash + }, + airdrop_2: { + address: secretThiolContractAddr, + code_hash: scrt20CodeHash + }, + airdrop_amount: "840", + start_date: null, + end_date: null, + decay_start: null, + merkle_root: "d599867bdb2ade1e470d9ec9456490adcd9da6e0cfd8f515e2b95d345a5cd92f", + total_accounts: 2, + claim_msg_plaintext: "{wallet}" + }; + + let tx = await secretjs.tx.compute.instantiateContract( + { + code_id: scrtHeadstashCodeId, + sender: wallet.address, + code_hash: scrtHeadstashCodeHash, + init_msg: initMsg, + label: "Secret Headstash Patch " + Math.ceil(Math.random() * 10000), + }, + { + gasLimit: 400_000, + } + ); + + console.log(tx); + //Find the contract_address in the logs + const contractAddress = tx.arrayLog.find( + (log) => log.type === "message" && log.key === "contract_address" + ).value; + + console.log(contractAddress); +} +let instantiate_contract = async (name, synbol, supported_denom) => { + const initMsg = { + name: "Terp Network Gas Token", + symbol: "THIOL", + decimals: 6, + prng_seed: Buffer.from("dezayum").toString("base64"), + admin: wallet.address, + supported_denoms: [supported_denom] + }; + let tx = await secretjs.tx.compute.instantiateContract( + { + code_id: codeId, + sender: wallet.address, + code_hash: contractCodeHash, + init_msg: initMsg, + label: " Secret Wrapped Terp Network Gas Tokens (THIOL)" + Math.ceil(Math.random() * 10000), + }, + { + gasLimit: 400_000, + } + ); + //Find the contract_address in the logs + const contractAddress = tx.arrayLog.find( + (log) => log.type === "message" && log.key === "contract_address" + ).value; + + console.log(contractAddress); +}; + + +// Process command line arguments +const args = process.argv.slice(2); + +// Determine which function to run based on the first argument +if (args.length < 1) { + console.error('Invalid option. Please provide -s to store the contract, or -i to instantiate the contract, followed by expected values [name] [symbol] [ibc-hash].'); +} else if (args[0] === '-s') { + upload_contract(args[1]); +} else if (args[0] === '-h') { + instantiate_headstash_contract(); +} else if (args[0] === '-i') { + if (args.length < 4) { + console.error('Usage: -i name symbol [supported_denoms]'); + process.exit(1); + } + const [, name, symbol, supported_denoms] = args; // Extracting values + instantiate_contract(name, symbol, supported_denoms) + .then(() => { + console.log("Upload completed!"); + }) + .catch((error) => { + console.error("Upload failed:", error); + }); +} else { + console.error('Invalid option. Please provide -s to store the contract, or -i to instantiate the contract, followed by expected values [name] [symbol] [ibc-hash].'); +} \ No newline at end of file diff --git a/tools/headstash/merkle-gen.sh b/tools/headstash/merkle-gen.sh new file mode 100644 index 0000000..e69de29 diff --git a/contracts/airdrop/helpers/.eslintignore b/tools/merkleTree/.eslintignore similarity index 100% rename from contracts/airdrop/helpers/.eslintignore rename to tools/merkleTree/.eslintignore diff --git a/contracts/airdrop/helpers/.eslintrc b/tools/merkleTree/.eslintrc similarity index 100% rename from contracts/airdrop/helpers/.eslintrc rename to tools/merkleTree/.eslintrc diff --git a/contracts/airdrop/helpers/.gitignore b/tools/merkleTree/.gitignore similarity index 100% rename from contracts/airdrop/helpers/.gitignore rename to tools/merkleTree/.gitignore diff --git a/contracts/airdrop/helpers/README.md b/tools/merkleTree/README.md similarity index 100% rename from contracts/airdrop/helpers/README.md rename to tools/merkleTree/README.md diff --git a/contracts/airdrop/helpers/src/airdrop.ts b/tools/merkleTree/src/airdrop.ts similarity index 100% rename from contracts/airdrop/helpers/src/airdrop.ts rename to tools/merkleTree/src/airdrop.ts diff --git a/contracts/airdrop/helpers/src/commands/generateProofs.ts b/tools/merkleTree/src/commands/generateProofs.ts similarity index 100% rename from contracts/airdrop/helpers/src/commands/generateProofs.ts rename to tools/merkleTree/src/commands/generateProofs.ts diff --git a/contracts/airdrop/helpers/src/commands/generateRoot.ts b/tools/merkleTree/src/commands/generateRoot.ts similarity index 100% rename from contracts/airdrop/helpers/src/commands/generateRoot.ts rename to tools/merkleTree/src/commands/generateRoot.ts diff --git a/contracts/airdrop/helpers/src/commands/verifyProofs.ts b/tools/merkleTree/src/commands/verifyProofs.ts similarity index 100% rename from contracts/airdrop/helpers/src/commands/verifyProofs.ts rename to tools/merkleTree/src/commands/verifyProofs.ts diff --git a/contracts/airdrop/helpers/src/index.ts b/tools/merkleTree/src/index.ts similarity index 100% rename from contracts/airdrop/helpers/src/index.ts rename to tools/merkleTree/src/index.ts diff --git a/tools/merkleTree/yarn.lock b/tools/merkleTree/yarn.lock new file mode 100644 index 0000000..84b9310 --- /dev/null +++ b/tools/merkleTree/yarn.lock @@ -0,0 +1,3228 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.1", "@babel/code-frame@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== + dependencies: + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" + +"@babel/compat-data@^7.23.5": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.1.tgz#31c1f66435f2a9c329bb5716a6d6186c516c3742" + integrity sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA== + +"@babel/core@^7.12.16": + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.3.tgz#568864247ea10fbd4eff04dda1e05f9e2ea985c3" + integrity sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.2" + "@babel/generator" "^7.24.1" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.24.1" + "@babel/parser" "^7.24.1" + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.1" + "@babel/types" "^7.24.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/eslint-parser@^7.12.16": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.24.1.tgz#e27eee93ed1d271637165ef3a86e2b9332395c32" + integrity sha512-d5guuzMlPeDfZIbpQ8+g1NaCNuAGBBGNECh0HVqz1sjOeVLh2CEaifuOysCH18URW6R7pqXINvf5PaR/dC6jLQ== + dependencies: + "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" + eslint-visitor-keys "^2.1.0" + semver "^6.3.1" + +"@babel/generator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.1.tgz#e67e06f68568a4ebf194d1c6014235344f0476d0" + integrity sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A== + dependencies: + "@babel/types" "^7.24.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-module-imports@^7.22.15": + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128" + integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg== + dependencies: + "@babel/types" "^7.24.0" + +"@babel/helper-module-transforms@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" + integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.23.4": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" + integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== + +"@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/helper-validator-option@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + +"@babel/helpers@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.1.tgz#183e44714b9eba36c3038e442516587b1e0a1a94" + integrity sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg== + dependencies: + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.1" + "@babel/types" "^7.24.0" + +"@babel/highlight@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" + integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.24.0", "@babel/parser@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.1.tgz#1e416d3627393fab1cb5b0f2f1796a100ae9133a" + integrity sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg== + +"@babel/template@^7.22.15", "@babel/template@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" + integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + +"@babel/traverse@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.1.tgz#d65c36ac9dd17282175d1e4a3c49d5b7988f530c" + integrity sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ== + dependencies: + "@babel/code-frame" "^7.24.1" + "@babel/generator" "^7.24.1" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.24.1" + "@babel/types" "^7.24.0" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" + integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + +"@cosmjs/crypto@^0.25.5": + version "0.25.6" + resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.25.6.tgz#695d2d0d2195bdbdd5825d415385646244900bbb" + integrity sha512-ec+YcQLrg2ibcxtNrh4FqQnG9kG9IE/Aik2NH6+OXQdFU/qFuBTxSFcKDgzzBOChwlkXwydllM9Jjbp+dgIzRw== + dependencies: + "@cosmjs/encoding" "^0.25.6" + "@cosmjs/math" "^0.25.6" + "@cosmjs/utils" "^0.25.6" + bip39 "^3.0.2" + bn.js "^4.11.8" + elliptic "^6.5.3" + js-sha3 "^0.8.0" + libsodium-wrappers "^0.7.6" + ripemd160 "^2.0.2" + sha.js "^2.4.11" + +"@cosmjs/encoding@^0.25.5", "@cosmjs/encoding@^0.25.6": + version "0.25.6" + resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.25.6.tgz#da741a33eaf063a6d3611d7d68db5ca3938e0ef5" + integrity sha512-0imUOB8XkUstI216uznPaX1hqgvLQ2Xso3zJj5IV5oJuNlsfDj9nt/iQxXWbJuettc6gvrFfpf+Vw2vBZSZ75g== + dependencies: + base64-js "^1.3.0" + bech32 "^1.1.4" + readonly-date "^1.0.0" + +"@cosmjs/math@^0.25.6": + version "0.25.6" + resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.25.6.tgz#25c7b106aaded889a5b80784693caa9e654b0c28" + integrity sha512-Fmyc9FJ8KMU34n7rdapMJrT/8rx5WhMw2F7WLBu7AVLcBh0yWsXIcMSJCoPHTOnMIiABjXsnrrwEaLrOOBfu6A== + dependencies: + bn.js "^4.11.8" + +"@cosmjs/utils@^0.25.6": + version "0.25.6" + resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.25.6.tgz#934d9a967180baa66163847616a74358732227ca" + integrity sha512-ofOYiuxVKNo238vCPPlaDzqPXy2AQ/5/nashBo5rvPZJkxt9LciGfUEQWPCOb1BIJDNx2Dzu0z4XCf/dwzl0Dg== + +"@ethereumjs/rlp@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" + integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== + +"@ethereumjs/util@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-8.1.0.tgz#299df97fb6b034e0577ce9f94c7d9d1004409ed4" + integrity sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA== + dependencies: + "@ethereumjs/rlp" "^4.0.1" + ethereum-cryptography "^2.0.0" + micro-ftch "^0.3.1" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": + version "5.1.1-v1" + resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" + integrity sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg== + dependencies: + eslint-scope "5.1.1" + +"@noble/curves@1.3.0", "@noble/curves@~1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e" + integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA== + dependencies: + "@noble/hashes" "1.3.3" + +"@noble/hashes@1.3.3", "@noble/hashes@~1.3.2": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" + integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== + +"@noble/hashes@^1.2.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" + integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@oclif/command@^1", "@oclif/command@^1.8.14", "@oclif/command@^1.8.15": + version "1.8.36" + resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.8.36.tgz#9739b9c268580d064a50887c4597d1b4e86ca8b5" + integrity sha512-/zACSgaYGtAQRzc7HjzrlIs14FuEYAZrMOEwicRoUnZVyRunG4+t5iSEeQu0Xy2bgbCD0U1SP/EdeNZSTXRwjQ== + dependencies: + "@oclif/config" "^1.18.2" + "@oclif/errors" "^1.3.6" + "@oclif/help" "^1.0.1" + "@oclif/parser" "^3.8.17" + debug "^4.1.1" + semver "^7.5.4" + +"@oclif/config@1.18.16": + version "1.18.16" + resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.18.16.tgz#3235d260ab1eb8388ebb6255bca3dd956249d796" + integrity sha512-VskIxVcN22qJzxRUq+raalq6Q3HUde7sokB7/xk5TqRZGEKRVbFeqdQBxDWwQeudiJEgcNiMvIFbMQ43dY37FA== + dependencies: + "@oclif/errors" "^1.3.6" + "@oclif/parser" "^3.8.16" + debug "^4.3.4" + globby "^11.1.0" + is-wsl "^2.1.1" + tslib "^2.6.1" + +"@oclif/config@1.18.2": + version "1.18.2" + resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.18.2.tgz#5bfe74a9ba6a8ca3dceb314a81bd9ce2e15ebbfe" + integrity sha512-cE3qfHWv8hGRCP31j7fIS7BfCflm/BNZ2HNqHexH+fDrdF2f1D5S8VmXWLC77ffv3oDvWyvE9AZeR0RfmHCCaA== + dependencies: + "@oclif/errors" "^1.3.3" + "@oclif/parser" "^3.8.0" + debug "^4.1.1" + globby "^11.0.1" + is-wsl "^2.1.1" + tslib "^2.0.0" + +"@oclif/config@^1", "@oclif/config@^1.18.2": + version "1.18.17" + resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.18.17.tgz#00aa4049da27edca8f06fc106832d9f0f38786a5" + integrity sha512-k77qyeUvjU8qAJ3XK3fr/QVAqsZO8QOBuESnfeM5HHtPNLSyfVcwiMM2zveSW5xRdLSG3MfV8QnLVkuyCL2ENg== + dependencies: + "@oclif/errors" "^1.3.6" + "@oclif/parser" "^3.8.17" + debug "^4.3.4" + globby "^11.1.0" + is-wsl "^2.1.1" + tslib "^2.6.1" + +"@oclif/dev-cli@^1": + version "1.26.10" + resolved "https://registry.yarnpkg.com/@oclif/dev-cli/-/dev-cli-1.26.10.tgz#d8df3a79009b68552f5e7f249d1d19ca52278382" + integrity sha512-dJ+II9rVXckzFvG+82PbfphMTnoqiHvsuAAbcHrLdZWPBnFAiDKhNYE0iHnA/knAC4VGXhogsrAJ3ERT5d5r2g== + dependencies: + "@oclif/command" "^1.8.15" + "@oclif/config" "^1.18.2" + "@oclif/errors" "^1.3.5" + "@oclif/plugin-help" "3.2.18" + cli-ux "5.6.7" + debug "^4.1.1" + find-yarn-workspace-root "^2.0.0" + fs-extra "^8.1" + github-slugger "^1.2.1" + lodash "^4.17.11" + normalize-package-data "^3.0.0" + qqjs "^0.3.10" + tslib "^2.0.3" + +"@oclif/errors@1.3.5": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@oclif/errors/-/errors-1.3.5.tgz#a1e9694dbeccab10fe2fe15acb7113991bed636c" + integrity sha512-OivucXPH/eLLlOT7FkCMoZXiaVYf8I/w1eTAM1+gKzfhALwWTusxEx7wBmW0uzvkSg/9ovWLycPaBgJbM3LOCQ== + dependencies: + clean-stack "^3.0.0" + fs-extra "^8.1" + indent-string "^4.0.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +"@oclif/errors@1.3.6", "@oclif/errors@^1.3.3", "@oclif/errors@^1.3.5", "@oclif/errors@^1.3.6": + version "1.3.6" + resolved "https://registry.yarnpkg.com/@oclif/errors/-/errors-1.3.6.tgz#e8fe1fc12346cb77c4f274e26891964f5175f75d" + integrity sha512-fYaU4aDceETd89KXP+3cLyg9EHZsLD3RxF2IU9yxahhBpspWjkWi3Dy3bTgcwZ3V47BgxQaGapzJWDM33XIVDQ== + dependencies: + clean-stack "^3.0.0" + fs-extra "^8.1" + indent-string "^4.0.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +"@oclif/help@^1.0.0", "@oclif/help@^1.0.1": + version "1.0.15" + resolved "https://registry.yarnpkg.com/@oclif/help/-/help-1.0.15.tgz#5e36e576b8132a4906d2662204ad9de7ece87e8f" + integrity sha512-Yt8UHoetk/XqohYX76DfdrUYLsPKMc5pgkzsZVHDyBSkLiGRzujVaGZdjr32ckVZU9q3a47IjhWxhip7Dz5W/g== + dependencies: + "@oclif/config" "1.18.16" + "@oclif/errors" "1.3.6" + chalk "^4.1.2" + indent-string "^4.0.0" + lodash "^4.17.21" + string-width "^4.2.0" + strip-ansi "^6.0.0" + widest-line "^3.1.0" + wrap-ansi "^6.2.0" + +"@oclif/linewrap@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@oclif/linewrap/-/linewrap-1.0.0.tgz#aedcb64b479d4db7be24196384897b5000901d91" + integrity sha512-Ups2dShK52xXa8w6iBWLgcjPJWjais6KPJQq3gQ/88AY6BXoTX+MIGFPrWQO1KLMiQfoTpcLnUwloN4brrVUHw== + +"@oclif/parser@^3.8.0", "@oclif/parser@^3.8.16", "@oclif/parser@^3.8.17": + version "3.8.17" + resolved "https://registry.yarnpkg.com/@oclif/parser/-/parser-3.8.17.tgz#e1ce0f29b22762d752d9da1c7abd57ad81c56188" + integrity sha512-l04iSd0xoh/16TGVpXb81Gg3z7tlQGrEup16BrVLsZBK6SEYpYHRJZnM32BwZrHI97ZSFfuSwVlzoo6HdsaK8A== + dependencies: + "@oclif/errors" "^1.3.6" + "@oclif/linewrap" "^1.0.0" + chalk "^4.1.0" + tslib "^2.6.2" + +"@oclif/plugin-help@3.2.18": + version "3.2.18" + resolved "https://registry.yarnpkg.com/@oclif/plugin-help/-/plugin-help-3.2.18.tgz#f2bf6ba86719c174fc0e4c2149f73b46006bfdbd" + integrity sha512-5n5Pkz4L0duknIvFwx2Ko9Xda3miT6RZP8bgaaK3Q/9fzVBrhi4bOM0u05/OThI6V+3NsSdxYS2o1NLcXToWDg== + dependencies: + "@oclif/command" "^1.8.14" + "@oclif/config" "1.18.2" + "@oclif/errors" "1.3.5" + "@oclif/help" "^1.0.0" + chalk "^4.1.2" + indent-string "^4.0.0" + lodash "^4.17.21" + string-width "^4.2.0" + strip-ansi "^6.0.0" + widest-line "^3.1.0" + wrap-ansi "^6.2.0" + +"@oclif/plugin-help@^3": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@oclif/plugin-help/-/plugin-help-3.3.1.tgz#36adb4e0173f741df409bb4b69036d24a53bfb24" + integrity sha512-QuSiseNRJygaqAdABYFWn/H1CwIZCp9zp/PLid6yXvy6VcQV7OenEFF5XuYaCvSARe2Tg9r8Jqls5+fw1A9CbQ== + dependencies: + "@oclif/command" "^1.8.15" + "@oclif/config" "1.18.2" + "@oclif/errors" "1.3.5" + "@oclif/help" "^1.0.1" + chalk "^4.1.2" + indent-string "^4.0.0" + lodash "^4.17.21" + string-width "^4.2.0" + strip-ansi "^6.0.0" + widest-line "^3.1.0" + wrap-ansi "^6.2.0" + +"@oclif/screen@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@oclif/screen/-/screen-1.0.4.tgz#b740f68609dfae8aa71c3a6cab15d816407ba493" + integrity sha512-60CHpq+eqnTxLZQ4PGHYNwUX572hgpMHGPtTWMjdTMsAvlm69lZV/4ly6O3sAYkomo4NggGcomrDpBe34rxUqw== + +"@scure/base@~1.1.4": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.6.tgz#8ce5d304b436e4c84f896e0550c83e4d88cb917d" + integrity sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g== + +"@scure/bip32@1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.3.tgz#a9624991dc8767087c57999a5d79488f48eae6c8" + integrity sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ== + dependencies: + "@noble/curves" "~1.3.0" + "@noble/hashes" "~1.3.2" + "@scure/base" "~1.1.4" + +"@scure/bip39@1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.2.tgz#f3426813f4ced11a47489cbcf7294aa963966527" + integrity sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA== + dependencies: + "@noble/hashes" "~1.3.2" + "@scure/base" "~1.1.4" + +"@types/bn.js@^5.1.0": + version "5.1.5" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.5.tgz#2e0dacdcce2c0f16b905d20ff87aedbc6f7b4bf0" + integrity sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A== + dependencies: + "@types/node" "*" + +"@types/crypto-js@^4.0.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.2.2.tgz#771c4a768d94eb5922cc202a3009558204df0cea" + integrity sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ== + +"@types/eslint-visitor-keys@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" + integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== + +"@types/glob@^7.1.1": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" + integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/json-schema@^7.0.3": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/minimatch@*": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" + integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== + +"@types/node@*": + version "20.11.30" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.30.tgz#9c33467fc23167a347e73834f788f4b9f399d66f" + integrity sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw== + dependencies: + undici-types "~5.26.4" + +"@types/node@^10": + version "10.17.60" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" + integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== + +"@types/normalize-package-data@^2.4.0": + version "2.4.4" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" + integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== + +"@types/pbkdf2@^3.0.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.1.2.tgz#2dc43808e9985a2c69ff02e2d2027bd4fe33e8dc" + integrity sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew== + dependencies: + "@types/node" "*" + +"@types/secp256k1@^4.0.1": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.6.tgz#d60ba2349a51c2cbc5e816dcd831a42029d376bf" + integrity sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ== + dependencies: + "@types/node" "*" + +"@typescript-eslint/eslint-plugin@^2.6.1": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9" + integrity sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ== + dependencies: + "@typescript-eslint/experimental-utils" "2.34.0" + functional-red-black-tree "^1.0.1" + regexpp "^3.0.0" + tsutils "^3.17.1" + +"@typescript-eslint/experimental-utils@2.34.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" + integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/typescript-estree" "2.34.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/parser@^2.6.1": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.34.0.tgz#50252630ca319685420e9a39ca05fe185a256bc8" + integrity sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA== + dependencies: + "@types/eslint-visitor-keys" "^1.0.0" + "@typescript-eslint/experimental-utils" "2.34.0" + "@typescript-eslint/typescript-estree" "2.34.0" + eslint-visitor-keys "^1.1.0" + +"@typescript-eslint/typescript-estree@2.34.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5" + integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== + dependencies: + debug "^4.1.1" + eslint-visitor-keys "^1.1.0" + glob "^7.1.6" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + +acorn-jsx@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^6.0.7: + version "6.4.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" + integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== + +ajv@^6.10.2, ajv@^6.9.1: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + +ansi-escapes@^4.3.0, ansi-escapes@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1" + integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw== + +ansi-regex@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0, ansi-styles@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansicolors@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" + integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg== + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base-x@^3.0.2: + version "3.0.9" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" + integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + dependencies: + safe-buffer "^5.0.1" + +base64-js@^1.3.0, base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bech32@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" + integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== + +bignumber.js@^9.0.1: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + +bip39@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.1.0.tgz#c55a418deaf48826a6ceb34ac55b3ee1577e18a3" + integrity sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A== + dependencies: + "@noble/hashes" "^1.2.0" + +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +blakejs@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" + integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== + +bn.js@4.11.6: + version "4.11.6" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" + integrity sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA== + +bn.js@^4.11.8, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + +browserify-aes@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserslist@^4.22.2: + version "4.23.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" + integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== + dependencies: + caniuse-lite "^1.0.30001587" + electron-to-chromium "^1.4.668" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +bs58@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== + dependencies: + base-x "^3.0.2" + +bs58check@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" + integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA== + dependencies: + bs58 "^4.0.0" + create-hash "^1.1.0" + safe-buffer "^5.1.2" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer-reverse@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-reverse/-/buffer-reverse-1.0.1.tgz#49283c8efa6f901bc01fa3304d06027971ae2f60" + integrity sha512-M87YIUBsZ6N924W57vDwT/aOu8hw7ZgdByz6ijksLjmHJELBASmYTTlNHRgjE+pTsT9oJXGaDSgqqwfdHotDUg== + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +caniuse-lite@^1.0.30001587: + version "1.0.30001600" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz#93a3ee17a35aa6a9f0c6ef1b2ab49507d1ab9079" + integrity sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ== + +cardinal@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" + integrity sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw== + dependencies: + ansicolors "~0.3.2" + redeyed "~2.1.0" + +chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +clean-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7" + integrity sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw== + dependencies: + escape-string-regexp "^1.0.5" + +clean-stack@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-3.0.1.tgz#155bf0b2221bf5f4fba89528d24c5953f17fe3a8" + integrity sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg== + dependencies: + escape-string-regexp "4.0.0" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw== + dependencies: + restore-cursor "^2.0.0" + +cli-progress@^3.4.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.12.0.tgz#807ee14b66bcc086258e444ad0f19e7d42577942" + integrity sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A== + dependencies: + string-width "^4.2.3" + +cli-ux@5.6.7: + version "5.6.7" + resolved "https://registry.yarnpkg.com/cli-ux/-/cli-ux-5.6.7.tgz#32ef9e6cb2b457be834280cc799028a11c8235a8" + integrity sha512-dsKAurMNyFDnO6X1TiiRNiVbL90XReLKcvIq4H777NMqXGBxBws23ag8ubCJE97vVZEgWG2eSUhsyLf63Jv8+g== + dependencies: + "@oclif/command" "^1.8.15" + "@oclif/errors" "^1.3.5" + "@oclif/linewrap" "^1.0.0" + "@oclif/screen" "^1.0.4" + ansi-escapes "^4.3.0" + ansi-styles "^4.2.0" + cardinal "^2.1.1" + chalk "^4.1.0" + clean-stack "^3.0.0" + cli-progress "^3.4.0" + extract-stack "^2.0.0" + fs-extra "^8.1" + hyperlinker "^1.0.0" + indent-string "^4.0.0" + is-wsl "^2.2.0" + js-yaml "^3.13.1" + lodash "^4.17.21" + natural-orderby "^2.0.1" + object-treeify "^1.1.4" + password-prompt "^1.1.2" + semver "^7.3.2" + string-width "^4.2.0" + strip-ansi "^6.0.0" + supports-color "^8.1.0" + supports-hyperlinks "^2.1.0" + tslib "^2.0.0" + +cli-width@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" + integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +confusing-browser-globals@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz#30d1e7f3d1b882b25ec4933d1d1adac353d20a59" + integrity sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA== + +content-type@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.4, create-hmac@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +cross-spawn@^6.0.0, cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-js@^3.1.9-1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.3.0.tgz#846dd1cce2f68aacfa156c8578f926a609b7976b" + integrity sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q== + +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +detect-indent@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" + integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +electron-to-chromium@^1.4.668: + version "1.4.721" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.721.tgz#a9ee55ba7e54d9ecbcc19825116f3752e7d60ef2" + integrity sha512-k1x2r6foI8iJOp+1qTxbbrrWMsOiHkzGBYwYigaq+apO1FSqtn44KTo3Sy69qt7CRr7149zTcsDvH7MUKsOuIQ== + +elliptic@^6.5.3, elliptic@^6.5.4: + version "6.5.5" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded" + integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + +escape-string-regexp@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +eslint-ast-utils@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/eslint-ast-utils/-/eslint-ast-utils-1.1.0.tgz#3d58ba557801cfb1c941d68131ee9f8c34bd1586" + integrity sha512-otzzTim2/1+lVrlH19EfQQJEhVJSu0zOb9ygb3iapN6UlyaDtyRq4b5U1FuW0v1lRa9Fp/GJyHkSwm6NqABgCA== + dependencies: + lodash.get "^4.4.2" + lodash.zip "^4.2.0" + +eslint-config-oclif-typescript@^0.1: + version "0.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-oclif-typescript/-/eslint-config-oclif-typescript-0.1.0.tgz#c310767c5ee8916ea5d08cf027d0317dd52ed8ba" + integrity sha512-BjXNJcH2F02MdaSFml9vJskviUFVkLHbTPGM5tinIt98H6klFNKP7/lQ+fB/Goc2wB45usEuuw6+l/fwAv9i7g== + dependencies: + "@typescript-eslint/eslint-plugin" "^2.6.1" + "@typescript-eslint/parser" "^2.6.1" + eslint-config-oclif "^3.1.0" + eslint-config-xo-space "^0.20.0" + eslint-plugin-mocha "^5.2.0" + eslint-plugin-node "^7.0.1" + eslint-plugin-unicorn "^6.0.1" + +eslint-config-oclif@^3.1, eslint-config-oclif@^3.1.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/eslint-config-oclif/-/eslint-config-oclif-3.1.2.tgz#d68bdcc3c3a7f548572ccc3582f7f345abef609d" + integrity sha512-66i2mWHb4luJHqJSUO5o9bTYQyH4yuItEikBghUixQB1tFpe/j3mKoRMncxGm2LDGcVIbgK7iLXWO9Ta7buIpg== + dependencies: + eslint-config-xo-space "^0.27.0" + eslint-plugin-mocha "^9.0.0" + eslint-plugin-node "^11.1.0" + eslint-plugin-unicorn "^36.0.0" + +eslint-config-xo-space@^0.20.0: + version "0.20.0" + resolved "https://registry.yarnpkg.com/eslint-config-xo-space/-/eslint-config-xo-space-0.20.0.tgz#75e1fb86d1b052fc1cc3036ca2fa441fa92b85e4" + integrity sha512-bOsoZA8M6v1HviDUIGVq1fLVnSu3mMZzn85m2tqKb73tSzu4GKD4Jd2Py4ZKjCgvCbRRByEB5HPC3fTMnnJ1uw== + dependencies: + eslint-config-xo "^0.24.0" + +eslint-config-xo-space@^0.27.0: + version "0.27.0" + resolved "https://registry.yarnpkg.com/eslint-config-xo-space/-/eslint-config-xo-space-0.27.0.tgz#9663e41d7bedc0f345488377770565aa9b0085e0" + integrity sha512-b8UjW+nQyOkhiANVpIptqlKPyE7XRyQ40uQ1NoBhzVfu95gxfZGrpliq8ZHBpaOF2wCLZaexTSjg7Rvm99vj4A== + dependencies: + eslint-config-xo "^0.35.0" + +eslint-config-xo@^0.24.0: + version "0.24.2" + resolved "https://registry.yarnpkg.com/eslint-config-xo/-/eslint-config-xo-0.24.2.tgz#f61b8ce692e9f9519bdb6edc4ed7ebcd5be48f48" + integrity sha512-ivQ7qISScW6gfBp+p31nQntz1rg34UCybd3uvlngcxt5Utsf4PMMi9QoAluLFcPUM5Tvqk4JGraR9qu3msKPKQ== + +eslint-config-xo@^0.35.0: + version "0.35.0" + resolved "https://registry.yarnpkg.com/eslint-config-xo/-/eslint-config-xo-0.35.0.tgz#8b5afca244c44129c32386c28602f973ad5cb762" + integrity sha512-+WyZTLWUJlvExFrBU/Ldw8AB/S0d3x+26JQdBWbcqig2ZaWh0zinYcHok+ET4IoPaEcRRf3FE9kjItNVjBwnAg== + dependencies: + confusing-browser-globals "1.0.10" + +eslint-plugin-es@^1.3.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.4.1.tgz#12acae0f4953e76ba444bfd1b2271081ac620998" + integrity sha512-5fa/gR2yR3NxQf+UXkeLeP8FBBl6tSgdrAz1+cF84v1FMM4twGwQoqTnn+QxFLcPOrF4pdKEJKDB/q9GoyJrCA== + dependencies: + eslint-utils "^1.4.2" + regexpp "^2.0.1" + +eslint-plugin-es@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893" + integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ== + dependencies: + eslint-utils "^2.0.0" + regexpp "^3.0.0" + +eslint-plugin-mocha@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-mocha/-/eslint-plugin-mocha-5.3.0.tgz#cf3eb18ae0e44e433aef7159637095a7cb19b15b" + integrity sha512-3uwlJVLijjEmBeNyH60nzqgA1gacUWLUmcKV8PIGNvj1kwP/CTgAWQHn2ayyJVwziX+KETkr9opNwT1qD/RZ5A== + dependencies: + ramda "^0.26.1" + +eslint-plugin-mocha@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-mocha/-/eslint-plugin-mocha-9.0.0.tgz#b4457d066941eecb070dc06ed301c527d9c61b60" + integrity sha512-d7knAcQj1jPCzZf3caeBIn3BnW6ikcvfz0kSqQpwPYcVGLoJV5sz0l0OJB2LR8I7dvTDbqq1oV6ylhSgzA10zg== + dependencies: + eslint-utils "^3.0.0" + ramda "^0.27.1" + +eslint-plugin-node@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" + integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== + dependencies: + eslint-plugin-es "^3.0.0" + eslint-utils "^2.0.0" + ignore "^5.1.1" + minimatch "^3.0.4" + resolve "^1.10.1" + semver "^6.1.0" + +eslint-plugin-node@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-7.0.1.tgz#a6e054e50199b2edd85518b89b4e7b323c9f36db" + integrity sha512-lfVw3TEqThwq0j2Ba/Ckn2ABdwmL5dkOgAux1rvOk6CO7A6yGyPI2+zIxN6FyNkp1X1X/BSvKOceD6mBWSj4Yw== + dependencies: + eslint-plugin-es "^1.3.1" + eslint-utils "^1.3.1" + ignore "^4.0.2" + minimatch "^3.0.4" + resolve "^1.8.1" + semver "^5.5.0" + +eslint-plugin-unicorn@^36.0.0: + version "36.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-36.0.0.tgz#db50e1426839e401d33c5a279f49d4a5bbb640d8" + integrity sha512-xxN2vSctGWnDW6aLElm/LKIwcrmk6mdiEcW55Uv5krcrVcIFSWMmEgc/hwpemYfZacKZ5npFERGNz4aThsp1AA== + dependencies: + "@babel/helper-validator-identifier" "^7.14.9" + ci-info "^3.2.0" + clean-regexp "^1.0.0" + eslint-template-visitor "^2.3.2" + eslint-utils "^3.0.0" + is-builtin-module "^3.1.0" + lodash "^4.17.21" + pluralize "^8.0.0" + read-pkg-up "^7.0.1" + regexp-tree "^0.1.23" + safe-regex "^2.1.1" + semver "^7.3.5" + +eslint-plugin-unicorn@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-6.0.1.tgz#4a97f0bc9449e20b82848dad12094ee2ba72347e" + integrity sha512-hjy9LhTdtL7pz8WTrzS0CGXRkWK3VAPLDjihofj8JC+uxQLfXm0WwZPPPB7xKmcjRyoH+jruPHOCrHNEINpG/Q== + dependencies: + clean-regexp "^1.0.0" + eslint-ast-utils "^1.0.0" + import-modules "^1.1.0" + lodash.camelcase "^4.1.1" + lodash.kebabcase "^4.0.1" + lodash.snakecase "^4.0.1" + lodash.upperfirst "^4.2.0" + safe-regex "^1.1.0" + +eslint-scope@5.1.1, eslint-scope@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-template-visitor@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/eslint-template-visitor/-/eslint-template-visitor-2.3.2.tgz#b52f96ff311e773a345d79053ccc78275bbc463d" + integrity sha512-3ydhqFpuV7x1M9EK52BPNj6V0Kwu0KKkcIAfpUhwHbR8ocRln/oUHgfxQupY8O1h4Qv/POHDumb/BwwNfxbtnA== + dependencies: + "@babel/core" "^7.12.16" + "@babel/eslint-parser" "^7.12.16" + eslint-visitor-keys "^2.0.0" + esquery "^1.3.1" + multimap "^1.1.0" + +eslint-utils@^1.3.1, eslint-utils@^1.4.2: + version "1.4.3" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" + integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-utils@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint@^5.13: + version "5.16.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea" + integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.9.1" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^4.0.3" + eslint-utils "^1.3.1" + eslint-visitor-keys "^1.0.0" + espree "^5.0.1" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob "^7.1.2" + globals "^11.7.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^6.2.2" + js-yaml "^3.13.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.11" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^5.5.1" + strip-ansi "^4.0.0" + strip-json-comments "^2.0.1" + table "^5.2.3" + text-table "^0.2.0" + +espree@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a" + integrity sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A== + dependencies: + acorn "^6.0.7" + acorn-jsx "^5.0.0" + eslint-visitor-keys "^1.0.0" + +esprima@^4.0.0, esprima@~4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.1, esquery@^1.3.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.1.0, esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +ethereum-bloom-filters@^1.0.6: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz#3ca07f4aed698e75bd134584850260246a5fed8a" + integrity sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA== + dependencies: + js-sha3 "^0.8.0" + +ethereum-cryptography@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz#8d6143cfc3d74bf79bbd8edecdf29e4ae20dd191" + integrity sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ== + dependencies: + "@types/pbkdf2" "^3.0.0" + "@types/secp256k1" "^4.0.1" + blakejs "^1.1.0" + browserify-aes "^1.2.0" + bs58check "^2.1.2" + create-hash "^1.2.0" + create-hmac "^1.1.7" + hash.js "^1.1.7" + keccak "^3.0.0" + pbkdf2 "^3.0.17" + randombytes "^2.1.0" + safe-buffer "^5.1.2" + scrypt-js "^3.0.0" + secp256k1 "^4.0.1" + setimmediate "^1.0.5" + +ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz#1352270ed3b339fe25af5ceeadcf1b9c8e30768a" + integrity sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA== + dependencies: + "@noble/curves" "1.3.0" + "@noble/hashes" "1.3.3" + "@scure/bip32" "1.3.3" + "@scure/bip39" "1.2.2" + +ethereumjs-util@^7.1.0: + version "7.1.5" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" + integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg== + dependencies: + "@types/bn.js" "^5.1.0" + bn.js "^5.1.2" + create-hash "^1.1.2" + ethereum-cryptography "^0.1.3" + rlp "^2.2.4" + +ethjs-unit@0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" + integrity sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw== + dependencies: + bn.js "4.11.6" + number-to-bn "1.7.0" + +evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +execa@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50" + integrity sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw== + dependencies: + cross-spawn "^6.0.0" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +extract-stack@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/extract-stack/-/extract-stack-2.0.0.tgz#11367bc865bfcd9bc0db3123e5edb57786f11f9b" + integrity sha512-AEo4zm+TenK7zQorGK1f9mJ8L14hnTDi2ZQPR+Mub1NX8zimka1mXpV5LpH8x9HoUmFSHZCfLHqWvp0Y4FxxzQ== + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.0.3, fast-glob@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + integrity sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-yarn-workspace-root@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" + integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== + dependencies: + micromatch "^4.0.2" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs-extra@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-6.0.1.tgz#8abc128f7946e310135ddc93b98bddb410e7a34b" + integrity sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^8.1: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + integrity sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ== + +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +github-slugger@^1.2.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.5.0.tgz#17891bbc73232051474d68bd867a34625c955f7d" + integrity sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.2, glob@^7.1.3, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0, globals@^11.7.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globby@^10, globby@^10.0.1: + version "10.0.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" + integrity sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg== + dependencies: + "@types/glob" "^7.1.1" + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.0.3" + glob "^7.1.3" + ignore "^5.1.1" + merge2 "^1.2.3" + slash "^3.0.0" + +globby@^11.0.1, globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hash-base@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== + dependencies: + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hasown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + +hosted-git-info@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" + integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== + dependencies: + lru-cache "^6.0.0" + +http-call@^5.1.2: + version "5.3.0" + resolved "https://registry.yarnpkg.com/http-call/-/http-call-5.3.0.tgz#4ded815b13f423de176eb0942d69c43b25b148db" + integrity sha512-ahwimsC23ICE4kPl9xTBjKB4inbRaeLyZeRunC/1Jy/Z6X8tv22MEAjK+KBOMSVLaqXPTTmd8638waVIKLGx2w== + dependencies: + content-type "^1.0.4" + debug "^4.1.1" + is-retry-allowed "^1.1.0" + is-stream "^2.0.0" + parse-json "^4.0.0" + tunnel-agent "^0.6.0" + +hyperlinker@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e" + integrity sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ== + +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^4.0.2, ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.1.1, ignore@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + +import-fresh@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-modules@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/import-modules/-/import-modules-1.1.0.tgz#748db79c5cc42bb9701efab424f894e72600e9dc" + integrity sha512-szMf9iPglnnDYueTxUzJWM+dXlwgfOIIONjVj36ZX8YHG9vBGCHPCpawKr+uIXD0Znm7QQlznIQtVjxfwJkq4g== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inquirer@^6.2.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" + integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== + dependencies: + ansi-escapes "^3.2.0" + chalk "^2.4.2" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^3.0.3" + figures "^2.0.0" + lodash "^4.17.12" + mute-stream "0.0.7" + run-async "^2.2.0" + rxjs "^6.4.0" + string-width "^2.1.0" + strip-ansi "^5.1.0" + through "^2.3.6" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-builtin-module@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + +is-core-module@^2.13.0, is-core-module@^2.5.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-hex-prefixed@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" + integrity sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-retry-allowed@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" + integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + +is-wsl@^2.1.1, is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +js-sha3@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.0, js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + +keccak@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.4.tgz#edc09b89e633c0549da444432ecf062ffadee86d" + integrity sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q== + dependencies: + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + readable-stream "^3.6.0" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +libsodium-wrappers@^0.7.6: + version "0.7.13" + resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.13.tgz#83299e06ee1466057ba0e64e532777d2929b90d3" + integrity sha512-kasvDsEi/r1fMzKouIDv7B8I6vNmknXwGiYodErGuESoFTohGSKZplFtVxZqHaoQ217AynyIFgnOVRitpHs0Qw== + dependencies: + libsodium "^0.7.13" + +libsodium@^0.7.13: + version "0.7.13" + resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.13.tgz#230712ec0b7447c57b39489c48a4af01985fb393" + integrity sha512-mK8ju0fnrKXXfleL53vtp9xiPq5hKM0zbDQtcxQIsSmxNgSxqCj6R7Hl9PkrNe2j29T4yoDaF7DJLK9/i5iWUw== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +load-json-file@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-6.2.0.tgz#5c7770b42cafa97074ca2848707c61662f4251a1" + integrity sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ== + dependencies: + graceful-fs "^4.1.15" + parse-json "^5.0.0" + strip-bom "^4.0.0" + type-fest "^0.6.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.camelcase@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== + +lodash.kebabcase@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" + integrity sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g== + +lodash.snakecase@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" + integrity sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw== + +lodash.upperfirst@^4.2.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" + integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg== + +lodash.zip@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.zip/-/lodash.zip-4.2.0.tgz#ec6662e4896408ed4ab6c542a3990b72cc080020" + integrity sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg== + +lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +merkletreejs@^0.2.23: + version "0.2.32" + resolved "https://registry.yarnpkg.com/merkletreejs/-/merkletreejs-0.2.32.tgz#cf1c0760e2904e4a1cc269108d6009459fd06223" + integrity sha512-TostQBiwYRIwSE5++jGmacu3ODcKAgqb0Y/pnIohXS7sWxh1gCkSptbmF1a43faehRDpcHf7J/kv0Ml2D/zblQ== + dependencies: + bignumber.js "^9.0.1" + buffer-reverse "^1.0.1" + crypto-js "^3.1.9-1" + treeify "^1.1.0" + web3-utils "^1.3.4" + +micro-ftch@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/micro-ftch/-/micro-ftch-0.3.1.tgz#6cb83388de4c1f279a034fb0cf96dfc050853c5f" + integrity sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg== + +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + +minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + +mkdirp@^0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +multimap@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/multimap/-/multimap-1.1.0.tgz#5263febc085a1791c33b59bb3afc6a76a2a10ca8" + integrity sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw== + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + integrity sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +natural-orderby@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/natural-orderby/-/natural-orderby-2.0.3.tgz#8623bc518ba162f8ff1cdb8941d74deb0fdcc016" + integrity sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q== + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-addon-api@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" + integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== + +node-gyp-build@^4.2.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.0.tgz#3fee9c1731df4581a3f9ead74664369ff00d26dd" + integrity sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og== + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-package-data@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" + integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== + dependencies: + hosted-git-info "^4.0.1" + is-core-module "^2.5.0" + semver "^7.3.4" + validate-npm-package-license "^3.0.1" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== + dependencies: + path-key "^2.0.0" + +number-to-bn@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" + integrity sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig== + dependencies: + bn.js "4.11.6" + strip-hex-prefix "1.0.0" + +object-treeify@^1.1.4: + version "1.1.33" + resolved "https://registry.yarnpkg.com/object-treeify/-/object-treeify-1.1.33.tgz#f06fece986830a3cba78ddd32d4c11d1f76cdf40" + integrity sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ== + dependencies: + mimic-fn "^1.0.0" + +optionator@^0.8.2: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +password-prompt@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/password-prompt/-/password-prompt-1.1.3.tgz#05e539f4e7ca4d6c865d479313f10eb9db63ee5f" + integrity sha512-HkrjG2aJlvF0t2BMH0e2LB/EHf3Lcq3fNMzy4GYHcQblAvOl+QQji1Lx7WRBMqpVK8p+KR7bCg7oqAMXtdgqyw== + dependencies: + ansi-escapes "^4.3.2" + cross-spawn "^7.0.3" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== + +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pbkdf2@^3.0.17: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +qqjs@^0.3.10: + version "0.3.11" + resolved "https://registry.yarnpkg.com/qqjs/-/qqjs-0.3.11.tgz#795b9f7d00807d75c391b1241b5be3077143d9ea" + integrity sha512-pB2X5AduTl78J+xRSxQiEmga1jQV0j43jOPs/MTgTLApGFEOn6NgdE2dEjp7nvDtjkIOZbvFIojAiYUx6ep3zg== + dependencies: + chalk "^2.4.1" + debug "^4.1.1" + execa "^0.10.0" + fs-extra "^6.0.1" + get-stream "^5.1.0" + glob "^7.1.2" + globby "^10.0.1" + http-call "^5.1.2" + load-json-file "^6.2.0" + pkg-dir "^4.2.0" + tar-fs "^2.0.0" + tmp "^0.1.0" + write-json-file "^4.1.1" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +ramda@^0.26.1: + version "0.26.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" + integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ== + +ramda@^0.27.1: + version "0.27.2" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.2.tgz#84463226f7f36dc33592f6f4ed6374c48306c3f1" + integrity sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readonly-date@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/readonly-date/-/readonly-date-1.0.0.tgz#5af785464d8c7d7c40b9d738cbde8c646f97dcd9" + integrity sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ== + +redeyed@~2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-2.1.1.tgz#8984b5815d99cb220469c99eeeffe38913e6cc0b" + integrity sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ== + dependencies: + esprima "~4.0.0" + +regexp-tree@^0.1.23, regexp-tree@~0.1.1: + version "0.1.27" + resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" + integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== + +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +regexpp@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.10.0, resolve@^1.10.1, resolve@^1.8.1: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q== + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +rlp@^2.2.4: + version "2.2.7" + resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf" + integrity sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ== + dependencies: + bn.js "^5.2.0" + +run-async@^2.2.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rxjs@^6.4.0: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg== + dependencies: + ret "~0.1.10" + +safe-regex@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-2.1.1.tgz#f7128f00d056e2fe5c11e81a1324dd974aadced2" + integrity sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A== + dependencies: + regexp-tree "~0.1.1" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +scrypt-js@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" + integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== + +secp256k1@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303" + integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA== + dependencies: + elliptic "^6.5.4" + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + +"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.5.1: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + +semver@^6.0.0, semver@^6.1.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.5.4: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + +sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== + dependencies: + shebang-regex "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +sort-keys@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-4.2.0.tgz#6b7638cee42c506fff8c1cecde7376d21315be18" + integrity sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg== + dependencies: + is-plain-obj "^2.0.0" + +source-map-support@^0.5.17: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spdx-correct@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz#5d607d27fc806f66d7b64a766650fa890f04ed66" + integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.17" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz#887da8aa73218e51a1d917502d79863161a93f9c" + integrity sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +string-width@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow== + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== + +strip-hex-prefix@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" + integrity sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A== + dependencies: + is-hex-prefixed "1.0.0" + +strip-json-comments@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.1.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" + integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +tar-fs@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tmp@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" + integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw== + dependencies: + rimraf "^2.6.3" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +treeify@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" + integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A== + +ts-node@^8: + version "8.10.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" + integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== + dependencies: + arg "^4.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + +tslib@^1, tslib@^1.8.1, tslib@^1.9.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.6.1, tslib@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +tsutils@^3.17.1: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== + dependencies: + prelude-ls "~1.1.2" + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typescript@^3.3: + version "3.9.10" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" + integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +utf8@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" + integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +web3-utils@^1.3.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.4.tgz#0daee7d6841641655d8b3726baf33b08eda1cbec" + integrity sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A== + dependencies: + "@ethereumjs/util" "^8.1.0" + bn.js "^5.2.1" + ethereum-bloom-filters "^1.0.6" + ethereum-cryptography "^2.1.2" + ethjs-unit "0.1.6" + number-to-bn "1.7.0" + randombytes "^2.1.0" + utf8 "3.0.0" + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" + +word-wrap@~1.2.3: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +write-json-file@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-4.3.0.tgz#908493d6fd23225344af324016e4ca8f702dd12d" + integrity sha512-PxiShnxf0IlnQuMYOPPhPkhExoCQuTUNPOa/2JWCYTmBquU9njyyDuwRKN26IZBlp4yn1nt+Agh2HOOBl+55HQ== + dependencies: + detect-indent "^6.0.0" + graceful-fs "^4.1.15" + is-plain-obj "^2.0.0" + make-dir "^3.0.0" + sort-keys "^4.0.0" + write-file-atomic "^3.0.0" + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== diff --git a/tools/multisig/broadcast_multi.sh b/tools/multisig/broadcast_multi.sh new file mode 100755 index 0000000..97b2ecd --- /dev/null +++ b/tools/multisig/broadcast_multi.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# GUIDE: +# Organize all the signatures and the tx to be signed in one directory +# Pass that directory as $1 +# Pass the name of the file to be signed as $2 + + + +cd $1 +res=`ls` +signatures="" +for files in $res +do + if [ $files == signedMultiTx.json ] + then + rm signedMultiTx.json + fi + if [ $files == $2 ] + then + continue + else + signatures=$signatures" "$files + fi +done + +secretd tx multisign $2 ss_multisig $signatures --chain-id secret-4 > signedMultiTx.json +secretd tx broadcast signedMultiTx.json diff --git a/tools/multisig/sign_mutli.sh b/tools/multisig/sign_mutli.sh new file mode 100755 index 0000000..f0b059c --- /dev/null +++ b/tools/multisig/sign_mutli.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# example: ./multisig.sh secret1y277c499f44nxe7geeaqw8t6gpge68rcpla9lf ~/json/output.json jsledger + +secretd config node https://rpc.scrt.network:443 +secretd config chain-id secret-4 +res=`secretd q account $1` +eval sequence=`echo $res | jq ".sequence"` +eval acc_num=`echo $res | jq ".account_number"` +outputdoc="signature_$3.json" +secretd tx sign $2 --multisig ss_multisig --from $3 --output-document $outputdoc --chain-id secret-4 --offline --sequence $sequence --account-number $acc_num --sign-mode amino-json diff --git a/tools/multisig/sign_permit.py b/tools/multisig/sign_permit.py new file mode 100644 index 0000000..6cd8c57 --- /dev/null +++ b/tools/multisig/sign_permit.py @@ -0,0 +1,36 @@ +import argparse +import os + +parser = argparse.ArgumentParser(description="Create a cosmwasm msg for offline signing") + +parser.add_argument("msg", type=str, help="Permit data") +parser.add_argument("account", type=str, help="Permit signer") +parser.add_argument("--account_number", type=str, help="Account number", default="0") +parser.add_argument("--chain_id", type=str, help="Chain id to which this permit is written for", default="secret-4") +parser.add_argument("--memo", type=str, help="Memo for the permit", default="") +parser.add_argument("--msg_type", type=str, help="Msg type used on the signed msg", default="signature_proof") +parser.add_argument("--sequence", type=str, help="Signature sequence number", default="0") + +parser.add_argument("-o", "--output", type=str, help="Output message") +parser.add_argument("--use_old", action="store_true", help="Uses secretcli instead of secretd") +args = parser.parse_args() + +bin = "secretd" + +if args.use_old: + bin = "secretcli" + +output = "signed.json" + +if args.output: + output = args.output + +unsigned_permit = f'echo \' {{ "account_number": "{args.account_number}", ' \ + f'"chain_id": "{args.chain_id}", ' \ + f'"fee": {{ "amount": [{{ "amount": "0", "denom": "uscrt"}}], "gas": "1" }}, ' \ + f'"memo": "{args.memo}", "msgs": [{{ "type": "{args.msg_type}", "value": {args.msg} }}], ' \ + f'"sequence": "{args.sequence}"}} \'> unsigned.json' +os.system(unsigned_permit) + +command = f'{bin} tx sign-doc unsigned.json --from {args.account} > {output}' +os.system(command) \ No newline at end of file diff --git a/tools/multisig/wasm_msg.py b/tools/multisig/wasm_msg.py new file mode 100644 index 0000000..197461a --- /dev/null +++ b/tools/multisig/wasm_msg.py @@ -0,0 +1,25 @@ +import argparse +import os + +parser = argparse.ArgumentParser(description="Create a cosmwasm msg for offline signing") +parser.add_argument("contract_address", type=str, help="Smart contract's address") +parser.add_argument("contract_codehash", type=str, help="Smart contract's code hash") +parser.add_argument("msg", type=str, help="Smart contract's msg to execute") +parser.add_argument("sender", type=str, help="The msg sender") +parser.add_argument("key", type=str, help="Enclave key certificate") +parser.add_argument("-o", "--output", type=str, help="Output message") +parser.add_argument("--use_old", action="store_true", help="Uses secretcli instead of secretd") +args = parser.parse_args() + +bin = "secretd" + +if args.use_old: + bin = "secretcli" + +output = "output.json" + +if args.output: + output = args.output + +command = f"{bin} tx compute execute {args.contract_address} '{args.msg}' --from {args.sender} --generate-only --enclave-key {args.key} --code-hash {args.contract_codehash} --offline --sign-mode amino-json > {output}" +os.system(command) \ No newline at end of file From 35553d83486c77d05d05df6b0da3425c467fb559 Mon Sep 17 00:00:00 2001 From: hard-nett Date: Wed, 3 Apr 2024 14:19:09 -0400 Subject: [PATCH 10/23] add eth_sig to account --- Cargo.lock | 2 +- contracts/airdrop/README.md | 1 - contracts/airdrop/src/contract.rs | 7 +- contracts/airdrop/src/handle.rs | 116 ++- contracts/airdrop/src/query.rs | 3 +- contracts/airdrop/src/state.rs | 9 + contracts/airdrop/src/test.rs | 2 - makefile | 2 +- packages/shade_protocol/Cargo.toml | 5 +- .../contract_interfaces/airdrop/account.rs | 21 +- .../src/contract_interfaces/airdrop/mod.rs | 11 +- .../mint/liability_mint.rs | 127 ---- .../src/contract_interfaces/mint/mint.rs | 174 ----- .../contract_interfaces/mint/mint_router.rs | 92 --- .../src/contract_interfaces/mint/mod.rs | 6 - packages/shade_protocol/src/schemas.rs | 24 +- tools/doc2book/Cargo.toml | 11 + tools/doc2book/README.md | 59 ++ tools/doc2book/src/doc2book.rs | 326 ++++++++ tools/headstash/account.js | 79 +- tools/headstash/main.js | 29 +- tools/headstash/yarn.lock | 709 ++++++++++++++++++ yarn.lock | 4 + 23 files changed, 1278 insertions(+), 541 deletions(-) delete mode 100644 packages/shade_protocol/src/contract_interfaces/mint/liability_mint.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/mint/mint.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/mint/mint_router.rs delete mode 100644 packages/shade_protocol/src/contract_interfaces/mint/mod.rs create mode 100644 tools/doc2book/Cargo.toml create mode 100644 tools/doc2book/README.md create mode 100644 tools/doc2book/src/doc2book.rs create mode 100644 tools/headstash/yarn.lock create mode 100644 yarn.lock diff --git a/Cargo.lock b/Cargo.lock index 96c951b..f4ad452 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,7 +15,7 @@ dependencies = [ [[package]] name = "airdrop" -version = "0.1.1" +version = "0.1.0" dependencies = [ "ethereum-verify", "hex", diff --git a/contracts/airdrop/README.md b/contracts/airdrop/README.md index cfc20ec..6403106 100644 --- a/contracts/airdrop/README.md +++ b/contracts/airdrop/README.md @@ -248,7 +248,6 @@ NOTE: The parameters must be in order ```json { - "coins": [], "contract": "", "execute_msg": "", "sender": "" diff --git a/contracts/airdrop/src/contract.rs b/contracts/airdrop/src/contract.rs index 940b0a1..838fba5 100644 --- a/contracts/airdrop/src/contract.rs +++ b/contracts/airdrop/src/contract.rs @@ -120,19 +120,18 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S ExecuteMsg::Account { addresses, eth_pubkey, + eth_sig, .. - } => try_account(deps, &env, &info, addresses, eth_pubkey), + } => try_account(deps, &env, &info, addresses, eth_pubkey, eth_sig), ExecuteMsg::DisablePermitKey { key, .. } => { try_disable_permit_key(deps, &env, &info, key) } ExecuteMsg::SetViewingKey { key, .. } => try_set_viewing_key(deps, &env, &info, key), ExecuteMsg::Claim { amount, - eth_pubkey, - eth_sig, proof, .. - } => try_claim(deps, &env, &info, amount, eth_pubkey, eth_sig, proof), + } => try_claim(deps, &env, &info, amount, proof), ExecuteMsg::ClaimDecay { .. } => try_claim_decay(deps, &env, &info), }, RESPONSE_BLOCK_SIZE, diff --git a/contracts/airdrop/src/handle.rs b/contracts/airdrop/src/handle.rs index 026137a..fcc26b9 100644 --- a/contracts/airdrop/src/handle.rs +++ b/contracts/airdrop/src/handle.rs @@ -1,7 +1,7 @@ use crate::state::{ account_r, account_viewkey_w, account_w, address_in_account_w, claim_status_w, config_r, - config_w, decay_claimed_w, eth_pubkey_claim_r, eth_pubkey_claim_w, revoke_permit, - total_claimed_r, total_claimed_w, validate_address_permit, + config_w, decay_claimed_w, eth_pubkey_claim_r, eth_pubkey_claim_w, eth_pubkey_in_account_w, + eth_sig_in_account_w, revoke_permit, total_claimed_r, total_claimed_w, validate_address_permit, }; use hex::decode_to_slice; // use rs_merkle::{algorithms::Sha256, Hasher, MerkleProof}; @@ -11,14 +11,14 @@ use shade_protocol::{ account::{Account, AccountKey, AddressProofMsg, AddressProofPermit}, errors::{ address_already_in_account, airdrop_ended, airdrop_not_started, already_claimed, - claim_too_high, decay_claimed, decay_not_set, expected_memo, failed_verification, - invalid_dates, not_admin, nothing_to_claim, wrong_length, + decay_claimed, decay_not_set, expected_memo, failed_verification, invalid_dates, + not_admin, nothing_to_claim, wrong_length, }, Config, ExecuteAnswer, }, c_std::{ - ensure_eq, from_binary, to_binary, Addr, Api, Binary, Decimal, DepsMut, Env, MessageInfo, - Response, StdResult, Storage, Uint128, + ensure_eq, from_binary, to_binary, Addr, Api, BankMsg, Binary, CosmosMsg, Decimal, DepsMut, + Env, MessageInfo, Response, StdResult, Storage, Uint128, }, query_authentication::viewing_keys::ViewingKey, snip20::helpers::send_msg, @@ -144,6 +144,7 @@ pub fn try_account( info: &MessageInfo, addresses: Vec, eth_pubkey: String, + eth_sig: String, ) -> StdResult { // Check if airdrop active let config = config_r(deps.storage).load()?; @@ -171,6 +172,7 @@ pub fn try_account( &mut account, addresses.clone(), eth_pubkey.clone(), + eth_sig.clone(), )?; // we setup an unchecked eth_pubkey for now. We will verify this eth_pubkey during @@ -199,7 +201,8 @@ pub fn try_account( &info.sender, &mut account, addresses.clone(), - eth_pubkey.clone(), + eth_pubkey, + eth_sig.clone(), )?; } @@ -251,8 +254,6 @@ pub fn try_claim( env: &Env, info: &MessageInfo, amount: Uint128, - eth_pubkey: String, - eth_sig: String, proof: Vec, ) -> StdResult { let config = config_r(deps.storage).load()?; @@ -269,7 +270,7 @@ pub fn try_claim( &deps, info.clone(), account.eth_pubkey.clone(), // uses the saved account eth_pubkey - eth_sig, + account.eth_sig.clone(), config.clone(), )?; @@ -305,21 +306,38 @@ pub fn try_claim( total_claimed_w(deps.storage) .update(|claimed| -> StdResult { Ok(claimed + redeem_amount) })?; + let mut msgs: Vec = vec![]; + + let hs1 = send_msg( + sender.clone(), + redeem_amount.into(), + None, + None, + None, + &config.airdrop_snip20, + )?; + msgs.push(hs1); + + if !config.airdrop_snip20_optional.is_none() { + let hs2 = send_msg( + sender.clone(), + redeem_amount.into(), + None, + None, + None, + &config.airdrop_snip20_optional.unwrap(), + )?; + msgs.push(hs2); + }; + Ok(Response::new() .set_data(to_binary(&ExecuteAnswer::Claim { status: ResponseStatus::Success, - claimed: eth_pubkey_claim_r(deps.storage).load(eth_pubkey.as_bytes())?, + claimed: eth_pubkey_claim_r(deps.storage).load(account.eth_pubkey.as_bytes())?, addresses: account.addresses, eth_pubkey: account.eth_pubkey, })?) - .add_message(send_msg( - sender.clone(), - redeem_amount.into(), - None, - None, - None, - &config.airdrop_snip20, - )?)) + .add_messages(msgs)) } pub fn try_claim_decay(deps: DepsMut, env: &Env, _info: &MessageInfo) -> StdResult { @@ -339,17 +357,34 @@ pub fn try_claim_decay(deps: DepsMut, env: &Env, _info: &MessageInfo) -> StdResu let total_claimed = total_claimed_r(deps.storage).load()?; let send_total = config.airdrop_amount.checked_sub(total_claimed)?; - let messages = vec![send_msg( + let mut msgs: Vec = vec![]; + + let hs1 = send_msg( dump_address.clone(), send_total.into(), None, None, None, &config.airdrop_snip20, - )?]; + )?; + + msgs.push(hs1); + + if !config.airdrop_snip20_optional.is_none() { + let hs2 = send_msg( + dump_address.clone(), + send_total.into(), + None, + None, + None, + &config.airdrop_snip20_optional.unwrap(), + )?; + msgs.push(hs2); + }; return Ok(Response::new() - .set_data(to_binary(&ExecuteAnswer::ClaimDecay { status: Success })?)); + .set_data(to_binary(&ExecuteAnswer::ClaimDecay { status: Success })?) + .add_messages(msgs)); } } } @@ -391,10 +426,8 @@ pub fn try_add_account_addresses( account: &mut Account, addresses: Vec, eth_pubkey: String, + eth_sig: String, ) -> StdResult<()> { - // Setup the items to validate - let mut leaves_to_validate: Vec<(usize, [u8; 32])> = vec![]; - // Iterate addresses for permit in addresses.iter() { if let Some(memo) = permit.memo.clone() { @@ -406,14 +439,6 @@ pub fn try_add_account_addresses( validate_address_permit(storage, api, permit, ¶ms, config.contract.clone())?; } - // Check that airdrop amount does not exceed maximum - if params.amount > config.max_amount { - return Err(claim_too_high( - params.amount.to_string().as_str(), - config.max_amount.to_string().as_str(), - )); - } - // Update address if its not in an account address_in_account_w(storage).update( params.address.to_string().as_bytes(), @@ -426,8 +451,33 @@ pub fn try_add_account_addresses( }, )?; + // Update eth_pubkey if its not in an account + eth_pubkey_in_account_w(storage).update( + eth_pubkey.to_string().as_bytes(), + |state| -> StdResult { + if state.is_some() { + return Err(address_already_in_account(ð_pubkey.as_str())); + } + + Ok(true) + }, + )?; + // Update eth_sig if its not in an account + eth_sig_in_account_w(storage).update( + eth_sig.to_string().as_bytes(), + |state| -> StdResult { + if state.is_some() { + return Err(address_already_in_account(ð_pubkey.as_str())); + } + + Ok(true) + }, + )?; + // If valid then add to account array and sum total amount account.addresses.push(params.address); + account.eth_pubkey = eth_pubkey.to_string(); + account.eth_sig = eth_sig.to_string(); } else { return Err(expected_memo()); } diff --git a/contracts/airdrop/src/query.rs b/contracts/airdrop/src/query.rs index 58753c4..e5deadf 100644 --- a/contracts/airdrop/src/query.rs +++ b/contracts/airdrop/src/query.rs @@ -50,7 +50,7 @@ fn account_information( let account = account_r(deps.storage).load(account_address.to_string().as_bytes())?; // Calculate eligible tasks - let config = config_r(deps.storage).load()?; + // let config = config_r(deps.storage).load()?; // Check if eth address has claimed let claim_state = eth_pubkey_claim_r(deps.storage).may_load(account.eth_pubkey.as_bytes())?; @@ -59,6 +59,7 @@ fn account_information( claimed: claim_state.unwrap(), addresses: account.addresses, eth_pubkey: account.eth_pubkey, + eth_sig: account.eth_sig, }) } diff --git a/contracts/airdrop/src/state.rs b/contracts/airdrop/src/state.rs index 87ff062..69923f4 100644 --- a/contracts/airdrop/src/state.rs +++ b/contracts/airdrop/src/state.rs @@ -17,6 +17,7 @@ pub static DECAY_CLAIMED_KEY: &[u8] = b"decay_claimed"; pub static CLAIM_STATUS_KEY: &[u8] = b"claim_status_"; pub static REWARD_IN_ACCOUNT_KEY: &[u8] = b"reward_in_account"; pub static ETH_PUBKEY_IN_ACCOUNT_KEY: &[u8] = b"eth_pubkey_in_account"; +pub static ETH_SIG_IN_ACCOUNT_KEY: &[u8] = b"eth_sig_in_account"; pub static ETH_PUBKEY: &str = "eth_pubkey"; pub static ACCOUNTS_KEY: &[u8] = b"accounts"; pub static TOTAL_CLAIMED_KEY: &[u8] = b"total_claimed"; @@ -87,6 +88,14 @@ pub fn eth_pubkey_in_account_r(storage: &dyn Storage) -> ReadonlyBucket { pub fn eth_pubkey_in_account_w(storage: &mut dyn Storage) -> Bucket { bucket(storage, ETH_PUBKEY_IN_ACCOUNT_KEY) } +// Is address added to an account +pub fn eth_sig_in_account_r(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, ETH_SIG_IN_ACCOUNT_KEY) +} + +pub fn eth_sig_in_account_w(storage: &mut dyn Storage) -> Bucket { + bucket(storage, ETH_SIG_IN_ACCOUNT_KEY) +} // Eth pubkey claimed pub fn eth_pubkey_claim_r(storage: &dyn Storage) -> ReadonlyBucket { diff --git a/contracts/airdrop/src/test.rs b/contracts/airdrop/src/test.rs index af4835c..2357487 100644 --- a/contracts/airdrop/src/test.rs +++ b/contracts/airdrop/src/test.rs @@ -306,9 +306,7 @@ pub mod tests { fn memo_deserialization() { let expected_memo = AddressProofMsg { address: Addr::unchecked("secret19q7h2zy8mgesy3r39el5fcm986nxqjd7cgylrz".to_string()), - amount: Uint128::new(1000000u128), contract: Addr::unchecked("secret1sr62lehajgwhdzpmnl65u35rugjrgznh2572mv".to_string()), - index: 10, key: "account-creation-permit".to_string(), }; diff --git a/makefile b/makefile index 966c6f4..77ca6f0 100755 --- a/makefile +++ b/makefile @@ -13,7 +13,7 @@ rm ./$(1).wasm endef CONTRACTS = \ - airdrop \ + airdrop PACKAGES = shade_protocol contract_harness cosmwasm_math_compat ethereum_verify diff --git a/packages/shade_protocol/Cargo.toml b/packages/shade_protocol/Cargo.toml index 9e307df..f026a7c 100644 --- a/packages/shade_protocol/Cargo.toml +++ b/packages/shade_protocol/Cargo.toml @@ -13,10 +13,7 @@ edition = "2018" name = "schemas" path = "src/schemas.rs" # Must have all of the contract_interfaces -required-features = [ - "admin", "airdrop", "bonds", "dao", "dex", "governance-impl", "mint", "oracles", - "peg_stability", "query_auth", "sky", "snip20", "staking", "mint_router", "snip20_migration", -] +required-features = ["airdrop"] [lib] crate-type = ["cdylib", "rlib"] diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs index f08e294..382a186 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs @@ -1,17 +1,17 @@ use crate::contract_interfaces::airdrop::errors::permit_rejected; -use crate::c_std::Uint128; -use crate::c_std::{Addr, StdResult, Api}; +use crate::c_std::{Addr, Api, StdResult}; use crate::query_authentication::{ permit::{bech32_to_canonical, Permit}, viewing_keys::ViewingKey, }; -use cosmwasm_schema::{cw_serde}; +use cosmwasm_schema::cw_serde; #[cw_serde] pub struct Account { pub addresses: Vec, pub eth_pubkey: String, + pub eth_sig: String, pub claimed: bool, } @@ -20,7 +20,8 @@ impl Default for Account { Self { addresses: vec![], eth_pubkey: "".to_string(), - claimed: false + eth_sig: "".to_string(), + claimed: false, } } } @@ -38,7 +39,6 @@ pub struct AccountPermitMsg { #[remain::sorted] #[cw_serde] pub struct FillerMsg { - pub coins: Vec, pub contract: String, pub execute_msg: EmptyMsg, pub sender: String, @@ -47,7 +47,6 @@ pub struct FillerMsg { impl Default for FillerMsg { fn default() -> Self { Self { - coins: vec![], contract: "".to_string(), sender: "".to_string(), execute_msg: EmptyMsg {}, @@ -62,7 +61,11 @@ pub struct EmptyMsg {} // Used to prove ownership over IBC addresses pub type AddressProofPermit = Permit; -pub fn authenticate_ownership(api: &dyn Api, permit: &AddressProofPermit, permit_address: &str) -> StdResult<()> { +pub fn authenticate_ownership( + api: &dyn Api, + permit: &AddressProofPermit, + permit_address: &str, +) -> StdResult<()> { let signer_address = permit .validate(api, Some("wasm/MsgExecuteContract".to_string()))? .as_canonical(); @@ -79,12 +82,8 @@ pub fn authenticate_ownership(api: &dyn Api, permit: &AddressProofPermit, permit pub struct AddressProofMsg { // Address is necessary since we have other network permits present pub address: Addr, - // Reward amount - pub amount: Uint128, // Used to prevent permits from being used elsewhere pub contract: Addr, - // Index of the address in the leaves array - pub index: u32, // Used to identify permits pub key: String, } diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs index 71b86ce..6e66543 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs @@ -3,11 +3,8 @@ pub mod claim_info; pub mod errors; use crate::{ - c_std::{Addr, Binary, Uint128}, - contract_interfaces::airdrop::{ - account::{AccountPermit, AddressProofPermit}, - claim_info::RequiredTask, - }, + c_std::{Addr, Uint128}, + contract_interfaces::airdrop::account::{AccountPermit, AddressProofPermit}, utils::{asset::Contract, generic_response::ResponseStatus}, }; @@ -95,6 +92,7 @@ pub enum ExecuteMsg { Account { addresses: Vec, eth_pubkey: String, + eth_sig: String, padding: Option, }, DisablePermitKey { @@ -107,8 +105,6 @@ pub enum ExecuteMsg { }, Claim { amount: Uint128, - eth_pubkey: String, - eth_sig: String, proof: Vec, padding: Option, }, @@ -193,6 +189,7 @@ pub enum QueryAnswer { claimed: bool, addresses: Vec, eth_pubkey: String, + eth_sig: String, }, } diff --git a/packages/shade_protocol/src/contract_interfaces/mint/liability_mint.rs b/packages/shade_protocol/src/contract_interfaces/mint/liability_mint.rs deleted file mode 100644 index 1622cd5..0000000 --- a/packages/shade_protocol/src/contract_interfaces/mint/liability_mint.rs +++ /dev/null @@ -1,127 +0,0 @@ -use crate::{ - c_std::{Addr, Binary, Uint128}, - contract_interfaces::snip20::helpers::Snip20Asset, - utils::{asset::Contract, generic_response::ResponseStatus}, -}; - -use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; -use cosmwasm_schema::cw_serde; -use std::convert::TryFrom; - -#[cw_serde] -pub struct Config { - //TODO shade_admin contract - pub admin: Addr, - pub token: Contract, - pub debt_ratio: Uint128, - pub oracle: Contract, - pub treasury: Contract, - //pub interest: Uint128 <- probs not -} - -#[cw_serde] -pub struct InstantiateMsg { - pub admin: Option, - pub token: Contract, - pub debt_ratio: Uint128, - pub oracle: Contract, - pub treasury: Contract, -} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteMsg { - UpdateConfig { - config: Config, - }, - RemoveWhitelist { - // Contract? - address: Addr, - }, - AddWhitelist { - // Contract? - address: Addr, - }, - AddCollateral { - asset: Contract, - }, - RemoveCollateral { - asset: Contract, - }, - Mint { - amount: Uint128, - }, - // Receive config.token to pay back liabilities - Receive { - sender: Addr, - from: Addr, - amount: Uint128, - memo: Option, - msg: Option, - }, -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteAnswer { - Init { - status: ResponseStatus, - address: Addr, - }, - RemoveWhitelist { - status: ResponseStatus, - }, - AddWhitelist { - status: ResponseStatus, - }, - RemoveCollateral { - status: ResponseStatus, - }, - AddCollateral { - status: ResponseStatus, - }, - UpdateConfig { - status: ResponseStatus, - }, - Mint { - status: ResponseStatus, - amount: Uint128, - }, -} - -#[cw_serde] -pub enum QueryMsg { - Whitelist {}, - Liabilities {}, - //TODO add once moved to storage - //Collateral {}, - Token {}, - Config {}, -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryAnswer { - Whitelist { - whitelist: Vec, - }, - Liabilities { - outstanding: Uint128, - limit: Uint128, - }, - Token { - token: Snip20Asset, - }, - Config { - config: Config, - }, -} diff --git a/packages/shade_protocol/src/contract_interfaces/mint/mint.rs b/packages/shade_protocol/src/contract_interfaces/mint/mint.rs deleted file mode 100644 index 321c4ca..0000000 --- a/packages/shade_protocol/src/contract_interfaces/mint/mint.rs +++ /dev/null @@ -1,174 +0,0 @@ -use crate::{ - contract_interfaces::snip20::helpers::Snip20Asset, - utils::{asset::Contract, generic_response::ResponseStatus}, -}; -use crate::c_std::Uint128; -use crate::c_std::{Binary, Addr}; - -use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; -use cosmwasm_schema::{cw_serde}; - - -#[cw_serde] -pub struct Config { - pub admin: Addr, - pub oracle: Contract, - // Both treasury & Commission must be set to function - pub treasury: Addr, - pub secondary_burn: Option, - pub activated: bool, - pub limit: Option, -} - -/// Used to store the assets allowed to be burned -#[cw_serde] -pub struct SupportedAsset { - pub asset: Snip20Asset, - // Capture a percentage of burned assets - pub capture: Uint128, - // Fee taken off the top of a given burned asset - pub fee: Uint128, - pub unlimited: bool, -} - -#[cw_serde] -pub enum Limit { - Daily { - supply_portion: Uint128, - days: Uint128, - }, - Monthly { - supply_portion: Uint128, - months: Uint128, - }, -} - -#[cw_serde] -pub struct InstantiateMsg { - pub admin: Option, - pub oracle: Contract, - - // Asset that is minted - pub native_asset: Contract, - - //Symbol to peg to, default to snip20 symbol - pub peg: Option, - - // Both treasury & asset capture must be set to function properly - pub treasury: Addr, - - // This is where the non-burnable assets will go, if not defined they will stay in this contract - pub secondary_burn: Option, - - pub limit: Option, -} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteMsg { - UpdateConfig { - config: Config, - }, - RegisterAsset { - contract: Contract, - // Commission * 100 e.g. 5 == .05 == 5% - capture: Option, - fee: Option, - unlimited: Option, - }, - RemoveAsset { - address: Addr, - }, - Receive { - sender: Addr, - from: Addr, - amount: Uint128, - memo: Option, - msg: Option, - }, -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub struct SnipMsgHook { - pub minimum_expected_amount: Uint128, - pub to_mint: Addr, -} - -#[cw_serde] -pub struct MintMsgHook { - pub minimum_expected_amount: Uint128, -} - -#[cw_serde] -pub enum ExecuteAnswer { - Init { - status: ResponseStatus, - address: Addr, - }, - UpdateConfig { - status: ResponseStatus, - }, - RegisterAsset { - status: ResponseStatus, - }, - RemoveAsset { - status: ResponseStatus, - }, - Mint { - status: ResponseStatus, - amount: Uint128, - }, -} - -#[cw_serde] -pub enum QueryMsg { - NativeAsset {}, - SupportedAssets {}, - Asset { - contract: String, - }, - Config {}, - Limit {}, - Mint { - offer_asset: Addr, - amount: Uint128, - }, -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryAnswer { - NativeAsset { - asset: Snip20Asset, - peg: String, - }, - SupportedAssets { - assets: Vec, - }, - Asset { - asset: SupportedAsset, - burned: Uint128, - }, - Config { - config: Config, - }, - Limit { - minted: Uint128, - limit: Uint128, - last_refresh: String, - }, - Mint { - asset: Contract, - amount: Uint128, - }, -} diff --git a/packages/shade_protocol/src/contract_interfaces/mint/mint_router.rs b/packages/shade_protocol/src/contract_interfaces/mint/mint_router.rs deleted file mode 100644 index c6bfcb5..0000000 --- a/packages/shade_protocol/src/contract_interfaces/mint/mint_router.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::{ - c_std::{Addr, Binary, Uint128}, - contract_interfaces::snip20::helpers::Snip20Asset, - utils::{asset::Contract, generic_response::ResponseStatus}, -}; - -use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; -use cosmwasm_schema::cw_serde; - -#[cw_serde] -pub struct Config { - pub admin: Addr, - pub path: Vec, -} - -/* -#[cw_serde] -pub struct MintMsgHook { - pub minimum_expected_amount: Option, - pub routing_flag: Option, -} -*/ - -#[cw_serde] -pub struct PathNode { - pub input_asset: Addr, - pub input_amount: Uint128, - pub mint: Addr, - pub output_asset: Addr, - pub output_amount: Uint128, -} - -#[cw_serde] -pub struct InstantiateMsg { - pub admin: Option, - pub path: Vec, -} - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteMsg { - UpdateConfig { - config: Config, - }, - Receive { - sender: Addr, - from: Addr, - amount: Uint128, - memo: Option, - msg: Option, - }, -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum ExecuteAnswer { - Init { - status: ResponseStatus, - address: Addr, - }, - UpdateConfig { - status: ResponseStatus, - }, - Mint { - status: ResponseStatus, - amount: Uint128, - }, -} - -#[cw_serde] -pub enum QueryMsg { - Config {}, - Assets {}, - Route { asset: Addr, amount: Uint128 }, -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = 256; -} - -#[cw_serde] -pub enum QueryAnswer { - Config { config: Config }, - Assets { assets: Vec }, - Route { path: Vec }, -} diff --git a/packages/shade_protocol/src/contract_interfaces/mint/mod.rs b/packages/shade_protocol/src/contract_interfaces/mint/mod.rs deleted file mode 100644 index 707ecad..0000000 --- a/packages/shade_protocol/src/contract_interfaces/mint/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[cfg(feature = "mint")] -pub mod mint; -#[cfg(feature = "mint_router")] -pub mod mint_router; -#[cfg(feature = "liability_mint")] -pub mod liability_mint; diff --git a/packages/shade_protocol/src/schemas.rs b/packages/shade_protocol/src/schemas.rs index 5a50a2d..149f155 100644 --- a/packages/shade_protocol/src/schemas.rs +++ b/packages/shade_protocol/src/schemas.rs @@ -44,34 +44,22 @@ macro_rules! generate_nested_schemas { } pub fn main() { - generate_schemas!( - airdrop, - bonds, - governance, - peg_stability, - query_auth, - sky, - snip20 - ); + generate_schemas!(airdrop); // generate_nested_schemas!(mint, liability_mint, mint, mint_router); - generate_nested_schemas!(oracles, oracle); - - generate_nested_schemas!(dao, treasury_manager, treasury, scrt_staking); - // generate_nested_schemas!(staking, snip20_staking); // TODO: make admin interface up to standard - use shade_protocol::contract_interfaces::admin; + // use shade_protocol::contract_interfaces::admin; let mut out_dir = current_dir().unwrap(); out_dir.push("schema"); - out_dir.push("admin"); + // out_dir.push("admin"); create_dir_all(&out_dir).unwrap(); remove_schemas(&out_dir).unwrap(); - export_schema(&schema_for!(admin::InstantiateMsg), &out_dir); - export_schema(&schema_for!(admin::ExecuteMsg), &out_dir); - export_schema(&schema_for!(admin::QueryMsg), &out_dir); + // export_schema(&schema_for!(admin::InstantiateMsg), &out_dir); + // export_schema(&schema_for!(admin::ExecuteMsg), &out_dir); + // export_schema(&schema_for!(admin::QueryMsg), &out_dir); } diff --git a/tools/doc2book/Cargo.toml b/tools/doc2book/Cargo.toml new file mode 100644 index 0000000..8d02b99 --- /dev/null +++ b/tools/doc2book/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "doc2book" +version = "0.1.0" +edition = "2018" + +[[bin]] +name = "doc2book" +path = "src/doc2book.rs" + +[dev-dependencies] +pretty_assertions = "1.0.0" diff --git a/tools/doc2book/README.md b/tools/doc2book/README.md new file mode 100644 index 0000000..3da4ab3 --- /dev/null +++ b/tools/doc2book/README.md @@ -0,0 +1,59 @@ +# `doc2book` +A simple tool that scrapes rustdoc comments, `doc2book`-only comments and specified Rust code from a crate's source files +and outputs them as a single file (e.g. a Markdown book chapter). + +## Usage +The usage is simple, specify the crate and the output file path +``` +USAGE: doc2book CRATE_DIR OUT_FILE_PATH +``` + +Example from Shade workspace root directory: +``` +cargo r -p doc2book --release -- packages/shade_protocol doc/book/src/smart_contracts.md +``` + +### Comments +The following comments are scraped: +- `//! `: [rustdoc][1] crate-level (inner) doc comments +- `/// `: [rustdoc][1] code-level (outer) doc comments +- `//# `: `doc2book` comments, these will be ignored by rustdoc but picked up by `doc2book` + +[1]: https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html + +### Code +The comments `// book_include_code` and `// end_book_include_code` mark a section of code to be copied verbatim and place in a Rust code block (Markdown syntax). +This code: + +```rust +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +// book_include_code +pub struct Contract { + /// The address of the contract + pub address: Addr, + /// The hex encoded hash of the contract code + pub code_hash: String, +} +// end_book_include_code +``` + +Would result in this Markdown section: + +~~~ +```rust +pub struct Contract { + /// The address of the contract + pub address: Addr, + /// The hex encoded hash of the contract code + pub code_hash: String, +} +``` +~~~ + +## Limitations +As the tool is currently very simple it has a few limitations: +- The crate structure is expected to be one file per module at the same directory level as the lib.rs file. +- The tool outputs everything as a single file, it could be made to create a 'sub-chapter' file for each module. +- Due to it creating a single file, the ordering of comments, modules declarations and the code is the order it will appear output file. +- Adding whitespace between scraped sections needs to be explicit, i.e. an empty comment line needs to be inserted where you want a blank line `//! `. diff --git a/tools/doc2book/src/doc2book.rs b/tools/doc2book/src/doc2book.rs new file mode 100644 index 0000000..8a00192 --- /dev/null +++ b/tools/doc2book/src/doc2book.rs @@ -0,0 +1,326 @@ +use std::{ + fs::File, + io::{BufRead, BufReader, Error as IoError, Lines, Write}, +}; + +const USAGE: &str = "USAGE: doc2book CRATE_DIR OUT_FILE_PATH"; + +type Result = std::result::Result; + +#[derive(Debug)] +enum Error { + CrateSourceDirNotFound(String), + LibRsNotFound(String), + ModuleSourceNotFound(String), + Io(IoError), +} + +impl From for Error { + fn from(err: IoError) -> Self { + Self::Io(err) + } +} + +// source reading interface +trait ReadSource { + type Src: BufRead; + + fn lib_rs_path(&self) -> &str; + + fn module_path_from_name(&self, module: &str) -> String; + + fn lines_from_path(&self, src_path: &str) -> Result>; +} + +// source reading implementor for crates +struct CrateSource { + crate_src_dir: String, + lib_rs_path: String, +} + +impl CrateSource { + // ensure the crate is valid, + // i.e. it's src directory exists and contains a mod file + fn new(crate_dir: &str) -> Result { + let crate_src_dir = format!("{}/src", crate_dir); + let lib_rs_path = format!("{}/mod", crate_src_dir); + + if std::fs::metadata(&crate_src_dir).is_err() { + return Err(Error::CrateSourceDirNotFound(crate_src_dir)); + } + + if std::fs::metadata(&lib_rs_path).is_err() { + return Err(Error::LibRsNotFound(lib_rs_path)); + } + + Ok(CrateSource { + crate_src_dir, + lib_rs_path, + }) + } +} + +impl ReadSource for CrateSource { + type Src = BufReader; + + fn lib_rs_path(&self) -> &str { + &self.lib_rs_path + } + + fn module_path_from_name(&self, module: &str) -> String { + format!("{}/{}.rs", self.crate_src_dir, module) + } + + fn lines_from_path(&self, src_path: &str) -> Result> { + if std::fs::metadata(src_path).is_err() { + return Err(Error::ModuleSourceNotFound(src_path.to_string())); + } + + let file = File::open(src_path)?; + let lines = BufReader::new(file).lines(); + + Ok(lines) + } +} + +struct Output { + output: Vec, +} + +impl Output { + fn new() -> Output { + // allocate a decent chunk of memory at the start + let output = Vec::with_capacity(1_000_000_000); + Output { output } + } + + fn push_line(&mut self, line: &str) { + writeln!(&mut self.output, "{}", line).unwrap(); + } + + fn write_into(&self, w: &mut dyn Write) -> Result<()> { + w.write_all(&self.output)?; + Ok(()) + } +} + +fn parse_args() -> Option<(String, String)> { + let mut args = std::env::args().skip(1); + let crate_dir = args.next()?; + let out_path = args.next()?; + Some((crate_dir, out_path)) +} + +fn find_doc_comment(line: &str) -> Option<&str> { + line.strip_prefix("//! ") + .or_else(|| line.strip_prefix("//# ")) + .or_else(|| line.strip_prefix("/// ")) +} + +// scrape from code between pragmas and each type of doc comment +fn process_module(input: &I, output: &mut Output, module_name: &str) -> Result<()> +where + I: ReadSource, +{ + let module_path = input.module_path_from_name(module_name); + eprintln!("Processing module file: {}", module_path); + + let mut code_transcribe_enabled = false; + + for line in input.lines_from_path(&module_path)? { + let line = line?; + + if line.starts_with("// book_include_code") { + output.push_line("```rust"); + code_transcribe_enabled = true; + continue; + } + + if line.starts_with("// end_book_include_code") { + output.push_line("```"); + code_transcribe_enabled = false; + continue; + } + + if code_transcribe_enabled { + output.push_line(&line); + continue; + } + + if let Some(line) = find_doc_comment(&line) { + output.push_line(line); + } + } + + Ok(()) +} + +// start with the crate root, mod +// scrape code comments and with each public module inserted in the order they appear +fn process_crate(input: I, output: &mut Output) -> Result<()> +where + I: ReadSource, +{ + let lib_rs_path = input.lib_rs_path(); + eprintln!("Processing mod file: {}", lib_rs_path); + + for line in input.lines_from_path(lib_rs_path)? { + let line = line?; + + if let Some(doc) = find_doc_comment(&line) { + output.push_line(doc); + continue; + } + + if line.starts_with("pub mod ") && line.ends_with(';') { + let module = line + .strip_prefix("pub mod ") + .unwrap() + .strip_suffix(';') + .unwrap(); + + process_module(&input, output, module)?; + } + } + + Ok(()) +} + +fn main() { + let (crate_root_dir, out_path) = match parse_args() { + Some(args) => args, + _ => return eprintln!("{}", USAGE), + }; + + let input = match CrateSource::new(&crate_root_dir) { + Ok(input) => input, + Err(Error::CrateSourceDirNotFound(path)) => { + eprintln!( + "{} does not exist. Make sure the specified directory is a Rust project.", + path + ); + return eprintln!("{}", USAGE); + } + Err(Error::LibRsNotFound(path)) => { + eprintln!("{} not does not exist. The crate must be a library.", path); + return eprintln!("{}", USAGE); + } + Err(err) => panic!("unexpected err: {:?}", err), + }; + + let mut output = Output::new(); + + let result = process_crate(input, &mut output).and_then(|_| { + let mut out_file = File::create(out_path)?; + output.write_into(&mut out_file) + }); + + if let Err(err) = result { + match err { + Error::ModuleSourceNotFound(path) => { + eprintln!( + "Processing module file {} failed: file does not exist", + path + ) + } + Error::Io(err) => eprintln!("Unexpected I/O Error: {}", err), + err => panic!("unexpected err: {:?}", err), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + const LIB_RS: &str = r#"//! # Main Title +//! Some text +//! + +pub mod helper_mod; + +//# ## Common Types +//# Some more text +//# + +pub mod common_types; +"#; + + const COMMON_TYPES_RS: &str = r#"use some_dep::module; +use some_other_dep::module; + +//# ### `Contract` +/// Represents another contract on the network +/// + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +// book_include_code +pub struct Contract { + /// The address of the contract + pub address: Addr, + /// The hex encoded hash of the contract code + pub code_hash: String, +} +// end_book_include_code"#; + + const EXPECTED_OUTPUT: &str = r#"# Main Title +Some text + +## Common Types +Some more text + +### `Contract` +Represents another contract on the network + +```rust +pub struct Contract { + /// The address of the contract + pub address: Addr, + /// The hex encoded hash of the contract code + pub code_hash: String, +} +``` +"#; + + struct TestInput; + + impl ReadSource for TestInput { + type Src = BufReader<&'static [u8]>; + + fn lib_rs_path(&self) -> &str { + "src/mod" + } + + fn module_path_from_name(&self, module: &str) -> String { + format!("src/{}.rs", module) + } + + fn lines_from_path(&self, src_path: &str) -> Result> { + let s: &'static str = match src_path { + "src/mod" => LIB_RS, + "src/common_types.rs" => COMMON_TYPES_RS, + "src/helper_mod.rs" => "", + _ => panic!("Unexpected path: {}", src_path), + }; + + Ok(BufReader::new(s.as_bytes()).lines()) + } + } + + #[test] + fn it_works() { + let mut output = Output::new(); + + process_crate(TestInput, &mut output).unwrap(); + + let mut actual = Vec::new(); + + output.write_into(&mut actual).unwrap(); + + let actual = String::from_utf8(actual).unwrap(); + + assert_eq!(actual, EXPECTED_OUTPUT.to_owned()) + } +} diff --git a/tools/headstash/account.js b/tools/headstash/account.js index 3d41f5a..ea41dc2 100644 --- a/tools/headstash/account.js +++ b/tools/headstash/account.js @@ -1,27 +1,28 @@ import { Wallet, SecretNetworkClient, EncryptionUtilsImpl, fromUtf8, MsgExecuteContract, MsgExecuteContractResponse, fromBase64, toBase64 } from "secretjs"; import * as fs from "fs"; +import { encodeJsonToB64 } from "@shadeprotocol/shadejs"; -const wallet = new Wallet( - "goat action fuel major strategy adult kind sand draw amazing pigeon inspire antenna forget six kiss loan script west jaguar again click review have" -); - +// wallet +const wallet = new Wallet(""); const txEncryptionSeed = EncryptionUtilsImpl.GenerateNewSeed(); +// snip-20 const scrt20codeId = 5697; const scrt20CodeHash = "c74bc4b0406507257ed033caa922272023ab013b0c74330efc16569528fa34fe"; const secretTerpContractAddr = "secret1c3lj7dr9r2pe83j3yx8jt5v800zs9sq7we6wrc"; const secretThiolContractAddr = "secret1umh28jgcp0g9jy3qc29xk42kq92xjrcdfgvwdz"; -const scrtHeadstashCodeId = 6272; -const scrtHeadstashCodeHash = "f87c7817a43ca68c99fcf425eb1f255393df813c9955501d89a24d09b9967512"; -const scrtHeadstashContractAddr = "secret1l97vzuf8lsr0kdvepxqut9w4mf2v49vh35zqxp"; +// airdrop contract +const scrtHeadstashCodeId = 6294; +const scrtHeadstashCodeHash = "8f1816b524f9246e421503c9e764fbfdec615e2c52f258286ffebc09798bbe6e"; +const secretHeadstashContractAddr = "secret1r8hpc5uvykea0hzc92nlfrn60rwlc02rsa4fyv"; -const viewingKey = "eretskeretjableret" +const viewingKey = "eretskeretjableret" const eth_pubkey = "0x254768D47Cf8958a68242ce5AA1aDB401E1feF2B"; -// client +// network client const secretjs = new SecretNetworkClient({ chainId: "pulsar-3", url: "https://api.pulsar.scrttestnet.com", @@ -32,9 +33,7 @@ const secretjs = new SecretNetworkClient({ // filler message of AddressProofPermit const fillerMsg = { - coins: [], - contract: scrtHeadstashContractAddr, - eth_pubkey: eth_pubkey, + contract: secretHeadstashContractAddr, execute_msg: {}, sender: wallet.address, } @@ -42,62 +41,48 @@ const fillerMsg = { // data to be encoded in memo of AddressProofPermit const addrProofMsg = { address: wallet.address, - amount: "420", - contract: scrtHeadstashContractAddr, + amount: 420, + contract: secretHeadstashContractAddr, index: 0, key: "eretskeretjableret" } - // Convert JSON object to JSON string const addrProofMsgJson = JSON.stringify(addrProofMsg); +const encoded = encodeJsonToB64(addrProofMsgJson); // signature documentate as defined here: // https://github.com/securesecrets/shade/blob/77abdc70bc645d97aee7de5eb9a2347d22da425f/packages/shade_protocol/src/signature/mod.rs#L100 - -const encodedAddrProofMsg = toBase64(addrProofMsgJson); - -const addMinterMsg = new MsgExecuteContract({ - sender: "secret13uazul89dp0lypuxcz0upygpjy0ftdah4lnrs4", - contract_address: scrtHeadstashContractAddr, - code_hash: scrtHeadstashCodeHash, // optional but way faster +const createAccount = new MsgExecuteContract({ + sender: wallet.address, + contract_address: secretHeadstashContractAddr, + code_hash: scrtHeadstashCodeHash, msg: { account: { - eth_pubkey: eth_pubkey, - addresses: [{ - params: { - account_number: 0, - chain_id: "pulsar-3" - }, - memo: "eyJhZGRyZXNzIjoic2VjcmV0MTN1YXp1bDg5ZHAwbHlwdXhjejB1cHlncGp5MGZ0ZGFoNGxucnM0IiwiYW1vdW50IjoiNDIwIiwiY29udHJhY3QiOiJzZWNyZXQxbDk3dnp1Zjhsc3Iwa2R2ZXB4cXV0OXc0bWYydjQ5dmgzNXpxeHAiLCJpbmRleCI6MCwia2V5IjoiZXJldHNrZXJldGphYmxlcmV0In0=", - signature: { - pub_key: { - type: "tendermint/PubKeySecp256k1", - value: "AyZtxhLgis4Ec66OVlKDnuzEZqqV641sm46R3mbE2cpO", + addresses: [ + { + memo: encoded, + params: fillerMsg, + signature: { + pub_key: { + type: "tendermint/PubKeySecp256k1", + value: "AyZtxhLgis4Ec66OVlKDnuzEZqqV641sm46R3mbE2cpO", + }, + signature: "tTjK3Mf4dpQrSQT2hsqXn+pgeXhVjQhw2EXP5N50uhBJ0kpV9IS5uyfo+PHvB20CVHMwux9leaByfXI3T6PD6A==" }, - signature: fromBase64("tTjK3Mf4dpQrSQT2hsqXn+pgeXhVjQhw2EXP5N50uhBJ0kpV9IS5uyfo+PHvB20CVHMwux9leaByfXI3T6PD6A==") - }, - // account_number: Option, - // chain_id: Option, - // sequence: Option, - // memo: Option, - }], + } + ], + eth_pubkey: eth_pubkey } }, sent_funds: [], // optional }); -const tx = await secretjs.tx.broadcast([addMinterMsg], { +const tx = await secretjs.tx.broadcast([createAccount], { gasLimit: 200_000, }); -// console.log(addrProofMsgJson); console.log(tx); -// let create_account = async () => { - -// let tx = await secretjs.tx.broadcast([], { gasLimit: 400_000, } -// ); -// }; diff --git a/tools/headstash/main.js b/tools/headstash/main.js index 065eca0..fe85662 100644 --- a/tools/headstash/main.js +++ b/tools/headstash/main.js @@ -1,21 +1,22 @@ import { Wallet, SecretNetworkClient, EncryptionUtilsImpl, fromUtf8, MsgExecuteContractResponse } from "secretjs"; import * as fs from "fs"; +import {encodedAddrProofMsg} from "./account" -const wallet = new Wallet( - "goat action fuel major strategy adult kind sand draw amazing pigeon inspire antenna forget six kiss loan script west jaguar again click review have" -); - +// wallet +const wallet = new Wallet(""); const txEncryptionSeed = EncryptionUtilsImpl.GenerateNewSeed(); -const contract_wasm = fs.readFileSync("./airdrop.wasm"); +const contract_wasm = fs.readFileSync("./target/wasm32-unknown-unknown/release/airdrop.wasm"); +// snip-20 const scrt20codeId = 5697; const scrt20CodeHash = "c74bc4b0406507257ed033caa922272023ab013b0c74330efc16569528fa34fe"; -const scrtHeadstashCodeId = 6282; -const scrtHeadstashCodeHash = "f87c7817a43ca68c99fcf425eb1f255393df813c9955501d89a24d09b9967512"; - const secretTerpContractAddr = "secret1c3lj7dr9r2pe83j3yx8jt5v800zs9sq7we6wrc"; const secretThiolContractAddr = "secret1umh28jgcp0g9jy3qc29xk42kq92xjrcdfgvwdz"; -const secretHeadstashContractAddr = "secret1l97vzuf8lsr0kdvepxqut9w4mf2v49vh35zqxp"; + +// airdrop contract +const scrtHeadstashCodeId = 6294; +const scrtHeadstashCodeHash = "8f1816b524f9246e421503c9e764fbfdec615e2c52f258286ffebc09798bbe6e"; +const secretHeadstashContractAddr = "secret1r8hpc5uvykea0hzc92nlfrn60rwlc02rsa4fyv"; const secretjs = new SecretNetworkClient({ chainId: "pulsar-3", @@ -50,8 +51,8 @@ let upload_contract = async () => { } let instantiate_headstash_contract = async () => { let initMsg = { - admin: "secret13uazul89dp0lypuxcz0upygpjy0ftdah4lnrs4", - dump_address: "secret13uazul89dp0lypuxcz0upygpjy0ftdah4lnrs4", + admin: wallet.address, + dump_address: wallet.address, airdrop_token: { address: secretTerpContractAddr, code_hash: scrt20CodeHash @@ -64,9 +65,11 @@ let instantiate_headstash_contract = async () => { start_date: null, end_date: null, decay_start: null, + max_amount: "420", merkle_root: "d599867bdb2ade1e470d9ec9456490adcd9da6e0cfd8f515e2b95d345a5cd92f", total_accounts: 2, - claim_msg_plaintext: "{wallet}" + claim_msg_plaintext: "{wallet}", + query_rounding: "1" }; let tx = await secretjs.tx.compute.instantiateContract( @@ -130,6 +133,8 @@ if (args.length < 1) { upload_contract(args[1]); } else if (args[0] === '-h') { instantiate_headstash_contract(); +} else if (args[0] === '-a') { + } else if (args[0] === '-i') { if (args.length < 4) { console.error('Usage: -i name symbol [supported_denoms]'); diff --git a/tools/headstash/yarn.lock b/tools/headstash/yarn.lock new file mode 100644 index 0000000..b213ca3 --- /dev/null +++ b/tools/headstash/yarn.lock @@ -0,0 +1,709 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@cosmjs/encoding@0.27.1": + version "0.27.1" + resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.27.1.tgz#3cd5bc0af743485eb2578cdb08cfa84c86d610e1" + integrity sha512-rayLsA0ojHeniaRfWWcqSsrE/T1rl1gl0OXVNtXlPwLJifKBeLEefGbOUiAQaT0wgJ8VNGBazVtAZBpJidfDhw== + dependencies: + base64-js "^1.3.0" + bech32 "^1.1.4" + readonly-date "^1.0.0" + +"@cosmjs/math@0.27.1": + version "0.27.1" + resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.27.1.tgz#be78857b008ffc6b1ed6fecaa1c4cd5bc38c07d7" + integrity sha512-cHWVjmfIjtRc7f80n7x+J5k8pe+vTVTQ0lA82tIxUgqUvgS6rogPP/TmGtTiZ4+NxWxd11DUISY6gVpr18/VNQ== + dependencies: + bn.js "^5.2.0" + +"@esbuild/android-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" + integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== + +"@esbuild/android-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" + integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== + +"@esbuild/android-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" + integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== + +"@esbuild/darwin-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" + integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== + +"@esbuild/darwin-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" + integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== + +"@esbuild/freebsd-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" + integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== + +"@esbuild/freebsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" + integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== + +"@esbuild/linux-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" + integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== + +"@esbuild/linux-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" + integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== + +"@esbuild/linux-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" + integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== + +"@esbuild/linux-loong64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" + integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== + +"@esbuild/linux-mips64el@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" + integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== + +"@esbuild/linux-ppc64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" + integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== + +"@esbuild/linux-riscv64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" + integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== + +"@esbuild/linux-s390x@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" + integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== + +"@esbuild/linux-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" + integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== + +"@esbuild/netbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" + integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== + +"@esbuild/openbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" + integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== + +"@esbuild/sunos-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" + integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== + +"@esbuild/win32-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" + integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== + +"@esbuild/win32-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" + integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== + +"@esbuild/win32-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" + integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== + +"@noble/hashes@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.0.0.tgz#d5e38bfbdaba174805a4e649f13be9a9ed3351ae" + integrity sha512-DZVbtY62kc3kkBtMHqwCOfXrT/hnoORy5BJ4+HU1IR59X0KWAOqsfzQPcUl/lQLlG7qXbe/fZ3r/emxtAl+sqg== + +"@noble/secp256k1@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.0.tgz#d15357f7c227e751d90aa06b05a0e5cf993ba8c1" + integrity sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw== + +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + +"@shadeprotocol/shadejs@^1.1.7": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@shadeprotocol/shadejs/-/shadejs-1.1.7.tgz#140f419d8ea828b3352a1b0f57da958c890b91ec" + integrity sha512-UfKBGbcDeXJtV9GsBCKN01q3hjMuGDgvhKGfHewG/WhVkd8g0uqERVXpwOuviCKg02sAiSxuXmOA0FR7MXxRfg== + dependencies: + bignumber.js "^9.1.2" + rxjs "^7.8.1" + secretjs "^1.12.1" + vite "^4.5.0" + whatwg-fetch "^3.6.19" + +"@types/node@10.12.18": + version "10.12.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67" + integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ== + +"@types/node@11.11.6": + version "11.11.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a" + integrity sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ== + +"@types/node@>=13.7.0": + version "20.12.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.2.tgz#9facdd11102f38b21b4ebedd9d7999663343d72e" + integrity sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ== + dependencies: + undici-types "~5.26.4" + +base-x@^3.0.2: + version "3.0.9" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" + integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + dependencies: + safe-buffer "^5.0.1" + +base64-js@^1.3.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bech32@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" + integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== + +bech32@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" + integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== + +big-integer@1.6.51: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + +bignumber.js@9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.2.tgz#71c6c6bed38de64e24a65ebe16cfcf23ae693673" + integrity sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw== + +bignumber.js@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + +bindings@^1.3.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bip32@2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/bip32/-/bip32-2.0.6.tgz#6a81d9f98c4cd57d05150c60d8f9e75121635134" + integrity sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA== + dependencies: + "@types/node" "10.12.18" + bs58check "^2.1.1" + create-hash "^1.2.0" + create-hmac "^1.1.7" + tiny-secp256k1 "^1.1.3" + typeforce "^1.11.5" + wif "^2.0.6" + +bip39@3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.4.tgz#5b11fed966840b5e1b8539f0f54ab6392969b2a0" + integrity sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw== + dependencies: + "@types/node" "11.11.6" + create-hash "^1.1.0" + pbkdf2 "^3.0.9" + randombytes "^2.0.1" + +bn.js@^4.11.8, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + +bs58@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== + dependencies: + base-x "^3.0.2" + +bs58check@<3.0.0, bs58check@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" + integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA== + dependencies: + bs58 "^4.0.0" + create-hash "^1.1.0" + safe-buffer "^5.1.2" + +cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.4, create-hmac@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +cross-fetch@3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + dependencies: + node-fetch "2.6.7" + +curve25519-js@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/curve25519-js/-/curve25519-js-0.0.4.tgz#e6ad967e8cd284590d657bbfc90d8b50e49ba060" + integrity sha512-axn2UMEnkhyDUPWOwVKBMVIzSQy2ejH2xRGy1wq81dqRwApXfIzfbE3hIX0ZRFBIihf/KDqK158DLwESu4AK1w== + +elliptic@^6.4.0: + version "6.5.5" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded" + integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +esbuild@^0.18.10: + version "0.18.20" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" + integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== + optionalDependencies: + "@esbuild/android-arm" "0.18.20" + "@esbuild/android-arm64" "0.18.20" + "@esbuild/android-x64" "0.18.20" + "@esbuild/darwin-arm64" "0.18.20" + "@esbuild/darwin-x64" "0.18.20" + "@esbuild/freebsd-arm64" "0.18.20" + "@esbuild/freebsd-x64" "0.18.20" + "@esbuild/linux-arm" "0.18.20" + "@esbuild/linux-arm64" "0.18.20" + "@esbuild/linux-ia32" "0.18.20" + "@esbuild/linux-loong64" "0.18.20" + "@esbuild/linux-mips64el" "0.18.20" + "@esbuild/linux-ppc64" "0.18.20" + "@esbuild/linux-riscv64" "0.18.20" + "@esbuild/linux-s390x" "0.18.20" + "@esbuild/linux-x64" "0.18.20" + "@esbuild/netbsd-x64" "0.18.20" + "@esbuild/openbsd-x64" "0.18.20" + "@esbuild/sunos-x64" "0.18.20" + "@esbuild/win32-arm64" "0.18.20" + "@esbuild/win32-ia32" "0.18.20" + "@esbuild/win32-x64" "0.18.20" + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +google-protobuf@^3.14.0: + version "3.21.2" + resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.2.tgz#4580a2bea8bbb291ee579d1fefb14d6fa3070ea4" + integrity sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA== + +hash-base@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== + dependencies: + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +long@^5.0.0: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== + +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + +miscreant@0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/miscreant/-/miscreant-0.3.2.tgz#a91c046566cca70bd6b5e9fbdd3f67617fa85034" + integrity sha512-fL9KxsQz9BJB2KGPMHFrReioywkiomBiuaLk6EuChijK0BsJsIKJXdVomR+/bPj5mvbFD6wM0CM3bZio9g7OHA== + +nan@^2.13.2: + version "2.19.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.19.0.tgz#bb58122ad55a6c5bc973303908d5b16cfdd5a8c0" + integrity sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw== + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +pako@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.4.tgz#6cebc4bbb0b6c73b0d5b8d7e8476e2b2fbea576d" + integrity sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg== + +pbkdf2@^3.0.9: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +postcss@^8.4.27: + version "8.4.38" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" + integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.2.0" + +protobufjs@7.2.5: + version "7.2.5" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.5.tgz#45d5c57387a6d29a17aab6846dcc283f9b8e7f2d" + integrity sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + +randombytes@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readonly-date@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/readonly-date/-/readonly-date-1.0.0.tgz#5af785464d8c7d7c40b9d738cbde8c646f97dcd9" + integrity sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ== + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +rollup@^3.27.1: + version "3.29.4" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981" + integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== + optionalDependencies: + fsevents "~2.3.2" + +rxjs@^7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +secretjs@^1.12.1, secretjs@^1.12.5: + version "1.12.5" + resolved "https://registry.yarnpkg.com/secretjs/-/secretjs-1.12.5.tgz#347a1593dfb97610bf4af5c4df5fa90badbeda63" + integrity sha512-JEnvJcfQRuJyu2QtsFQOZ3Vb0sQO8puKV1hK7rVsgROFWHE0s2wSmamTn9SV+hncgRwEtEtGht3Mu1WtQ5xfYA== + dependencies: + "@cosmjs/encoding" "0.27.1" + "@cosmjs/math" "0.27.1" + "@noble/hashes" "1.0.0" + "@noble/secp256k1" "1.7.0" + bech32 "2.0.0" + big-integer "1.6.51" + bignumber.js "9.0.2" + bip32 "2.0.6" + bip39 "3.0.4" + cross-fetch "3.1.5" + curve25519-js "0.0.4" + google-protobuf "^3.14.0" + miscreant "0.3.2" + pako "2.0.4" + protobufjs "7.2.5" + secure-random "1.1.2" + +secure-random@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/secure-random/-/secure-random-1.1.2.tgz#ed103b460a851632d420d46448b2a900a41e7f7c" + integrity sha512-H2bdSKERKdBV1SwoqYm6C0y+9EA94v6SUBOWO8kDndc4NoUih7Dv6Tsgma7zO1lv27wIvjlD0ZpMQk7um5dheQ== + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +tiny-secp256k1@^1.1.3: + version "1.1.6" + resolved "https://registry.yarnpkg.com/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz#7e224d2bee8ab8283f284e40e6b4acb74ffe047c" + integrity sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA== + dependencies: + bindings "^1.3.0" + bn.js "^4.11.8" + create-hmac "^1.1.7" + elliptic "^6.4.0" + nan "^2.13.2" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +tslib@^2.1.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +typeforce@^1.11.5: + version "1.18.0" + resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc" + integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +vite@^4.5.0: + version "4.5.3" + resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.3.tgz#d88a4529ea58bae97294c7e2e6f0eab39a50fb1a" + integrity sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg== + dependencies: + esbuild "^0.18.10" + postcss "^8.4.27" + rollup "^3.27.1" + optionalDependencies: + fsevents "~2.3.2" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-fetch@^3.6.19: + version "3.6.20" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70" + integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wif@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704" + integrity sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ== + dependencies: + bs58check "<3.0.0" diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..fb57ccd --- /dev/null +++ b/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + From 80a917f7b7437b6e1a55cb7e29a4426e31ea06cb Mon Sep 17 00:00:00 2001 From: hard-nett Date: Wed, 3 Apr 2024 16:20:11 -0400 Subject: [PATCH 11/23] cleanup scripts --- contracts/airdrop/README.md | 30 +++++++----------------------- tools/headstash/account.js | 35 +++++------------------------------ tools/headstash/index.js | 1 + tools/headstash/main.js | 36 +++++++++++++++++++++--------------- 4 files changed, 34 insertions(+), 68 deletions(-) create mode 100644 tools/headstash/index.js diff --git a/contracts/airdrop/README.md b/contracts/airdrop/README.md index 6403106..be5357f 100644 --- a/contracts/airdrop/README.md +++ b/contracts/airdrop/README.md @@ -6,11 +6,8 @@ * [Admin](#Admin) * Messages * [UpdateConfig](#UpdateConfig) - * [AddTasks](#AddTasks) * [ClaimDecay](#ClaimDecay) * [Admin](#Admin) - * Messages - * [CompleteTask](#CompleteTask) * [User](#User) * Messages * [Account](#Account) @@ -89,6 +86,7 @@ Drains the decayed amount of airdrop into the specified dump_address |--------------|----------------------------------------------------|-----------------------------------------------------------|----------| | addresses | Array of [AddressProofPermit](#AddressProofPermit) | Proof that the user owns those addresses | no | | eth_pubkey | string | Key included in headstash distribution | no | +| eth_sig | string | Signature from eth_pubkey of msg.sender, for merkle tree verification | no | | padding | string | Allows for enforcing constant length messages | yes | ##### Response @@ -98,7 +96,8 @@ Drains the decayed amount of airdrop into the specified dump_address "status": "success", "claimed": "boolean", "addresses": ["claimed addresses"], - "eth_pubkey": "eth Pubkey" + "eth_pubkey": "eth_pubkey", + "eth_sig": "eth_sig", } } ``` @@ -165,23 +164,6 @@ Gets the contract's config } ``` -## Dates -Get the contracts airdrop timeframe, can calculate the decay factor if a time is given -##### Request -| Name | Type | Description | optional | -|--------------|------|---------------------------------|----------| -| current_date | u64 | The current time in UNIX format | yes | -```json -{ - "dates": { - "start": "Airdrop start", - "end": "Airdrop end", - "decay_start": "Airdrop start of decay", - "decay_factor": "Decay percentage" - } -} -``` - ## TotalClaimed Shows the total amount of the token that has been claimed. If airdrop hasn't ended then it'll just show an estimation. ##### Request @@ -205,7 +187,8 @@ Get the account's information "account": { "claimed": "boolean", "addresses": ["claimed addresses"], - "eth_pubkey": "" + "eth_pubkey": "", + "eth_sig": "", } } ``` @@ -223,7 +206,8 @@ Get the account's information using a viewing key "account_with_key": { "claimed": "boolean", "addresses": ["claimed addresses"], - "eth_pubkey": "" + "eth_pubkey": "", + "eth_sig": "", } } ``` diff --git a/tools/headstash/account.js b/tools/headstash/account.js index ea41dc2..493fc88 100644 --- a/tools/headstash/account.js +++ b/tools/headstash/account.js @@ -1,35 +1,10 @@ -import { Wallet, SecretNetworkClient, EncryptionUtilsImpl, fromUtf8, MsgExecuteContract, MsgExecuteContractResponse, fromBase64, toBase64 } from "secretjs"; -import * as fs from "fs"; +import { MsgExecuteContract } from "secretjs"; import { encodeJsonToB64 } from "@shadeprotocol/shadejs"; - -// wallet -const wallet = new Wallet(""); -const txEncryptionSeed = EncryptionUtilsImpl.GenerateNewSeed(); - -// snip-20 -const scrt20codeId = 5697; -const scrt20CodeHash = "c74bc4b0406507257ed033caa922272023ab013b0c74330efc16569528fa34fe"; -const secretTerpContractAddr = "secret1c3lj7dr9r2pe83j3yx8jt5v800zs9sq7we6wrc"; -const secretThiolContractAddr = "secret1umh28jgcp0g9jy3qc29xk42kq92xjrcdfgvwdz"; - -// airdrop contract -const scrtHeadstashCodeId = 6294; -const scrtHeadstashCodeHash = "8f1816b524f9246e421503c9e764fbfdec615e2c52f258286ffebc09798bbe6e"; -const secretHeadstashContractAddr = "secret1r8hpc5uvykea0hzc92nlfrn60rwlc02rsa4fyv"; - +import { scrtHeadstashCodeHash, secretHeadstashContractAddr, secretjs, txEncryptionSeed, wallet } from "./main.js"; const viewingKey = "eretskeretjableret" const eth_pubkey = "0x254768D47Cf8958a68242ce5AA1aDB401E1feF2B"; - - -// network client -const secretjs = new SecretNetworkClient({ - chainId: "pulsar-3", - url: "https://api.pulsar.scrttestnet.com", - wallet: wallet, - walletAddress: wallet.address, - txEncryptionSeed: txEncryptionSeed -}); +const eth_sig = "0xf7992bd3f7cb1030b5d69d3326c6e2e28bfde2e38cbb8de753d1be7b5a5ecbcf2d3eccd3fe2e1fccb2454c47dcb926bd047ecf5b74c7330584cbfd619248de811b" // filler message of AddressProofPermit const fillerMsg = { @@ -41,7 +16,6 @@ const fillerMsg = { // data to be encoded in memo of AddressProofPermit const addrProofMsg = { address: wallet.address, - amount: 420, contract: secretHeadstashContractAddr, index: 0, key: "eretskeretjableret" @@ -72,7 +46,8 @@ const createAccount = new MsgExecuteContract({ }, } ], - eth_pubkey: eth_pubkey + eth_pubkey: eth_pubkey, + eth_sig: eth_sig.slice(2), } }, sent_funds: [], // optional diff --git a/tools/headstash/index.js b/tools/headstash/index.js new file mode 100644 index 0000000..c37636d --- /dev/null +++ b/tools/headstash/index.js @@ -0,0 +1 @@ +export * from './main' \ No newline at end of file diff --git a/tools/headstash/main.js b/tools/headstash/main.js index fe85662..2b9661d 100644 --- a/tools/headstash/main.js +++ b/tools/headstash/main.js @@ -1,31 +1,34 @@ import { Wallet, SecretNetworkClient, EncryptionUtilsImpl, fromUtf8, MsgExecuteContractResponse } from "secretjs"; import * as fs from "fs"; -import {encodedAddrProofMsg} from "./account" // wallet -const wallet = new Wallet(""); -const txEncryptionSeed = EncryptionUtilsImpl.GenerateNewSeed(); -const contract_wasm = fs.readFileSync("./target/wasm32-unknown-unknown/release/airdrop.wasm"); +export const chain_id = "pulsar-3"; +export const wallet = new Wallet(""); +export const txEncryptionSeed = EncryptionUtilsImpl.GenerateNewSeed(); +export const contract_wasm = fs.readFileSync("./target/wasm32-unknown-unknown/release/airdrop.wasm"); // snip-20 -const scrt20codeId = 5697; -const scrt20CodeHash = "c74bc4b0406507257ed033caa922272023ab013b0c74330efc16569528fa34fe"; -const secretTerpContractAddr = "secret1c3lj7dr9r2pe83j3yx8jt5v800zs9sq7we6wrc"; -const secretThiolContractAddr = "secret1umh28jgcp0g9jy3qc29xk42kq92xjrcdfgvwdz"; +export const scrt20codeId = 5697; +export const scrt20CodeHash = "c74bc4b0406507257ed033caa922272023ab013b0c74330efc16569528fa34fe"; +export const secretTerpContractAddr = "secret1c3lj7dr9r2pe83j3yx8jt5v800zs9sq7we6wrc"; +export const secretThiolContractAddr = "secret1umh28jgcp0g9jy3qc29xk42kq92xjrcdfgvwdz"; // airdrop contract -const scrtHeadstashCodeId = 6294; -const scrtHeadstashCodeHash = "8f1816b524f9246e421503c9e764fbfdec615e2c52f258286ffebc09798bbe6e"; -const secretHeadstashContractAddr = "secret1r8hpc5uvykea0hzc92nlfrn60rwlc02rsa4fyv"; +export const scrtHeadstashCodeId = 6294; +export const scrtHeadstashCodeHash = "8f1816b524f9246e421503c9e764fbfdec615e2c52f258286ffebc09798bbe6e"; +export const secretHeadstashContractAddr = "secret1r8hpc5uvykea0hzc92nlfrn60rwlc02rsa4fyv"; +export const merkle_root = "d599867bdb2ade1e470d9ec9456490adcd9da6e0cfd8f515e2b95d345a5cd92f"; -const secretjs = new SecretNetworkClient({ - chainId: "pulsar-3", +// signing client +export const secretjs = new SecretNetworkClient({ + chainId: chain_id, url: "https://api.pulsar.scrttestnet.com", wallet: wallet, walletAddress: wallet.address, txEncryptionSeed: txEncryptionSeed }); +// stores contract, prints code hash & code id let upload_contract = async () => { let tx = await secretjs.tx.compute.storeCode( { @@ -45,10 +48,11 @@ let upload_contract = async () => { ); console.log("codeId: ", codeId); - // contract hash, useful for contract composition const contractCodeHash = (await secretjs.query.compute.codeHashByCodeId({ code_id: codeId })).code_hash; console.log(`Contract hash: ${contractCodeHash}`); } + +// initialize a new headstash contract let instantiate_headstash_contract = async () => { let initMsg = { admin: wallet.address, @@ -66,7 +70,7 @@ let instantiate_headstash_contract = async () => { end_date: null, decay_start: null, max_amount: "420", - merkle_root: "d599867bdb2ade1e470d9ec9456490adcd9da6e0cfd8f515e2b95d345a5cd92f", + merkle_root: merkle_root, total_accounts: 2, claim_msg_plaintext: "{wallet}", query_rounding: "1" @@ -93,6 +97,8 @@ let instantiate_headstash_contract = async () => { console.log(contractAddress); } + +// initiates a new snip-20 let instantiate_contract = async (name, synbol, supported_denom) => { const initMsg = { name: "Terp Network Gas Token", From 9c79b339cb62c3912678738db30d4b89001c16c9 Mon Sep 17 00:00:00 2001 From: Hard-Nett Date: Wed, 3 Apr 2024 21:19:14 +0000 Subject: [PATCH 12/23] update scripts --- .../contract_interfaces/airdrop/account.rs | 2 +- tools/headstash/account.js | 26 ++++++++++++++----- tools/headstash/main.js | 8 +++--- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs index 382a186..1a637e7 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs @@ -48,8 +48,8 @@ impl Default for FillerMsg { fn default() -> Self { Self { contract: "".to_string(), - sender: "".to_string(), execute_msg: EmptyMsg {}, + sender: "".to_string(), } } } diff --git a/tools/headstash/account.js b/tools/headstash/account.js index 493fc88..45785ac 100644 --- a/tools/headstash/account.js +++ b/tools/headstash/account.js @@ -1,4 +1,4 @@ -import { MsgExecuteContract } from "secretjs"; +import { MsgExecuteContract, toUtf8 } from "secretjs"; import { encodeJsonToB64 } from "@shadeprotocol/shadejs"; import { scrtHeadstashCodeHash, secretHeadstashContractAddr, secretjs, txEncryptionSeed, wallet } from "./main.js"; @@ -17,12 +17,21 @@ const fillerMsg = { const addrProofMsg = { address: wallet.address, contract: secretHeadstashContractAddr, - index: 0, - key: "eretskeretjableret" + key: 'eretskeretjableret' } // Convert JSON object to JSON string -const addrProofMsgJson = JSON.stringify(addrProofMsg); -const encoded = encodeJsonToB64(addrProofMsgJson); +let jsonString = JSON.stringify(addrProofMsg, (key, value) => { + if (typeof value === 'string') { + return value.replace(/\\/g, ''); + } + return value; +}); +const encoded = encodeJsonToB64(jsonString); + + + +console.log("AddrProofMsg:", jsonString); +console.log("Base64 String of AddrProofMsg:", encoded) // signature documentate as defined here: // https://github.com/securesecrets/shade/blob/77abdc70bc645d97aee7de5eb9a2347d22da425f/packages/shade_protocol/src/signature/mod.rs#L100 @@ -35,15 +44,18 @@ const createAccount = new MsgExecuteContract({ account: { addresses: [ { - memo: encoded, params: fillerMsg, signature: { pub_key: { type: "tendermint/PubKeySecp256k1", value: "AyZtxhLgis4Ec66OVlKDnuzEZqqV641sm46R3mbE2cpO", }, - signature: "tTjK3Mf4dpQrSQT2hsqXn+pgeXhVjQhw2EXP5N50uhBJ0kpV9IS5uyfo+PHvB20CVHMwux9leaByfXI3T6PD6A==" + signature: "tTjK3Mf4dpQrSQT2hsqXn+pgeXhVjQhw2EXP5N50uhBJ0kpV9IS5uyfo+PHvB20CVHMwux9leaByfXI3T6PD6A==", }, + account_number: null, + chain_id: null, + sequence: null, + memo: encoded, } ], eth_pubkey: eth_pubkey, diff --git a/tools/headstash/main.js b/tools/headstash/main.js index 2b9661d..fa97657 100644 --- a/tools/headstash/main.js +++ b/tools/headstash/main.js @@ -3,7 +3,7 @@ import * as fs from "fs"; // wallet export const chain_id = "pulsar-3"; -export const wallet = new Wallet(""); +export const wallet = new Wallet(""); export const txEncryptionSeed = EncryptionUtilsImpl.GenerateNewSeed(); export const contract_wasm = fs.readFileSync("./target/wasm32-unknown-unknown/release/airdrop.wasm"); @@ -14,9 +14,9 @@ export const secretTerpContractAddr = "secret1c3lj7dr9r2pe83j3yx8jt5v800zs9sq7we export const secretThiolContractAddr = "secret1umh28jgcp0g9jy3qc29xk42kq92xjrcdfgvwdz"; // airdrop contract -export const scrtHeadstashCodeId = 6294; -export const scrtHeadstashCodeHash = "8f1816b524f9246e421503c9e764fbfdec615e2c52f258286ffebc09798bbe6e"; -export const secretHeadstashContractAddr = "secret1r8hpc5uvykea0hzc92nlfrn60rwlc02rsa4fyv"; +export const scrtHeadstashCodeId = 6410; +export const scrtHeadstashCodeHash = "1b037fcd711ebf057cecd11103c6a5feee65e8e4c6ace281560531344d01aaec"; +export const secretHeadstashContractAddr = "secret17vftlyw20j08hsey6u7r6vl98pwejlqhdrpnhm"; export const merkle_root = "d599867bdb2ade1e470d9ec9456490adcd9da6e0cfd8f515e2b95d345a5cd92f"; // signing client From b1e94d0f4219a4e71a6deccacc6c484ffd90ddec Mon Sep 17 00:00:00 2001 From: hard-nett Date: Wed, 3 Apr 2024 18:51:34 -0400 Subject: [PATCH 13/23] tests --- contracts/airdrop/README.md | 3 +- contracts/airdrop/src/handle.rs | 53 ++++++++++++++++--- contracts/airdrop/src/test.rs | 4 +- .../contract_interfaces/airdrop/account.rs | 2 + 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/contracts/airdrop/README.md b/contracts/airdrop/README.md index be5357f..fafde92 100644 --- a/contracts/airdrop/README.md +++ b/contracts/airdrop/README.md @@ -215,9 +215,8 @@ Get the account's information using a viewing key ## AddressProofPermit This is a structure used to prove that the user has permission to query that address's information (when querying account info). This is also used to prove that the user owns that address (when creating/updating accounts) and the given amount is in the airdrop. -This permit is written differently from the rest since its made taking into consideration many of Terra's limitations compared to Keplr's flexibility. -NOTE: The parameters must be in order +NOTE: The parameters must be in order. [How to sign](https://github.com/securesecrets/shade/blob/77abdc70bc645d97aee7de5eb9a2347d22da425f/packages/shade_protocol/src/signature/mod.rs#L100) #### Structure diff --git a/contracts/airdrop/src/handle.rs b/contracts/airdrop/src/handle.rs index fcc26b9..582387f 100644 --- a/contracts/airdrop/src/handle.rs +++ b/contracts/airdrop/src/handle.rs @@ -157,10 +157,12 @@ pub fn try_account( // These variables are setup to facilitate updating let updating_account: bool; + // let old_claim_amount: Uint128; let mut account = match account_r(deps.storage).may_load(sender.as_bytes())? { None => { updating_account = false; + // old_claim_amount = Uint128::zero(); let mut account = Account::default(); // Validate permits @@ -175,25 +177,21 @@ pub fn try_account( eth_sig.clone(), )?; - // we setup an unchecked eth_pubkey for now. We will verify this eth_pubkey during - // the claim msg, and will update to verified eth_pubkey. - // sets the accounts eth_pubkey claim status to false. note we always check claim function when checking - // the signed msg with the stored address, never both or isolated; - // (to avoid contract panic, sender.clone is required for reading the account details, - // prevents ability to determine if a eth_pubkey not yours has claimed)* + // account_total_claimed_w(deps.storage).save(sender.as_bytes(), &Uint128::zero())?; claim_status_w(deps.storage, 0).save(sender.as_bytes(), &false)?; account } Some(acc) => { updating_account = true; + // old_claim_amount = acc.total_claimable; acc } }; // Update account after claim to calculate difference if updating_account { - // Validate permits + // TODO: verify eth_pubkey & eth_sig from this function. try_add_account_addresses( deps.storage, deps.api, @@ -439,6 +437,14 @@ pub fn try_add_account_addresses( validate_address_permit(storage, api, permit, ¶ms, config.contract.clone())?; } + // // Check that airdrop amount does not exceed maximum + // if params.amount > config.max_amount { + // return Err(claim_too_high( + // params.amount.to_string().as_str(), + // config.max_amount.to_string().as_str(), + // )); + // } + // Update address if its not in an account address_in_account_w(storage).update( params.address.to_string().as_bytes(), @@ -451,6 +457,11 @@ pub fn try_add_account_addresses( }, )?; + // // Add account as a leaf + // let leaf_hash = + // Sha256::hash((params.address.to_string() + ¶ms.amount.to_string()).as_bytes()); + // leaves_to_validate.push((params.index as usize, leaf_hash)); + // Update eth_pubkey if its not in an account eth_pubkey_in_account_w(storage).update( eth_pubkey.to_string().as_bytes(), @@ -481,6 +492,34 @@ pub fn try_add_account_addresses( } else { return Err(expected_memo()); } + + // // Need to sort by index in order for the proof to work + // leaves_to_validate.sort_by_key(|item| item.0); + + // let mut indices: Vec = vec![]; + // let mut leaves: Vec<[u8; 32]> = vec![]; + + // for leaf in leaves_to_validate.iter() { + // indices.push(leaf.0); + // leaves.push(leaf.1); + // } + + // // Convert partial tree from base64 to binary + // let mut partial_tree_binary: Vec<[u8; 32]> = vec![]; + // for node in partial_tree.iter() { + // let mut arr: [u8; 32] = Default::default(); + // arr.clone_from_slice(node.as_slice()); + // partial_tree_binary.push(arr); + // } + + // // Prove that user is in airdrop + // let proof = MerkleProof::::new(partial_tree_binary); + // // Convert to a fixed length array without messing up the contract + // let mut root: [u8; 32] = Default::default(); + // root.clone_from_slice(config.merkle_root.as_slice()); + // if !proof.verify(root, &indices, &leaves, config.total_accounts as usize) { + // return Err(invalid_partial_tree()); + // } } Ok(()) } diff --git a/contracts/airdrop/src/test.rs b/contracts/airdrop/src/test.rs index 2357487..264b9a9 100644 --- a/contracts/airdrop/src/test.rs +++ b/contracts/airdrop/src/test.rs @@ -281,7 +281,7 @@ pub mod tests { "h/RpG1eKzN03oId0GvN7TSxoHOUibjmqPEQ1E+ZWh+BvghPL99lBj4L3BKpjjsaRtXX3lexO7ztafLKBVtq4xA==").unwrap(), }, account_number: None, - memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) + memo: Some("eyJhZGRyZXNzIjoic2VjcmV0MTlxN2gyenk4bWdlc3kzcjM5ZWw1ZmNtOTg2bnhxamQ3Y2d5bHJ6IiwiY29udHJhY3QiOiJzZWNyZXQxc3I2MmxlaGFqZ3doZHpwbW5sNjV1MzVydWdqcmd6bmgyNTcybXYiLCJrZXkiOiJhY2NvdW50LWNyZWF0aW9uLXBlcm1pdCJ9".to_string()) }; let deps = mock_dependencies(); @@ -312,7 +312,7 @@ pub mod tests { let deserialized_memo: AddressProofMsg = from_binary( &Binary::from_base64( - &"eyJhZGRyZXNzIjoic2VjcmV0MTlxN2gyenk4bWdlc3kzcjM5ZWw1ZmNtOTg2bnhxamQ3Y2d5bHJ6IiwiYW1vdW50IjoiMTAwMDAwMCIsImNvbnRyYWN0Ijoic2VjcmV0MXNyNjJsZWhhamd3aGR6cG1ubDY1dTM1cnVnanJnem5oMjU3Mm12IiwiaW5kZXgiOjEwLCJrZXkiOiJhY2NvdW50LWNyZWF0aW9uLXBlcm1pdCJ9" + &"eyJhZGRyZXNzIjoic2VjcmV0MTlxN2gyenk4bWdlc3kzcjM5ZWw1ZmNtOTg2bnhxamQ3Y2d5bHJ6IiwiY29udHJhY3QiOiJzZWNyZXQxc3I2MmxlaGFqZ3doZHpwbW5sNjV1MzVydWdqcmd6bmgyNTcybXYiLCJrZXkiOiJhY2NvdW50LWNyZWF0aW9uLXBlcm1pdCJ9" .to_string()).unwrap()).unwrap(); assert_eq!(deserialized_memo, expected_memo) diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs index 382a186..adc0d65 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs @@ -39,6 +39,7 @@ pub struct AccountPermitMsg { #[remain::sorted] #[cw_serde] pub struct FillerMsg { + pub coins: Vec, pub contract: String, pub execute_msg: EmptyMsg, pub sender: String, @@ -47,6 +48,7 @@ pub struct FillerMsg { impl Default for FillerMsg { fn default() -> Self { Self { + coins: vec![], contract: "".to_string(), sender: "".to_string(), execute_msg: EmptyMsg {}, From 9e844aedba6753b5a3e8f555133040cf1908f4ff Mon Sep 17 00:00:00 2001 From: hard-nett Date: Wed, 3 Apr 2024 20:17:46 -0400 Subject: [PATCH 14/23] add back original functions --- .gitignore | 2 +- contracts/airdrop/README.md | 102 ++++- contracts/airdrop/src/contract.rs | 51 ++- contracts/airdrop/src/handle.rs | 376 +++++++++++++----- contracts/airdrop/src/query.rs | 55 ++- contracts/airdrop/src/state.rs | 68 ++-- contracts/airdrop/src/test.rs | 6 +- .../contract_interfaces/airdrop/account.rs | 23 +- .../src/contract_interfaces/airdrop/mod.rs | 78 ++-- 9 files changed, 563 insertions(+), 198 deletions(-) diff --git a/.gitignore b/.gitignore index c8dbb2c..7d84bf6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ target/ # Testing configs -*.json +packages/tools/headstash/package-lock.json # Code coverage stuff *.profraw diff --git a/contracts/airdrop/README.md b/contracts/airdrop/README.md index fafde92..ed33134 100644 --- a/contracts/airdrop/README.md +++ b/contracts/airdrop/README.md @@ -6,8 +6,11 @@ * [Admin](#Admin) * Messages * [UpdateConfig](#UpdateConfig) + * [AddTasks](#AddTasks) * [ClaimDecay](#ClaimDecay) - * [Admin](#Admin) + * [Task_Admin](#Task_Admin) + * Messages + * [CompleteTask](#CompleteTask) * [User](#User) * Messages * [Account](#Account) @@ -33,7 +36,6 @@ Contract responsible to handle snip20 airdrop | admin | String | New contract owner; SHOULD be a valid bech32 address | yes | | dump_address | String | Where the decay amount will be sent | yes | | airdrop_token | Contract | The token that will be airdropped | no | -| airdrop_2 | Contract | The token that will be airdropped | yes | | airdrop_amount | String | Total airdrop amount to be claimed | no | | start_date | u64 | When the airdrop starts in UNIX time | yes | | end_date | u64 | When the airdrop ends in UNIX time | yes | @@ -41,7 +43,8 @@ Contract responsible to handle snip20 airdrop | merkle_root | String | Base 64 encoded merkle root of the airdrop data tree | no | | total_accounts | u32 | Total accounts in airdrop (needed for merkle proof) | no | | max_amount | String | Used to limit the user permit amounts (lowers exploit possibility) | no | -| claim_msg_plaintext | String | {wallet} | no | +| default_claim | String | The default amount to be gifted regardless of tasks | no | +| task_claim | RequiredTasks | The amounts per tasks to gift | no | | query_rounding | string | To prevent leaking information, total claimed is rounded off to this value | no | ##Admin @@ -61,6 +64,29 @@ Updates the given values | decay_start | u64 | When the airdrop decay starts in UNIX time | yes | | padding | string | Allows for enforcing constant length messages | yes | +#### AddTasks +Adds another task that can unlock the users claim percentage, total task percentage cannot exceed 100% +##### Task +| Name | Type | Description | optional | +|---------|--------|--------------------------------------------------|----------| +| address | String | The address that will grant the task to accounts | no | +| percent | string | The percent to be unlocked when completed | no | + +##### Request +| Name | Type | Description | optional | +|---------|--------|-----------------------------------------------|----------| +| tasks | Tasks | The new tasks to be added | no | +| padding | string | Allows for enforcing constant length messages | yes | + +##### Response +```json +{ + "add_tasks": { + "status": "success" + } +} +``` + #### ClaimDecay Drains the decayed amount of airdrop into the specified dump_address @@ -73,8 +99,27 @@ Drains the decayed amount of airdrop into the specified dump_address } ``` +##Task Admin + ### Messages +#### CompleteTask +Complete that address' tasks for a given user +##### Request +| Name | Type | Description | optional | +|---------|--------|-----------------------------------------------|----------| +| address | String | The address that completed the task | no | +| padding | string | Allows for enforcing constant length messages | yes | + +##### Response +```json +{ + "complete_task": { + "status": "success" + } +} +``` + ##User ### Messages @@ -85,8 +130,7 @@ Drains the decayed amount of airdrop into the specified dump_address | Name | Type | Description | optional | |--------------|----------------------------------------------------|-----------------------------------------------------------|----------| | addresses | Array of [AddressProofPermit](#AddressProofPermit) | Proof that the user owns those addresses | no | -| eth_pubkey | string | Key included in headstash distribution | no | -| eth_sig | string | Signature from eth_pubkey of msg.sender, for merkle tree verification | no | +| partial_tree | Array of string | An array of nodes that serve as a proof for the addresses | no | | padding | string | Allows for enforcing constant length messages | yes | ##### Response @@ -94,10 +138,10 @@ Drains the decayed amount of airdrop into the specified dump_address { "account": { "status": "success", - "claimed": "boolean", - "addresses": ["claimed addresses"], - "eth_pubkey": "eth_pubkey", - "eth_sig": "eth_sig", + "total": "Total airdrop amount", + "claimed": "Claimed amount", + "finished_tasks": "All of the finished tasks", + "addresses": ["claimed addresses"] } } ``` @@ -146,6 +190,7 @@ Claim the user's available claimable amount "status": "success", "total": "Total airdrop amount", "claimed": "Claimed amount", + "finished_tasks": "All of the finished tasks", "addresses": ["claimed addresses"] } } @@ -164,6 +209,23 @@ Gets the contract's config } ``` +## Dates +Get the contracts airdrop timeframe, can calculate the decay factor if a time is given +##### Request +| Name | Type | Description | optional | +|--------------|------|---------------------------------|----------| +| current_date | u64 | The current time in UNIX format | yes | +```json +{ + "dates": { + "start": "Airdrop start", + "end": "Airdrop end", + "decay_start": "Airdrop start of decay", + "decay_factor": "Decay percentage" + } +} +``` + ## TotalClaimed Shows the total amount of the token that has been claimed. If airdrop hasn't ended then it'll just show an estimation. ##### Request @@ -185,10 +247,11 @@ Get the account's information ```json { "account": { - "claimed": "boolean", - "addresses": ["claimed addresses"], - "eth_pubkey": "", - "eth_sig": "", + "total": "Total airdrop amount", + "claimed": "Claimed amount", + "unclaimed": "Amount available to claim", + "finished_tasks": "All of the finished tasks", + "addresses": ["claimed addresses"] } } ``` @@ -204,10 +267,11 @@ Get the account's information using a viewing key ```json { "account_with_key": { - "claimed": "boolean", - "addresses": ["claimed addresses"], - "eth_pubkey": "", - "eth_sig": "", + "total": "Total airdrop amount", + "claimed": "Claimed amount", + "unclaimed": "Amount available to claim", + "finished_tasks": "All of the finished tasks", + "addresses": ["claimed addresses"] } } ``` @@ -215,8 +279,9 @@ Get the account's information using a viewing key ## AddressProofPermit This is a structure used to prove that the user has permission to query that address's information (when querying account info). This is also used to prove that the user owns that address (when creating/updating accounts) and the given amount is in the airdrop. +This permit is written differently from the rest since its made taking into consideration many of Terra's limitations compared to Keplr's flexibility. -NOTE: The parameters must be in order. +NOTE: The parameters must be in order [How to sign](https://github.com/securesecrets/shade/blob/77abdc70bc645d97aee7de5eb9a2347d22da425f/packages/shade_protocol/src/signature/mod.rs#L100) #### Structure @@ -231,6 +296,7 @@ NOTE: The parameters must be in order. ```json { + "coins": [], "contract": "", "execute_msg": "", "sender": "" diff --git a/contracts/airdrop/src/contract.rs b/contracts/airdrop/src/contract.rs index 838fba5..1966ae2 100644 --- a/contracts/airdrop/src/contract.rs +++ b/contracts/airdrop/src/contract.rs @@ -1,13 +1,17 @@ use crate::{ handle::{ - try_account, try_claim, try_claim_decay, try_disable_permit_key, try_set_viewing_key, - try_update_config, + try_account, try_add_tasks, try_claim, try_claim_decay, try_complete_task, + try_disable_permit_key, try_set_viewing_key, try_update_config, }, query, state::{config_w, decay_claimed_w, total_claimed_w}, }; use shade_protocol::{ - airdrop::{errors::invalid_dates, Config, ExecuteMsg, InstantiateMsg, QueryMsg}, + airdrop::{ + claim_info::RequiredTask, + errors::{invalid_dates, invalid_task_percentage}, + Config, ExecuteMsg, InstantiateMsg, QueryMsg, + }, c_std::{ shd_entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Uint128, @@ -25,6 +29,24 @@ pub fn instantiate( info: MessageInfo, msg: InstantiateMsg, ) -> StdResult { + // Setup task claim + let mut task_claim = vec![RequiredTask { + address: env.contract.address.clone(), + percent: msg.default_claim, + }]; + let mut claim = msg.task_claim; + task_claim.append(&mut claim); + + // Validate claim percentage + let mut count = Uint128::zero(); + for claim in task_claim.iter() { + count += claim.percent; + } + + if count > Uint128::new(100u128) { + return Err(invalid_task_percentage(count.to_string().as_str())); + } + let start_date = match msg.start_date { None => env.block.time.seconds(), Some(date) => date, @@ -67,6 +89,7 @@ pub fn instantiate( return Err(StdError::generic_err("Decay must have an end date")); } } + let config = Config { admin: msg.admin.unwrap_or(info.sender), contract: env.contract.address, @@ -74,6 +97,7 @@ pub fn instantiate( airdrop_snip20: msg.airdrop_token.clone(), airdrop_snip20_optional: msg.airdrop_2.clone(), airdrop_amount: msg.airdrop_amount, + task_claim, start_date, end_date: msg.end_date, decay_start: msg.decay_start, @@ -117,21 +141,30 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S end_date, start_decay, ), + ExecuteMsg::AddTasks { tasks, .. } => try_add_tasks(deps, &env, &info, tasks), + ExecuteMsg::CompleteTask { address, .. } => { + try_complete_task(deps, &env, &info, address) + } ExecuteMsg::Account { addresses, eth_pubkey, eth_sig, + partial_tree, .. - } => try_account(deps, &env, &info, addresses, eth_pubkey, eth_sig), + } => try_account( + deps, + &env, + &info, + addresses, + eth_pubkey, + eth_sig, + partial_tree, + ), ExecuteMsg::DisablePermitKey { key, .. } => { try_disable_permit_key(deps, &env, &info, key) } ExecuteMsg::SetViewingKey { key, .. } => try_set_viewing_key(deps, &env, &info, key), - ExecuteMsg::Claim { - amount, - proof, - .. - } => try_claim(deps, &env, &info, amount, proof), + ExecuteMsg::Claim { .. } => try_claim(deps, &env, &info), ExecuteMsg::ClaimDecay { .. } => try_claim_decay(deps, &env, &info), }, RESPONSE_BLOCK_SIZE, diff --git a/contracts/airdrop/src/handle.rs b/contracts/airdrop/src/handle.rs index 582387f..a0f0528 100644 --- a/contracts/airdrop/src/handle.rs +++ b/contracts/airdrop/src/handle.rs @@ -1,30 +1,30 @@ use crate::state::{ - account_r, account_viewkey_w, account_w, address_in_account_w, claim_status_w, config_r, - config_w, decay_claimed_w, eth_pubkey_claim_r, eth_pubkey_claim_w, eth_pubkey_in_account_w, - eth_sig_in_account_w, revoke_permit, total_claimed_r, total_claimed_w, validate_address_permit, + account_r, account_total_claimed_r, account_total_claimed_w, account_viewkey_w, account_w, + address_in_account_w, claim_status_r, claim_status_w, config_r, config_w, decay_claimed_w, + eth_pubkey_in_account_w, eth_sig_in_account_w, revoke_permit, total_claimed_r, total_claimed_w, + validate_address_permit, }; -use hex::decode_to_slice; -// use rs_merkle::{algorithms::Sha256, Hasher, MerkleProof}; -use sha2::Digest; +use rs_merkle::{algorithms::Sha256, Hasher, MerkleProof}; use shade_protocol::{ airdrop::{ account::{Account, AccountKey, AddressProofMsg, AddressProofPermit}, + claim_info::RequiredTask, errors::{ - address_already_in_account, airdrop_ended, airdrop_not_started, already_claimed, - decay_claimed, decay_not_set, expected_memo, failed_verification, invalid_dates, - not_admin, nothing_to_claim, wrong_length, + account_does_not_exist, address_already_in_account, airdrop_ended, airdrop_not_started, + claim_too_high, decay_claimed, decay_not_set, expected_memo, invalid_dates, + invalid_partial_tree, invalid_task_percentage, not_admin, nothing_to_claim, + unexpected_error, }, Config, ExecuteAnswer, }, c_std::{ - ensure_eq, from_binary, to_binary, Addr, Api, BankMsg, Binary, CosmosMsg, Decimal, DepsMut, - Env, MessageInfo, Response, StdResult, Storage, Uint128, + from_binary, to_binary, Addr, Api, Binary, CosmosMsg, Decimal, DepsMut, Env, MessageInfo, + Response, StdResult, Storage, Uint128, }, query_authentication::viewing_keys::ViewingKey, snip20::helpers::send_msg, utils::generic_response::ResponseStatus::{self, Success}, }; -use std::{convert::TryInto, fmt::Write}; #[allow(clippy::too_many_arguments)] pub fn try_update_config( @@ -138,6 +138,38 @@ pub fn try_update_config( Ok(Response::new().set_data(to_binary(&ExecuteAnswer::UpdateConfig { status: Success })?)) } +pub fn try_add_tasks( + deps: DepsMut, + _env: &Env, + info: &MessageInfo, + tasks: Vec, +) -> StdResult { + let config = config_r(deps.storage).load()?; + // Check if admin + if info.sender != config.admin { + return Err(not_admin(config.admin.as_str())); + } + + config_w(deps.storage).update(|mut config| { + let mut task_list = tasks; + config.task_claim.append(&mut task_list); + + //Validate that they do not exceed 100 + let mut count = Uint128::zero(); + for task in config.task_claim.iter() { + count += task.percent; + } + + if count > Uint128::new(100u128) { + return Err(invalid_task_percentage(count.to_string().as_str())); + } + + Ok(config) + })?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::AddTask { status: Success })?)) +} + pub fn try_account( deps: DepsMut, env: &Env, @@ -145,6 +177,7 @@ pub fn try_account( addresses: Vec, eth_pubkey: String, eth_sig: String, + partial_tree: Vec, ) -> StdResult { // Check if airdrop active let config = config_r(deps.storage).load()?; @@ -157,12 +190,12 @@ pub fn try_account( // These variables are setup to facilitate updating let updating_account: bool; - // let old_claim_amount: Uint128; + let old_claim_amount: Uint128; let mut account = match account_r(deps.storage).may_load(sender.as_bytes())? { None => { updating_account = false; - // old_claim_amount = Uint128::zero(); + old_claim_amount = Uint128::zero(); let mut account = Account::default(); // Validate permits @@ -175,23 +208,45 @@ pub fn try_account( addresses.clone(), eth_pubkey.clone(), eth_sig.clone(), + partial_tree.clone(), )?; - // account_total_claimed_w(deps.storage).save(sender.as_bytes(), &Uint128::zero())?; + // Add default claim at index 0 + account_total_claimed_w(deps.storage).save(sender.as_bytes(), &Uint128::zero())?; claim_status_w(deps.storage, 0).save(sender.as_bytes(), &false)?; account } Some(acc) => { updating_account = true; - // old_claim_amount = acc.total_claimable; + old_claim_amount = acc.total_claimable; acc } }; + // Claim airdrop + let mut messages = vec![]; + + let (completed_percentage, unclaimed_percentage) = + update_tasks(deps.storage, &config, sender.clone())?; + + let mut redeem_amount = Uint128::zero(); + + if unclaimed_percentage > Uint128::zero() { + redeem_amount = claim_tokens( + deps.storage, + env, + info, + &config, + &account, + completed_percentage, + unclaimed_percentage, + )?; + } + // Update account after claim to calculate difference if updating_account { - // TODO: verify eth_pubkey & eth_sig from this function. + // Validate permits try_add_account_addresses( deps.storage, deps.api, @@ -199,20 +254,61 @@ pub fn try_account( &info.sender, &mut account, addresses.clone(), - eth_pubkey, + eth_pubkey.clone(), eth_sig.clone(), + partial_tree.clone(), )?; } + if updating_account && completed_percentage > Uint128::zero() { + // Calculate the total new address amount + let added_address_total = account.total_claimable.checked_sub(old_claim_amount)?; + account_total_claimed_w(deps.storage).update(sender.as_bytes(), |claimed| { + if let Some(claimed) = claimed { + let new_redeem: Uint128; + if completed_percentage == Uint128::new(100u128) { + new_redeem = + added_address_total * decay_factor(env.block.time.seconds(), &config); + } else { + new_redeem = completed_percentage + .multiply_ratio(added_address_total, Uint128::new(100u128)) + * decay_factor(env.block.time.seconds(), &config); + } + + redeem_amount += new_redeem; + Ok(claimed + new_redeem) + } else { + Err(unexpected_error()) + } + })?; + } + + if redeem_amount > Uint128::zero() { + total_claimed_w(deps.storage) + .update(|claimed| -> StdResult { Ok(claimed + redeem_amount) })?; + + messages.push(send_msg( + info.sender.clone(), + redeem_amount.into(), + None, + None, + None, + &config.airdrop_snip20, + )?); + } + // Save account account_w(deps.storage).save(sender.as_bytes(), &account)?; Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Account { status: ResponseStatus::Success, - claimed: eth_pubkey_claim_r(deps.storage) - .load(account.eth_pubkey.to_string().as_bytes())?, // Will always be 0 since rewards are automatically claimed here + total: account.total_claimable, + claimed: account_total_claimed_r(deps.storage).load(sender.to_string().as_bytes())?, + // Will always be 0 since rewards are automatically claimed here + finished_tasks: finished_tasks(deps.storage, sender.clone())?, addresses: account.addresses, - eth_pubkey: account.eth_pubkey, + eth_pubkey, + eth_sig, })?)) } @@ -247,15 +343,36 @@ pub fn try_set_viewing_key( ) } -pub fn try_claim( +pub fn try_complete_task( deps: DepsMut, - env: &Env, + _env: &Env, info: &MessageInfo, - amount: Uint128, - proof: Vec, + account: Addr, ) -> StdResult { let config = config_r(deps.storage).load()?; + for (i, task) in config.task_claim.iter().enumerate() { + if task.address == info.sender { + claim_status_w(deps.storage, i).update( + account.to_string().as_bytes(), + |status| -> StdResult { + // If there was a state then ignore + if let Some(status) = status { + Ok(status) + } else { + Ok(false) + } + }, + )?; + } + } + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::CompleteTask { status: Success })?)) +} + +pub fn try_claim(deps: DepsMut, env: &Env, info: &MessageInfo) -> StdResult { + let config = config_r(deps.storage).load()?; + // Check that airdrop hasn't ended available(&config, env)?; @@ -272,40 +389,25 @@ pub fn try_claim( config.clone(), )?; - // generate merkleTree leaf with eth_pubkey & amount - let user_input = format!("{}{}", account.eth_pubkey, amount); - let hash = sha2::Sha256::digest(user_input.as_bytes()) - .as_slice() - .try_into() - .map_err(|_| nothing_to_claim())?; - - let hash: [u8; 32] = proof.into_iter().try_fold(hash, |hash, p| { - let mut proof_buf = [0; 32]; - hex::decode_to_slice(p, &mut proof_buf).unwrap(); - let mut hashes = [hash, proof_buf]; - hashes.sort_unstable(); - sha2::Sha256::digest(&hashes.concat()) - .as_slice() - .try_into() - .map_err(|_| wrong_length()) - })?; - - let merkle = config.merkle_root.clone(); - let mut root_buf: [u8; 32] = [0; 32]; - decode_to_slice(merkle, &mut root_buf).unwrap(); - ensure_eq!(root_buf, hash, failed_verification()); + // Calculate airdrop + let (completed_percentage, unclaimed_percentage) = + update_tasks(deps.storage, &config, sender.to_string())?; - if account.claimed == true { + if unclaimed_percentage == Uint128::zero() { return Err(nothing_to_claim()); } - let redeem_amount = claim_tokens(deps.storage, env, info, &config, &account, amount)?; - - total_claimed_w(deps.storage) - .update(|claimed| -> StdResult { Ok(claimed + redeem_amount) })?; + let redeem_amount = claim_tokens( + deps.storage, + env, + info, + &config, + &account, + completed_percentage, + unclaimed_percentage, + )?; let mut msgs: Vec = vec![]; - let hs1 = send_msg( sender.clone(), redeem_amount.into(), @@ -328,12 +430,18 @@ pub fn try_claim( msgs.push(hs2); }; + total_claimed_w(deps.storage) + .update(|claimed| -> StdResult { Ok(claimed + redeem_amount) })?; + Ok(Response::new() .set_data(to_binary(&ExecuteAnswer::Claim { status: ResponseStatus::Success, - claimed: eth_pubkey_claim_r(deps.storage).load(account.eth_pubkey.as_bytes())?, + total: account.total_claimable, + claimed: account_total_claimed_r(deps.storage).load(sender.to_string().as_bytes())?, + finished_tasks: finished_tasks(deps.storage, sender.to_string())?, addresses: account.addresses, eth_pubkey: account.eth_pubkey, + eth_sig: account.eth_sig, })?) .add_messages(msgs)) } @@ -365,7 +473,6 @@ pub fn try_claim_decay(deps: DepsMut, env: &Env, _info: &MessageInfo) -> StdResu None, &config.airdrop_snip20, )?; - msgs.push(hs1); if !config.airdrop_snip20_optional.is_none() { @@ -390,29 +497,89 @@ pub fn try_claim_decay(deps: DepsMut, env: &Env, _info: &MessageInfo) -> StdResu Err(decay_not_set()) } +pub fn finished_tasks(storage: &dyn Storage, account: String) -> StdResult> { + let mut finished_tasks = vec![]; + let config = config_r(storage).load()?; + + for (index, _task) in config.task_claim.iter().enumerate() { + match claim_status_r(storage, index).may_load(account.as_bytes())? { + None => {} + Some(_) => { + finished_tasks.push(config.task_claim[index].clone()); + } + } + } + + Ok(finished_tasks) +} + +/// Gets task information and sets them +pub fn update_tasks( + storage: &mut dyn Storage, + config: &Config, + sender: String, +) -> StdResult<(Uint128, Uint128)> { + // Calculate eligible tasks + let mut completed_percentage = Uint128::zero(); + let mut unclaimed_percentage = Uint128::zero(); + for (index, task) in config.task_claim.iter().enumerate() { + // Check if task has been completed + let state = claim_status_r(storage, index).may_load(sender.as_bytes())?; + + match state { + // Ignore if none + None => {} + Some(claimed) => { + completed_percentage += task.percent; + if !claimed { + // Set claim status to true since we're going to claim it now + claim_status_w(storage, index).save(sender.as_bytes(), &true)?; + + unclaimed_percentage += task.percent; + } + } + } + } + + Ok((completed_percentage, unclaimed_percentage)) +} + pub fn claim_tokens( storage: &mut dyn Storage, env: &Env, info: &MessageInfo, config: &Config, account: &Account, - amount: Uint128, + completed_percentage: Uint128, + unclaimed_percentage: Uint128, ) -> StdResult { // send_amount - let sender = account.eth_pubkey.to_string(); + let sender = info.sender.to_string(); // Amount to be redeemed + let mut redeem_amount = Uint128::zero(); // Update total claimed and calculate claimable - eth_pubkey_claim_w(storage).update(sender.as_bytes(), |claimed| { - if let Some(_claimed) = claimed { - Err(already_claimed()) + account_total_claimed_w(storage).update(sender.as_bytes(), |claimed| { + if let Some(claimed) = claimed { + // This solves possible uToken inaccuracies + if completed_percentage == Uint128::new(100u128) { + redeem_amount = account.total_claimable.checked_sub(claimed)?; + } else { + redeem_amount = unclaimed_percentage + .multiply_ratio(account.total_claimable, Uint128::new(100u128)); + } + + // Update redeem amount with the decay multiplier + redeem_amount = redeem_amount * decay_factor(env.block.time.seconds(), config); + + Ok(claimed + redeem_amount) } else { - Ok(true) + Err(account_does_not_exist()) } })?; - Ok(amount) + Ok(redeem_amount) } /// Validates all of the information and updates relevant states @@ -425,7 +592,11 @@ pub fn try_add_account_addresses( addresses: Vec, eth_pubkey: String, eth_sig: String, + partial_tree: Vec, ) -> StdResult<()> { + // Setup the items to validate + let mut leaves_to_validate: Vec<(usize, [u8; 32])> = vec![]; + // Iterate addresses for permit in addresses.iter() { if let Some(memo) = permit.memo.clone() { @@ -437,13 +608,13 @@ pub fn try_add_account_addresses( validate_address_permit(storage, api, permit, ¶ms, config.contract.clone())?; } - // // Check that airdrop amount does not exceed maximum - // if params.amount > config.max_amount { - // return Err(claim_too_high( - // params.amount.to_string().as_str(), - // config.max_amount.to_string().as_str(), - // )); - // } + // Check that airdrop amount does not exceed maximum + if params.amount > config.max_amount { + return Err(claim_too_high( + params.amount.to_string().as_str(), + config.max_amount.to_string().as_str(), + )); + } // Update address if its not in an account address_in_account_w(storage).update( @@ -457,11 +628,6 @@ pub fn try_add_account_addresses( }, )?; - // // Add account as a leaf - // let leaf_hash = - // Sha256::hash((params.address.to_string() + ¶ms.amount.to_string()).as_bytes()); - // leaves_to_validate.push((params.index as usize, leaf_hash)); - // Update eth_pubkey if its not in an account eth_pubkey_in_account_w(storage).update( eth_pubkey.to_string().as_bytes(), @@ -473,6 +639,7 @@ pub fn try_add_account_addresses( Ok(true) }, )?; + // Update eth_sig if its not in an account eth_sig_in_account_w(storage).update( eth_sig.to_string().as_bytes(), @@ -485,42 +652,49 @@ pub fn try_add_account_addresses( }, )?; + // Add account as a leaf + let leaf_hash = + Sha256::hash((eth_pubkey.to_string() + ¶ms.amount.to_string()).as_bytes()); + leaves_to_validate.push((params.index as usize, leaf_hash)); + // If valid then add to account array and sum total amount account.addresses.push(params.address); account.eth_pubkey = eth_pubkey.to_string(); account.eth_sig = eth_sig.to_string(); + account.total_claimable += params.amount; } else { return Err(expected_memo()); } + } + + // Need to sort by index in order for the proof to work + leaves_to_validate.sort_by_key(|item| item.0); - // // Need to sort by index in order for the proof to work - // leaves_to_validate.sort_by_key(|item| item.0); - - // let mut indices: Vec = vec![]; - // let mut leaves: Vec<[u8; 32]> = vec![]; - - // for leaf in leaves_to_validate.iter() { - // indices.push(leaf.0); - // leaves.push(leaf.1); - // } - - // // Convert partial tree from base64 to binary - // let mut partial_tree_binary: Vec<[u8; 32]> = vec![]; - // for node in partial_tree.iter() { - // let mut arr: [u8; 32] = Default::default(); - // arr.clone_from_slice(node.as_slice()); - // partial_tree_binary.push(arr); - // } - - // // Prove that user is in airdrop - // let proof = MerkleProof::::new(partial_tree_binary); - // // Convert to a fixed length array without messing up the contract - // let mut root: [u8; 32] = Default::default(); - // root.clone_from_slice(config.merkle_root.as_slice()); - // if !proof.verify(root, &indices, &leaves, config.total_accounts as usize) { - // return Err(invalid_partial_tree()); - // } + let mut indices: Vec = vec![]; + let mut leaves: Vec<[u8; 32]> = vec![]; + + for leaf in leaves_to_validate.iter() { + indices.push(leaf.0); + leaves.push(leaf.1); + } + + // Convert partial tree from base64 to binary + let mut partial_tree_binary: Vec<[u8; 32]> = vec![]; + for node in partial_tree.iter() { + let mut arr: [u8; 32] = Default::default(); + arr.clone_from_slice(node.as_slice()); + partial_tree_binary.push(arr); } + + // Prove that user is in airdrop + let proof = MerkleProof::::new(partial_tree_binary); + // Convert to a fixed length array without messing up the contract + let mut root: [u8; 32] = Default::default(); + root.clone_from_slice(config.merkle_root.as_slice()); + if !proof.verify(root, &indices, &leaves, config.total_accounts as usize) { + return Err(invalid_partial_tree()); + } + Ok(()) } diff --git a/contracts/airdrop/src/query.rs b/contracts/airdrop/src/query.rs index e5deadf..6780f15 100644 --- a/contracts/airdrop/src/query.rs +++ b/contracts/airdrop/src/query.rs @@ -1,12 +1,20 @@ use crate::{ handle::decay_factor, state::{ - account_r, account_viewkey_r, config_r, decay_claimed_r, eth_pubkey_claim_r, total_claimed_r, validate_account_permit + account_r, + account_total_claimed_r, + account_viewkey_r, + claim_status_r, + config_r, + decay_claimed_r, + total_claimed_r, + validate_account_permit, }, }; use shade_protocol::{ airdrop::{ account::{AccountKey, AccountPermit}, + claim_info::RequiredTask, errors::invalid_viewing_key, QueryAnswer, }, @@ -50,16 +58,49 @@ fn account_information( let account = account_r(deps.storage).load(account_address.to_string().as_bytes())?; // Calculate eligible tasks - // let config = config_r(deps.storage).load()?; + let config = config_r(deps.storage).load()?; + let mut finished_tasks: Vec = vec![]; + let mut completed_percentage = Uint128::zero(); + let mut unclaimed_percentage = Uint128::zero(); + for (index, task) in config.task_claim.iter().enumerate() { + // Check if task has been completed + let state = + claim_status_r(deps.storage, index).may_load(account_address.to_string().as_bytes())?; + + match state { + // Ignore if none + None => {} + Some(claimed) => { + finished_tasks.push(task.clone()); + if !claimed { + unclaimed_percentage += task.percent; + } else { + completed_percentage += task.percent; + } + } + } + } + + let mut unclaimed: Uint128; - // Check if eth address has claimed - let claim_state = eth_pubkey_claim_r(deps.storage).may_load(account.eth_pubkey.as_bytes())?; + if unclaimed_percentage == Uint128::new(100u128) { + unclaimed = account.total_claimable; + } else { + unclaimed = + unclaimed_percentage.multiply_ratio(account.total_claimable, Uint128::new(100u128)); + } + + if let Some(time) = current_date { + unclaimed = unclaimed * decay_factor(time, &config); + } Ok(QueryAnswer::Account { - claimed: claim_state.unwrap(), + total: account.total_claimable, + claimed: account_total_claimed_r(deps.storage) + .load(account_address.to_string().as_bytes())?, + unclaimed, + finished_tasks, addresses: account.addresses, - eth_pubkey: account.eth_pubkey, - eth_sig: account.eth_sig, }) } diff --git a/contracts/airdrop/src/state.rs b/contracts/airdrop/src/state.rs index 69923f4..6d54af7 100644 --- a/contracts/airdrop/src/state.rs +++ b/contracts/airdrop/src/state.rs @@ -16,13 +16,14 @@ pub static CONFIG_KEY: &[u8] = b"config"; pub static DECAY_CLAIMED_KEY: &[u8] = b"decay_claimed"; pub static CLAIM_STATUS_KEY: &[u8] = b"claim_status_"; pub static REWARD_IN_ACCOUNT_KEY: &[u8] = b"reward_in_account"; -pub static ETH_PUBKEY_IN_ACCOUNT_KEY: &[u8] = b"eth_pubkey_in_account"; -pub static ETH_SIG_IN_ACCOUNT_KEY: &[u8] = b"eth_sig_in_account"; -pub static ETH_PUBKEY: &str = "eth_pubkey"; pub static ACCOUNTS_KEY: &[u8] = b"accounts"; pub static TOTAL_CLAIMED_KEY: &[u8] = b"total_claimed"; +pub static USER_TOTAL_CLAIMED_KEY: &[u8] = b"user_total_claimed"; pub static ACCOUNT_PERMIT_KEY: &str = "account_permit_key"; pub static ACCOUNT_VIEWING_KEY: &[u8] = b"account_viewing_key"; +pub static ETH_PUBKEY_IN_ACCOUNT_KEY: &str = "eth_pubkey_in_account"; +pub static ETH_SIG_IN_ACCOUNT_KEY: &str = "eth_sig_in_account"; +pub static ETH_PUBKEY: &str = "eth_pubkey"; pub fn config_w(storage: &mut dyn Storage) -> Singleton { singleton(storage, CONFIG_KEY) @@ -80,32 +81,13 @@ pub fn total_claimed_w(storage: &mut dyn Storage) -> Singleton { singleton(storage, TOTAL_CLAIMED_KEY) } -// Is address added to an account -pub fn eth_pubkey_in_account_r(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, ETH_PUBKEY_IN_ACCOUNT_KEY) +// Total account claimed +pub fn account_total_claimed_r(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, USER_TOTAL_CLAIMED_KEY) } -pub fn eth_pubkey_in_account_w(storage: &mut dyn Storage) -> Bucket { - bucket(storage, ETH_PUBKEY_IN_ACCOUNT_KEY) -} -// Is address added to an account -pub fn eth_sig_in_account_r(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, ETH_SIG_IN_ACCOUNT_KEY) -} - -pub fn eth_sig_in_account_w(storage: &mut dyn Storage) -> Bucket { - bucket(storage, ETH_SIG_IN_ACCOUNT_KEY) -} - -// Eth pubkey claimed -pub fn eth_pubkey_claim_r(storage: &dyn Storage) -> ReadonlyBucket { - let key = ETH_PUBKEY.to_string(); - bucket_read(storage, key.as_bytes()) -} - -pub fn eth_pubkey_claim_w(storage: &mut dyn Storage) -> Bucket { - let key = ETH_PUBKEY.to_string(); - bucket(storage, key.as_bytes()) +pub fn account_total_claimed_w(storage: &mut dyn Storage) -> Bucket { + bucket(storage, USER_TOTAL_CLAIMED_KEY) } // Account viewing key @@ -134,6 +116,38 @@ pub fn revoke_permit(storage: &mut dyn Storage, account: String, permit_key: Str .unwrap(); } +// Is address added to an account +pub fn eth_pubkey_in_account_r(storage: &dyn Storage) -> ReadonlyBucket { + let key = ETH_PUBKEY_IN_ACCOUNT_KEY.to_string(); + bucket_read(storage, key.as_bytes()) +} + +pub fn eth_pubkey_in_account_w(storage: &mut dyn Storage) -> Bucket { + let key = ETH_PUBKEY_IN_ACCOUNT_KEY.to_string(); + bucket(storage, key.as_bytes()) +} +// Is address added to an account +pub fn eth_sig_in_account_r(storage: &dyn Storage) -> ReadonlyBucket { + let key = ETH_SIG_IN_ACCOUNT_KEY.to_string(); + bucket_read(storage, key.as_bytes()) +} + +pub fn eth_sig_in_account_w(storage: &mut dyn Storage) -> Bucket { + let key = ETH_SIG_IN_ACCOUNT_KEY.to_string(); + bucket(storage, key.as_bytes()) +} + +// Eth pubkey claimed +pub fn eth_pubkey_claim_r(storage: &dyn Storage) -> ReadonlyBucket { + let key = ETH_PUBKEY.to_string(); + bucket_read(storage, key.as_bytes()) +} + +pub fn eth_pubkey_claim_w(storage: &mut dyn Storage) -> Bucket { + let key = ETH_PUBKEY.to_string(); + bucket(storage, key.as_bytes()) +} + pub fn is_permit_revoked( storage: &dyn Storage, account: String, diff --git a/contracts/airdrop/src/test.rs b/contracts/airdrop/src/test.rs index 264b9a9..af4835c 100644 --- a/contracts/airdrop/src/test.rs +++ b/contracts/airdrop/src/test.rs @@ -281,7 +281,7 @@ pub mod tests { "h/RpG1eKzN03oId0GvN7TSxoHOUibjmqPEQ1E+ZWh+BvghPL99lBj4L3BKpjjsaRtXX3lexO7ztafLKBVtq4xA==").unwrap(), }, account_number: None, - memo: Some("eyJhZGRyZXNzIjoic2VjcmV0MTlxN2gyenk4bWdlc3kzcjM5ZWw1ZmNtOTg2bnhxamQ3Y2d5bHJ6IiwiY29udHJhY3QiOiJzZWNyZXQxc3I2MmxlaGFqZ3doZHpwbW5sNjV1MzVydWdqcmd6bmgyNTcybXYiLCJrZXkiOiJhY2NvdW50LWNyZWF0aW9uLXBlcm1pdCJ9".to_string()) + memo: Some("eyJhbW91bnQiOiIxMDAwMDAwMCIsImluZGV4IjoxMCwia2V5IjoiYWNjb3VudC1jcmVhdGlvbi1wZXJtaXQifQ==".to_string()) }; let deps = mock_dependencies(); @@ -306,13 +306,15 @@ pub mod tests { fn memo_deserialization() { let expected_memo = AddressProofMsg { address: Addr::unchecked("secret19q7h2zy8mgesy3r39el5fcm986nxqjd7cgylrz".to_string()), + amount: Uint128::new(1000000u128), contract: Addr::unchecked("secret1sr62lehajgwhdzpmnl65u35rugjrgznh2572mv".to_string()), + index: 10, key: "account-creation-permit".to_string(), }; let deserialized_memo: AddressProofMsg = from_binary( &Binary::from_base64( - &"eyJhZGRyZXNzIjoic2VjcmV0MTlxN2gyenk4bWdlc3kzcjM5ZWw1ZmNtOTg2bnhxamQ3Y2d5bHJ6IiwiY29udHJhY3QiOiJzZWNyZXQxc3I2MmxlaGFqZ3doZHpwbW5sNjV1MzVydWdqcmd6bmgyNTcybXYiLCJrZXkiOiJhY2NvdW50LWNyZWF0aW9uLXBlcm1pdCJ9" + &"eyJhZGRyZXNzIjoic2VjcmV0MTlxN2gyenk4bWdlc3kzcjM5ZWw1ZmNtOTg2bnhxamQ3Y2d5bHJ6IiwiYW1vdW50IjoiMTAwMDAwMCIsImNvbnRyYWN0Ijoic2VjcmV0MXNyNjJsZWhhamd3aGR6cG1ubDY1dTM1cnVnanJnem5oMjU3Mm12IiwiaW5kZXgiOjEwLCJrZXkiOiJhY2NvdW50LWNyZWF0aW9uLXBlcm1pdCJ9" .to_string()).unwrap()).unwrap(); assert_eq!(deserialized_memo, expected_memo) diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs index b0f6fc1..3599a36 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/account.rs @@ -1,27 +1,30 @@ use crate::contract_interfaces::airdrop::errors::permit_rejected; -use crate::c_std::{Addr, Api, StdResult}; +use crate::c_std::Uint128; +use crate::c_std::{Addr, StdResult, Api}; use crate::query_authentication::{ permit::{bech32_to_canonical, Permit}, viewing_keys::ViewingKey, }; -use cosmwasm_schema::cw_serde; +use cosmwasm_schema::{cw_serde}; #[cw_serde] pub struct Account { pub addresses: Vec, + pub total_claimable: Uint128, pub eth_pubkey: String, pub eth_sig: String, - pub claimed: bool, + // pub claimed: bool, } impl Default for Account { fn default() -> Self { Self { addresses: vec![], + total_claimable: Uint128::zero(), eth_pubkey: "".to_string(), eth_sig: "".to_string(), - claimed: false, + // claimed: false, } } } @@ -50,8 +53,8 @@ impl Default for FillerMsg { Self { coins: vec![], contract: "".to_string(), - execute_msg: EmptyMsg {}, sender: "".to_string(), + execute_msg: EmptyMsg {}, } } } @@ -63,11 +66,7 @@ pub struct EmptyMsg {} // Used to prove ownership over IBC addresses pub type AddressProofPermit = Permit; -pub fn authenticate_ownership( - api: &dyn Api, - permit: &AddressProofPermit, - permit_address: &str, -) -> StdResult<()> { +pub fn authenticate_ownership(api: &dyn Api, permit: &AddressProofPermit, permit_address: &str) -> StdResult<()> { let signer_address = permit .validate(api, Some("wasm/MsgExecuteContract".to_string()))? .as_canonical(); @@ -84,8 +83,12 @@ pub fn authenticate_ownership( pub struct AddressProofMsg { // Address is necessary since we have other network permits present pub address: Addr, + // Reward amount + pub amount: Uint128, // Used to prevent permits from being used elsewhere pub contract: Addr, + // Index of the address in the leaves array + pub index: u32, // Used to identify permits pub key: String, } diff --git a/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs index 6e66543..b2f4432 100644 --- a/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs +++ b/packages/shade_protocol/src/contract_interfaces/airdrop/mod.rs @@ -3,8 +3,11 @@ pub mod claim_info; pub mod errors; use crate::{ - c_std::{Addr, Uint128}, - contract_interfaces::airdrop::account::{AccountPermit, AddressProofPermit}, + c_std::{Addr, Binary, Uint128}, + contract_interfaces::airdrop::{ + account::{AccountPermit, AddressProofPermit}, + claim_info::RequiredTask, + }, utils::{asset::Contract, generic_response::ResponseStatus}, }; @@ -18,14 +21,14 @@ pub struct Config { pub contract: Addr, // Where the decayed tokens will be dumped, if none then nothing happens pub dump_address: Option, - // The snip20 to be mint, + // The snip20 to be minted pub airdrop_snip20: Contract, // An optional, second snip20 to be minted pub airdrop_snip20_optional: Option, // Airdrop amount pub airdrop_amount: Uint128, // Required tasks - // pub task_claim: Vec, + pub task_claim: Vec, // Checks if airdrop has started / ended pub start_date: u64, // Airdrop stops at end date if there is one @@ -34,7 +37,7 @@ pub struct Config { pub decay_start: Option, // This is necessary to validate the airdrop information // tree root - pub merkle_root: String, + pub merkle_root: Binary, // tree height pub total_accounts: u32, // {wallet} @@ -50,24 +53,27 @@ pub struct InstantiateMsg { pub admin: Option, // Where the decayed tokens will be dumped, if none then nothing happens pub dump_address: Option, - // primary scrt-20 contract being distributed pub airdrop_token: Contract, + // Airdrop amount + pub airdrop_amount: Uint128, // an optional, second snip20 to be minted pub airdrop_2: Option, - // total amount of airdrop - pub airdrop_amount: Uint128, // The airdrop time limit pub start_date: Option, // Can be set to never end pub end_date: Option, // Starts to decay at this date pub decay_start: Option, - // max possible reward amount; used to prevent collision possibility - pub max_amount: Uint128, // Base64 encoded version of the tree root - pub merkle_root: String, + pub merkle_root: Binary, // Root height pub total_accounts: u32, + // Max possible reward amount + pub max_amount: Uint128, + // Default gifted amount + pub default_claim: Uint128, + // The task related claims + pub task_claim: Vec, /// {wallet} pub claim_msg_plaintext: String, // Protects from leaking user information by limiting amount detail @@ -89,10 +95,19 @@ pub enum ExecuteMsg { decay_start: Option, padding: Option, }, + AddTasks { + tasks: Vec, + padding: Option, + }, + CompleteTask { + address: Addr, + padding: Option, + }, Account { addresses: Vec, eth_pubkey: String, eth_sig: String, + partial_tree: Vec, padding: Option, }, DisablePermitKey { @@ -104,16 +119,11 @@ pub enum ExecuteMsg { padding: Option, }, Claim { - amount: Uint128, - proof: Vec, padding: Option, }, ClaimDecay { padding: Option, }, - // CreateViewingKey { - // key: String, - // }, } impl ExecuteCallback for ExecuteMsg { @@ -125,11 +135,23 @@ pub enum ExecuteAnswer { UpdateConfig { status: ResponseStatus, }, + AddTask { + status: ResponseStatus, + }, + CompleteTask { + status: ResponseStatus, + }, Account { status: ResponseStatus, + // Total eligible + total: Uint128, + // Total claimed + claimed: Uint128, + finished_tasks: Vec, + // Addresses claimed addresses: Vec, eth_pubkey: String, - claimed: bool, + eth_sig: String, }, DisablePermitKey { status: ResponseStatus, @@ -137,12 +159,17 @@ pub enum ExecuteAnswer { SetViewingKey { status: ResponseStatus, }, - Claim { status: ResponseStatus, - claimed: bool, + // Total eligible + total: Uint128, + // Total claimed + claimed: Uint128, + finished_tasks: Vec, + // Addresses claimed addresses: Vec, eth_pubkey: String, + eth_sig: String, }, ClaimDecay { status: ResponseStatus, @@ -186,16 +213,21 @@ pub enum QueryAnswer { claimed: Uint128, }, Account { - claimed: bool, + // Total eligible + total: Uint128, + // Total claimed + claimed: Uint128, + // Total unclaimed but available + unclaimed: Uint128, + finished_tasks: Vec, + // Addresses claimed addresses: Vec, - eth_pubkey: String, - eth_sig: String, }, } #[cw_serde] pub struct AccountVerification { - pub eth_pubkey: String, + // pub eth_pubkey: String, pub account: Addr, pub claimed: bool, } From ab97142e5feffc1b3fd4186a9e248591e84ee5db Mon Sep 17 00:00:00 2001 From: Hard-Nett Date: Thu, 4 Apr 2024 00:56:09 +0000 Subject: [PATCH 15/23] update scripts --- tools/headstash/main.js | 15 ++++++++++----- tools/headstash/package.json | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 tools/headstash/package.json diff --git a/tools/headstash/main.js b/tools/headstash/main.js index fa97657..1dd7735 100644 --- a/tools/headstash/main.js +++ b/tools/headstash/main.js @@ -14,9 +14,9 @@ export const secretTerpContractAddr = "secret1c3lj7dr9r2pe83j3yx8jt5v800zs9sq7we export const secretThiolContractAddr = "secret1umh28jgcp0g9jy3qc29xk42kq92xjrcdfgvwdz"; // airdrop contract -export const scrtHeadstashCodeId = 6410; -export const scrtHeadstashCodeHash = "1b037fcd711ebf057cecd11103c6a5feee65e8e4c6ace281560531344d01aaec"; -export const secretHeadstashContractAddr = "secret17vftlyw20j08hsey6u7r6vl98pwejlqhdrpnhm"; +export const scrtHeadstashCodeId = 6459; +export const scrtHeadstashCodeHash = "f494eda77c7816c4882d0dfde8bbd35b87975e427ea74315ed96c051d5674f82"; +export const secretHeadstashContractAddr = "secret1u4ndydgspk0ff6ttvs0kq7utdhcmqec405jtqg"; export const merkle_root = "d599867bdb2ade1e470d9ec9456490adcd9da6e0cfd8f515e2b95d345a5cd92f"; // signing client @@ -65,13 +65,18 @@ let instantiate_headstash_contract = async () => { address: secretThiolContractAddr, code_hash: scrt20CodeHash }, - airdrop_amount: "840", start_date: null, end_date: null, decay_start: null, - max_amount: "420", merkle_root: merkle_root, + airdrop_amount: "840", total_accounts: 2, + max_amount: "420", + default_claim: "50", + task_claim: [{ + address: secretHeadstashContractAddr, + percent: "50", + }], claim_msg_plaintext: "{wallet}", query_rounding: "1" }; diff --git a/tools/headstash/package.json b/tools/headstash/package.json new file mode 100644 index 0000000..2df2167 --- /dev/null +++ b/tools/headstash/package.json @@ -0,0 +1,17 @@ +{ + "name": "scripts", + "version": "1.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@shadeprotocol/shadejs": "^1.1.7", + "secretjs": "^1.12.5" + } +} From 5187957eca7b96ca5666812e31af42a22e38fa76 Mon Sep 17 00:00:00 2001 From: Hard-Nett Date: Fri, 5 Apr 2024 20:42:07 +0000 Subject: [PATCH 16/23] update merkle tools --- tools/merkleTree/.eslintignore | 2 +- tools/merkleTree/.eslintrc | 10 +- tools/merkleTree/.gitignore | 2 +- tools/merkleTree/README.md | 2 +- tools/merkleTree/bin/run | 5 + tools/merkleTree/bin/run.cmd | 3 + tools/merkleTree/package.json | 60 + tools/merkleTree/src/airdrop.ts | 2 +- .../merkleTree/src/commands/generateProofs.ts | 2 +- tools/merkleTree/src/commands/generateRoot.ts | 2 +- tools/merkleTree/src/commands/verifyProofs.ts | 2 +- tools/merkleTree/src/index.ts | 2 +- tools/merkleTree/tsconfig.json | 33 + tools/merkleTree/yarn.lock | 1695 ++++++----------- 14 files changed, 731 insertions(+), 1091 deletions(-) create mode 100755 tools/merkleTree/bin/run create mode 100644 tools/merkleTree/bin/run.cmd create mode 100644 tools/merkleTree/package.json create mode 100644 tools/merkleTree/tsconfig.json diff --git a/tools/merkleTree/.eslintignore b/tools/merkleTree/.eslintignore index dc55552..502167f 100644 --- a/tools/merkleTree/.eslintignore +++ b/tools/merkleTree/.eslintignore @@ -1 +1 @@ -/lib \ No newline at end of file +/lib diff --git a/tools/merkleTree/.eslintrc b/tools/merkleTree/.eslintrc index d15adc8..7b84619 100644 --- a/tools/merkleTree/.eslintrc +++ b/tools/merkleTree/.eslintrc @@ -1,6 +1,6 @@ { - "extends": [ - "oclif", - "oclif-typescript" - ] -} \ No newline at end of file + "extends": [ + "oclif", + "oclif-typescript" + ] +} diff --git a/tools/merkleTree/.gitignore b/tools/merkleTree/.gitignore index 7caeba7..9d6ea2c 100644 --- a/tools/merkleTree/.gitignore +++ b/tools/merkleTree/.gitignore @@ -5,4 +5,4 @@ /lib /package-lock.json /tmp -node_modules \ No newline at end of file +node_modules diff --git a/tools/merkleTree/README.md b/tools/merkleTree/README.md index e8d91e5..c9e4a97 100644 --- a/tools/merkleTree/README.md +++ b/tools/merkleTree/README.md @@ -44,4 +44,4 @@ merkle-airdrop-cli verifyProofs --file ../testdata/airdrop.json \ --address wasm1k9hwzxs889jpvd7env8z49gad3a3633vg350tq \ --amount 100 \ --proofs $PROOFS -``` \ No newline at end of file +``` diff --git a/tools/merkleTree/bin/run b/tools/merkleTree/bin/run new file mode 100755 index 0000000..30b14e1 --- /dev/null +++ b/tools/merkleTree/bin/run @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +require('@oclif/command').run() +.then(require('@oclif/command/flush')) +.catch(require('@oclif/errors/handle')) diff --git a/tools/merkleTree/bin/run.cmd b/tools/merkleTree/bin/run.cmd new file mode 100644 index 0000000..968fc30 --- /dev/null +++ b/tools/merkleTree/bin/run.cmd @@ -0,0 +1,3 @@ +@echo off + +node "%~dp0\run" %* diff --git a/tools/merkleTree/package.json b/tools/merkleTree/package.json new file mode 100644 index 0000000..8e249ee --- /dev/null +++ b/tools/merkleTree/package.json @@ -0,0 +1,60 @@ +{ + "name": "merkle-airdrop-cli", + "version": "0.1.0", + "author": "Orkun Külçe @orkunkl", + "bin": { + "merkle-airdrop-cli": "./bin/run" + }, + "dependencies": { + "@cosmjs/crypto": "^0.25.5", + "@cosmjs/encoding": "^0.25.5", + "@oclif/command": "^1", + "@oclif/config": "^1", + "@oclif/plugin-help": "^3", + "@types/crypto-js": "^4.0.2", + "ethereumjs-util": "^7.1.0", + "merkletreejs": "^0.2.23", + "tslib": "^1" + }, + "devDependencies": { + "@oclif/dev-cli": "^1", + "@types/node": "^10", + "eslint": "^5.13", + "eslint-config-oclif": "^3.1", + "eslint-config-oclif-typescript": "^0.1", + "globby": "^10", + "ts-node": "^8", + "typescript": "^3.3" + }, + "engines": { + "node": ">=8.0.0" + }, + "files": [ + "/bin", + "/lib", + "/npm-shrinkwrap.json", + "/oclif.manifest.json" + ], + "keywords": [ + "cosmwasm", + "cw20" + ], + "license": "Apache-2.0", + "main": "lib/index.js", + "oclif": { + "commands": "./lib/commands", + "bin": "merkle-airdrop-cli", + "plugins": [ + "@oclif/plugin-help" + ] + }, + "repository": "CosmWasm/cosmwasm-plus/cw20-merkle-airdrop/merkle-airdrop-cli", + "scripts": { + "postpack": "rm -f oclif.manifest.json", + "posttest": "eslint . --ext .ts --config .eslintrc", + "prepack": "rm -rf lib && tsc -b && oclif-dev manifest && oclif-dev readme", + "test": "echo NO TESTS", + "version": "oclif-dev readme && git add README.md" + }, + "types": "lib/index.d.ts" +} diff --git a/tools/merkleTree/src/airdrop.ts b/tools/merkleTree/src/airdrop.ts index c798e40..87972b0 100644 --- a/tools/merkleTree/src/airdrop.ts +++ b/tools/merkleTree/src/airdrop.ts @@ -41,4 +41,4 @@ class Airdrop { } } -export {Airdrop} \ No newline at end of file +export {Airdrop} diff --git a/tools/merkleTree/src/commands/generateProofs.ts b/tools/merkleTree/src/commands/generateProofs.ts index 35bcf1b..6181e1d 100644 --- a/tools/merkleTree/src/commands/generateProofs.ts +++ b/tools/merkleTree/src/commands/generateProofs.ts @@ -45,4 +45,4 @@ export default class GenerateProof extends Command { let proof = airdrop.getMerkleProof({address: flags.address, amount: flags.amount}) console.log(proof) } -} \ No newline at end of file +} diff --git a/tools/merkleTree/src/commands/generateRoot.ts b/tools/merkleTree/src/commands/generateRoot.ts index 311868a..a8875fe 100644 --- a/tools/merkleTree/src/commands/generateRoot.ts +++ b/tools/merkleTree/src/commands/generateRoot.ts @@ -34,4 +34,4 @@ export default class GenerateRoot extends Command { let airdrop = new Airdrop(receivers) console.log(airdrop.getMerkleRoot()) } -} \ No newline at end of file +} diff --git a/tools/merkleTree/src/commands/verifyProofs.ts b/tools/merkleTree/src/commands/verifyProofs.ts index d52fbab..3915ae4 100644 --- a/tools/merkleTree/src/commands/verifyProofs.ts +++ b/tools/merkleTree/src/commands/verifyProofs.ts @@ -52,4 +52,4 @@ export default class VerifyProof extends Command { console.log(airdrop.verify(proofs, {address: flags.address, amount: flags.amount})) } -} \ No newline at end of file +} diff --git a/tools/merkleTree/src/index.ts b/tools/merkleTree/src/index.ts index 639cad3..4caa481 100644 --- a/tools/merkleTree/src/index.ts +++ b/tools/merkleTree/src/index.ts @@ -1 +1 @@ -export {run} from '@oclif/command' \ No newline at end of file +export {run} from '@oclif/command' diff --git a/tools/merkleTree/tsconfig.json b/tools/merkleTree/tsconfig.json new file mode 100644 index 0000000..8964312 --- /dev/null +++ b/tools/merkleTree/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "importHelpers": true, + "outDir": "lib", + "rootDir": "src", + "strict": true, + "target": "es2017", + "allowSyntheticDefaultImports": true, + "alwaysStrict": true, + "baseUrl": "./", + "declaration": true, + "esModuleInterop": true, + "lib": ["es2015", "es2016", "es2017", "dom"], + "module": "commonjs", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "sourceMap": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "paths": { + "*": ["src/*"] + } + }, + "include": [ + "src/**/*" + ] +} diff --git a/tools/merkleTree/yarn.lock b/tools/merkleTree/yarn.lock index 84b9310..a074719 100644 --- a/tools/merkleTree/yarn.lock +++ b/tools/merkleTree/yarn.lock @@ -2,202 +2,26 @@ # yarn lockfile v1 -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.1", "@babel/code-frame@^7.24.2": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" - integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== - dependencies: - "@babel/highlight" "^7.24.2" - picocolors "^1.0.0" - -"@babel/compat-data@^7.23.5": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.1.tgz#31c1f66435f2a9c329bb5716a6d6186c516c3742" - integrity sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA== - -"@babel/core@^7.12.16": - version "7.24.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.3.tgz#568864247ea10fbd4eff04dda1e05f9e2ea985c3" - integrity sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.24.2" - "@babel/generator" "^7.24.1" - "@babel/helper-compilation-targets" "^7.23.6" - "@babel/helper-module-transforms" "^7.23.3" - "@babel/helpers" "^7.24.1" - "@babel/parser" "^7.24.1" - "@babel/template" "^7.24.0" - "@babel/traverse" "^7.24.1" - "@babel/types" "^7.24.0" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/eslint-parser@^7.12.16": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.24.1.tgz#e27eee93ed1d271637165ef3a86e2b9332395c32" - integrity sha512-d5guuzMlPeDfZIbpQ8+g1NaCNuAGBBGNECh0HVqz1sjOeVLh2CEaifuOysCH18URW6R7pqXINvf5PaR/dC6jLQ== - dependencies: - "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" - eslint-visitor-keys "^2.1.0" - semver "^6.3.1" - -"@babel/generator@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.1.tgz#e67e06f68568a4ebf194d1c6014235344f0476d0" - integrity sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A== - dependencies: - "@babel/types" "^7.24.0" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^2.5.1" - -"@babel/helper-compilation-targets@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" - integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== - dependencies: - "@babel/compat-data" "^7.23.5" - "@babel/helper-validator-option" "^7.23.5" - browserslist "^4.22.2" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-module-imports@^7.22.15": - version "7.24.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128" - integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg== - dependencies: - "@babel/types" "^7.24.0" - -"@babel/helper-module-transforms@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" - integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== - dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-module-imports" "^7.22.15" - "@babel/helper-simple-access" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.20" - -"@babel/helper-simple-access@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" - integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-string-parser@^7.23.4": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" - integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== - -"@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== - -"@babel/helper-validator-option@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" - integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== - -"@babel/helpers@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.1.tgz#183e44714b9eba36c3038e442516587b1e0a1a94" - integrity sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg== - dependencies: - "@babel/template" "^7.24.0" - "@babel/traverse" "^7.24.1" - "@babel/types" "^7.24.0" - -"@babel/highlight@^7.24.2": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" - integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== - dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" +"@babel/code-frame@^7.0.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" + integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== + dependencies: + "@babel/highlight" "^7.14.5" + +"@babel/helper-validator-identifier@^7.14.5": + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" + integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== + +"@babel/highlight@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" + integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.5" + chalk "^2.0.0" js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@babel/parser@^7.24.0", "@babel/parser@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.1.tgz#1e416d3627393fab1cb5b0f2f1796a100ae9133a" - integrity sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg== - -"@babel/template@^7.22.15", "@babel/template@^7.24.0": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" - integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== - dependencies: - "@babel/code-frame" "^7.23.5" - "@babel/parser" "^7.24.0" - "@babel/types" "^7.24.0" - -"@babel/traverse@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.1.tgz#d65c36ac9dd17282175d1e4a3c49d5b7988f530c" - integrity sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ== - dependencies: - "@babel/code-frame" "^7.24.1" - "@babel/generator" "^7.24.1" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.24.1" - "@babel/types" "^7.24.0" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.24.0": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" - integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== - dependencies: - "@babel/helper-string-parser" "^7.23.4" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" "@cosmjs/crypto@^0.25.5": version "0.25.6" @@ -236,76 +60,6 @@ resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.25.6.tgz#934d9a967180baa66163847616a74358732227ca" integrity sha512-ofOYiuxVKNo238vCPPlaDzqPXy2AQ/5/nashBo5rvPZJkxt9LciGfUEQWPCOb1BIJDNx2Dzu0z4XCf/dwzl0Dg== -"@ethereumjs/rlp@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" - integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== - -"@ethereumjs/util@^8.1.0": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-8.1.0.tgz#299df97fb6b034e0577ce9f94c7d9d1004409ed4" - integrity sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA== - dependencies: - "@ethereumjs/rlp" "^4.0.1" - ethereum-cryptography "^2.0.0" - micro-ftch "^0.3.1" - -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" - integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": - version "5.1.1-v1" - resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" - integrity sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg== - dependencies: - eslint-scope "5.1.1" - -"@noble/curves@1.3.0", "@noble/curves@~1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e" - integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA== - dependencies: - "@noble/hashes" "1.3.3" - -"@noble/hashes@1.3.3", "@noble/hashes@~1.3.2": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" - integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== - -"@noble/hashes@^1.2.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" - integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -327,34 +81,22 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@oclif/command@^1", "@oclif/command@^1.8.14", "@oclif/command@^1.8.15": - version "1.8.36" - resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.8.36.tgz#9739b9c268580d064a50887c4597d1b4e86ca8b5" - integrity sha512-/zACSgaYGtAQRzc7HjzrlIs14FuEYAZrMOEwicRoUnZVyRunG4+t5iSEeQu0Xy2bgbCD0U1SP/EdeNZSTXRwjQ== +"@oclif/command@^1", "@oclif/command@^1.5.20", "@oclif/command@^1.6.0", "@oclif/command@^1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.8.0.tgz#c1a499b10d26e9d1a611190a81005589accbb339" + integrity sha512-5vwpq6kbvwkQwKqAoOU3L72GZ3Ta8RRrewKj9OJRolx28KLJJ8Dg9Rf7obRwt5jQA9bkYd8gqzMTrI7H3xLfaw== dependencies: - "@oclif/config" "^1.18.2" - "@oclif/errors" "^1.3.6" - "@oclif/help" "^1.0.1" - "@oclif/parser" "^3.8.17" + "@oclif/config" "^1.15.1" + "@oclif/errors" "^1.3.3" + "@oclif/parser" "^3.8.3" + "@oclif/plugin-help" "^3" debug "^4.1.1" - semver "^7.5.4" - -"@oclif/config@1.18.16": - version "1.18.16" - resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.18.16.tgz#3235d260ab1eb8388ebb6255bca3dd956249d796" - integrity sha512-VskIxVcN22qJzxRUq+raalq6Q3HUde7sokB7/xk5TqRZGEKRVbFeqdQBxDWwQeudiJEgcNiMvIFbMQ43dY37FA== - dependencies: - "@oclif/errors" "^1.3.6" - "@oclif/parser" "^3.8.16" - debug "^4.3.4" - globby "^11.1.0" - is-wsl "^2.1.1" - tslib "^2.6.1" + semver "^7.3.2" -"@oclif/config@1.18.2": - version "1.18.2" - resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.18.2.tgz#5bfe74a9ba6a8ca3dceb314a81bd9ce2e15ebbfe" - integrity sha512-cE3qfHWv8hGRCP31j7fIS7BfCflm/BNZ2HNqHexH+fDrdF2f1D5S8VmXWLC77ffv3oDvWyvE9AZeR0RfmHCCaA== +"@oclif/config@^1", "@oclif/config@^1.15.1", "@oclif/config@^1.17.0": + version "1.17.0" + resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.17.0.tgz#ba8639118633102a7e481760c50054623d09fcab" + integrity sha512-Lmfuf6ubjQ4ifC/9bz1fSCHc6F6E653oyaRXxg+lgT4+bYf9bk+nqrUpAbrXyABkCqgIBiFr3J4zR/kiFdE1PA== dependencies: "@oclif/errors" "^1.3.3" "@oclif/parser" "^3.8.0" @@ -363,28 +105,16 @@ is-wsl "^2.1.1" tslib "^2.0.0" -"@oclif/config@^1", "@oclif/config@^1.18.2": - version "1.18.17" - resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.18.17.tgz#00aa4049da27edca8f06fc106832d9f0f38786a5" - integrity sha512-k77qyeUvjU8qAJ3XK3fr/QVAqsZO8QOBuESnfeM5HHtPNLSyfVcwiMM2zveSW5xRdLSG3MfV8QnLVkuyCL2ENg== - dependencies: - "@oclif/errors" "^1.3.6" - "@oclif/parser" "^3.8.17" - debug "^4.3.4" - globby "^11.1.0" - is-wsl "^2.1.1" - tslib "^2.6.1" - "@oclif/dev-cli@^1": - version "1.26.10" - resolved "https://registry.yarnpkg.com/@oclif/dev-cli/-/dev-cli-1.26.10.tgz#d8df3a79009b68552f5e7f249d1d19ca52278382" - integrity sha512-dJ+II9rVXckzFvG+82PbfphMTnoqiHvsuAAbcHrLdZWPBnFAiDKhNYE0iHnA/knAC4VGXhogsrAJ3ERT5d5r2g== - dependencies: - "@oclif/command" "^1.8.15" - "@oclif/config" "^1.18.2" - "@oclif/errors" "^1.3.5" - "@oclif/plugin-help" "3.2.18" - cli-ux "5.6.7" + version "1.26.0" + resolved "https://registry.yarnpkg.com/@oclif/dev-cli/-/dev-cli-1.26.0.tgz#e3ec294b362c010ffc8948003d3770955c7951fd" + integrity sha512-272udZP+bG4qahoAcpWcMTJKiA+V42kRMqQM7n4tgW35brYb2UP5kK+p08PpF8sgSfRTV8MoJVJG9ax5kY82PA== + dependencies: + "@oclif/command" "^1.8.0" + "@oclif/config" "^1.17.0" + "@oclif/errors" "^1.3.3" + "@oclif/plugin-help" "^3.2.0" + cli-ux "^5.2.1" debug "^4.1.1" find-yarn-workspace-root "^2.0.0" fs-extra "^8.1" @@ -394,7 +124,7 @@ qqjs "^0.3.10" tslib "^2.0.3" -"@oclif/errors@1.3.5": +"@oclif/errors@^1.2.1", "@oclif/errors@^1.2.2", "@oclif/errors@^1.3.3": version "1.3.5" resolved "https://registry.yarnpkg.com/@oclif/errors/-/errors-1.3.5.tgz#a1e9694dbeccab10fe2fe15acb7113991bed636c" integrity sha512-OivucXPH/eLLlOT7FkCMoZXiaVYf8I/w1eTAM1+gKzfhALwWTusxEx7wBmW0uzvkSg/9ovWLycPaBgJbM3LOCQ== @@ -405,119 +135,53 @@ strip-ansi "^6.0.0" wrap-ansi "^7.0.0" -"@oclif/errors@1.3.6", "@oclif/errors@^1.3.3", "@oclif/errors@^1.3.5", "@oclif/errors@^1.3.6": - version "1.3.6" - resolved "https://registry.yarnpkg.com/@oclif/errors/-/errors-1.3.6.tgz#e8fe1fc12346cb77c4f274e26891964f5175f75d" - integrity sha512-fYaU4aDceETd89KXP+3cLyg9EHZsLD3RxF2IU9yxahhBpspWjkWi3Dy3bTgcwZ3V47BgxQaGapzJWDM33XIVDQ== - dependencies: - clean-stack "^3.0.0" - fs-extra "^8.1" - indent-string "^4.0.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -"@oclif/help@^1.0.0", "@oclif/help@^1.0.1": - version "1.0.15" - resolved "https://registry.yarnpkg.com/@oclif/help/-/help-1.0.15.tgz#5e36e576b8132a4906d2662204ad9de7ece87e8f" - integrity sha512-Yt8UHoetk/XqohYX76DfdrUYLsPKMc5pgkzsZVHDyBSkLiGRzujVaGZdjr32ckVZU9q3a47IjhWxhip7Dz5W/g== - dependencies: - "@oclif/config" "1.18.16" - "@oclif/errors" "1.3.6" - chalk "^4.1.2" - indent-string "^4.0.0" - lodash "^4.17.21" - string-width "^4.2.0" - strip-ansi "^6.0.0" - widest-line "^3.1.0" - wrap-ansi "^6.2.0" - "@oclif/linewrap@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@oclif/linewrap/-/linewrap-1.0.0.tgz#aedcb64b479d4db7be24196384897b5000901d91" integrity sha512-Ups2dShK52xXa8w6iBWLgcjPJWjais6KPJQq3gQ/88AY6BXoTX+MIGFPrWQO1KLMiQfoTpcLnUwloN4brrVUHw== -"@oclif/parser@^3.8.0", "@oclif/parser@^3.8.16", "@oclif/parser@^3.8.17": - version "3.8.17" - resolved "https://registry.yarnpkg.com/@oclif/parser/-/parser-3.8.17.tgz#e1ce0f29b22762d752d9da1c7abd57ad81c56188" - integrity sha512-l04iSd0xoh/16TGVpXb81Gg3z7tlQGrEup16BrVLsZBK6SEYpYHRJZnM32BwZrHI97ZSFfuSwVlzoo6HdsaK8A== +"@oclif/parser@^3.8.0", "@oclif/parser@^3.8.3": + version "3.8.5" + resolved "https://registry.yarnpkg.com/@oclif/parser/-/parser-3.8.5.tgz#c5161766a1efca7343e1f25d769efbefe09f639b" + integrity sha512-yojzeEfmSxjjkAvMRj0KzspXlMjCfBzNRPkWw8ZwOSoNWoJn+OCS/m/S+yfV6BvAM4u2lTzX9Y5rCbrFIgkJLg== dependencies: - "@oclif/errors" "^1.3.6" + "@oclif/errors" "^1.2.2" "@oclif/linewrap" "^1.0.0" + chalk "^2.4.2" + tslib "^1.9.3" + +"@oclif/plugin-help@^3", "@oclif/plugin-help@^3.2.0": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@oclif/plugin-help/-/plugin-help-3.2.2.tgz#063ee08cee556573a5198fbdfdaa32796deba0ed" + integrity sha512-SPZ8U8PBYK0n4srFjCLedk0jWU4QlxgEYLCXIBShJgOwPhTTQknkUlsEwaMIevvCU4iCQZhfMX+D8Pz5GZjFgA== + dependencies: + "@oclif/command" "^1.5.20" + "@oclif/config" "^1.15.1" + "@oclif/errors" "^1.2.2" chalk "^4.1.0" - tslib "^2.6.2" - -"@oclif/plugin-help@3.2.18": - version "3.2.18" - resolved "https://registry.yarnpkg.com/@oclif/plugin-help/-/plugin-help-3.2.18.tgz#f2bf6ba86719c174fc0e4c2149f73b46006bfdbd" - integrity sha512-5n5Pkz4L0duknIvFwx2Ko9Xda3miT6RZP8bgaaK3Q/9fzVBrhi4bOM0u05/OThI6V+3NsSdxYS2o1NLcXToWDg== - dependencies: - "@oclif/command" "^1.8.14" - "@oclif/config" "1.18.2" - "@oclif/errors" "1.3.5" - "@oclif/help" "^1.0.0" - chalk "^4.1.2" - indent-string "^4.0.0" - lodash "^4.17.21" - string-width "^4.2.0" - strip-ansi "^6.0.0" - widest-line "^3.1.0" - wrap-ansi "^6.2.0" - -"@oclif/plugin-help@^3": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@oclif/plugin-help/-/plugin-help-3.3.1.tgz#36adb4e0173f741df409bb4b69036d24a53bfb24" - integrity sha512-QuSiseNRJygaqAdABYFWn/H1CwIZCp9zp/PLid6yXvy6VcQV7OenEFF5XuYaCvSARe2Tg9r8Jqls5+fw1A9CbQ== - dependencies: - "@oclif/command" "^1.8.15" - "@oclif/config" "1.18.2" - "@oclif/errors" "1.3.5" - "@oclif/help" "^1.0.1" - chalk "^4.1.2" indent-string "^4.0.0" - lodash "^4.17.21" + lodash.template "^4.4.0" string-width "^4.2.0" strip-ansi "^6.0.0" widest-line "^3.1.0" - wrap-ansi "^6.2.0" + wrap-ansi "^4.0.0" -"@oclif/screen@^1.0.4": +"@oclif/screen@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@oclif/screen/-/screen-1.0.4.tgz#b740f68609dfae8aa71c3a6cab15d816407ba493" integrity sha512-60CHpq+eqnTxLZQ4PGHYNwUX572hgpMHGPtTWMjdTMsAvlm69lZV/4ly6O3sAYkomo4NggGcomrDpBe34rxUqw== -"@scure/base@~1.1.4": - version "1.1.6" - resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.6.tgz#8ce5d304b436e4c84f896e0550c83e4d88cb917d" - integrity sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g== - -"@scure/bip32@1.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.3.tgz#a9624991dc8767087c57999a5d79488f48eae6c8" - integrity sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ== - dependencies: - "@noble/curves" "~1.3.0" - "@noble/hashes" "~1.3.2" - "@scure/base" "~1.1.4" - -"@scure/bip39@1.2.2": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.2.tgz#f3426813f4ced11a47489cbcf7294aa963966527" - integrity sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA== - dependencies: - "@noble/hashes" "~1.3.2" - "@scure/base" "~1.1.4" - "@types/bn.js@^5.1.0": - version "5.1.5" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.5.tgz#2e0dacdcce2c0f16b905d20ff87aedbc6f7b4bf0" - integrity sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A== + version "5.1.0" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.0.tgz#32c5d271503a12653c62cf4d2b45e6eab8cebc68" + integrity sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA== dependencies: "@types/node" "*" "@types/crypto-js@^4.0.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.2.2.tgz#771c4a768d94eb5922cc202a3009558204df0cea" - integrity sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ== + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.0.2.tgz#4524325a175bf819fec6e42560c389ce1fb92c97" + integrity sha512-sCVniU+h3GcGqxOmng11BRvf9TfN9yIs8KKjB8C8d75W69cpTfZG80gau9yTx5SxF3gvHGbJhdESzzvnjtf3Og== "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" @@ -525,51 +189,49 @@ integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== "@types/glob@^7.1.1": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" - integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== + version "7.1.4" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.4.tgz#ea59e21d2ee5c517914cb4bc8e4153b99e566672" + integrity sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA== dependencies: "@types/minimatch" "*" "@types/node" "*" "@types/json-schema@^7.0.3": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + version "7.0.9" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" + integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== "@types/minimatch@*": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" - integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== "@types/node@*": - version "20.11.30" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.30.tgz#9c33467fc23167a347e73834f788f4b9f399d66f" - integrity sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw== - dependencies: - undici-types "~5.26.4" + version "16.4.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.4.11.tgz#245030af802c776c31f00eb0cdde40ee615db462" + integrity sha512-nWSFUbuNiPKJEe1IViuodSI+9cM+vpM8SWF/O6dJK7wmGRNq55U7XavJHrlRrPkSMuUZUFzg1xaZ1B+ZZCrRWw== + +"@types/node@11.11.6": + version "11.11.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a" + integrity sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ== "@types/node@^10": version "10.17.60" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== -"@types/normalize-package-data@^2.4.0": - version "2.4.4" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" - integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== - "@types/pbkdf2@^3.0.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.1.2.tgz#2dc43808e9985a2c69ff02e2d2027bd4fe33e8dc" - integrity sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew== + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.1.0.tgz#039a0e9b67da0cdc4ee5dab865caa6b267bb66b1" + integrity sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ== dependencies: "@types/node" "*" "@types/secp256k1@^4.0.1": - version "4.0.6" - resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.6.tgz#d60ba2349a51c2cbc5e816dcd831a42029d376bf" - integrity sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ== + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.3.tgz#1b8e55d8e00f08ee7220b4d59a6abe89c37a901c" + integrity sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w== dependencies: "@types/node" "*" @@ -636,12 +298,12 @@ ajv@^6.10.2, ajv@^6.9.1: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-escapes@^3.2.0: +ansi-escapes@^3.1.0, ansi-escapes@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== -ansi-escapes@^4.3.0, ansi-escapes@^4.3.2: +ansi-escapes@^4.3.0: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== @@ -649,19 +311,19 @@ ansi-escapes@^4.3.0, ansi-escapes@^4.3.2: type-fest "^0.21.3" ansi-regex@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1" - integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw== + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= ansi-regex@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" - integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" @@ -680,7 +342,7 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0, ansi-styles@^4.2.0: ansicolors@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" - integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg== + integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= arg@^4.1.0: version "4.1.3" @@ -710,9 +372,9 @@ balanced-match@^1.0.0: integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base-x@^3.0.2: - version "3.0.9" - resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" - integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + version "3.0.8" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d" + integrity sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA== dependencies: safe-buffer "^5.0.1" @@ -727,16 +389,19 @@ bech32@^1.1.4: integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== bignumber.js@^9.0.1: - version "9.1.2" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" - integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + version "9.0.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" + integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA== bip39@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.1.0.tgz#c55a418deaf48826a6ceb34ac55b3ee1577e18a3" - integrity sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A== + version "3.0.4" + resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.4.tgz#5b11fed966840b5e1b8539f0f54ab6392969b2a0" + integrity sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw== dependencies: - "@noble/hashes" "^1.2.0" + "@types/node" "11.11.6" + create-hash "^1.1.0" + pbkdf2 "^3.0.9" + randombytes "^2.0.1" bl@^4.0.3: version "4.1.0" @@ -748,24 +413,24 @@ bl@^4.0.3: readable-stream "^3.4.0" blakejs@^1.1.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" - integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== + version "1.1.1" + resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.1.1.tgz#bf313053978b2cd4c444a48795710be05c785702" + integrity sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg== bn.js@4.11.6: version "4.11.6" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" - integrity sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA== + integrity sha1-UzRK2xRhehP26N0s4okF0cC6MhU= -bn.js@^4.11.8, bn.js@^4.11.9: +bn.js@^4.11.1, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" - integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== +bn.js@^5.1.2: + version "5.2.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" + integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== brace-expansion@^1.1.7: version "1.1.11" @@ -775,7 +440,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.2: +braces@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -785,7 +450,7 @@ braces@^3.0.2: brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= browserify-aes@^1.2.0: version "1.2.0" @@ -799,20 +464,10 @@ browserify-aes@^1.2.0: inherits "^2.0.1" safe-buffer "^5.0.1" -browserslist@^4.22.2: - version "4.23.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" - integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== - dependencies: - caniuse-lite "^1.0.30001587" - electron-to-chromium "^1.4.668" - node-releases "^2.0.14" - update-browserslist-db "^1.0.13" - bs58@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" - integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== + integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo= dependencies: base-x "^3.0.2" @@ -833,12 +488,17 @@ buffer-from@^1.0.0: buffer-reverse@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-reverse/-/buffer-reverse-1.0.1.tgz#49283c8efa6f901bc01fa3304d06027971ae2f60" - integrity sha512-M87YIUBsZ6N924W57vDwT/aOu8hw7ZgdByz6ijksLjmHJELBASmYTTlNHRgjE+pTsT9oJXGaDSgqqwfdHotDUg== + integrity sha1-SSg8jvpvkBvAH6MwTQYCeXGuL2A= + +buffer-to-arraybuffer@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz#6064a40fa76eb43c723aba9ef8f6e1216d10511a" + integrity sha1-YGSkD6dutDxyOrqe+PbhIW0QURo= buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== + integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= buffer@^5.5.0: version "5.7.1" @@ -848,30 +508,20 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" -builtin-modules@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" - integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -caniuse-lite@^1.0.30001587: - version "1.0.30001600" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz#93a3ee17a35aa6a9f0c6ef1b2ab49507d1ab9079" - integrity sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ== - cardinal@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" - integrity sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw== + integrity sha1-fMEFXYItISlU0HsIXeolHMe8VQU= dependencies: ansicolors "~0.3.2" redeyed "~2.1.0" -chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -880,7 +530,7 @@ chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.1.0, chalk@^4.1.2: +chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -898,11 +548,6 @@ chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== -ci-info@^3.2.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" - integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== - cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -914,7 +559,7 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: clean-regexp@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7" - integrity sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw== + integrity sha1-jffHquUf02h06PjQW5GAvBGj/tc= dependencies: escape-string-regexp "^1.0.5" @@ -928,26 +573,27 @@ clean-stack@^3.0.0: cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - integrity sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw== + integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= dependencies: restore-cursor "^2.0.0" cli-progress@^3.4.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.12.0.tgz#807ee14b66bcc086258e444ad0f19e7d42577942" - integrity sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A== + version "3.9.0" + resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.9.0.tgz#25db83447deb812e62d05bac1af9aec5387ef3d4" + integrity sha512-g7rLWfhAo/7pF+a/STFH/xPyosaL1zgADhI0OM83hl3c7S43iGvJWEAV2QuDOnQ8i6EMBj/u4+NTd0d5L+4JfA== dependencies: - string-width "^4.2.3" + colors "^1.1.2" + string-width "^4.2.0" -cli-ux@5.6.7: - version "5.6.7" - resolved "https://registry.yarnpkg.com/cli-ux/-/cli-ux-5.6.7.tgz#32ef9e6cb2b457be834280cc799028a11c8235a8" - integrity sha512-dsKAurMNyFDnO6X1TiiRNiVbL90XReLKcvIq4H777NMqXGBxBws23ag8ubCJE97vVZEgWG2eSUhsyLf63Jv8+g== +cli-ux@^5.2.1: + version "5.6.3" + resolved "https://registry.yarnpkg.com/cli-ux/-/cli-ux-5.6.3.tgz#eecdb2e0261171f2b28f2be6b18c490291c3a287" + integrity sha512-/oDU4v8BiDjX2OKcSunGH0iGDiEtj2rZaGyqNuv9IT4CgcSMyVWAMfn0+rEHaOc4n9ka78B0wo1+N1QX89f7mw== dependencies: - "@oclif/command" "^1.8.15" - "@oclif/errors" "^1.3.5" + "@oclif/command" "^1.6.0" + "@oclif/errors" "^1.2.1" "@oclif/linewrap" "^1.0.0" - "@oclif/screen" "^1.0.4" + "@oclif/screen" "^1.0.3" ansi-escapes "^4.3.0" ansi-styles "^4.2.0" cardinal "^2.1.1" @@ -960,7 +606,7 @@ cli-ux@5.6.7: indent-string "^4.0.0" is-wsl "^2.2.0" js-yaml "^3.13.1" - lodash "^4.17.21" + lodash "^4.17.11" natural-orderby "^2.0.1" object-treeify "^1.1.4" password-prompt "^1.1.2" @@ -993,32 +639,27 @@ color-convert@^2.0.1: color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +colors@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -confusing-browser-globals@1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz#30d1e7f3d1b882b25ec4933d1d1adac353d20a59" - integrity sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA== + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= content-type@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" @@ -1054,31 +695,34 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - crypto-js@^3.1.9-1: version "3.3.0" resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.3.0.tgz#846dd1cce2f68aacfa156c8578f926a609b7976b" integrity sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q== -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== +debug@^4.0.1, debug@^4.1.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== dependencies: ms "2.1.2" +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= + dependencies: + mimic-response "^1.0.0" + deep-is@~0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= detect-indent@^6.0.0: version "6.1.0" @@ -1104,15 +748,15 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -electron-to-chromium@^1.4.668: - version "1.4.721" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.721.tgz#a9ee55ba7e54d9ecbcc19825116f3752e7d60ef2" - integrity sha512-k1x2r6foI8iJOp+1qTxbbrrWMsOiHkzGBYwYigaq+apO1FSqtn44KTo3Sy69qt7CRr7149zTcsDvH7MUKsOuIQ== +dom-walk@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" + integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== -elliptic@^6.5.3, elliptic@^6.5.4: - version "6.5.5" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded" - integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw== +elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.3: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== dependencies: bn.js "^4.11.9" brorand "^1.1.0" @@ -1122,6 +766,11 @@ elliptic@^6.5.3, elliptic@^6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +"emoji-regex@>=6.0.0 <=6.1.1": + version "6.1.1" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e" + integrity sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4= + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -1146,11 +795,6 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -escalade@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" - integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== - escape-string-regexp@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -1159,7 +803,7 @@ escape-string-regexp@4.0.0: escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= eslint-ast-utils@^1.0.0: version "1.1.0" @@ -1183,14 +827,14 @@ eslint-config-oclif-typescript@^0.1: eslint-plugin-unicorn "^6.0.1" eslint-config-oclif@^3.1, eslint-config-oclif@^3.1.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/eslint-config-oclif/-/eslint-config-oclif-3.1.2.tgz#d68bdcc3c3a7f548572ccc3582f7f345abef609d" - integrity sha512-66i2mWHb4luJHqJSUO5o9bTYQyH4yuItEikBghUixQB1tFpe/j3mKoRMncxGm2LDGcVIbgK7iLXWO9Ta7buIpg== + version "3.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-oclif/-/eslint-config-oclif-3.1.0.tgz#cbc207ced09e31676dcee2f724fc509cd20eb0bd" + integrity sha512-Tqgy43cNXsSdhTLWW4RuDYGFhV240sC4ISSv/ZiUEg/zFxExSEUpRE6J+AGnkKY9dYwIW4C9b2YSUVv8z/miMA== dependencies: - eslint-config-xo-space "^0.27.0" - eslint-plugin-mocha "^9.0.0" - eslint-plugin-node "^11.1.0" - eslint-plugin-unicorn "^36.0.0" + eslint-config-xo-space "^0.20.0" + eslint-plugin-mocha "^5.2.0" + eslint-plugin-node "^7.0.1" + eslint-plugin-unicorn "^6.0.1" eslint-config-xo-space@^0.20.0: version "0.20.0" @@ -1199,25 +843,11 @@ eslint-config-xo-space@^0.20.0: dependencies: eslint-config-xo "^0.24.0" -eslint-config-xo-space@^0.27.0: - version "0.27.0" - resolved "https://registry.yarnpkg.com/eslint-config-xo-space/-/eslint-config-xo-space-0.27.0.tgz#9663e41d7bedc0f345488377770565aa9b0085e0" - integrity sha512-b8UjW+nQyOkhiANVpIptqlKPyE7XRyQ40uQ1NoBhzVfu95gxfZGrpliq8ZHBpaOF2wCLZaexTSjg7Rvm99vj4A== - dependencies: - eslint-config-xo "^0.35.0" - eslint-config-xo@^0.24.0: version "0.24.2" resolved "https://registry.yarnpkg.com/eslint-config-xo/-/eslint-config-xo-0.24.2.tgz#f61b8ce692e9f9519bdb6edc4ed7ebcd5be48f48" integrity sha512-ivQ7qISScW6gfBp+p31nQntz1rg34UCybd3uvlngcxt5Utsf4PMMi9QoAluLFcPUM5Tvqk4JGraR9qu3msKPKQ== -eslint-config-xo@^0.35.0: - version "0.35.0" - resolved "https://registry.yarnpkg.com/eslint-config-xo/-/eslint-config-xo-0.35.0.tgz#8b5afca244c44129c32386c28602f973ad5cb762" - integrity sha512-+WyZTLWUJlvExFrBU/Ldw8AB/S0d3x+26JQdBWbcqig2ZaWh0zinYcHok+ET4IoPaEcRRf3FE9kjItNVjBwnAg== - dependencies: - confusing-browser-globals "1.0.10" - eslint-plugin-es@^1.3.1: version "1.4.1" resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.4.1.tgz#12acae0f4953e76ba444bfd1b2271081ac620998" @@ -1226,14 +856,6 @@ eslint-plugin-es@^1.3.1: eslint-utils "^1.4.2" regexpp "^2.0.1" -eslint-plugin-es@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893" - integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ== - dependencies: - eslint-utils "^2.0.0" - regexpp "^3.0.0" - eslint-plugin-mocha@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/eslint-plugin-mocha/-/eslint-plugin-mocha-5.3.0.tgz#cf3eb18ae0e44e433aef7159637095a7cb19b15b" @@ -1241,26 +863,6 @@ eslint-plugin-mocha@^5.2.0: dependencies: ramda "^0.26.1" -eslint-plugin-mocha@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-mocha/-/eslint-plugin-mocha-9.0.0.tgz#b4457d066941eecb070dc06ed301c527d9c61b60" - integrity sha512-d7knAcQj1jPCzZf3caeBIn3BnW6ikcvfz0kSqQpwPYcVGLoJV5sz0l0OJB2LR8I7dvTDbqq1oV6ylhSgzA10zg== - dependencies: - eslint-utils "^3.0.0" - ramda "^0.27.1" - -eslint-plugin-node@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" - integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== - dependencies: - eslint-plugin-es "^3.0.0" - eslint-utils "^2.0.0" - ignore "^5.1.1" - minimatch "^3.0.4" - resolve "^1.10.1" - semver "^6.1.0" - eslint-plugin-node@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-7.0.1.tgz#a6e054e50199b2edd85518b89b4e7b323c9f36db" @@ -1273,24 +875,6 @@ eslint-plugin-node@^7.0.1: resolve "^1.8.1" semver "^5.5.0" -eslint-plugin-unicorn@^36.0.0: - version "36.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-36.0.0.tgz#db50e1426839e401d33c5a279f49d4a5bbb640d8" - integrity sha512-xxN2vSctGWnDW6aLElm/LKIwcrmk6mdiEcW55Uv5krcrVcIFSWMmEgc/hwpemYfZacKZ5npFERGNz4aThsp1AA== - dependencies: - "@babel/helper-validator-identifier" "^7.14.9" - ci-info "^3.2.0" - clean-regexp "^1.0.0" - eslint-template-visitor "^2.3.2" - eslint-utils "^3.0.0" - is-builtin-module "^3.1.0" - lodash "^4.17.21" - pluralize "^8.0.0" - read-pkg-up "^7.0.1" - regexp-tree "^0.1.23" - safe-regex "^2.1.1" - semver "^7.3.5" - eslint-plugin-unicorn@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-6.0.1.tgz#4a97f0bc9449e20b82848dad12094ee2ba72347e" @@ -1305,14 +889,6 @@ eslint-plugin-unicorn@^6.0.1: lodash.upperfirst "^4.2.0" safe-regex "^1.1.0" -eslint-scope@5.1.1, eslint-scope@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -1321,16 +897,13 @@ eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-template-visitor@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/eslint-template-visitor/-/eslint-template-visitor-2.3.2.tgz#b52f96ff311e773a345d79053ccc78275bbc463d" - integrity sha512-3ydhqFpuV7x1M9EK52BPNj6V0Kwu0KKkcIAfpUhwHbR8ocRln/oUHgfxQupY8O1h4Qv/POHDumb/BwwNfxbtnA== +eslint-scope@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== dependencies: - "@babel/core" "^7.12.16" - "@babel/eslint-parser" "^7.12.16" - eslint-visitor-keys "^2.0.0" - esquery "^1.3.1" - multimap "^1.1.0" + esrecurse "^4.3.0" + estraverse "^4.1.1" eslint-utils@^1.3.1, eslint-utils@^1.4.2: version "1.4.3" @@ -1346,23 +919,11 @@ eslint-utils@^2.0.0: dependencies: eslint-visitor-keys "^1.1.0" -eslint-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" - integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== - dependencies: - eslint-visitor-keys "^2.0.0" - eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== -eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - eslint@^5.13: version "5.16.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea" @@ -1419,10 +980,10 @@ esprima@^4.0.0, esprima@~4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.0.1, esquery@^1.3.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== +esquery@^1.0.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" @@ -1439,15 +1000,24 @@ estraverse@^4.1.1: integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== estraverse@^5.1.0, estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + version "5.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +eth-lib@0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.2.8.tgz#b194058bef4b220ad12ea497431d6cb6aa0623c8" + integrity sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw== + dependencies: + bn.js "^4.11.6" + elliptic "^6.4.0" + xhr-request-promise "^0.1.2" + ethereum-bloom-filters@^1.0.6: version "1.0.10" resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz#3ca07f4aed698e75bd134584850260246a5fed8a" @@ -1476,35 +1046,34 @@ ethereum-cryptography@^0.1.3: secp256k1 "^4.0.1" setimmediate "^1.0.5" -ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz#1352270ed3b339fe25af5ceeadcf1b9c8e30768a" - integrity sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA== - dependencies: - "@noble/curves" "1.3.0" - "@noble/hashes" "1.3.3" - "@scure/bip32" "1.3.3" - "@scure/bip39" "1.2.2" - ethereumjs-util@^7.1.0: - version "7.1.5" - resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" - integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg== + version "7.1.0" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.0.tgz#e2b43a30bfcdbcb432a4eb42bd5f2393209b3fd5" + integrity sha512-kR+vhu++mUDARrsMMhsjjzPduRVAeundLGXucGRHF3B4oEltOUspfgCVco4kckucj3FMlLaZHUl9n7/kdmr6Tw== dependencies: "@types/bn.js" "^5.1.0" bn.js "^5.1.2" create-hash "^1.1.2" ethereum-cryptography "^0.1.3" + ethjs-util "0.1.6" rlp "^2.2.4" ethjs-unit@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" - integrity sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw== + integrity sha1-xmWSHkduh7ziqdWIpv4EBbLEFpk= dependencies: bn.js "4.11.6" number-to-bn "1.7.0" +ethjs-util@0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" + integrity sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w== + dependencies: + is-hex-prefixed "1.0.0" + strip-hex-prefix "1.0.0" + evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -1545,10 +1114,10 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.0.3, fast-glob@^3.2.9: - version "3.3.2" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" - integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== +fast-glob@^3.0.3, fast-glob@^3.1.1: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -1564,19 +1133,19 @@ fast-json-stable-stringify@^2.0.0: fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= fastq@^1.6.0: - version "1.17.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" - integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + version "1.11.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.1.tgz#5d8175aae17db61947f8b162cfc7f63264d22807" + integrity sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw== dependencies: reusify "^1.0.4" figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - integrity sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA== + integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= dependencies: escape-string-regexp "^1.0.5" @@ -1594,7 +1163,7 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -find-up@^4.0.0, find-up@^4.1.0: +find-up@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -1649,27 +1218,22 @@ fs-extra@^8.1: fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ== + integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= get-stream@^5.1.0: version "5.2.0" @@ -1679,9 +1243,11 @@ get-stream@^5.1.0: pump "^3.0.0" github-slugger@^1.2.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.5.0.tgz#17891bbc73232051474d68bd867a34625c955f7d" - integrity sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw== + version "1.3.0" + resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.3.0.tgz#9bd0a95c5efdfc46005e82a906ef8e2a059124c9" + integrity sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q== + dependencies: + emoji-regex ">=6.0.0 <=6.1.1" glob-parent@^5.1.2: version "5.1.2" @@ -1691,18 +1257,26 @@ glob-parent@^5.1.2: is-glob "^4.0.1" glob@^7.1.2, glob@^7.1.3, glob@^7.1.6: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.1.1" + minimatch "^3.0.4" once "^1.3.0" path-is-absolute "^1.0.0" -globals@^11.1.0, globals@^11.7.0: +global@~4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" + integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== + dependencies: + min-document "^2.19.0" + process "^0.11.10" + +globals@^11.7.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== @@ -1721,33 +1295,40 @@ globby@^10, globby@^10.0.1: merge2 "^1.2.3" slash "^3.0.0" -globby@^11.0.1, globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== +globby@^11.0.1: + version "11.0.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" slash "^3.0.0" graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + hash-base@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" @@ -1765,31 +1346,19 @@ hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: inherits "^2.0.3" minimalistic-assert "^1.0.1" -hasown@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= dependencies: hash.js "^1.0.3" minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - hosted-git-info@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" - integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== + version "4.0.2" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961" + integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg== dependencies: lru-cache "^6.0.0" @@ -1827,10 +1396,10 @@ ignore@^4.0.2, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.1.1, ignore@^5.2.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" - integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== +ignore@^5.1.1, ignore@^5.1.4: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== import-fresh@^3.0.0: version "3.3.0" @@ -1843,12 +1412,12 @@ import-fresh@^3.0.0: import-modules@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/import-modules/-/import-modules-1.1.0.tgz#748db79c5cc42bb9701efab424f894e72600e9dc" - integrity sha512-szMf9iPglnnDYueTxUzJWM+dXlwgfOIIONjVj36ZX8YHG9vBGCHPCpawKr+uIXD0Znm7QQlznIQtVjxfwJkq4g== + integrity sha1-dI23nFzEK7lwHvq0JPiU5yYA6dw= imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= indent-string@^4.0.0: version "4.0.0" @@ -1858,7 +1427,7 @@ indent-string@^4.0.0: inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= dependencies: once "^1.3.0" wrappy "1" @@ -1890,21 +1459,14 @@ inquirer@^6.2.2: is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-builtin-module@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" - integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== - dependencies: - builtin-modules "^3.3.0" - -is-core-module@^2.13.0, is-core-module@^2.5.0: - version "2.13.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" - integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== +is-core-module@^2.2.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" + integrity sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg== dependencies: - hasown "^2.0.0" + has "^1.0.3" is-docker@^2.0.0: version "2.2.1" @@ -1914,29 +1476,34 @@ is-docker@^2.0.0: is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-function@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08" + integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ== + is-glob@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== dependencies: is-extglob "^2.1.1" is-hex-prefixed@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" - integrity sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA== + integrity sha1-fY035q135dEnFIkTxXPggtd39VQ= is-number@^7.0.0: version "7.0.0" @@ -1956,7 +1523,7 @@ is-retry-allowed@^1.1.0: is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= is-stream@^2.0.0: version "2.0.1" @@ -1966,7 +1533,7 @@ is-stream@^2.0.0: is-typedarray@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= is-wsl@^2.1.1, is-wsl@^2.2.0: version "2.2.0" @@ -1978,7 +1545,7 @@ is-wsl@^2.1.1, is-wsl@^2.2.0: isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= js-sha3@^0.8.0: version "0.8.0" @@ -1998,11 +1565,6 @@ js-yaml@^3.13.0, js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -2021,53 +1583,47 @@ json-schema-traverse@^0.4.1: json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= optionalDependencies: graceful-fs "^4.1.6" keccak@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.4.tgz#edc09b89e633c0549da444432ecf062ffadee86d" - integrity sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q== + version "3.0.1" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.1.tgz#ae30a0e94dbe43414f741375cff6d64c8bea0bff" + integrity sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA== dependencies: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" - readable-stream "^3.6.0" levn@^0.3.0, levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= dependencies: prelude-ls "~1.1.2" type-check "~0.3.2" libsodium-wrappers@^0.7.6: - version "0.7.13" - resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.13.tgz#83299e06ee1466057ba0e64e532777d2929b90d3" - integrity sha512-kasvDsEi/r1fMzKouIDv7B8I6vNmknXwGiYodErGuESoFTohGSKZplFtVxZqHaoQ217AynyIFgnOVRitpHs0Qw== + version "0.7.9" + resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.9.tgz#4ffc2b69b8f7c7c7c5594a93a4803f80f6d0f346" + integrity sha512-9HaAeBGk1nKTRFRHkt7nzxqCvnkWTjn1pdjKgcUnZxj0FyOP4CnhgFhMdrFfgNsukijBGyBLpP2m2uKT1vuWhQ== dependencies: - libsodium "^0.7.13" + libsodium "^0.7.0" -libsodium@^0.7.13: - version "0.7.13" - resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.13.tgz#230712ec0b7447c57b39489c48a4af01985fb393" - integrity sha512-mK8ju0fnrKXXfleL53vtp9xiPq5hKM0zbDQtcxQIsSmxNgSxqCj6R7Hl9PkrNe2j29T4yoDaF7DJLK9/i5iWUw== +libsodium@^0.7.0: + version "0.7.9" + resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.9.tgz#4bb7bcbf662ddd920d8795c227ae25bbbfa3821b" + integrity sha512-gfeADtR4D/CM0oRUviKBViMGXZDgnFdMKMzHsvBdqLBHd9ySi6EtYnmuhHVDDYgYpAO8eU8hEY+F8vIUAPh08A== lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= load-json-file@^6.2.0: version "6.2.0" @@ -2086,48 +1642,61 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash._reinterpolate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= + lodash.camelcase@^4.1.1: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= lodash.kebabcase@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" - integrity sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g== + integrity sha1-hImxyw0p/4gZXM7KRI/21swpXDY= lodash.snakecase@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" - integrity sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw== + integrity sha1-OdcUo1NXFHg3rv1ktdy7Fr7Nj40= + +lodash.template@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" + integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.templatesettings "^4.0.0" + +lodash.templatesettings@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" + integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== + dependencies: + lodash._reinterpolate "^3.0.0" lodash.upperfirst@^4.2.0: version "4.3.1" resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" - integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg== + integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984= lodash.zip@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.zip/-/lodash.zip-4.2.0.tgz#ec6662e4896408ed4ab6c542a3990b72cc080020" - integrity sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg== + integrity sha1-7GZi5IlkCO1KtsVCo5kLcswIACA= -lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21: +lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -2156,15 +1725,15 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" -merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1: +merge2@^1.2.3, merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== merkletreejs@^0.2.23: - version "0.2.32" - resolved "https://registry.yarnpkg.com/merkletreejs/-/merkletreejs-0.2.32.tgz#cf1c0760e2904e4a1cc269108d6009459fd06223" - integrity sha512-TostQBiwYRIwSE5++jGmacu3ODcKAgqb0Y/pnIohXS7sWxh1gCkSptbmF1a43faehRDpcHf7J/kv0Ml2D/zblQ== + version "0.2.24" + resolved "https://registry.yarnpkg.com/merkletreejs/-/merkletreejs-0.2.24.tgz#6dc52b3e0946846c25816216f1b60094a18a5e7a" + integrity sha512-JUv2zSFuTpMj9uxqNXAOAQz6LKXL/AUalyuDzvqyf0fV09VeU7WjNDMDD+wbdtrA1mNEbV5w1XDWXMud8aNYTg== dependencies: bignumber.js "^9.0.1" buffer-reverse "^1.0.1" @@ -2172,24 +1741,31 @@ merkletreejs@^0.2.23: treeify "^1.1.0" web3-utils "^1.3.4" -micro-ftch@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/micro-ftch/-/micro-ftch-0.3.1.tgz#6cb83388de4c1f279a034fb0cf96dfc050853c5f" - integrity sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg== - micromatch@^4.0.2, micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== dependencies: - braces "^3.0.2" - picomatch "^2.3.1" + braces "^3.0.1" + picomatch "^2.2.3" mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +min-document@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" + integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU= + dependencies: + dom-walk "^0.1.0" + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -2198,19 +1774,19 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@^3.0.4, minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" -minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== mkdirp-classic@^0.5.2: version "0.5.3" @@ -2218,31 +1794,26 @@ mkdirp-classic@^0.5.2: integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== mkdirp@^0.5.1: - version "0.5.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" - integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: - minimist "^1.2.6" + minimist "^1.2.5" ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -multimap@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/multimap/-/multimap-1.1.0.tgz#5263febc085a1791c33b59bb3afc6a76a2a10ca8" - integrity sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw== - mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" - integrity sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ== + integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= natural-orderby@^2.0.1: version "2.0.3" @@ -2260,50 +1831,40 @@ node-addon-api@^2.0.0: integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== node-gyp-build@^4.2.0: - version "4.8.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.0.tgz#3fee9c1731df4581a3f9ead74664369ff00d26dd" - integrity sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og== - -node-releases@^2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" - integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== - -normalize-package-data@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" + version "4.2.3" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739" + integrity sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg== normalize-package-data@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" - integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== + version "3.0.2" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.2.tgz#cae5c410ae2434f9a6c1baa65d5bc3b9366c8699" + integrity sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg== dependencies: hosted-git-info "^4.0.1" - is-core-module "^2.5.0" + resolve "^1.20.0" semver "^7.3.4" validate-npm-package-license "^3.0.1" npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= dependencies: path-key "^2.0.0" number-to-bn@1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" - integrity sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig== + integrity sha1-uzYjWS9+X54AMLGXe9QaDFP+HqA= dependencies: bn.js "4.11.6" strip-hex-prefix "1.0.0" +object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + object-treeify@^1.1.4: version "1.1.33" resolved "https://registry.yarnpkg.com/object-treeify/-/object-treeify-1.1.33.tgz#f06fece986830a3cba78ddd32d4c11d1f76cdf40" @@ -2312,14 +1873,14 @@ object-treeify@^1.1.4: once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= dependencies: wrappy "1" onetime@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ== + integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= dependencies: mimic-fn "^1.0.0" @@ -2338,12 +1899,12 @@ optionator@^0.8.2: os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= p-limit@^2.2.0: version "2.3.0" @@ -2371,10 +1932,15 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-headers@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.3.tgz#5e8e7512383d140ba02f0c7aa9f49b4399c92515" + integrity sha512-QhhZ+DCCit2Coi2vmAKbq5RGTRcQUOE2+REgv8vdyu7MnYx2eZztegqtTx99TZ86GTIwqiy3+4nQTWZ2tgmdCA== + parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= dependencies: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" @@ -2390,12 +1956,12 @@ parse-json@^5.0.0: lines-and-columns "^1.1.6" password-prompt@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/password-prompt/-/password-prompt-1.1.3.tgz#05e539f4e7ca4d6c865d479313f10eb9db63ee5f" - integrity sha512-HkrjG2aJlvF0t2BMH0e2LB/EHf3Lcq3fNMzy4GYHcQblAvOl+QQji1Lx7WRBMqpVK8p+KR7bCg7oqAMXtdgqyw== + version "1.1.2" + resolved "https://registry.yarnpkg.com/password-prompt/-/password-prompt-1.1.2.tgz#85b2f93896c5bd9e9f2d6ff0627fa5af3dc00923" + integrity sha512-bpuBhROdrhuN3E7G/koAju0WjVw9/uQOG5Co5mokNj0MiOSBVZS1JTwM4zl55hu0WFmIEFvO9cU9sJQiBIYeIA== dependencies: - ansi-escapes "^4.3.2" - cross-spawn "^7.0.3" + ansi-escapes "^3.1.0" + cross-spawn "^6.0.5" path-exists@^4.0.0: version "4.0.0" @@ -2405,24 +1971,19 @@ path-exists@^4.0.0: path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== + integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -path-parse@^1.0.7: +path-parse@^1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== @@ -2432,7 +1993,7 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pbkdf2@^3.0.17: +pbkdf2@^3.0.17, pbkdf2@^3.0.9: version "3.1.2" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== @@ -2443,15 +2004,10 @@ pbkdf2@^3.0.17: safe-buffer "^5.0.1" sha.js "^2.4.8" -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@^2.2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== pkg-dir@^4.2.0: version "4.2.0" @@ -2460,15 +2016,15 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -pluralize@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" - integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== - prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= progress@^2.0.0: version "2.0.3" @@ -2484,9 +2040,9 @@ pump@^3.0.0: once "^1.3.1" punycode@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== qqjs@^0.3.10: version "0.3.11" @@ -2507,6 +2063,15 @@ qqjs@^0.3.10: tmp "^0.1.0" write-json-file "^4.1.1" +query-string@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" + integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== + dependencies: + decode-uri-component "^0.2.0" + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -2517,41 +2082,17 @@ ramda@^0.26.1: resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ== -ramda@^0.27.1: - version "0.27.2" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.2.tgz#84463226f7f36dc33592f6f4ed6374c48306c3f1" - integrity sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA== - -randombytes@^2.1.0: +randombytes@^2.0.1, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: safe-buffer "^5.1.0" -read-pkg-up@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" - integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== - dependencies: - find-up "^4.1.0" - read-pkg "^5.2.0" - type-fest "^0.8.1" - -read-pkg@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" - integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== - dependencies: - "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" - readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" @@ -2565,15 +2106,10 @@ readonly-date@^1.0.0: redeyed@~2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-2.1.1.tgz#8984b5815d99cb220469c99eeeffe38913e6cc0b" - integrity sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ== + integrity sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs= dependencies: esprima "~4.0.0" -regexp-tree@^0.1.23, regexp-tree@~0.1.1: - version "0.1.27" - resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" - integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== - regexpp@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" @@ -2589,19 +2125,18 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve@^1.10.0, resolve@^1.10.1, resolve@^1.8.1: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== +resolve@^1.20.0, resolve@^1.8.1: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" + is-core-module "^2.2.0" + path-parse "^1.0.6" restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - integrity sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q== + integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= dependencies: onetime "^2.0.0" signal-exit "^3.0.2" @@ -2639,11 +2174,11 @@ ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.2: inherits "^2.0.1" rlp@^2.2.4: - version "2.2.7" - resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf" - integrity sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ== + version "2.2.6" + resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.6.tgz#c80ba6266ac7a483ef1e69e8e2f056656de2fb2c" + integrity sha512-HAfAmL6SDYNWPUOJNrM500x4Thn4PZsEy5pijPh40U9WfNk0z15hUYzO9xVIMAdIHdFtD8CBDHd75Td1g36Mjg== dependencies: - bn.js "^5.2.0" + bn.js "^4.11.1" run-async@^2.2.0: version "2.4.1" @@ -2672,17 +2207,10 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg== + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= dependencies: ret "~0.1.10" -safe-regex@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-2.1.1.tgz#f7128f00d056e2fe5c11e81a1324dd974aadced2" - integrity sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A== - dependencies: - regexp-tree "~0.1.1" - "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -2694,35 +2222,35 @@ scrypt-js@^3.0.0: integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== secp256k1@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303" - integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA== + version "4.0.2" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.2.tgz#15dd57d0f0b9fdb54ac1fa1694f40e5e9a54f4a1" + integrity sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg== dependencies: - elliptic "^6.5.4" + elliptic "^6.5.2" node-addon-api "^2.0.0" node-gyp-build "^4.2.0" -"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.5.1: - version "5.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== +semver@^5.5.0, semver@^5.5.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^6.0.0, semver@^6.1.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +semver@^6.0.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.5.4: - version "7.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" - integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== +semver@^7.3.2, semver@^7.3.4: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== dependencies: lru-cache "^6.0.0" setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8: version "2.4.11" @@ -2735,31 +2263,33 @@ sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8: shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= dependencies: shebang-regex "^1.0.0" -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^2.7.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.1.tgz#0e22e91d4575d87620620bc91308d57a77f44b5d" + integrity sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw== + dependencies: + decompress-response "^3.3.0" + once "^1.3.1" + simple-concat "^1.0.0" slash@^3.0.0: version "3.0.0" @@ -2783,9 +2313,9 @@ sort-keys@^4.0.0: is-plain-obj "^2.0.0" source-map-support@^0.5.17: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -2796,17 +2326,17 @@ source-map@^0.6.0: integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== spdx-correct@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" - integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== dependencies: spdx-expression-parse "^3.0.0" spdx-license-ids "^3.0.0" spdx-exceptions@^2.1.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz#5d607d27fc806f66d7b64a766650fa890f04ed66" - integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== spdx-expression-parse@^3.0.0: version "3.0.1" @@ -2817,16 +2347,21 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.17" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz#887da8aa73218e51a1d917502d79863161a93f9c" - integrity sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg== + version "3.0.9" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz#8a595135def9592bda69709474f1cbeea7c2467f" + integrity sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ== sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= -string-width@^2.1.0: +string-width@^2.1.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -2843,14 +2378,14 @@ string-width@^3.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" + integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== dependencies: emoji-regex "^8.0.0" is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" + strip-ansi "^6.0.0" string_decoder@^1.1.1: version "1.3.0" @@ -2862,7 +2397,7 @@ string_decoder@^1.1.1: strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow== + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= dependencies: ansi-regex "^3.0.0" @@ -2873,12 +2408,12 @@ strip-ansi@^5.1.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== dependencies: - ansi-regex "^5.0.1" + ansi-regex "^5.0.0" strip-bom@^4.0.0: version "4.0.0" @@ -2888,19 +2423,19 @@ strip-bom@^4.0.0: strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= strip-hex-prefix@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" - integrity sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A== + integrity sha1-DF8VX+8RUTczd96du1iNoFUA428= dependencies: is-hex-prefixed "1.0.0" strip-json-comments@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= supports-color@^5.3.0: version "5.5.0" @@ -2924,18 +2459,13 @@ supports-color@^8.1.0: has-flag "^4.0.0" supports-hyperlinks@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" - integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== + version "2.2.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" + integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== dependencies: has-flag "^4.0.0" supports-color "^7.0.0" -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - table@^5.2.3: version "5.4.6" resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" @@ -2970,12 +2500,17 @@ tar-stream@^2.1.4: text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +timed-out@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= tmp@^0.0.33: version "0.0.33" @@ -2991,11 +2526,6 @@ tmp@^0.1.0: dependencies: rimraf "^2.6.3" -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -3019,15 +2549,15 @@ ts-node@^8: source-map-support "^0.5.17" yn "3.1.1" -tslib@^1, tslib@^1.8.1, tslib@^1.9.0: +tslib@^1, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.3, tslib@^2.6.1, tslib@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== +tslib@^2.0.0, tslib@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" + integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== tsutils@^3.17.1: version "3.21.0" @@ -3039,14 +2569,14 @@ tsutils@^3.17.1: tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= dependencies: safe-buffer "^5.0.1" type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= dependencies: prelude-ls "~1.1.2" @@ -3060,11 +2590,6 @@ type-fest@^0.6.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -3077,24 +2602,11 @@ typescript@^3.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -update-browserslist-db@^1.0.13: - version "1.0.13" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" - integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -3102,6 +2614,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +url-set-query@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/url-set-query/-/url-set-query-1.0.0.tgz#016e8cfd7c20ee05cafe7795e892bd0702faa339" + integrity sha1-AW6M/Xwg7gXK/neV6JK9BwL6ozk= + utf8@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" @@ -3110,7 +2627,7 @@ utf8@3.0.0: util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= validate-npm-package-license@^3.0.1: version "3.0.4" @@ -3121,14 +2638,13 @@ validate-npm-package-license@^3.0.1: spdx-expression-parse "^3.0.0" web3-utils@^1.3.4: - version "1.10.4" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.4.tgz#0daee7d6841641655d8b3726baf33b08eda1cbec" - integrity sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A== + version "1.5.0" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.5.0.tgz#48c8ba0d95694e73b9a6d473d955880cd4758e4a" + integrity sha512-hNyw7Oxi6TM3ivXmv4hK5Cvyi9ML3UoKtcCYvLF9woPWh5v2dwCCVO1U3Iq5HHK7Dqq28t1d4CxWHqUfOfAkgg== dependencies: - "@ethereumjs/util" "^8.1.0" - bn.js "^5.2.1" + bn.js "^4.11.9" + eth-lib "0.2.8" ethereum-bloom-filters "^1.0.6" - ethereum-cryptography "^2.1.2" ethjs-unit "0.1.6" number-to-bn "1.7.0" randombytes "^2.1.0" @@ -3141,13 +2657,6 @@ which@^1.2.9: dependencies: isexe "^2.0.0" -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - widest-line@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" @@ -3156,18 +2665,18 @@ widest-line@^3.1.0: string-width "^4.0.0" word-wrap@~1.2.3: - version "1.2.5" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" - integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== +wrap-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-4.0.0.tgz#b3570d7c70156159a2d42be5cc942e957f7b1131" + integrity sha512-uMTsj9rDb0/7kk1PbcbCcwvHUxp60fGDB/NNXpVa0Q+ic/e7y5+BwTxKfQ33VYgDppSwi/FBzpetYzo8s6tfbg== dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" + ansi-styles "^3.2.0" + string-width "^2.1.1" + strip-ansi "^4.0.0" wrap-ansi@^7.0.0: version "7.0.0" @@ -3181,7 +2690,7 @@ wrap-ansi@^7.0.0: wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= write-file-atomic@^3.0.0: version "3.0.3" @@ -3212,10 +2721,40 @@ write@1.0.3: dependencies: mkdirp "^0.5.1" -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +xhr-request-promise@^0.1.2: + version "0.1.3" + resolved "https://registry.yarnpkg.com/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz#2d5f4b16d8c6c893be97f1a62b0ed4cf3ca5f96c" + integrity sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg== + dependencies: + xhr-request "^1.1.0" + +xhr-request@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/xhr-request/-/xhr-request-1.1.0.tgz#f4a7c1868b9f198723444d82dcae317643f2e2ed" + integrity sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA== + dependencies: + buffer-to-arraybuffer "^0.0.5" + object-assign "^4.1.1" + query-string "^5.0.1" + simple-get "^2.7.0" + timed-out "^4.0.1" + url-set-query "^1.0.0" + xhr "^2.0.4" + +xhr@^2.0.4: + version "2.6.0" + resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.6.0.tgz#b69d4395e792b4173d6b7df077f0fc5e4e2b249d" + integrity sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA== + dependencies: + global "~4.4.0" + is-function "^1.0.1" + parse-headers "^2.0.0" + xtend "^4.0.0" + +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== yallist@^4.0.0: version "4.0.0" From 216b2f02f7c9e23fb37733a6c65dc7cd3fad0de1 Mon Sep 17 00:00:00 2001 From: Hard-Nett Date: Fri, 5 Apr 2024 21:13:46 +0000 Subject: [PATCH 17/23] update scripts, add schema --- schema/airdrop/execute_answer.json | 267 +++++++++++++++++ schema/airdrop/execute_msg.json | 427 ++++++++++++++++++++++++++++ schema/airdrop/instantiate_msg.json | 153 ++++++++++ schema/airdrop/query_answer.json | 282 ++++++++++++++++++ schema/airdrop/query_msg.json | 234 +++++++++++++++ tools/headstash/account.js | 55 ++-- tools/headstash/main.js | 4 +- 7 files changed, 1402 insertions(+), 20 deletions(-) create mode 100644 schema/airdrop/execute_answer.json create mode 100644 schema/airdrop/execute_msg.json create mode 100644 schema/airdrop/instantiate_msg.json create mode 100644 schema/airdrop/query_answer.json create mode 100644 schema/airdrop/query_msg.json diff --git a/schema/airdrop/execute_answer.json b/schema/airdrop/execute_answer.json new file mode 100644 index 0000000..6ac9be7 --- /dev/null +++ b/schema/airdrop/execute_answer.json @@ -0,0 +1,267 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteAnswer", + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "add_task" + ], + "properties": { + "add_task": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "complete_task" + ], + "properties": { + "complete_task": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "account" + ], + "properties": { + "account": { + "type": "object", + "required": [ + "addresses", + "claimed", + "eth_pubkey", + "eth_sig", + "finished_tasks", + "status", + "total" + ], + "properties": { + "addresses": { + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + } + }, + "claimed": { + "$ref": "#/definitions/Uint128" + }, + "eth_pubkey": { + "type": "string" + }, + "eth_sig": { + "type": "string" + }, + "finished_tasks": { + "type": "array", + "items": { + "$ref": "#/definitions/RequiredTask" + } + }, + "status": { + "$ref": "#/definitions/ResponseStatus" + }, + "total": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "disable_permit_key" + ], + "properties": { + "disable_permit_key": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "set_viewing_key" + ], + "properties": { + "set_viewing_key": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claim" + ], + "properties": { + "claim": { + "type": "object", + "required": [ + "addresses", + "claimed", + "eth_pubkey", + "eth_sig", + "finished_tasks", + "status", + "total" + ], + "properties": { + "addresses": { + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + } + }, + "claimed": { + "$ref": "#/definitions/Uint128" + }, + "eth_pubkey": { + "type": "string" + }, + "eth_sig": { + "type": "string" + }, + "finished_tasks": { + "type": "array", + "items": { + "$ref": "#/definitions/RequiredTask" + } + }, + "status": { + "$ref": "#/definitions/ResponseStatus" + }, + "total": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claim_decay" + ], + "properties": { + "claim_decay": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/ResponseStatus" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "RequiredTask": { + "type": "object", + "required": [ + "address", + "percent" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "percent": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "ResponseStatus": { + "type": "string", + "enum": [ + "success", + "failure" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use secret_cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schema/airdrop/execute_msg.json b/schema/airdrop/execute_msg.json new file mode 100644 index 0000000..afa47c8 --- /dev/null +++ b/schema/airdrop/execute_msg.json @@ -0,0 +1,427 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "admin": { + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "decay_start": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "dump_address": { + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "end_date": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "padding": { + "type": [ + "string", + "null" + ] + }, + "query_rounding": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "start_date": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "add_tasks" + ], + "properties": { + "add_tasks": { + "type": "object", + "required": [ + "tasks" + ], + "properties": { + "padding": { + "type": [ + "string", + "null" + ] + }, + "tasks": { + "type": "array", + "items": { + "$ref": "#/definitions/RequiredTask" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "complete_task" + ], + "properties": { + "complete_task": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "padding": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "account" + ], + "properties": { + "account": { + "type": "object", + "required": [ + "addresses", + "eth_pubkey", + "eth_sig", + "partial_tree" + ], + "properties": { + "addresses": { + "type": "array", + "items": { + "$ref": "#/definitions/Permit_for_FillerMsg" + } + }, + "eth_pubkey": { + "type": "string" + }, + "eth_sig": { + "type": "string" + }, + "padding": { + "type": [ + "string", + "null" + ] + }, + "partial_tree": { + "type": "array", + "items": { + "$ref": "#/definitions/Binary" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "disable_permit_key" + ], + "properties": { + "disable_permit_key": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + }, + "padding": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "set_viewing_key" + ], + "properties": { + "set_viewing_key": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + }, + "padding": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claim" + ], + "properties": { + "claim": { + "type": "object", + "properties": { + "padding": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claim_decay" + ], + "properties": { + "claim_decay": { + "type": "object", + "properties": { + "padding": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "EmptyMsg": { + "type": "object", + "additionalProperties": false + }, + "FillerMsg": { + "type": "object", + "required": [ + "coins", + "contract", + "execute_msg", + "sender" + ], + "properties": { + "coins": { + "type": "array", + "items": { + "type": "string" + } + }, + "contract": { + "type": "string" + }, + "execute_msg": { + "$ref": "#/definitions/EmptyMsg" + }, + "sender": { + "type": "string" + } + }, + "additionalProperties": false + }, + "PermitSignature": { + "type": "object", + "required": [ + "pub_key", + "signature" + ], + "properties": { + "pub_key": { + "$ref": "#/definitions/PubKey" + }, + "signature": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + }, + "Permit_for_FillerMsg": { + "description": "Where the information will be stored", + "type": "object", + "required": [ + "params", + "signature" + ], + "properties": { + "account_number": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "chain_id": { + "type": [ + "string", + "null" + ] + }, + "memo": { + "type": [ + "string", + "null" + ] + }, + "params": { + "$ref": "#/definitions/FillerMsg" + }, + "sequence": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "signature": { + "$ref": "#/definitions/PermitSignature" + } + }, + "additionalProperties": false + }, + "PubKey": { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "description": "ignored, but must be \"tendermint/PubKeySecp256k1\" otherwise the verification will fail", + "type": "string" + }, + "value": { + "description": "Secp256k1 PubKey", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + }, + "RequiredTask": { + "type": "object", + "required": [ + "address", + "percent" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "percent": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use secret_cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schema/airdrop/instantiate_msg.json b/schema/airdrop/instantiate_msg.json new file mode 100644 index 0000000..a215a04 --- /dev/null +++ b/schema/airdrop/instantiate_msg.json @@ -0,0 +1,153 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "airdrop_amount", + "airdrop_token", + "claim_msg_plaintext", + "default_claim", + "max_amount", + "merkle_root", + "query_rounding", + "task_claim", + "total_accounts" + ], + "properties": { + "admin": { + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "airdrop_2": { + "anyOf": [ + { + "$ref": "#/definitions/Contract" + }, + { + "type": "null" + } + ] + }, + "airdrop_amount": { + "$ref": "#/definitions/Uint128" + }, + "airdrop_token": { + "$ref": "#/definitions/Contract" + }, + "claim_msg_plaintext": { + "description": "{wallet}", + "type": "string" + }, + "decay_start": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "default_claim": { + "$ref": "#/definitions/Uint128" + }, + "dump_address": { + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "end_date": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "max_amount": { + "$ref": "#/definitions/Uint128" + }, + "merkle_root": { + "$ref": "#/definitions/Binary" + }, + "query_rounding": { + "$ref": "#/definitions/Uint128" + }, + "start_date": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "task_claim": { + "type": "array", + "items": { + "$ref": "#/definitions/RequiredTask" + } + }, + "total_accounts": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Contract": { + "description": "In the process of being deprecated for [cosmwasm_std::ContractInfo] so use that instead when possible.", + "type": "object", + "required": [ + "address", + "code_hash" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "code_hash": { + "type": "string" + } + }, + "additionalProperties": false + }, + "RequiredTask": { + "type": "object", + "required": [ + "address", + "percent" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "percent": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use secret_cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schema/airdrop/query_answer.json b/schema/airdrop/query_answer.json new file mode 100644 index 0000000..9124227 --- /dev/null +++ b/schema/airdrop/query_answer.json @@ -0,0 +1,282 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryAnswer", + "oneOf": [ + { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "$ref": "#/definitions/Config" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "dates" + ], + "properties": { + "dates": { + "type": "object", + "required": [ + "start" + ], + "properties": { + "decay_factor": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "decay_start": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "end": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "start": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "total_claimed" + ], + "properties": { + "total_claimed": { + "type": "object", + "required": [ + "claimed" + ], + "properties": { + "claimed": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "account" + ], + "properties": { + "account": { + "type": "object", + "required": [ + "addresses", + "claimed", + "finished_tasks", + "total", + "unclaimed" + ], + "properties": { + "addresses": { + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + } + }, + "claimed": { + "$ref": "#/definitions/Uint128" + }, + "finished_tasks": { + "type": "array", + "items": { + "$ref": "#/definitions/RequiredTask" + } + }, + "total": { + "$ref": "#/definitions/Uint128" + }, + "unclaimed": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Config": { + "type": "object", + "required": [ + "admin", + "airdrop_amount", + "airdrop_snip20", + "claim_msg_plaintext", + "contract", + "max_amount", + "merkle_root", + "query_rounding", + "start_date", + "task_claim", + "total_accounts" + ], + "properties": { + "admin": { + "$ref": "#/definitions/Addr" + }, + "airdrop_amount": { + "$ref": "#/definitions/Uint128" + }, + "airdrop_snip20": { + "$ref": "#/definitions/Contract" + }, + "airdrop_snip20_optional": { + "anyOf": [ + { + "$ref": "#/definitions/Contract" + }, + { + "type": "null" + } + ] + }, + "claim_msg_plaintext": { + "type": "string" + }, + "contract": { + "$ref": "#/definitions/Addr" + }, + "decay_start": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "dump_address": { + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "end_date": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "max_amount": { + "$ref": "#/definitions/Uint128" + }, + "merkle_root": { + "$ref": "#/definitions/Binary" + }, + "query_rounding": { + "$ref": "#/definitions/Uint128" + }, + "start_date": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "task_claim": { + "type": "array", + "items": { + "$ref": "#/definitions/RequiredTask" + } + }, + "total_accounts": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Contract": { + "description": "In the process of being deprecated for [cosmwasm_std::ContractInfo] so use that instead when possible.", + "type": "object", + "required": [ + "address", + "code_hash" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "code_hash": { + "type": "string" + } + }, + "additionalProperties": false + }, + "RequiredTask": { + "type": "object", + "required": [ + "address", + "percent" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "percent": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use secret_cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schema/airdrop/query_msg.json b/schema/airdrop/query_msg.json new file mode 100644 index 0000000..897cd12 --- /dev/null +++ b/schema/airdrop/query_msg.json @@ -0,0 +1,234 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "dates" + ], + "properties": { + "dates": { + "type": "object", + "properties": { + "current_date": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "total_claimed" + ], + "properties": { + "total_claimed": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "account" + ], + "properties": { + "account": { + "type": "object", + "required": [ + "permit" + ], + "properties": { + "current_date": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "permit": { + "$ref": "#/definitions/Permit_for_AccountPermitMsg" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "account_with_key" + ], + "properties": { + "account_with_key": { + "type": "object", + "required": [ + "account", + "key" + ], + "properties": { + "account": { + "$ref": "#/definitions/Addr" + }, + "current_date": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "key": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "AccountPermitMsg": { + "type": "object", + "required": [ + "contract", + "key" + ], + "properties": { + "contract": { + "$ref": "#/definitions/Addr" + }, + "key": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "PermitSignature": { + "type": "object", + "required": [ + "pub_key", + "signature" + ], + "properties": { + "pub_key": { + "$ref": "#/definitions/PubKey" + }, + "signature": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + }, + "Permit_for_AccountPermitMsg": { + "description": "Where the information will be stored", + "type": "object", + "required": [ + "params", + "signature" + ], + "properties": { + "account_number": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "chain_id": { + "type": [ + "string", + "null" + ] + }, + "memo": { + "type": [ + "string", + "null" + ] + }, + "params": { + "$ref": "#/definitions/AccountPermitMsg" + }, + "sequence": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "signature": { + "$ref": "#/definitions/PermitSignature" + } + }, + "additionalProperties": false + }, + "PubKey": { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "description": "ignored, but must be \"tendermint/PubKeySecp256k1\" otherwise the verification will fail", + "type": "string" + }, + "value": { + "description": "Secp256k1 PubKey", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use secret_cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/tools/headstash/account.js b/tools/headstash/account.js index 45785ac..de81198 100644 --- a/tools/headstash/account.js +++ b/tools/headstash/account.js @@ -1,13 +1,23 @@ -import { MsgExecuteContract, toUtf8 } from "secretjs"; +import { MsgExecuteContract, fromBase64, toUtf8 } from "secretjs"; import { encodeJsonToB64 } from "@shadeprotocol/shadejs"; -import { scrtHeadstashCodeHash, secretHeadstashContractAddr, secretjs, txEncryptionSeed, wallet } from "./main.js"; +import { chain_id, scrtHeadstashCodeHash, secretHeadstashContractAddr, secretjs, txEncryptionSeed, wallet } from "./main.js"; const viewingKey = "eretskeretjableret" const eth_pubkey = "0x254768D47Cf8958a68242ce5AA1aDB401E1feF2B"; -const eth_sig = "0xf7992bd3f7cb1030b5d69d3326c6e2e28bfde2e38cbb8de753d1be7b5a5ecbcf2d3eccd3fe2e1fccb2454c47dcb926bd047ecf5b74c7330584cbfd619248de811b" +const eth_sig = "0xf7992bd3f7cb1030b5d69d3326c6e2e28bfde2e38cbb8de753d1be7b5a5ecbcf2d3eccd3fe2e1fccb2454c47dcb926bd047ecf5b74c7330584cbfd619248de811b" +const cosmos_sig = "0x"; +const pubkey = { + type: "tendermint/PubKeySecp256k1", + value: "AyZtxhLgis4Ec66OVlKDnuzEZqqV641sm46R3mbE2cpO", +} +const partial_tree = ['fbff7c66d3f610bcf8223e61ce12b10bb64a3433622ff39af83443bcec78920a'] // filler message of AddressProofPermit const fillerMsg = { + coins: [ + { denom: "uterpx", amount: "420" }, + { denom: "uthiolx", amount: "420" } + ], contract: secretHeadstashContractAddr, execute_msg: {}, sender: wallet.address, @@ -19,19 +29,31 @@ const addrProofMsg = { contract: secretHeadstashContractAddr, key: 'eretskeretjableret' } -// Convert JSON object to JSON string -let jsonString = JSON.stringify(addrProofMsg, (key, value) => { + +// Convert memo to single string +let addrProofMsgJson = JSON.stringify(addrProofMsg, (key, value) => { if (typeof value === 'string') { - return value.replace(/\\/g, ''); + return value.replace(/\\/g, ''); } return value; }); -const encoded = encodeJsonToB64(jsonString); +// Convert memo to single string +let partialTreeJson = JSON.stringify(partial_tree, (key, value) => { + if (typeof value === 'string') { + return value.replace(/\\/g, ''); + } + return value; +}); +// encode memo to base64 string +const encoded_memo = encodeJsonToB64(addrProofMsgJson); +const encoded_partial_tree = encodeJsonToB64(partialTreeJson); -console.log("AddrProofMsg:", jsonString); -console.log("Base64 String of AddrProofMsg:", encoded) +console.log("PubKey:", pubkey); +console.log("AddrProofMsg:", addrProofMsgJson); +console.log("Encoded AddrProofMsg:", encoded_memo); +console.log("Encoded Partial Key:", encoded_partial_tree); // signature documentate as defined here: // https://github.com/securesecrets/shade/blob/77abdc70bc645d97aee7de5eb9a2347d22da425f/packages/shade_protocol/src/signature/mod.rs#L100 @@ -45,21 +67,18 @@ const createAccount = new MsgExecuteContract({ addresses: [ { params: fillerMsg, + chain_id: chain_id, + sequence: null, signature: { - pub_key: { - type: "tendermint/PubKeySecp256k1", - value: "AyZtxhLgis4Ec66OVlKDnuzEZqqV641sm46R3mbE2cpO", - }, - signature: "tTjK3Mf4dpQrSQT2hsqXn+pgeXhVjQhw2EXP5N50uhBJ0kpV9IS5uyfo+PHvB20CVHMwux9leaByfXI3T6PD6A==", + pub_key: pubkey, + signature: cosmos_sig, }, - account_number: null, - chain_id: null, - sequence: null, - memo: encoded, + memo: encoded_memo, } ], eth_pubkey: eth_pubkey, eth_sig: eth_sig.slice(2), + partial_tree: encoded_partial_tree, } }, sent_funds: [], // optional diff --git a/tools/headstash/main.js b/tools/headstash/main.js index 1dd7735..f887932 100644 --- a/tools/headstash/main.js +++ b/tools/headstash/main.js @@ -14,9 +14,9 @@ export const secretTerpContractAddr = "secret1c3lj7dr9r2pe83j3yx8jt5v800zs9sq7we export const secretThiolContractAddr = "secret1umh28jgcp0g9jy3qc29xk42kq92xjrcdfgvwdz"; // airdrop contract -export const scrtHeadstashCodeId = 6459; +export const scrtHeadstashCodeId = 6469; export const scrtHeadstashCodeHash = "f494eda77c7816c4882d0dfde8bbd35b87975e427ea74315ed96c051d5674f82"; -export const secretHeadstashContractAddr = "secret1u4ndydgspk0ff6ttvs0kq7utdhcmqec405jtqg"; +export const secretHeadstashContractAddr = "secret1ykf6ysxy25qddd62c4yjyw0wn60uxtvvrm7xjn"; export const merkle_root = "d599867bdb2ade1e470d9ec9456490adcd9da6e0cfd8f515e2b95d345a5cd92f"; // signing client From c3141a72d42ee1762c348a9fe3845cb825f0dd59 Mon Sep 17 00:00:00 2001 From: hard-nett Date: Wed, 10 Apr 2024 03:57:38 +0000 Subject: [PATCH 18/23] organize account scripts --- schema/airdrop/execute_answer.json | 267 --------------- schema/airdrop/execute_msg.json | 427 ------------------------ schema/airdrop/instantiate_msg.json | 153 --------- schema/airdrop/query_answer.json | 282 ---------------- schema/airdrop/query_msg.json | 234 ------------- tools/headstash/account.js | 52 ++- tools/headstash/main.js | 6 +- tools/headstash/permit-sig-example.json | 20 ++ yarn.lock | 4 - 9 files changed, 48 insertions(+), 1397 deletions(-) delete mode 100644 schema/airdrop/execute_answer.json delete mode 100644 schema/airdrop/execute_msg.json delete mode 100644 schema/airdrop/instantiate_msg.json delete mode 100644 schema/airdrop/query_answer.json delete mode 100644 schema/airdrop/query_msg.json create mode 100644 tools/headstash/permit-sig-example.json delete mode 100644 yarn.lock diff --git a/schema/airdrop/execute_answer.json b/schema/airdrop/execute_answer.json deleted file mode 100644 index 6ac9be7..0000000 --- a/schema/airdrop/execute_answer.json +++ /dev/null @@ -1,267 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteAnswer", - "oneOf": [ - { - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "status": { - "$ref": "#/definitions/ResponseStatus" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "add_task" - ], - "properties": { - "add_task": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "status": { - "$ref": "#/definitions/ResponseStatus" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "complete_task" - ], - "properties": { - "complete_task": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "status": { - "$ref": "#/definitions/ResponseStatus" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "account" - ], - "properties": { - "account": { - "type": "object", - "required": [ - "addresses", - "claimed", - "eth_pubkey", - "eth_sig", - "finished_tasks", - "status", - "total" - ], - "properties": { - "addresses": { - "type": "array", - "items": { - "$ref": "#/definitions/Addr" - } - }, - "claimed": { - "$ref": "#/definitions/Uint128" - }, - "eth_pubkey": { - "type": "string" - }, - "eth_sig": { - "type": "string" - }, - "finished_tasks": { - "type": "array", - "items": { - "$ref": "#/definitions/RequiredTask" - } - }, - "status": { - "$ref": "#/definitions/ResponseStatus" - }, - "total": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "disable_permit_key" - ], - "properties": { - "disable_permit_key": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "status": { - "$ref": "#/definitions/ResponseStatus" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "set_viewing_key" - ], - "properties": { - "set_viewing_key": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "status": { - "$ref": "#/definitions/ResponseStatus" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "claim" - ], - "properties": { - "claim": { - "type": "object", - "required": [ - "addresses", - "claimed", - "eth_pubkey", - "eth_sig", - "finished_tasks", - "status", - "total" - ], - "properties": { - "addresses": { - "type": "array", - "items": { - "$ref": "#/definitions/Addr" - } - }, - "claimed": { - "$ref": "#/definitions/Uint128" - }, - "eth_pubkey": { - "type": "string" - }, - "eth_sig": { - "type": "string" - }, - "finished_tasks": { - "type": "array", - "items": { - "$ref": "#/definitions/RequiredTask" - } - }, - "status": { - "$ref": "#/definitions/ResponseStatus" - }, - "total": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "claim_decay" - ], - "properties": { - "claim_decay": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "status": { - "$ref": "#/definitions/ResponseStatus" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "RequiredTask": { - "type": "object", - "required": [ - "address", - "percent" - ], - "properties": { - "address": { - "$ref": "#/definitions/Addr" - }, - "percent": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - }, - "ResponseStatus": { - "type": "string", - "enum": [ - "success", - "failure" - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use secret_cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/schema/airdrop/execute_msg.json b/schema/airdrop/execute_msg.json deleted file mode 100644 index afa47c8..0000000 --- a/schema/airdrop/execute_msg.json +++ /dev/null @@ -1,427 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "properties": { - "admin": { - "anyOf": [ - { - "$ref": "#/definitions/Addr" - }, - { - "type": "null" - } - ] - }, - "decay_start": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "dump_address": { - "anyOf": [ - { - "$ref": "#/definitions/Addr" - }, - { - "type": "null" - } - ] - }, - "end_date": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "padding": { - "type": [ - "string", - "null" - ] - }, - "query_rounding": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "start_date": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "add_tasks" - ], - "properties": { - "add_tasks": { - "type": "object", - "required": [ - "tasks" - ], - "properties": { - "padding": { - "type": [ - "string", - "null" - ] - }, - "tasks": { - "type": "array", - "items": { - "$ref": "#/definitions/RequiredTask" - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "complete_task" - ], - "properties": { - "complete_task": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "$ref": "#/definitions/Addr" - }, - "padding": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "account" - ], - "properties": { - "account": { - "type": "object", - "required": [ - "addresses", - "eth_pubkey", - "eth_sig", - "partial_tree" - ], - "properties": { - "addresses": { - "type": "array", - "items": { - "$ref": "#/definitions/Permit_for_FillerMsg" - } - }, - "eth_pubkey": { - "type": "string" - }, - "eth_sig": { - "type": "string" - }, - "padding": { - "type": [ - "string", - "null" - ] - }, - "partial_tree": { - "type": "array", - "items": { - "$ref": "#/definitions/Binary" - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "disable_permit_key" - ], - "properties": { - "disable_permit_key": { - "type": "object", - "required": [ - "key" - ], - "properties": { - "key": { - "type": "string" - }, - "padding": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "set_viewing_key" - ], - "properties": { - "set_viewing_key": { - "type": "object", - "required": [ - "key" - ], - "properties": { - "key": { - "type": "string" - }, - "padding": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "claim" - ], - "properties": { - "claim": { - "type": "object", - "properties": { - "padding": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "claim_decay" - ], - "properties": { - "claim_decay": { - "type": "object", - "properties": { - "padding": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "EmptyMsg": { - "type": "object", - "additionalProperties": false - }, - "FillerMsg": { - "type": "object", - "required": [ - "coins", - "contract", - "execute_msg", - "sender" - ], - "properties": { - "coins": { - "type": "array", - "items": { - "type": "string" - } - }, - "contract": { - "type": "string" - }, - "execute_msg": { - "$ref": "#/definitions/EmptyMsg" - }, - "sender": { - "type": "string" - } - }, - "additionalProperties": false - }, - "PermitSignature": { - "type": "object", - "required": [ - "pub_key", - "signature" - ], - "properties": { - "pub_key": { - "$ref": "#/definitions/PubKey" - }, - "signature": { - "$ref": "#/definitions/Binary" - } - }, - "additionalProperties": false - }, - "Permit_for_FillerMsg": { - "description": "Where the information will be stored", - "type": "object", - "required": [ - "params", - "signature" - ], - "properties": { - "account_number": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "chain_id": { - "type": [ - "string", - "null" - ] - }, - "memo": { - "type": [ - "string", - "null" - ] - }, - "params": { - "$ref": "#/definitions/FillerMsg" - }, - "sequence": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "signature": { - "$ref": "#/definitions/PermitSignature" - } - }, - "additionalProperties": false - }, - "PubKey": { - "type": "object", - "required": [ - "type", - "value" - ], - "properties": { - "type": { - "description": "ignored, but must be \"tendermint/PubKeySecp256k1\" otherwise the verification will fail", - "type": "string" - }, - "value": { - "description": "Secp256k1 PubKey", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - }, - "additionalProperties": false - }, - "RequiredTask": { - "type": "object", - "required": [ - "address", - "percent" - ], - "properties": { - "address": { - "$ref": "#/definitions/Addr" - }, - "percent": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use secret_cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/schema/airdrop/instantiate_msg.json b/schema/airdrop/instantiate_msg.json deleted file mode 100644 index a215a04..0000000 --- a/schema/airdrop/instantiate_msg.json +++ /dev/null @@ -1,153 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "airdrop_amount", - "airdrop_token", - "claim_msg_plaintext", - "default_claim", - "max_amount", - "merkle_root", - "query_rounding", - "task_claim", - "total_accounts" - ], - "properties": { - "admin": { - "anyOf": [ - { - "$ref": "#/definitions/Addr" - }, - { - "type": "null" - } - ] - }, - "airdrop_2": { - "anyOf": [ - { - "$ref": "#/definitions/Contract" - }, - { - "type": "null" - } - ] - }, - "airdrop_amount": { - "$ref": "#/definitions/Uint128" - }, - "airdrop_token": { - "$ref": "#/definitions/Contract" - }, - "claim_msg_plaintext": { - "description": "{wallet}", - "type": "string" - }, - "decay_start": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "default_claim": { - "$ref": "#/definitions/Uint128" - }, - "dump_address": { - "anyOf": [ - { - "$ref": "#/definitions/Addr" - }, - { - "type": "null" - } - ] - }, - "end_date": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "max_amount": { - "$ref": "#/definitions/Uint128" - }, - "merkle_root": { - "$ref": "#/definitions/Binary" - }, - "query_rounding": { - "$ref": "#/definitions/Uint128" - }, - "start_date": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "task_claim": { - "type": "array", - "items": { - "$ref": "#/definitions/RequiredTask" - } - }, - "total_accounts": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - } - }, - "additionalProperties": false, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Contract": { - "description": "In the process of being deprecated for [cosmwasm_std::ContractInfo] so use that instead when possible.", - "type": "object", - "required": [ - "address", - "code_hash" - ], - "properties": { - "address": { - "$ref": "#/definitions/Addr" - }, - "code_hash": { - "type": "string" - } - }, - "additionalProperties": false - }, - "RequiredTask": { - "type": "object", - "required": [ - "address", - "percent" - ], - "properties": { - "address": { - "$ref": "#/definitions/Addr" - }, - "percent": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use secret_cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/schema/airdrop/query_answer.json b/schema/airdrop/query_answer.json deleted file mode 100644 index 9124227..0000000 --- a/schema/airdrop/query_answer.json +++ /dev/null @@ -1,282 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryAnswer", - "oneOf": [ - { - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "$ref": "#/definitions/Config" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "dates" - ], - "properties": { - "dates": { - "type": "object", - "required": [ - "start" - ], - "properties": { - "decay_factor": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "decay_start": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "end": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "start": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "total_claimed" - ], - "properties": { - "total_claimed": { - "type": "object", - "required": [ - "claimed" - ], - "properties": { - "claimed": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "account" - ], - "properties": { - "account": { - "type": "object", - "required": [ - "addresses", - "claimed", - "finished_tasks", - "total", - "unclaimed" - ], - "properties": { - "addresses": { - "type": "array", - "items": { - "$ref": "#/definitions/Addr" - } - }, - "claimed": { - "$ref": "#/definitions/Uint128" - }, - "finished_tasks": { - "type": "array", - "items": { - "$ref": "#/definitions/RequiredTask" - } - }, - "total": { - "$ref": "#/definitions/Uint128" - }, - "unclaimed": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Config": { - "type": "object", - "required": [ - "admin", - "airdrop_amount", - "airdrop_snip20", - "claim_msg_plaintext", - "contract", - "max_amount", - "merkle_root", - "query_rounding", - "start_date", - "task_claim", - "total_accounts" - ], - "properties": { - "admin": { - "$ref": "#/definitions/Addr" - }, - "airdrop_amount": { - "$ref": "#/definitions/Uint128" - }, - "airdrop_snip20": { - "$ref": "#/definitions/Contract" - }, - "airdrop_snip20_optional": { - "anyOf": [ - { - "$ref": "#/definitions/Contract" - }, - { - "type": "null" - } - ] - }, - "claim_msg_plaintext": { - "type": "string" - }, - "contract": { - "$ref": "#/definitions/Addr" - }, - "decay_start": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "dump_address": { - "anyOf": [ - { - "$ref": "#/definitions/Addr" - }, - { - "type": "null" - } - ] - }, - "end_date": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "max_amount": { - "$ref": "#/definitions/Uint128" - }, - "merkle_root": { - "$ref": "#/definitions/Binary" - }, - "query_rounding": { - "$ref": "#/definitions/Uint128" - }, - "start_date": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "task_claim": { - "type": "array", - "items": { - "$ref": "#/definitions/RequiredTask" - } - }, - "total_accounts": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - "Contract": { - "description": "In the process of being deprecated for [cosmwasm_std::ContractInfo] so use that instead when possible.", - "type": "object", - "required": [ - "address", - "code_hash" - ], - "properties": { - "address": { - "$ref": "#/definitions/Addr" - }, - "code_hash": { - "type": "string" - } - }, - "additionalProperties": false - }, - "RequiredTask": { - "type": "object", - "required": [ - "address", - "percent" - ], - "properties": { - "address": { - "$ref": "#/definitions/Addr" - }, - "percent": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use secret_cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/schema/airdrop/query_msg.json b/schema/airdrop/query_msg.json deleted file mode 100644 index 897cd12..0000000 --- a/schema/airdrop/query_msg.json +++ /dev/null @@ -1,234 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "dates" - ], - "properties": { - "dates": { - "type": "object", - "properties": { - "current_date": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "total_claimed" - ], - "properties": { - "total_claimed": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "account" - ], - "properties": { - "account": { - "type": "object", - "required": [ - "permit" - ], - "properties": { - "current_date": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "permit": { - "$ref": "#/definitions/Permit_for_AccountPermitMsg" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "account_with_key" - ], - "properties": { - "account_with_key": { - "type": "object", - "required": [ - "account", - "key" - ], - "properties": { - "account": { - "$ref": "#/definitions/Addr" - }, - "current_date": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "key": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "AccountPermitMsg": { - "type": "object", - "required": [ - "contract", - "key" - ], - "properties": { - "contract": { - "$ref": "#/definitions/Addr" - }, - "key": { - "type": "string" - } - }, - "additionalProperties": false - }, - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "PermitSignature": { - "type": "object", - "required": [ - "pub_key", - "signature" - ], - "properties": { - "pub_key": { - "$ref": "#/definitions/PubKey" - }, - "signature": { - "$ref": "#/definitions/Binary" - } - }, - "additionalProperties": false - }, - "Permit_for_AccountPermitMsg": { - "description": "Where the information will be stored", - "type": "object", - "required": [ - "params", - "signature" - ], - "properties": { - "account_number": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "chain_id": { - "type": [ - "string", - "null" - ] - }, - "memo": { - "type": [ - "string", - "null" - ] - }, - "params": { - "$ref": "#/definitions/AccountPermitMsg" - }, - "sequence": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "signature": { - "$ref": "#/definitions/PermitSignature" - } - }, - "additionalProperties": false - }, - "PubKey": { - "type": "object", - "required": [ - "type", - "value" - ], - "properties": { - "type": { - "description": "ignored, but must be \"tendermint/PubKeySecp256k1\" otherwise the verification will fail", - "type": "string" - }, - "value": { - "description": "Secp256k1 PubKey", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - }, - "additionalProperties": false - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use secret_cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/tools/headstash/account.js b/tools/headstash/account.js index de81198..5ecda63 100644 --- a/tools/headstash/account.js +++ b/tools/headstash/account.js @@ -2,22 +2,15 @@ import { MsgExecuteContract, fromBase64, toUtf8 } from "secretjs"; import { encodeJsonToB64 } from "@shadeprotocol/shadejs"; import { chain_id, scrtHeadstashCodeHash, secretHeadstashContractAddr, secretjs, txEncryptionSeed, wallet } from "./main.js"; -const viewingKey = "eretskeretjableret" const eth_pubkey = "0x254768D47Cf8958a68242ce5AA1aDB401E1feF2B"; const eth_sig = "0xf7992bd3f7cb1030b5d69d3326c6e2e28bfde2e38cbb8de753d1be7b5a5ecbcf2d3eccd3fe2e1fccb2454c47dcb926bd047ecf5b74c7330584cbfd619248de811b" -const cosmos_sig = "0x"; -const pubkey = { - type: "tendermint/PubKeySecp256k1", - value: "AyZtxhLgis4Ec66OVlKDnuzEZqqV641sm46R3mbE2cpO", -} +const cosmos_sig = "c5uzRIuxO91I8BYxJ8CREuHoDkhH4wJXa5W8mng/gbhXnhAQQ9WEYsGkJHtEK8Ppnt6rXG/IcvL7x7AdBbmpfw=="; +const pubkey = { type: "tendermint/PubKeySecp256k1", value: "AyZtxhLgis4Ec66OVlKDnuzEZqqV641sm46R3mbE2cpO" } const partial_tree = ['fbff7c66d3f610bcf8223e61ce12b10bb64a3433622ff39af83443bcec78920a'] // filler message of AddressProofPermit const fillerMsg = { - coins: [ - { denom: "uterpx", amount: "420" }, - { denom: "uthiolx", amount: "420" } - ], + coins: [{ amount: 420, denom: "uterp" }], contract: secretHeadstashContractAddr, execute_msg: {}, sender: wallet.address, @@ -27,7 +20,7 @@ const fillerMsg = { const addrProofMsg = { address: wallet.address, contract: secretHeadstashContractAddr, - key: 'eretskeretjableret' + key: "eretskeretjableret", } // Convert memo to single string @@ -48,37 +41,37 @@ let partialTreeJson = JSON.stringify(partial_tree, (key, value) => { // encode memo to base64 string const encoded_memo = encodeJsonToB64(addrProofMsgJson); -const encoded_partial_tree = encodeJsonToB64(partialTreeJson); +// const encoded_partial_tree = encodeJsonToB64(partialTreeJson); console.log("PubKey:", pubkey); console.log("AddrProofMsg:", addrProofMsgJson); console.log("Encoded AddrProofMsg:", encoded_memo); -console.log("Encoded Partial Key:", encoded_partial_tree); +// console.log("Encoded Partial Key:", encoded_partial_tree); + +const permitParams = { + account_number: null, + chain_id: chain_id, + memo: encoded_memo, + params: fillerMsg, + sequence: null, + signature: { + pub_key: pubkey, + signature: cosmos_sig, + }, +} // signature documentate as defined here: // https://github.com/securesecrets/shade/blob/77abdc70bc645d97aee7de5eb9a2347d22da425f/packages/shade_protocol/src/signature/mod.rs#L100 - const createAccount = new MsgExecuteContract({ sender: wallet.address, contract_address: secretHeadstashContractAddr, code_hash: scrtHeadstashCodeHash, msg: { account: { - addresses: [ - { - params: fillerMsg, - chain_id: chain_id, - sequence: null, - signature: { - pub_key: pubkey, - signature: cosmos_sig, - }, - memo: encoded_memo, - } - ], + addresses: [permitParams], eth_pubkey: eth_pubkey, eth_sig: eth_sig.slice(2), - partial_tree: encoded_partial_tree, + partial_tree: partial_tree, } }, sent_funds: [], // optional @@ -86,6 +79,11 @@ const createAccount = new MsgExecuteContract({ const tx = await secretjs.tx.broadcast([createAccount], { gasLimit: 200_000, + // explicitSignerData: { + // accountNumber: 22761, + // sequence: 170, + // chainId: "pulsar-3" + // } }); console.log(tx); diff --git a/tools/headstash/main.js b/tools/headstash/main.js index f887932..e210195 100644 --- a/tools/headstash/main.js +++ b/tools/headstash/main.js @@ -3,9 +3,9 @@ import * as fs from "fs"; // wallet export const chain_id = "pulsar-3"; -export const wallet = new Wallet(""); +export const wallet = new Wallet("goat action fuel major strategy adult kind sand draw amazing pigeon inspire antenna forget six kiss loan script west jaguar again click review have"); export const txEncryptionSeed = EncryptionUtilsImpl.GenerateNewSeed(); -export const contract_wasm = fs.readFileSync("./target/wasm32-unknown-unknown/release/airdrop.wasm"); +// export const contract_wasm = fs.readFileSync("./target/wasm32-unknown-unknown/release/airdrop.wasm"); // snip-20 export const scrt20codeId = 5697; @@ -141,7 +141,7 @@ const args = process.argv.slice(2); if (args.length < 1) { console.error('Invalid option. Please provide -s to store the contract, or -i to instantiate the contract, followed by expected values [name] [symbol] [ibc-hash].'); } else if (args[0] === '-s') { - upload_contract(args[1]); + // upload_contract(args[1]); } else if (args[0] === '-h') { instantiate_headstash_contract(); } else if (args[0] === '-a') { diff --git a/tools/headstash/permit-sig-example.json b/tools/headstash/permit-sig-example.json new file mode 100644 index 0000000..45b9342 --- /dev/null +++ b/tools/headstash/permit-sig-example.json @@ -0,0 +1,20 @@ + { + "account_number": "0", + "chain_id": "90u-4", + "fee": { + "amount": [{ + "amount": "0", + "denom": "uscrt" + }], + "gas": "1" + }, + "memo": "", + "msgs": [{ + "type": "signature_proof", + "value": { + "address": "secret1sc0cldtf7nznvy6sxfdvngj953e6s0w90w9r4v", + "some_number": "10" + } + }], + "sequence": "0" + } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index fb57ccd..0000000 --- a/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - From 4725477c5863e218cc1f105611a6bf562473f877 Mon Sep 17 00:00:00 2001 From: hard-nett Date: Wed, 10 Apr 2024 20:37:24 +0000 Subject: [PATCH 19/23] scripts --- tools/headstash/account.js | 50 ++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/tools/headstash/account.js b/tools/headstash/account.js index 5ecda63..8e175c3 100644 --- a/tools/headstash/account.js +++ b/tools/headstash/account.js @@ -2,37 +2,24 @@ import { MsgExecuteContract, fromBase64, toUtf8 } from "secretjs"; import { encodeJsonToB64 } from "@shadeprotocol/shadejs"; import { chain_id, scrtHeadstashCodeHash, secretHeadstashContractAddr, secretjs, txEncryptionSeed, wallet } from "./main.js"; +const cosmos_sig = "c5uzRIuxO91I8BYxJ8CREuHoDkhH4wJXa5W8mng/gbhXnhAQQ9WEYsGkJHtEK8Ppnt6rXG/IcvL7x7AdBbmpfw=="; const eth_pubkey = "0x254768D47Cf8958a68242ce5AA1aDB401E1feF2B"; const eth_sig = "0xf7992bd3f7cb1030b5d69d3326c6e2e28bfde2e38cbb8de753d1be7b5a5ecbcf2d3eccd3fe2e1fccb2454c47dcb926bd047ecf5b74c7330584cbfd619248de811b" -const cosmos_sig = "c5uzRIuxO91I8BYxJ8CREuHoDkhH4wJXa5W8mng/gbhXnhAQQ9WEYsGkJHtEK8Ppnt6rXG/IcvL7x7AdBbmpfw=="; const pubkey = { type: "tendermint/PubKeySecp256k1", value: "AyZtxhLgis4Ec66OVlKDnuzEZqqV641sm46R3mbE2cpO" } const partial_tree = ['fbff7c66d3f610bcf8223e61ce12b10bb64a3433622ff39af83443bcec78920a'] +const permitKey = "" -// filler message of AddressProofPermit -const fillerMsg = { - coins: [{ amount: 420, denom: "uterp" }], - contract: secretHeadstashContractAddr, - execute_msg: {}, - sender: wallet.address, -} -// data to be encoded in memo of AddressProofPermit -const addrProofMsg = { +const addressProofMsg = { address: wallet.address, + amount: "", contract: secretHeadstashContractAddr, - key: "eretskeretjableret", + index: 1, + key: permitKey, } // Convert memo to single string -let addrProofMsgJson = JSON.stringify(addrProofMsg, (key, value) => { - if (typeof value === 'string') { - return value.replace(/\\/g, ''); - } - return value; -}); - -// Convert memo to single string -let partialTreeJson = JSON.stringify(partial_tree, (key, value) => { +let addrProofMsgJson = JSON.stringify(addressProofMsg, (key, value) => { if (typeof value === 'string') { return value.replace(/\\/g, ''); } @@ -41,25 +28,30 @@ let partialTreeJson = JSON.stringify(partial_tree, (key, value) => { // encode memo to base64 string const encoded_memo = encodeJsonToB64(addrProofMsgJson); -// const encoded_partial_tree = encodeJsonToB64(partialTreeJson); -console.log("PubKey:", pubkey); -console.log("AddrProofMsg:", addrProofMsgJson); -console.log("Encoded AddrProofMsg:", encoded_memo); -// console.log("Encoded Partial Key:", encoded_partial_tree); +const fillerMsg = { + coins: [], + contract: secretHeadstashContractAddr, + execute_msg: "", + sender: wallet.address, +} +// account const permitParams = { - account_number: null, - chain_id: chain_id, - memo: encoded_memo, params: fillerMsg, - sequence: null, + memo: encoded_memo, + chain_id: chain_id, signature: { pub_key: pubkey, signature: cosmos_sig, }, } +console.log("PubKey:", pubkey); +console.log("AddrProofMsg:", addrProofMsgJson); +console.log("Encoded AddrProofMsg:", encoded_memo); +// console.log("Encoded Partial Key:", encoded_partial_tree); + // signature documentate as defined here: // https://github.com/securesecrets/shade/blob/77abdc70bc645d97aee7de5eb9a2347d22da425f/packages/shade_protocol/src/signature/mod.rs#L100 const createAccount = new MsgExecuteContract({ From 864acd0c6baff149bbf17408389e49711bfce121 Mon Sep 17 00:00:00 2001 From: Hard-Nett Date: Wed, 10 Apr 2024 22:06:44 +0000 Subject: [PATCH 20/23] script stuff, execute enclave error --- tools/headstash/account.js | 102 ++++++++++++++++--------------------- tools/headstash/main.js | 51 ++++++++++++------- 2 files changed, 76 insertions(+), 77 deletions(-) diff --git a/tools/headstash/account.js b/tools/headstash/account.js index 8e175c3..ce4fede 100644 --- a/tools/headstash/account.js +++ b/tools/headstash/account.js @@ -9,76 +9,62 @@ const pubkey = { type: "tendermint/PubKeySecp256k1", value: "AyZtxhLgis4Ec66OVlK const partial_tree = ['fbff7c66d3f610bcf8223e61ce12b10bb64a3433622ff39af83443bcec78920a'] const permitKey = "" - -const addressProofMsg = { - address: wallet.address, - amount: "", - contract: secretHeadstashContractAddr, - index: 1, - key: permitKey, -} - -// Convert memo to single string -let addrProofMsgJson = JSON.stringify(addressProofMsg, (key, value) => { - if (typeof value === 'string') { - return value.replace(/\\/g, ''); +let create_account = async () => { + const addressProofMsg = { + address: wallet.address, + amount: "420", + contract: secretHeadstashContractAddr, + index: 1, + key: permitKey, } - return value; -}); - -// encode memo to base64 string -const encoded_memo = encodeJsonToB64(addrProofMsgJson); + // encode memo to base64 string + const encoded_memo = Buffer.from(JSON.stringify(addressProofMsg)).toString('base64'); -const fillerMsg = { - coins: [], - contract: secretHeadstashContractAddr, - execute_msg: "", - sender: wallet.address, -} - -// account -const permitParams = { - params: fillerMsg, - memo: encoded_memo, - chain_id: chain_id, - signature: { - pub_key: pubkey, - signature: cosmos_sig, - }, -} + const fillerMsg = { + coins: [], + contract: secretHeadstashContractAddr, + execute_msg: {}, + sender: wallet.address, + } -console.log("PubKey:", pubkey); -console.log("AddrProofMsg:", addrProofMsgJson); -console.log("Encoded AddrProofMsg:", encoded_memo); -// console.log("Encoded Partial Key:", encoded_partial_tree); + // account + const permitParams = { + params: fillerMsg, + signature: { + pub_key: pubkey, + signature: cosmos_sig, + }, + chain_id: chain_id, + memo: encoded_memo, + } -// signature documentate as defined here: -// https://github.com/securesecrets/shade/blob/77abdc70bc645d97aee7de5eb9a2347d22da425f/packages/shade_protocol/src/signature/mod.rs#L100 -const createAccount = new MsgExecuteContract({ - sender: wallet.address, - contract_address: secretHeadstashContractAddr, - code_hash: scrtHeadstashCodeHash, - msg: { + const createAccount = { account: { addresses: [permitParams], eth_pubkey: eth_pubkey, eth_sig: eth_sig.slice(2), partial_tree: partial_tree, } - }, - sent_funds: [], // optional -}); + } -const tx = await secretjs.tx.broadcast([createAccount], { - gasLimit: 200_000, - // explicitSignerData: { - // accountNumber: 22761, - // sequence: 170, - // chainId: "pulsar-3" - // } -}); + const tx = await secretjs.tx.compute.executeContract({ + sender: wallet.address, + contract_address: secretHeadstashContractAddr, + msg: createAccount, + code_hash: scrtHeadstashCodeHash, + }, + { + gasLimit: 400_000, + explicitSignerData: { + accountNumber: 22761, + sequence: 191, + chainId: "pulsar-3" + } + }) -console.log(tx); + console.log(tx); +} +export {create_account} diff --git a/tools/headstash/main.js b/tools/headstash/main.js index e210195..b284ced 100644 --- a/tools/headstash/main.js +++ b/tools/headstash/main.js @@ -1,11 +1,12 @@ -import { Wallet, SecretNetworkClient, EncryptionUtilsImpl, fromUtf8, MsgExecuteContractResponse } from "secretjs"; +import { Wallet, SecretNetworkClient, EncryptionUtilsImpl, MsgExecuteContract, fromUtf8, MsgExecuteContractResponse } from "secretjs"; +import {create_account} from './account.js' import * as fs from "fs"; // wallet export const chain_id = "pulsar-3"; export const wallet = new Wallet("goat action fuel major strategy adult kind sand draw amazing pigeon inspire antenna forget six kiss loan script west jaguar again click review have"); export const txEncryptionSeed = EncryptionUtilsImpl.GenerateNewSeed(); -// export const contract_wasm = fs.readFileSync("./target/wasm32-unknown-unknown/release/airdrop.wasm"); +export const contract_wasm = fs.readFileSync("./target/wasm32-unknown-unknown/release/airdrop.wasm"); // snip-20 export const scrt20codeId = 5697; @@ -14,11 +15,19 @@ export const secretTerpContractAddr = "secret1c3lj7dr9r2pe83j3yx8jt5v800zs9sq7we export const secretThiolContractAddr = "secret1umh28jgcp0g9jy3qc29xk42kq92xjrcdfgvwdz"; // airdrop contract -export const scrtHeadstashCodeId = 6469; +export const scrtHeadstashCodeId = 6559; export const scrtHeadstashCodeHash = "f494eda77c7816c4882d0dfde8bbd35b87975e427ea74315ed96c051d5674f82"; -export const secretHeadstashContractAddr = "secret1ykf6ysxy25qddd62c4yjyw0wn60uxtvvrm7xjn"; +export const secretHeadstashContractAddr = "secret1dx5a9ut29nv2n673hh06n0zh7z2fg63n0xylqg"; export const merkle_root = "d599867bdb2ade1e470d9ec9456490adcd9da6e0cfd8f515e2b95d345a5cd92f"; +// account stuff +const cosmos_sig = "c5uzRIuxO91I8BYxJ8CREuHoDkhH4wJXa5W8mng/gbhXnhAQQ9WEYsGkJHtEK8Ppnt6rXG/IcvL7x7AdBbmpfw=="; +const eth_pubkey = "0x254768D47Cf8958a68242ce5AA1aDB401E1feF2B"; +const eth_sig = "0xf7992bd3f7cb1030b5d69d3326c6e2e28bfde2e38cbb8de753d1be7b5a5ecbcf2d3eccd3fe2e1fccb2454c47dcb926bd047ecf5b74c7330584cbfd619248de811b" +const pubkey = { type: "tendermint/PubKeySecp256k1", value: "AyZtxhLgis4Ec66OVlKDnuzEZqqV641sm46R3mbE2cpO" } +const partial_tree = ['fbff7c66d3f610bcf8223e61ce12b10bb64a3433622ff39af83443bcec78920a'] +const permitKey = "dezaym" + // signing client export const secretjs = new SecretNetworkClient({ chainId: chain_id, @@ -42,16 +51,17 @@ let upload_contract = async () => { } ); - const codeId = Number( - tx.arrayLog.find((log) => log.type === "message" && log.key === "code_id") - .value - ); - - console.log("codeId: ", codeId); - const contractCodeHash = (await secretjs.query.compute.codeHashByCodeId({ code_id: codeId })).code_hash; - console.log(`Contract hash: ${contractCodeHash}`); + if (tx.code == 0) { + const codeId = Number( + tx.arrayLog.find((log) => log.type === "message" && log.key === "code_id").value + ); + console.log("codeId: ", codeId); + const contractCodeHash = (await secretjs.query.compute.codeHashByCodeId({ code_id: codeId })).code_hash; + console.log(`Contract hash: ${contractCodeHash}`); + } } + // initialize a new headstash contract let instantiate_headstash_contract = async () => { let initMsg = { @@ -103,6 +113,7 @@ let instantiate_headstash_contract = async () => { console.log(contractAddress); } + // initiates a new snip-20 let instantiate_contract = async (name, synbol, supported_denom) => { const initMsg = { @@ -125,12 +136,14 @@ let instantiate_contract = async (name, synbol, supported_denom) => { gasLimit: 400_000, } ); - //Find the contract_address in the logs - const contractAddress = tx.arrayLog.find( - (log) => log.type === "message" && log.key === "contract_address" - ).value; + if (tx.code == 0) { + //Find the contract_address in the logs + const contractAddress = tx.arrayLog.find( + (log) => log.type === "message" && log.key === "contract_address" + ).value; - console.log(contractAddress); + console.log(contractAddress); + } }; @@ -141,11 +154,11 @@ const args = process.argv.slice(2); if (args.length < 1) { console.error('Invalid option. Please provide -s to store the contract, or -i to instantiate the contract, followed by expected values [name] [symbol] [ibc-hash].'); } else if (args[0] === '-s') { - // upload_contract(args[1]); + upload_contract(args[1]); } else if (args[0] === '-h') { instantiate_headstash_contract(); } else if (args[0] === '-a') { - + create_account(args[1]) } else if (args[0] === '-i') { if (args.length < 4) { console.error('Usage: -i name symbol [supported_denoms]'); From 462d7dbe91ca3ada69e031a57f44f247ada68d16 Mon Sep 17 00:00:00 2001 From: hard-nett Date: Thu, 11 Apr 2024 21:38:05 +0000 Subject: [PATCH 21/23] update permit-sig-example --- contracts/airdrop/src/handle.rs | 24 ++++++++++++------------ tools/headstash/account.js | 12 ++++++------ tools/headstash/main.js | 4 ++-- tools/headstash/permit-sig-example.json | 8 +++++--- 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/contracts/airdrop/src/handle.rs b/contracts/airdrop/src/handle.rs index a0f0528..cfb011e 100644 --- a/contracts/airdrop/src/handle.rs +++ b/contracts/airdrop/src/handle.rs @@ -205,10 +205,10 @@ pub fn try_account( &config, &info.sender, &mut account, - addresses.clone(), - eth_pubkey.clone(), - eth_sig.clone(), - partial_tree.clone(), + &addresses, + ð_pubkey, + ð_sig, + &partial_tree, )?; // Add default claim at index 0 @@ -253,10 +253,10 @@ pub fn try_account( &config, &info.sender, &mut account, - addresses.clone(), - eth_pubkey.clone(), - eth_sig.clone(), - partial_tree.clone(), + &addresses, + ð_pubkey, + ð_sig, + &partial_tree, )?; } @@ -589,10 +589,10 @@ pub fn try_add_account_addresses( config: &Config, sender: &Addr, account: &mut Account, - addresses: Vec, - eth_pubkey: String, - eth_sig: String, - partial_tree: Vec, + addresses: &Vec, + eth_pubkey: &String, + eth_sig: &String, + partial_tree: &Vec, ) -> StdResult<()> { // Setup the items to validate let mut leaves_to_validate: Vec<(usize, [u8; 32])> = vec![]; diff --git a/tools/headstash/account.js b/tools/headstash/account.js index ce4fede..71bbdb5 100644 --- a/tools/headstash/account.js +++ b/tools/headstash/account.js @@ -7,7 +7,7 @@ const eth_pubkey = "0x254768D47Cf8958a68242ce5AA1aDB401E1feF2B"; const eth_sig = "0xf7992bd3f7cb1030b5d69d3326c6e2e28bfde2e38cbb8de753d1be7b5a5ecbcf2d3eccd3fe2e1fccb2454c47dcb926bd047ecf5b74c7330584cbfd619248de811b" const pubkey = { type: "tendermint/PubKeySecp256k1", value: "AyZtxhLgis4Ec66OVlKDnuzEZqqV641sm46R3mbE2cpO" } const partial_tree = ['fbff7c66d3f610bcf8223e61ce12b10bb64a3433622ff39af83443bcec78920a'] -const permitKey = "" +const permitKey = "test123" let create_account = async () => { const addressProofMsg = { @@ -55,11 +55,11 @@ let create_account = async () => { }, { gasLimit: 400_000, - explicitSignerData: { - accountNumber: 22761, - sequence: 191, - chainId: "pulsar-3" - } + // explicitSignerData: { + // accountNumber: 22761, + // sequence: 191, + // chainId: "pulsar-3" + // } }) console.log(tx); diff --git a/tools/headstash/main.js b/tools/headstash/main.js index b284ced..d19261e 100644 --- a/tools/headstash/main.js +++ b/tools/headstash/main.js @@ -6,7 +6,7 @@ import * as fs from "fs"; export const chain_id = "pulsar-3"; export const wallet = new Wallet("goat action fuel major strategy adult kind sand draw amazing pigeon inspire antenna forget six kiss loan script west jaguar again click review have"); export const txEncryptionSeed = EncryptionUtilsImpl.GenerateNewSeed(); -export const contract_wasm = fs.readFileSync("./target/wasm32-unknown-unknown/release/airdrop.wasm"); +// export const contract_wasm = fs.readFileSync("./target/wasm32-unknown-unknown/release/airdrop.wasm"); // snip-20 export const scrt20codeId = 5697; @@ -154,7 +154,7 @@ const args = process.argv.slice(2); if (args.length < 1) { console.error('Invalid option. Please provide -s to store the contract, or -i to instantiate the contract, followed by expected values [name] [symbol] [ibc-hash].'); } else if (args[0] === '-s') { - upload_contract(args[1]); + // upload_contract(args[1]); } else if (args[0] === '-h') { instantiate_headstash_contract(); } else if (args[0] === '-a') { diff --git a/tools/headstash/permit-sig-example.json b/tools/headstash/permit-sig-example.json index 45b9342..cb22017 100644 --- a/tools/headstash/permit-sig-example.json +++ b/tools/headstash/permit-sig-example.json @@ -1,6 +1,6 @@ { "account_number": "0", - "chain_id": "90u-4", + "chain_id": "pulsar-3", "fee": { "amount": [{ "amount": "0", @@ -12,8 +12,10 @@ "msgs": [{ "type": "signature_proof", "value": { - "address": "secret1sc0cldtf7nznvy6sxfdvngj953e6s0w90w9r4v", - "some_number": "10" + "coins": [], + "contract": "", + "execute_msg": {}, + "sender": "" } }], "sequence": "0" From e8be7d499633c3df29bd717d6836faaba50d8a01 Mon Sep 17 00:00:00 2001 From: hard-nett Date: Wed, 17 Apr 2024 20:35:18 +0000 Subject: [PATCH 22/23] cleanup scripts --- tools/headstash/account.js | 11 ++--------- tools/headstash/main.js | 12 ++++++------ tools/headstash/permit-sig-example.json | 4 ++-- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/tools/headstash/account.js b/tools/headstash/account.js index 71bbdb5..12c2baa 100644 --- a/tools/headstash/account.js +++ b/tools/headstash/account.js @@ -1,13 +1,6 @@ import { MsgExecuteContract, fromBase64, toUtf8 } from "secretjs"; import { encodeJsonToB64 } from "@shadeprotocol/shadejs"; -import { chain_id, scrtHeadstashCodeHash, secretHeadstashContractAddr, secretjs, txEncryptionSeed, wallet } from "./main.js"; - -const cosmos_sig = "c5uzRIuxO91I8BYxJ8CREuHoDkhH4wJXa5W8mng/gbhXnhAQQ9WEYsGkJHtEK8Ppnt6rXG/IcvL7x7AdBbmpfw=="; -const eth_pubkey = "0x254768D47Cf8958a68242ce5AA1aDB401E1feF2B"; -const eth_sig = "0xf7992bd3f7cb1030b5d69d3326c6e2e28bfde2e38cbb8de753d1be7b5a5ecbcf2d3eccd3fe2e1fccb2454c47dcb926bd047ecf5b74c7330584cbfd619248de811b" -const pubkey = { type: "tendermint/PubKeySecp256k1", value: "AyZtxhLgis4Ec66OVlKDnuzEZqqV641sm46R3mbE2cpO" } -const partial_tree = ['fbff7c66d3f610bcf8223e61ce12b10bb64a3433622ff39af83443bcec78920a'] -const permitKey = "test123" +import { chain_id, scrtHeadstashCodeHash, secretHeadstashContractAddr, secretjs, txEncryptionSeed, wallet, permitKey, pubkey, cosmos_sig, eth_pubkey, eth_sig, partial_tree } from "./main.js"; let create_account = async () => { const addressProofMsg = { @@ -64,7 +57,7 @@ let create_account = async () => { console.log(tx); } -export {create_account} +export { create_account } diff --git a/tools/headstash/main.js b/tools/headstash/main.js index d19261e..2cef3c7 100644 --- a/tools/headstash/main.js +++ b/tools/headstash/main.js @@ -21,12 +21,12 @@ export const secretHeadstashContractAddr = "secret1dx5a9ut29nv2n673hh06n0zh7z2fg export const merkle_root = "d599867bdb2ade1e470d9ec9456490adcd9da6e0cfd8f515e2b95d345a5cd92f"; // account stuff -const cosmos_sig = "c5uzRIuxO91I8BYxJ8CREuHoDkhH4wJXa5W8mng/gbhXnhAQQ9WEYsGkJHtEK8Ppnt6rXG/IcvL7x7AdBbmpfw=="; -const eth_pubkey = "0x254768D47Cf8958a68242ce5AA1aDB401E1feF2B"; -const eth_sig = "0xf7992bd3f7cb1030b5d69d3326c6e2e28bfde2e38cbb8de753d1be7b5a5ecbcf2d3eccd3fe2e1fccb2454c47dcb926bd047ecf5b74c7330584cbfd619248de811b" -const pubkey = { type: "tendermint/PubKeySecp256k1", value: "AyZtxhLgis4Ec66OVlKDnuzEZqqV641sm46R3mbE2cpO" } -const partial_tree = ['fbff7c66d3f610bcf8223e61ce12b10bb64a3433622ff39af83443bcec78920a'] -const permitKey = "dezaym" +export const cosmos_sig = "esaFDwiCoi6R5h/xdW/vPyyyFGrwfujMdR/2t65GmRczF3ZYwzQvzfVEBrOBopCa2x98dpEusml8ysi1khZLhQ=="; +export const eth_pubkey = "0x254768D47Cf8958a68242ce5AA1aDB401E1feF2B"; +export const eth_sig = "0xf7992bd3f7cb1030b5d69d3326c6e2e28bfde2e38cbb8de753d1be7b5a5ecbcf2d3eccd3fe2e1fccb2454c47dcb926bd047ecf5b74c7330584cbfd619248de811b" +export const pubkey = { type: "tendermint/PubKeySecp256k1", value: "AyZtxhLgis4Ec66OVlKDnuzEZqqV641sm46R3mbE2cpO" } +export const partial_tree = ['fbff7c66d3f610bcf8223e61ce12b10bb64a3433622ff39af83443bcec78920a'] +export const permitKey = "test123" // signing client export const secretjs = new SecretNetworkClient({ diff --git a/tools/headstash/permit-sig-example.json b/tools/headstash/permit-sig-example.json index cb22017..c9307e7 100644 --- a/tools/headstash/permit-sig-example.json +++ b/tools/headstash/permit-sig-example.json @@ -13,9 +13,9 @@ "type": "signature_proof", "value": { "coins": [], - "contract": "", + "contract": "secret1dx5a9ut29nv2n673hh06n0zh7z2fg63n0xylqg", "execute_msg": {}, - "sender": "" + "sender": "secret13uazul89dp0lypuxcz0upygpjy0ftdah4lnrs4" } }], "sequence": "0" From bd9c44ebfdbeb0d8c32000151f12bd028238f345 Mon Sep 17 00:00:00 2001 From: hard-nett Date: Wed, 17 Apr 2024 20:50:18 +0000 Subject: [PATCH 23/23] scripts --- tools/headstash/main.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/headstash/main.js b/tools/headstash/main.js index 2cef3c7..5b126bd 100644 --- a/tools/headstash/main.js +++ b/tools/headstash/main.js @@ -75,9 +75,9 @@ let instantiate_headstash_contract = async () => { address: secretThiolContractAddr, code_hash: scrt20CodeHash }, - start_date: null, - end_date: null, - decay_start: null, + start_date: 1713386815, + end_date: 1744922815, + decay_start: 1723927615, merkle_root: merkle_root, airdrop_amount: "840", total_accounts: 2, @@ -97,7 +97,7 @@ let instantiate_headstash_contract = async () => { sender: wallet.address, code_hash: scrtHeadstashCodeHash, init_msg: initMsg, - label: "Secret Headstash Patch " + Math.ceil(Math.random() * 10000), + label: "Secret Headstash Patch" + Math.ceil(Math.random() * 10000), }, { gasLimit: 400_000, @@ -152,9 +152,9 @@ const args = process.argv.slice(2); // Determine which function to run based on the first argument if (args.length < 1) { - console.error('Invalid option. Please provide -s to store the contract, or -i to instantiate the contract, followed by expected values [name] [symbol] [ibc-hash].'); + console.error('Invalid option. Please provide -s to store the contract, -i to instantiate the snip20 tokens followed by expected values [name] [symbol] [ibc-hash], -h to instantiate the headstash airdrop contract, -a to create the account,'); } else if (args[0] === '-s') { - // upload_contract(args[1]); + upload_contract(args[1]); } else if (args[0] === '-h') { instantiate_headstash_contract(); } else if (args[0] === '-a') {