From 79a87f849b722f24bc9476398f0e655a0646ff65 Mon Sep 17 00:00:00 2001 From: Nikita Jerschow Date: Wed, 13 Sep 2023 07:40:23 -0400 Subject: [PATCH] feat: add cl vault (#445) Co-authored-by: LaurensKubat <32776056+LaurensKubat@users.noreply.github.com> Co-authored-by: nahem Co-authored-by: magiodev <31893902+magiodev@users.noreply.github.com> --- .github/workflows/build.yml | 32 +- demos/cl-vault-demo/.gitignore | 1 + demos/cl-vault-demo/demo.md | 18 + demos/cl-vault-demo/keys/osmo.key | 1 + demos/cl-vault-demo/local_execute_contract.sh | 44 + demos/cl-vault-demo/osmo_localnet.sh | 257 ++ demos/cl-vault-demo/osmosis.yml | 34 + .../cl-vault-demo/testnet_execute_contract.sh | 44 + demos/orion-manual-demo/sample_pool2.json | 2 +- demos/orion-manual-demo/sample_pool3.json | 2 +- smart-contracts/.cargo/config | 3 +- smart-contracts/Cargo.lock | 2392 +++++++++++++---- smart-contracts/Cargo.toml | 10 +- .../contracts/cl-vault/.cargo/config | 6 + .../.circleci/config.yml | 0 .../.editorconfig | 0 .../.github/workflows/Basic.yml | 4 +- .../cl-vault/.github/workflows/Release.yml | 35 + .../.gitignore | 2 + .../Cargo.toml | 43 +- .../LICENSE | 0 .../NOTICE | 2 +- smart-contracts/contracts/cl-vault/README.md | 157 ++ .../contracts/cl-vault/src/bin/schema.rs | 11 + .../contracts/cl-vault/src/contract.rs | 154 ++ .../contracts/cl-vault/src/error.rs | 120 + .../contracts/cl-vault/src/helpers.rs | 453 ++++ .../contracts/cl-vault/src/instantiate.rs | 139 + smart-contracts/contracts/cl-vault/src/lib.rs | 26 + .../contracts/cl-vault/src/math/liquidity.rs | 121 + .../contracts/cl-vault/src/math/mod.rs | 2 + .../contracts/cl-vault/src/math/tick.rs | 481 ++++ smart-contracts/contracts/cl-vault/src/msg.rs | 124 + .../contracts/cl-vault/src/query.rs | 148 + .../contracts/cl-vault/src/reply.rs | 36 + .../cl-vault/src/rewards/distribution.rs | 513 ++++ .../contracts/cl-vault/src/rewards/helpers.rs | 350 +++ .../contracts/cl-vault/src/rewards/mod.rs | 5 + .../contracts/cl-vault/src/state.rs | 145 + .../contracts/cl-vault/src/test_helpers.rs | 128 + .../contracts/cl-vault/src/test_tube/admin.rs | 12 + .../src/test_tube/deposit_withdraw.rs | 128 + .../cl-vault/src/test_tube/initialize.rs | 295 ++ .../contracts/cl-vault/src/test_tube/mod.rs | 9 + .../cl-vault/src/test_tube/proptest.rs | 431 +++ .../contracts/cl-vault/src/test_tube/range.rs | 278 ++ .../cl-vault/src/test_tube/rewards.rs | 160 ++ .../contracts/cl-vault/src/vault/admin.rs | 424 +++ .../contracts/cl-vault/src/vault/claim.rs | 30 + .../src/vault/concentrated_liquidity.rs | 179 ++ .../contracts/cl-vault/src/vault/deposit.rs | 568 ++++ .../contracts/cl-vault/src/vault/merge.rs | 237 ++ .../contracts/cl-vault/src/vault/mod.rs | 8 + .../contracts/cl-vault/src/vault/range.rs | 759 ++++++ .../contracts/cl-vault/src/vault/swap.rs | 200 ++ .../contracts/cl-vault/src/vault/withdraw.rs | 177 ++ .../contracts/lp-strategy/src/admin.rs | 2 +- .../contracts/lp-strategy/src/ibc.rs | 2 +- .../contracts/multihop-router/src/route.rs | 27 - .../qoracle-bindings-test/.cargo/config | 4 - .../qoracle-bindings-test/.gitpod.Dockerfile | 17 - .../qoracle-bindings-test/.gitpod.yml | 10 - .../qoracle-bindings-test/Cargo.lock | 738 ----- .../qoracle-bindings-test/Developing.md | 104 - .../qoracle-bindings-test/Importing.md | 62 - .../qoracle-bindings-test/Publishing.md | 115 - .../contracts/qoracle-bindings-test/README.md | 100 - .../qoracle-bindings-test/examples/schema.rs | 19 - .../qoracle-bindings-test/rustfmt.toml | 15 - .../schema/execute_msg.json | 39 - .../schema/get_count_response.json | 14 - .../schema/instantiate_msg.json | 14 - .../schema/query_msg.json | 18 - .../qoracle-bindings-test/schema/state.json | 24 - .../qoracle-bindings-test/src/contract.rs | 47 - .../qoracle-bindings-test/src/error.rs | 16 - .../qoracle-bindings-test/src/execute.rs | 58 - .../qoracle-bindings-test/src/ibc.rs | 6 - .../src/integration_tests.rs | 70 - .../qoracle-bindings-test/src/lib.rs | 7 - .../qoracle-bindings-test/src/msg.rs | 17 - .../qoracle-bindings-test/src/state.rs | 14 - .../contracts/qoracle-bindings-test/test.sh | 0 .../contracts/strategy/.cargo/config | 4 - smart-contracts/contracts/strategy/Cargo.toml | 24 - smart-contracts/contracts/strategy/README.md | 9 - .../contracts/strategy/src/contract.rs | 195 -- .../contracts/strategy/src/error.rs | 20 - .../contracts/strategy/src/helpers.rs | 9 - smart-contracts/contracts/strategy/src/ibc.rs | 341 --- smart-contracts/contracts/strategy/src/lib.rs | 14 - smart-contracts/contracts/strategy/src/msg.rs | 38 - .../contracts/strategy/src/queue.rs | 103 - .../contracts/strategy/src/state.rs | 18 - .../contracts/strategy/src/strategy.rs | 17 - smart-contracts/test/yarn.lock | 478 ++++ 96 files changed, 9896 insertions(+), 2873 deletions(-) create mode 100644 demos/cl-vault-demo/.gitignore create mode 100644 demos/cl-vault-demo/demo.md create mode 100644 demos/cl-vault-demo/keys/osmo.key create mode 100755 demos/cl-vault-demo/local_execute_contract.sh create mode 100755 demos/cl-vault-demo/osmo_localnet.sh create mode 100644 demos/cl-vault-demo/osmosis.yml create mode 100755 demos/cl-vault-demo/testnet_execute_contract.sh create mode 100644 smart-contracts/contracts/cl-vault/.cargo/config rename smart-contracts/contracts/{qoracle-bindings-test => cl-vault}/.circleci/config.yml (100%) rename smart-contracts/contracts/{qoracle-bindings-test => cl-vault}/.editorconfig (100%) rename smart-contracts/contracts/{qoracle-bindings-test => cl-vault}/.github/workflows/Basic.yml (96%) create mode 100644 smart-contracts/contracts/cl-vault/.github/workflows/Release.yml rename smart-contracts/contracts/{qoracle-bindings-test => cl-vault}/.gitignore (89%) rename smart-contracts/contracts/{qoracle-bindings-test => cl-vault}/Cargo.toml (50%) rename smart-contracts/contracts/{qoracle-bindings-test => cl-vault}/LICENSE (100%) rename smart-contracts/contracts/{qoracle-bindings-test => cl-vault}/NOTICE (87%) create mode 100644 smart-contracts/contracts/cl-vault/README.md create mode 100644 smart-contracts/contracts/cl-vault/src/bin/schema.rs create mode 100644 smart-contracts/contracts/cl-vault/src/contract.rs create mode 100644 smart-contracts/contracts/cl-vault/src/error.rs create mode 100644 smart-contracts/contracts/cl-vault/src/helpers.rs create mode 100644 smart-contracts/contracts/cl-vault/src/instantiate.rs create mode 100644 smart-contracts/contracts/cl-vault/src/lib.rs create mode 100644 smart-contracts/contracts/cl-vault/src/math/liquidity.rs create mode 100644 smart-contracts/contracts/cl-vault/src/math/mod.rs create mode 100644 smart-contracts/contracts/cl-vault/src/math/tick.rs create mode 100644 smart-contracts/contracts/cl-vault/src/msg.rs create mode 100644 smart-contracts/contracts/cl-vault/src/query.rs create mode 100644 smart-contracts/contracts/cl-vault/src/reply.rs create mode 100644 smart-contracts/contracts/cl-vault/src/rewards/distribution.rs create mode 100644 smart-contracts/contracts/cl-vault/src/rewards/helpers.rs create mode 100644 smart-contracts/contracts/cl-vault/src/rewards/mod.rs create mode 100644 smart-contracts/contracts/cl-vault/src/state.rs create mode 100644 smart-contracts/contracts/cl-vault/src/test_helpers.rs create mode 100644 smart-contracts/contracts/cl-vault/src/test_tube/admin.rs create mode 100644 smart-contracts/contracts/cl-vault/src/test_tube/deposit_withdraw.rs create mode 100644 smart-contracts/contracts/cl-vault/src/test_tube/initialize.rs create mode 100644 smart-contracts/contracts/cl-vault/src/test_tube/mod.rs create mode 100644 smart-contracts/contracts/cl-vault/src/test_tube/proptest.rs create mode 100644 smart-contracts/contracts/cl-vault/src/test_tube/range.rs create mode 100644 smart-contracts/contracts/cl-vault/src/test_tube/rewards.rs create mode 100644 smart-contracts/contracts/cl-vault/src/vault/admin.rs create mode 100644 smart-contracts/contracts/cl-vault/src/vault/claim.rs create mode 100644 smart-contracts/contracts/cl-vault/src/vault/concentrated_liquidity.rs create mode 100644 smart-contracts/contracts/cl-vault/src/vault/deposit.rs create mode 100644 smart-contracts/contracts/cl-vault/src/vault/merge.rs create mode 100644 smart-contracts/contracts/cl-vault/src/vault/mod.rs create mode 100644 smart-contracts/contracts/cl-vault/src/vault/range.rs create mode 100644 smart-contracts/contracts/cl-vault/src/vault/swap.rs create mode 100644 smart-contracts/contracts/cl-vault/src/vault/withdraw.rs delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/.cargo/config delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/.gitpod.Dockerfile delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/.gitpod.yml delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/Cargo.lock delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/Developing.md delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/Importing.md delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/Publishing.md delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/README.md delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/examples/schema.rs delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/rustfmt.toml delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/schema/execute_msg.json delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/schema/get_count_response.json delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/schema/instantiate_msg.json delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/schema/query_msg.json delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/schema/state.json delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/src/contract.rs delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/src/error.rs delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/src/execute.rs delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/src/ibc.rs delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/src/integration_tests.rs delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/src/lib.rs delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/src/msg.rs delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/src/state.rs delete mode 100644 smart-contracts/contracts/qoracle-bindings-test/test.sh delete mode 100644 smart-contracts/contracts/strategy/.cargo/config delete mode 100644 smart-contracts/contracts/strategy/Cargo.toml delete mode 100644 smart-contracts/contracts/strategy/README.md delete mode 100644 smart-contracts/contracts/strategy/src/contract.rs delete mode 100644 smart-contracts/contracts/strategy/src/error.rs delete mode 100644 smart-contracts/contracts/strategy/src/helpers.rs delete mode 100644 smart-contracts/contracts/strategy/src/ibc.rs delete mode 100644 smart-contracts/contracts/strategy/src/lib.rs delete mode 100644 smart-contracts/contracts/strategy/src/msg.rs delete mode 100644 smart-contracts/contracts/strategy/src/queue.rs delete mode 100644 smart-contracts/contracts/strategy/src/state.rs delete mode 100644 smart-contracts/contracts/strategy/src/strategy.rs create mode 100644 smart-contracts/test/yarn.lock diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e8213d546..495b5f902 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,6 @@ jobs: uses: technote-space/get-diff-action@v6.1.2 with: PATTERNS: | - **/**.rs **/**.go go.mod go.sum @@ -81,3 +80,34 @@ jobs: path: | smart-contracts/artifacts/basic_vault.wasm smart-contracts/artifacts/lp_strategy.wasm + smart-contracts/artifacts/cl_vault.wasm + test-test-tube: + runs-on: ubuntu-latest + needs: build-rust + steps: + - name: Check out repository code + uses: actions/checkout@v3 + - name: Get git diff + uses: technote-space/get-diff-action@v6.1.2 + with: + PATTERNS: | + **/**.rs + Makefile + .github/workflows/build.yml + - name: Download contracts + if: env.GIT_DIFF + uses: actions/download-artifact@v3 + with: + name: + smart-contracts + - name: Install Rust + if: env.GIT_DIFF + uses: dtolnay/rust-toolchain@stable + - name: the classic ls + run: ls -R + - name: Move the cl-vault + if: env.GIT_DIFF + run: mkdir -p smart-contracts/contracts/cl-vault/test-tube-build/wasm32-unknown-unknown/release && mv ./cl_vault.wasm smart-contracts/contracts/cl-vault/test-tube-build/wasm32-unknown-unknown/release/cl_vault.wasm + - name: Run test-tube tests + if: env.GIT_DIFF + run: cd smart-contracts && cargo test --package cl-vault --lib -- test_tube::* --nocapture --ignored \ No newline at end of file diff --git a/demos/cl-vault-demo/.gitignore b/demos/cl-vault-demo/.gitignore new file mode 100644 index 000000000..cd3d22536 --- /dev/null +++ b/demos/cl-vault-demo/.gitignore @@ -0,0 +1 @@ +logs \ No newline at end of file diff --git a/demos/cl-vault-demo/demo.md b/demos/cl-vault-demo/demo.md new file mode 100644 index 000000000..0a85dc48e --- /dev/null +++ b/demos/cl-vault-demo/demo.md @@ -0,0 +1,18 @@ +# CL-vault demo + +In this demo we instantiate and deposit into a CL-pool using the cl-vault contract. + + + +## Setup + +```zsh +./osmo_localnet.sh +./local_execute_contract.sh +``` + +## For testnet + +```zsh +./testnet_execute_contract.sh +``` diff --git a/demos/cl-vault-demo/keys/osmo.key b/demos/cl-vault-demo/keys/osmo.key new file mode 100644 index 000000000..771f7031a --- /dev/null +++ b/demos/cl-vault-demo/keys/osmo.key @@ -0,0 +1 @@ +rabbit garlic monitor wish pony magic budget someone room torch celery empower word assume digital rack electric weapon urban foot sketch jelly wet myself diff --git a/demos/cl-vault-demo/local_execute_contract.sh b/demos/cl-vault-demo/local_execute_contract.sh new file mode 100755 index 000000000..0ae128b99 --- /dev/null +++ b/demos/cl-vault-demo/local_execute_contract.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +set -e + +on_error() { + echo "Some error occurred" + + afplay /System/Library/Sounds/Sosumi.aiff + say -r 60 you suck +} + +trap 'on_error' ERR + +CHAIN_ID="osmosis" +TESTNET_NAME="osmosis" +FEE_DENOM="uosmo" +RPC="http://127.0.0.1:26679" +NODE="--node $RPC" +TXFLAG="$NODE --chain-id $CHAIN_ID --gas-prices 10$FEE_DENOM --gas auto --gas-adjustment 1.3" +echo $NODE + +# Alice: osmo1sqlsc5024sszglyh7pswk5hfpc5xtl77wcmrz0 +# Bob: osmo1ez43ye5qn3q2zwh8uvswppvducwnkq6wjqc87d +INIT='{"thesis":"hello world","name":"Distilled","admin":"osmo1sqlsc5024sszglyh7pswk5hfpc5xtl77wcmrz0","range_admin":"osmo1sqlsc5024sszglyh7pswk5hfpc5xtl77wcmrz0","pool_id":1,"config":{"performance_fee":"0.1","treasury":"osmo1sqlsc5024sszglyh7pswk5hfpc5xtl77wcmrz0","swap_max_slippage":"0.01"},"vault_token_subdenom":"test-cl-vault-1","initial_lower_tick":0,"initial_upper_tick":100000}' + +cd ../../smart-contracts + +# 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/workspace-optimizer-arm64:0.12.11 + +echo "Running store code" +RES=$(osmosisd tx wasm store artifacts/cl_vault-aarch64.wasm --from bob --keyring-backend test -y --output json -b block $TXFLAG) +CODE_ID=$(echo $RES | jq -r '.logs[0].events[-1].attributes[1].value') +echo "Got CODE_ID = $CODE_ID" + +echo "Deploying contract" +# swallow output +OUT1=$(osmosisd tx wasm instantiate $CODE_ID "$INIT" --from bob --keyring-backend test --label "my first contract" --gas-prices 10$FEE_DENOM --gas auto --gas-adjustment 1.3 -b block -y --admin osmo1sqlsc5024sszglyh7pswk5hfpc5xtl77wcmrz0 $NODE --chain-id $CHAIN_ID) +ADDR1=$(osmosisd query wasm list-contract-by-code $CODE_ID --output json $NODE | jq -r '.contracts[0]') +echo "Got address of deployed contract = $ADDR1" + +afplay /System/Library/Sounds/Funk.aiff +say "I want to die" + +cd - diff --git a/demos/cl-vault-demo/osmo_localnet.sh b/demos/cl-vault-demo/osmo_localnet.sh new file mode 100755 index 000000000..9855707b0 --- /dev/null +++ b/demos/cl-vault-demo/osmo_localnet.sh @@ -0,0 +1,257 @@ +#!/bin/sh + +# Configure variables +BINARY=osmosisd +HOME_OSMOSIS=$HOME/.osmosis +CHAIN_ID=osmosis +ALICE="cruise scene law sea push expose scorpion wire trick repair wave quote task dose inner denial alpha favorite certain blouse motion flash split lunch" +BOB="lizard garlic canyon winner cheese tent drip task because ecology clay bridge junk critic track artefact gather harsh deliver unit vacant earth diesel stool" +USER_1="guard cream sadness conduct invite crumble clock pudding hole grit liar hotel maid produce squeeze return argue turtle know drive eight casino maze host" +USER_2="fuel obscure melt april direct second usual hair leave hobby beef bacon solid drum used law mercy worry fat super must ritual bring faculty" +RELAYER_ACC="$(cat ./keys/osmo.key)" + +mkdir logs + +ALICE_GENESIS_COINS=20000000uosmo,2000000000stake,1000000000000fakestake +BOB_GENESIS_COINS=10000000000000uosmo,1000000000stake,1000000000000fakestake,100000000000000usdc +USER_1_GENESIS_COINS=10000000000stake,10000000000uosmo +USER_2_GENESIS_COINS=10000000000stake,10000000000uosmo +RELAYER_ACC_GENESIS_COINS=10000000uosmo,10000000000stake + +echo $HOME_OSMOSIS + +rm -rf $HOME_OSMOSIS +# Bootstrap +$BINARY init $CHAIN_ID --chain-id $CHAIN_ID --home $HOME_OSMOSIS + +echo $ALICE | $BINARY keys add alice --keyring-backend test --recover --home $HOME_OSMOSIS +echo $BOB | $BINARY keys add bob --keyring-backend test --recover --home $HOME_OSMOSIS +echo $USER_1 | $BINARY keys add user1 --keyring-backend test --recover --home $HOME_OSMOSIS +echo $USER_2 | $BINARY keys add user2 --keyring-backend test --recover --home $HOME_OSMOSIS +echo $RELAYER_ACC | $BINARY keys add relayer_acc --keyring-backend test --recover --home $HOME_OSMOSIS +$BINARY add-genesis-account $($BINARY keys show alice --keyring-backend test -a --home $HOME_OSMOSIS) $ALICE_GENESIS_COINS --home $HOME_OSMOSIS +$BINARY add-genesis-account $($BINARY keys show bob --keyring-backend test -a --home $HOME_OSMOSIS) $BOB_GENESIS_COINS --home $HOME_OSMOSIS +$BINARY add-genesis-account $($BINARY keys show user1 --keyring-backend test -a --home $HOME_OSMOSIS) $USER_1_GENESIS_COINS --home $HOME_OSMOSIS +$BINARY add-genesis-account $($BINARY keys show user2 --keyring-backend test -a --home $HOME_OSMOSIS) $USER_2_GENESIS_COINS --home $HOME_OSMOSIS +$BINARY add-genesis-account $($BINARY keys show relayer_acc --keyring-backend test -a --home $HOME_OSMOSIS) $RELAYER_ACC_GENESIS_COINS --home $HOME_OSMOSIS +$BINARY add-genesis-account osmo15td5pfjkmfn8d6l4t8dc67l3apgt9epw4ct298 $RELAYER_ACC_GENESIS_COINS --home $HOME_OSMOSIS +$BINARY gentx alice 10000000uosmo --chain-id $CHAIN_ID --keyring-backend test --home $HOME_OSMOSIS +$BINARY collect-gentxs --home $HOME_OSMOSIS + +# Check platform +platform='unknown' +unamestr=$(uname) +if [ "$unamestr" = 'Linux' ]; then + platform='linux' +elif [ "$unamestr" = 'Darwin' ]; then + platform='macos' +fi + +if [ $platform = 'linux' ]; then + sed -i 's/enable = false/enable = true/g' $HOME_OSMOSIS/config/app.toml + sed -i 's/swagger = false/swagger = true/g' $HOME_OSMOSIS/config/app.toml + sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0uosmo"/g' $HOME_OSMOSIS/config/app.toml + sed -i 's+laddr = "tcp://127.0.0.1:26657"+laddr = "tcp://127.0.0.1:26679"+g' $HOME_OSMOSIS/config/config.toml + sed -i 's+node = "tcp://localhost:26657"+node = "tcp://localhost:26679"+g' $HOME_OSMOSIS/config/client.toml + sed -i 's+laddr = "tcp://0.0.0.0:26656"+laddr = "tcp://0.0.0.0:26662"+g' $HOME_OSMOSIS/config/config.toml + sed -i 's+pprof_laddr = "localhost:6060"+pprof_laddr = "localhost:6062"+g' $HOME_OSMOSIS/config/config.toml + sed -i 's+address = "0.0.0.0:9090"+address = "0.0.0.0:9096"+g' $HOME_OSMOSIS/config/app.toml + sed -i 's+address = "0.0.0.0:9091"+address = "0.0.0.0:8092"+g' $HOME_OSMOSIS/config/app.toml + sed -i 's+address = "tcp://0.0.0.0:1317"+address = "tcp://0.0.0.0:1312"+g' $HOME_OSMOSIS/config/app.toml + sed -i 's+address = ":8080"+address = ":8082"+g' $HOME_OSMOSIS/config/app.toml +elif [ $platform = 'macos' ]; then + sed -i'.original' -e 's/enable = false/enable = true/g' $HOME_OSMOSIS/config/app.toml + sed -i'.original' -e 's/swagger = false/swagger = true/g' $HOME_OSMOSIS/config/app.toml + sed -i'.original' -e 's/minimum-gas-prices = ""/minimum-gas-prices = "0uosmo"/g' $HOME_OSMOSIS/config/app.toml + sed -i'.original' -e 's+laddr = "tcp://127.0.0.1:26657"+laddr = "tcp://127.0.0.1:26679"+g' $HOME_OSMOSIS/config/config.toml + sed -i'.original' -e 's+node = "tcp://localhost:26657"+node = "tcp://localhost:26679"+g' $HOME_OSMOSIS/config/client.toml + sed -i'.original' -e 's+laddr = "tcp://0.0.0.0:26656"+laddr = "tcp://0.0.0.0:26662"+g' $HOME_OSMOSIS/config/config.toml + sed -i'.original' -e 's+pprof_laddr = "localhost:6060"+pprof_laddr = "localhost:6062"+g' $HOME_OSMOSIS/config/config.toml + sed -i'.original' -e 's+address = "0.0.0.0:9090"+address = "0.0.0.0:9096"+g' $HOME_OSMOSIS/config/app.toml + sed -i'.original' -e 's+address = "0.0.0.0:9091"+address = "0.0.0.0:8092"+g' $HOME_OSMOSIS/config/app.toml + sed -i'.original' -e 's+address = "tcp://0.0.0.0:1317"+address = "tcp://0.0.0.0:1312"+g' $HOME_OSMOSIS/config/app.toml + sed -i'.original' -e 's+address = ":8080"+address = ":8082"+g' $HOME_OSMOSIS/config/app.toml +else + echo "only linux and macos platforms are supported, if you are using other platforms you should probably improve this script." + + exit 1 + sed -i '' 's/enable = false/enable = true/g' $HOME_OSMOSIS/config/app.toml + sed -i '' 's/swagger = false/swagger = true/g' $HOME_OSMOSIS/config/app.toml +fi + +cp $HOME_OSMOSIS/config/genesis.json $HOME_OSMOSIS/config/genesis_original.json +cat $HOME_OSMOSIS/config/genesis_original.json | + jq '.app_state.crisis.constant_fee.denom="uosmo"' | + jq '.app_state.staking.params.bond_denom="uosmo"' | + jq '.app_state.mint = { + minter: { + epoch_provisions: "0.000000000000000000" + }, + params: { + distribution_proportions: { + community_pool: "0.100000000000000000", + developer_rewards: "0.200000000000000000", + pool_incentives: "0.300000000000000000", + staking: "0.400000000000000000" + }, + epoch_identifier: "day", + genesis_epoch_provisions: "5000000.000000000000000000", + mint_denom: "uosmo", + minting_rewards_distribution_start_epoch: "0", + reduction_factor: "0.500000000000000000", + reduction_period_in_epochs: "156", + weighted_developer_rewards_receivers: [] + } + }' | + jq '.app_state.incentives = { + last_gauge_id: "0", + lockable_durations: [ + "1s", + "120s", + "180s", + "240s" + ], + params: { + distr_epoch_identifier: "day" + } + }' | + jq '.app_state.poolincentives = { + distr_info: { + records: [ + { + gauge_id: "0", + weight: "10000" + }, + { + gauge_id: "1", + weight: "1000" + }, + { + gauge_id: "2", + weight: "100" + } + ], + total_weight: "11100" + }, + lockable_durations: [ + "120s", + "180s", + "240s" + ], + params: { + minted_denom: "uosmo" + } + }' | + jq '.app_state.txfees.basedenom="uosmo"' | + jq '.app_state.gov.deposit_params.min_deposit=[{denom:"uosmo",amount:"1"}]' | + jq '.app_state.gov.voting_params.voting_period="30s"' | + jq '.app_state.gov.tally_params={quorum:"0.000000000000000001",threshold:"0.5",veto_threshold:"0.334"}' | + jq '.app_state.interchainaccounts = { + host_genesis_state: { + port: "icahost", + params: { + host_enabled: true, + allow_messages: [ + "/ibc.applications.transfer.v1.MsgTransfer", + "/cosmos.bank.v1beta1.MsgSend", + "/cosmos.staking.v1beta1.MsgDelegate", + "/cosmos.staking.v1beta1.MsgBeginRedelegate", + "/cosmos.staking.v1beta1.MsgCreateValidator", + "/cosmos.staking.v1beta1.MsgEditValidator", + "/cosmos.staking.v1beta1.MsgUndelegate", + "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward", + "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress", + "/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission", + "/cosmos.distribution.v1beta1.MsgFundCommunityPool", + "/cosmos.gov.v1beta1.MsgVote", + "/osmosis.gamm.v1beta1.MsgJoinPool", + "/osmosis.gamm.v1beta1.MsgExitPool", + "/osmosis.gamm.v1beta1.MsgSwapExactAmountIn", + "/osmosis.gamm.v1beta1.MsgSwapExactAmountOut", + "/osmosis.gamm.v1beta1.MsgJoinSwapExternAmountIn", + "/osmosis.gamm.v1beta1.MsgJoinSwapShareAmountOut", + "/osmosis.gamm.v1beta1.MsgExitSwapExternAmountOut", + "/osmosis.gamm.v1beta1.MsgExitSwapShareAmountIn", + "/osmosis.lockup.MsgBeginUnlocking", + "/osmosis.lockup.MsgLockTokens", + "/osmosis.superfluid.MsgSuperfluidUnbondLock" + ] + } + } + }' | + jq '.app_state.interchainquery = { + host_port: "icqhost", + params: { + host_enabled: true, + allow_queries: [ + "/ibc.applications.transfer.v1.Query/DenomTrace", + "/cosmos.auth.v1beta1.Query/Account", + "/cosmos.auth.v1beta1.Query/Params", + "/cosmos.bank.v1beta1.Query/Balance", + "/cosmos.bank.v1beta1.Query/DenomMetadata", + "/cosmos.bank.v1beta1.Query/Params", + "/cosmos.bank.v1beta1.Query/SupplyOf", + "/cosmos.distribution.v1beta1.Query/Params", + "/cosmos.distribution.v1beta1.Query/DelegatorWithdrawAddress", + "/cosmos.distribution.v1beta1.Query/ValidatorCommission", + "/cosmos.gov.v1beta1.Query/Deposit", + "/cosmos.gov.v1beta1.Query/Params", + "/cosmos.gov.v1beta1.Query/Vote", + "/cosmos.slashing.v1beta1.Query/Params", + "/cosmos.slashing.v1beta1.Query/SigningInfo", + "/cosmos.staking.v1beta1.Query/Delegation", + "/cosmos.staking.v1beta1.Query/Params", + "/cosmos.staking.v1beta1.Query/Validator", + "/osmosis.epochs.v1beta1.Query/EpochInfos", + "/osmosis.epochs.v1beta1.Query/CurrentEpoch", + "/osmosis.gamm.v1beta1.Query/NumPools", + "/osmosis.gamm.v1beta1.Query/TotalLiquidity", + "/osmosis.gamm.v1beta1.Query/Pool", + "/osmosis.gamm.v1beta1.Query/PoolParams", + "/osmosis.gamm.v1beta1.Query/TotalPoolLiquidity", + "/osmosis.gamm.v1beta1.Query/TotalShares", + "/osmosis.gamm.v1beta1.Query/CalcJoinPoolShares", + "/osmosis.gamm.v1beta1.Query/CalcExitPoolCoinsFromShares", + "/osmosis.gamm.v1beta1.Query/CalcJoinPoolNoSwapShares", + "/osmosis.gamm.v1beta1.Query/PoolType", + "/osmosis.gamm.v2.Query/SpotPrice", + "/osmosis.gamm.v1beta1.Query/EstimateSwapExactAmountIn", + "/osmosis.gamm.v1beta1.Query/EstimateSwapExactAmountOut", + "/osmosis.incentives.Query/ModuleToDistributeCoins", + "/osmosis.incentives.Query/LockableDurations", + "/osmosis.lockup.Query/ModuleBalance", + "/osmosis.lockup.Query/ModuleLockedAmount", + "/osmosis.lockup.Query/AccountUnlockableCoins", + "/osmosis.lockup.Query/AccountUnlockingCoins", + "/osmosis.lockup.Query/LockedDenom", + "/osmosis.lockup.Query/LockedByID", + "/osmosis.lockup.Query/NextLockID", + "/osmosis.mint.v1beta1.Query/EpochProvisions", + "/osmosis.mint.v1beta1.Query/Params", + "/osmosis.poolincentives.v1beta1.Query/GaugeIds", + "/osmosis.superfluid.Query/Params", + "/osmosis.superfluid.Query/AssetType", + "/osmosis.superfluid.Query/AllAssets", + "/osmosis.superfluid.Query/AssetMultiplier", + "/osmosis.poolmanager.v1beta1.Query/NumPools", + "/osmosis.poolmanager.v1beta1.Query/EstimateSwapExactAmountIn", + "/osmosis.poolmanager.v1beta1.Query/EstimateSwapExactAmountOut", + "/osmosis.txfees.v1beta1.Query/FeeTokens", + "/osmosis.txfees.v1beta1.Query/DenomSpotPrice", + "/osmosis.txfees.v1beta1.Query/DenomPoolId", + "/osmosis.txfees.v1beta1.Query/BaseDenom", + "/osmosis.tokenfactory.v1beta1.Query/Params", + "/osmosis.tokenfactory.v1beta1.Query/DenomAuthorityMetadata", + "/osmosis.twap.v1beta1.Query/ArithmeticTwap", + "/osmosis.twap.v1beta1.Query/ArithmeticTwapToNow", + "/osmosis.twap.v1beta1.Query/GeometricTwap", + "/osmosis.twap.v1beta1.Query/GeometricTwapToNow", + "/osmosis.twap.v1beta1.Query/Params", + "/osmosis.downtimedetector.v1beta1.Query/RecoveredSinceDowntimeOfLength" + ] + } + }' \ + >$HOME_OSMOSIS/config/genesis.json + +# Start +$BINARY start --home $HOME_OSMOSIS >>./logs/osmo_localnet.log 2>&1 diff --git a/demos/cl-vault-demo/osmosis.yml b/demos/cl-vault-demo/osmosis.yml new file mode 100644 index 000000000..b0ca8c6eb --- /dev/null +++ b/demos/cl-vault-demo/osmosis.yml @@ -0,0 +1,34 @@ +version: 1 +accounts: + - name: alice + mnemonic: cruise scene law sea push expose scorpion wire trick repair wave quote task dose inner denial alpha favorite certain blouse motion flash split lunch + coins: ["20000000000000uosmo", "200000000000stake"] + - name: bob + mnemonic: lizard garlic canyon winner cheese tent drip task because ecology clay bridge junk critic track artefact gather harsh deliver unit vacant earth diesel stool + coins: ["1000000000000000uosmo", "1000000000stake"] + - name: relayer_acc + mnemonic: rabbit garlic monitor wish pony magic budget someone room torch celery empower word assume digital rack electric weapon urban foot sketch jelly wet myself + # corresponding address is osmo194580p9pyxakf3y3nqqk9hc3w9a7x0yrnv7wcz + coins: ["100000stake", "10000000000000uosmo"] +validator: + name: alice + staked: "100000000stake" +faucet: + host: ":4501" + name: bob + coins: ["100000000000uosmo", "100000000stake"] +host: + rpc: ":26679" + p2p: ":26662" + prof: ":6062" + grpc: ":9096" + grpc-web: ":8092" + api: ":1312" + frontend: ":8082" + dev-ui: ":12352" +genesis: + chain_id: "osmosis" +#init: +# home: "${home_dir}" +build: + main: "cmd/osmosisd" diff --git a/demos/cl-vault-demo/testnet_execute_contract.sh b/demos/cl-vault-demo/testnet_execute_contract.sh new file mode 100755 index 000000000..b9a81e3c0 --- /dev/null +++ b/demos/cl-vault-demo/testnet_execute_contract.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +set -e + +on_error() { + echo "Some error occurred" + + afplay /System/Library/Sounds/Sosumi.aiff + say -r 60 you suck +} + +trap 'on_error' ERR + +CHAIN_ID="osmo-test-5" +TESTNET_NAME="osmosis" +FEE_DENOM="uosmo" +RPC="https://rpc.testnet.osmosis.zone:443" +NODE="--node $RPC" +TXFLAG="$NODE --chain-id $CHAIN_ID --gas-prices 10$FEE_DENOM --gas auto --gas-adjustment 1.3" +echo $NODE + +# Alice: osmo1sqlsc5024sszglyh7pswk5hfpc5xtl77wcmrz0 +# Bob: osmo1ez43ye5qn3q2zwh8uvswppvducwnkq6wjqc87d +INIT='{"thesis":"hello world","name":"Distilled","admin":"osmo1sqlsc5024sszglyh7pswk5hfpc5xtl77wcmrz0","range_admin":"osmo1sqlsc5024sszglyh7pswk5hfpc5xtl77wcmrz0","pool_id":1,"config":{"performance_fee":"0.1","treasury":"osmo1sqlsc5024sszglyh7pswk5hfpc5xtl77wcmrz0","swap_max_slippage":"0.01"},"vault_token_subdenom":"distilled","initial_lower_tick":0,"initial_upper_tick":100000}' + +cd ../../smart-contracts + +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/workspace-optimizer-arm64:0.12.11 + +echo "Running store code" +RES=$(osmosisd tx wasm store artifacts/cl_vault-aarch64.wasm --from bob --keyring-backend test -y --output json -b block $TXFLAG) +CODE_ID=$(echo $RES | jq -r '.logs[0].events[-1].attributes[1].value') +echo "Got CODE_ID = $CODE_ID" + +echo "Deploying contract" +# swallow output +OUT1=$(osmosisd tx wasm instantiate $CODE_ID "$INIT" --from bob --keyring-backend test --label "my first contract" --gas-prices 10$FEE_DENOM --gas auto --gas-adjustment 1.3 -b block -y --admin osmo1sqlsc5024sszglyh7pswk5hfpc5xtl77wcmrz0 $NODE --chain-id $CHAIN_ID) +ADDR1=$(osmosisd query wasm list-contract-by-code $CODE_ID --output json $NODE | jq -r '.contracts[0]') +echo "Got address of deployed contract = $ADDR1" + +afplay /System/Library/Sounds/Funk.aiff +say "I want to die" + +cd - diff --git a/demos/orion-manual-demo/sample_pool2.json b/demos/orion-manual-demo/sample_pool2.json index 0d761dbd7..4bbcb95fe 100644 --- a/demos/orion-manual-demo/sample_pool2.json +++ b/demos/orion-manual-demo/sample_pool2.json @@ -2,6 +2,6 @@ "weights": "4uosmo,4usdc", "initial-deposit": "100000000uosmo,100000000usdc", "swap-fee": "0.01", - "exit-fee": "0.0", + "exit-fee": "0.00", "future-governor": "168h" } \ No newline at end of file diff --git a/demos/orion-manual-demo/sample_pool3.json b/demos/orion-manual-demo/sample_pool3.json index cf2bb2fba..94465073a 100644 --- a/demos/orion-manual-demo/sample_pool3.json +++ b/demos/orion-manual-demo/sample_pool3.json @@ -2,6 +2,6 @@ "weights": "4fakestake,4uosmo", "initial-deposit": "100000000fakestake,100000000uosmo", "swap-fee": "0.01", - "exit-fee": "0.0", + "exit-fee": "0.00", "future-governor": "168h" } \ No newline at end of file diff --git a/smart-contracts/.cargo/config b/smart-contracts/.cargo/config index 96b0e78ce..fcfdb2f1a 100644 --- a/smart-contracts/.cargo/config +++ b/smart-contracts/.cargo/config @@ -1,7 +1,8 @@ [alias] wasm = "build --release --target wasm32-unknown-unknown" schema = "run --example schema" -lint = "clippy -- -D warnings --A deprecated" +lint = "clippy -- -D warnings" +lint-fix = "clippy --fix -- -D warnings" unit-test = "test --lib" test-vault = "test --lib -p basic-vault" clip = "clippy -- --D warnings --A deprecated" \ No newline at end of file diff --git a/smart-contracts/Cargo.lock b/smart-contracts/Cargo.lock index 8152ac59c..681698190 100644 --- a/smart-contracts/Cargo.lock +++ b/smart-contracts/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "ahash" version = "0.7.6" @@ -14,27 +29,78 @@ dependencies = [ ] [[package]] -name = "ahash" -version = "0.8.3" +name = "aho-corasick" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" dependencies = [ - "cfg-if", - "once_cell", - "version_check", + "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "anyhow" -version = "1.0.69" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "apollo-cw-asset" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "423502406a307052f6877030f48b5fb4e9fb338fc5e7c8ca1064210def52876b" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 1.1.0", + "cw20 1.1.0", + "schemars", + "serde", +] + +[[package]] +name = "apollo-utils" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +checksum = "4b05ef4d29df7a8d2459008f09ba1c9f1f0aa7ac1bdfaa510029c0cf6921c2c2" +dependencies = [ + "apollo-cw-asset", + "cosmwasm-schema", + "cosmwasm-std", + "cw20 1.1.0", +] [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "async-trait" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "atty" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] [[package]] name = "autocfg" @@ -42,6 +108,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base16ct" version = "0.1.1" @@ -70,7 +151,7 @@ dependencies = [ "cw-asset", "cw-controllers 0.16.0", "cw-multi-test 0.16.2-alpha.0", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw-utils 0.16.0", "cw2 0.16.0", "cw20 0.16.0", @@ -85,6 +166,47 @@ dependencies = [ "vault-rewards", ] +[[package]] +name = "bindgen" +version = "0.60.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "which", +] + +[[package]] +name = "bip32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30ed1d6f8437a487a266c8293aeb95b61a23261273e3e02912cdb8b68bf798b" +dependencies = [ + "bs58", + "hmac", + "k256", + "once_cell", + "pbkdf2", + "rand_core 0.6.4", + "ripemd", + "sha2 0.10.7", + "subtle", + "zeroize", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -106,6 +228,24 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -124,62 +264,77 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bnum" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" + [[package]] name = "borsh" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40f9ca3698b2e4cb7c15571db0abc5551dca417a21ae8140460b50309bb2cc62" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" dependencies = [ "borsh-derive", - "hashbrown 0.13.2", + "hashbrown 0.12.3", ] [[package]] name = "borsh-derive" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598b3eacc6db9c3ee57b22707ad8f6a8d2f6d442bfe24ffeb8cbb70ca59e6a35" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", - "syn", + "syn 1.0.109", ] [[package]] name = "borsh-derive-internal" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186b734fa1c9f6743e90c95d7233c9faab6360d1a96d4ffa19d9cfd1e9350f8a" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "borsh-schema-derive-internal" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b7ff1008316626f485991b960ade129253d4034014616b94f309a15366cc49" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" +dependencies = [ + "sha2 0.9.9", ] [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytecheck" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13fe11640a23eb24562225322cd3e452b93a3d4091d62fab69c70542fcd17d1f" +checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -188,13 +343,13 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31225543cb46f81a7e224762764f4a6a0f097b1db0b175f69e8065efaa42de5" +checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -214,9 +369,21 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +dependencies = [ + "libc", +] + +[[package]] +name = "cexpr" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] [[package]] name = "cfg-if" @@ -226,19 +393,102 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.23" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ - "num-integer", + "android-tzdata", "num-traits", ] +[[package]] +name = "cl-vault" +version = "0.1.0" +dependencies = [ + "apollo-cw-asset", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-controllers 1.1.0", + "cw-dex", + "cw-dex-router", + "cw-multi-test 0.16.5", + "cw-storage-plus 1.1.0", + "cw-utils 0.16.0", + "cw-vault-multi-standard", + "cw-vault-token", + "cw2 1.1.0", + "cw20 1.1.0", + "cw20-base 1.1.0", + "derive_builder", + "liquidity-helper", + "num_enum", + "osmosis-std 0.17.0-rc0", + "osmosis-test-tube", + "proptest", + "prost 0.11.9", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_lex", + "indexmap 1.9.3", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "const-oid" -version = "0.9.2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cosmos-sdk-proto" @@ -262,13 +512,34 @@ dependencies = [ "tendermint-proto 0.26.0", ] +[[package]] +name = "cosmrs" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3903590099dcf1ea580d9353034c9ba1dbf55d1389a5bd2ade98535c3445d1f9" +dependencies = [ + "bip32", + "cosmos-sdk-proto 0.14.0", + "ecdsa", + "eyre", + "getrandom", + "k256", + "rand_core 0.6.4", + "serde", + "serde_json", + "subtle-encoding", + "tendermint", + "tendermint-rpc", + "thiserror", +] + [[package]] name = "cosmwasm-crypto" -version = "1.2.2" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8263ce52392898aa17c2a0984b7c542df416e434f6e0cb1c1a11771054d159" +checksum = "51dd316b3061747d6f57c1c4a131a5ba2f9446601a9276d05a4d25ab2ce0a7e0" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "ed25519-zebra", "k256", "rand_core 0.6.4", @@ -277,18 +548,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.2.2" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1895f6d7a191bb044e3c555190d1da555c2571a3af41f849f60c855580e392f" +checksum = "03b14230c6942a301afb96f601af97ae09966601bd1007067a2c7fe8ffcfe303" dependencies = [ - "syn", + "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.2.2" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1b99f612ccf162940ae2eef9f370ee37cf2ddcf4a9a8f5ee15ec6b46a5ecd2e" +checksum = "1027bdd5941b7d4b45bd773b6d88818dcc043e8db68916bfbd5caf971024dbea" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -299,22 +570,23 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.2" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92ceea61033cb69c336abf673da017ddf251fc4e26e0cdd387eaf8bedb14e49" +checksum = "b6e069f6e65a9a1f55f8d7423703bed35e9311d029d91b357b17a07010d95cd7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "cosmwasm-std" -version = "1.2.2" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc01051aab3bb88d5efe0b90f24a6df1ca96a873b12fc21b862b17539c84ee9" +checksum = "c27a06f0f6c35b178563c6b1044245b3f750c4a66d9f6d2b942a6b29ad77d3ae" dependencies = [ "base64", + "bnum", "cosmwasm-crypto", "cosmwasm-derive", "derivative", @@ -322,17 +594,16 @@ dependencies = [ "hex", "schemars", "serde", - "serde-json-wasm 0.5.0", - "sha2 0.10.6", + "serde-json-wasm 0.5.1", + "sha2 0.10.7", "thiserror", - "uint", ] [[package]] name = "cosmwasm-storage" -version = "1.2.2" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719b9895ef880f172bfe698c975aa6ce5712abb2d0162148d51f346706df80" +checksum = "81854e8f4cb8d6d0ff956de34af56ed70c5a09cb61431dbc854982d10f8886b7" dependencies = [ "cosmwasm-std", "serde", @@ -340,19 +611,13 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-bigint" version = "0.4.9" @@ -375,6 +640,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "ct-logs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" +dependencies = [ + "sct", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -406,8 +680,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-address-like", - "cw-storage-plus 1.0.1", - "cw20 1.0.1", + "cw-storage-plus 1.1.0", + "cw20 1.1.0", "thiserror", ] @@ -427,13 +701,14 @@ dependencies = [ [[package]] name = "cw-controllers" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b81b8c4647f13be38baea7345885fcd97cffe8ad4e0454a43f975be9609127ed" +checksum = "24bd6738c3fd59c87d2f84911c1cad1e4f2d1c58ecaa6e52549b4f78f4ed6f07" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.14.0", - "cw-utils 0.14.0", + "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", "schemars", "serde", "thiserror", @@ -441,19 +716,53 @@ dependencies = [ [[package]] name = "cw-controllers" -version = "0.16.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24bd6738c3fd59c87d2f84911c1cad1e4f2d1c58ecaa6e52549b4f78f4ed6f07" +checksum = "d5d8edce4b78785f36413f67387e4be7d0cb7d032b5d4164bcc024f9c3f3f2ea" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.16.0", - "cw-utils 0.16.0", + "cw-storage-plus 1.1.0", + "cw-utils 1.0.1", "schemars", "serde", "thiserror", ] +[[package]] +name = "cw-dex" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c4c002da51161e832615de09aa796e6915507418a7e4658204cd6c91ce89e7" +dependencies = [ + "apollo-cw-asset", + "apollo-utils", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.1.0", + "cw-utils 1.0.1", + "cw20 1.1.0", + "thiserror", +] + +[[package]] +name = "cw-dex-router" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81c676d4d069b16a4d3dd397319d57fcdb9ae2f6db1e6a63329e547c48bffe34" +dependencies = [ + "apollo-cw-asset", + "apollo-utils", + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers 1.1.0", + "cw-dex", + "cw-storage-plus 1.1.0", + "cw2 1.1.0", + "cw20 1.1.0", + "thiserror", +] + [[package]] name = "cw-multi-test" version = "0.16.2-alpha.0" @@ -461,7 +770,7 @@ source = "git+https://github.com/njerschow/cw-multi-test.git#4cc9cc2dfb61f0f7f4e dependencies = [ "anyhow", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "derivative", "itertools", @@ -474,13 +783,13 @@ dependencies = [ [[package]] name = "cw-multi-test" -version = "0.16.2" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2eb84554bbfa6b66736abcd6a9bfdf237ee0ecb83910f746dff7f799093c80a" +checksum = "127c7bb95853b8e828bdab97065c81cb5ddc20f7339180b61b2300565aaa99d1" dependencies = [ "anyhow", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "derivative", "itertools", @@ -502,28 +811,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cw-storage-plus" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - -[[package]] -name = "cw-storage-plus" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c8b264257c4f44c49b7ce09377af63aa040768ecd3fd7bdd2d48a09323a1e90" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - [[package]] name = "cw-storage-plus" version = "0.16.0" @@ -537,9 +824,9 @@ dependencies = [ [[package]] name = "cw-storage-plus" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053a5083c258acd68386734f428a5a171b29f7d733151ae83090c6fcc9417ffa" +checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" dependencies = [ "cosmwasm-std", "schemars", @@ -570,20 +857,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cw-utils" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414b91f3d7a619bb26c835119d7095804596a1382ddc1d184c33c1d2c17f6c5e" -dependencies = [ - "cosmwasm-std", - "cw2 0.14.0", - "schemars", - "semver", - "serde", - "thiserror", -] - [[package]] name = "cw-utils" version = "0.16.0" @@ -607,7 +880,7 @@ checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2 1.0.1", + "cw2 1.1.0", "schemars", "semver", "serde", @@ -615,37 +888,40 @@ dependencies = [ ] [[package]] -name = "cw2" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1d81d7c359d6c1fba3aa83dad7ec6f999e512571380ae62f81257c3db569743" +name = "cw-vault-multi-standard" +version = "0.3.0" +source = "git+https://github.com/quasar-finance/cw-vault-standard#5d66e887a3abf107e58f3cdab7ac0c683fc0deec" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.11.1", + "cw-utils 1.0.1", "schemars", "serde", ] [[package]] -name = "cw2" -version = "0.13.4" +name = "cw-vault-token" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" +checksum = "ead4f3aad0bf40c72cd1549f5803be1884ef4a9f6684542285243964c0851b85" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.13.4", - "schemars", - "serde", + "cw-utils 1.0.1", + "cw20 1.1.0", + "cw20-base 1.1.0", + "osmosis-std 0.14.0", + "thiserror", ] [[package]] name = "cw2" -version = "0.14.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa74c324af8e3506fd8d50759a265bead3f87402e413c840042af5d2808463d6" +checksum = "c1d81d7c359d6c1fba3aa83dad7ec6f999e512571380ae62f81257c3db569743" dependencies = [ "cosmwasm-std", - "cw-storage-plus 0.14.0", + "cw-storage-plus 0.11.1", "schemars", "serde", ] @@ -665,15 +941,16 @@ dependencies = [ [[package]] name = "cw2" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb70cee2cf0b4a8ff7253e6bc6647107905e8eb37208f87d54f67810faa62f8" +checksum = "29ac2dc7a55ad64173ca1e0a46697c31b7a5c51342f55a1e84a724da4eb99908" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "schemars", "serde", + "thiserror", ] [[package]] @@ -700,18 +977,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cw20" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f446f59c519fbac5ab8b9f6c7f8dcaa05ee761703971406b28221ea778bb737" -dependencies = [ - "cosmwasm-std", - "cw-utils 0.14.0", - "schemars", - "serde", -] - [[package]] name = "cw20" version = "0.16.0" @@ -727,9 +992,9 @@ dependencies = [ [[package]] name = "cw20" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91666da6c7b40c8dd5ff94df655a28114efc10c79b70b4d06f13c31e37d60609" +checksum = "011c45920f8200bd5d32d4fe52502506f64f2f75651ab408054d4cfc75ca3a9b" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -772,6 +1037,24 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw20-base" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b3ad456059901a36cfa68b596d85d579c3df2b797dae9950dc34c27e14e995f" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.1.0", + "cw-utils 1.0.1", + "cw2 1.1.0", + "cw20 1.1.0", + "schemars", + "semver", + "serde", + "thiserror", +] + [[package]] name = "cw20-staking" version = "0.11.1" @@ -790,6 +1073,41 @@ dependencies = [ "thiserror", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + [[package]] name = "der" version = "0.6.1" @@ -808,7 +1126,38 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07adf7be193b71cc36b193d0f5fe60b918a3a9db4dad0449f57bcfd519704a3" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68" +dependencies = [ + "derive_builder_core", + "syn 1.0.109", ] [[package]] @@ -819,7 +1168,7 @@ checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -833,9 +1182,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common", @@ -844,9 +1193,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" +checksum = "bbfc4744c1b8f2a09adc0e55242f60b1af195d88596bd8700be74418c056c555" [[package]] name = "ecdsa" @@ -860,6 +1209,27 @@ dependencies = [ "signature", ] +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek", + "ed25519", + "sha2 0.9.9", + "zeroize", +] + [[package]] name = "ed25519-zebra" version = "3.1.0" @@ -877,9 +1247,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "elliptic-curve" @@ -890,7 +1260,7 @@ dependencies = [ "base16ct", "crypto-bigint", "der", - "digest 0.10.6", + "digest 0.10.7", "ff", "generic-array", "group", @@ -901,15 +1271,34 @@ dependencies = [ "zeroize", ] +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -923,14 +1312,21 @@ dependencies = [ ] [[package]] -name = "fastrand" -version = "1.9.0" +name = "eyre" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" dependencies = [ - "instant", + "indenter", + "once_cell", ] +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + [[package]] name = "ff" version = "0.12.1" @@ -947,6 +1343,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" dependencies = [ + "eyre", "paste", ] @@ -956,17 +1353,121 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + [[package]] name = "forward_ref" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +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.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -974,9 +1475,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "js-sys", @@ -985,6 +1486,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "group" version = "0.12.1" @@ -996,29 +1509,79 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 1.9.3", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash", ] [[package]] name = "hashbrown" -version = "0.13.2" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "headers" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +dependencies = [ + "base64", + "bitflags 1.3.2", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" dependencies = [ - "ahash 0.8.3", + "http", ] [[package]] name = "hermit-abi" -version = "0.3.1" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -1032,123 +1595,178 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] -name = "ibc_transfer" -version = "0.1.0" +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.14.0", - "cw-utils 0.16.0", - "cw20-base 0.16.0", - "osmosis-std 0.12.0", - "quasar-bindings", - "quasar-traits", - "quasar-types", - "schemars", - "serde", - "serde-json-wasm 0.4.1", - "thiserror", + "bytes", + "fnv", + "itoa", ] [[package]] -name = "icq" -version = "0.1.0" +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "base64", - "cosmos-sdk-proto 0.14.0", - "cosmwasm-schema", - "cosmwasm-std", - "cw-controllers 0.14.0", - "cw-storage-plus 0.14.0", - "cw-utils 0.14.0", - "cw2 0.14.0", - "cw20 0.14.0", - "osmosis-std 0.12.0", - "prost 0.11.9", - "schemars", - "semver", - "serde", - "thiserror", + "bytes", + "http", + "pin-project-lite", ] [[package]] -name = "instant" -version = "0.1.12" +name = "httparse" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] -name = "intergamm-bindings" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus 0.14.0", - "schemars", - "serde", - "thiserror", -] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] -name = "intergamm-bindings-test" -version = "0.1.0" +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cosmwasm-storage", - "cw-multi-test 0.16.2-alpha.0", - "cw-storage-plus 0.14.0", - "cw-utils 0.14.0", - "cw2 0.14.0", - "intergamm-bindings", - "schemars", - "serde", - "thiserror", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.9", + "tokio", + "tower-service", + "tracing", + "want", ] [[package]] -name = "io-lifetimes" -version = "1.0.10" +name = "hyper-proxy" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", + "bytes", + "futures", + "headers", + "http", + "hyper", + "hyper-rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", + "webpki", ] [[package]] -name = "itertools" -version = "0.10.5" +name = "hyper-rustls" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" dependencies = [ - "either", + "ct-logs", + "futures-util", + "hyper", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "webpki", + "webpki-roots", ] [[package]] -name = "itoa" -version = "1.0.6" +name = "ident_case" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] -name = "js-sys" -version = "0.3.61" +name = "idna" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ - "wasm-bindgen", + "unicode-bidi", + "unicode-normalization", ] [[package]] -name = "k256" +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k256" version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" @@ -1156,7 +1774,17 @@ dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "sha2 0.10.6", + "sha2 0.10.7", + "sha3", +] + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", ] [[package]] @@ -1165,33 +1793,61 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" -version = "0.2.142" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] [[package]] name = "libm" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "linux-raw-sys" -version = "0.3.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] -name = "log" -version = "0.4.17" +name = "liquidity-helper" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "6fcbaa0b50025e319bec34d39de1af4684252b22b0bdeff260bfb68b8c250d8f" dependencies = [ - "cfg-if", + "apollo-cw-asset", + "apollo-utils", + "cosmwasm-schema", + "cosmwasm-std", + "cw20 1.1.0", + "schemars", + "serde", ] +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + [[package]] name = "lp-strategy" version = "0.1.1" @@ -1199,8 +1855,8 @@ dependencies = [ "cosmos-sdk-proto 0.15.0", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 0.16.2", - "cw-storage-plus 1.0.1", + "cw-multi-test 0.16.5", + "cw-storage-plus 1.1.0", "cw-utils 0.16.0", "cw2 0.16.0", "cw20 0.16.0", @@ -1213,7 +1869,45 @@ dependencies = [ "serde", "serde-json-wasm 0.4.1", "thiserror", - "uuid", + "uuid 1.4.1", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi", + "windows-sys", ] [[package]] @@ -1224,17 +1918,27 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-multi-test 0.16.2", - "cw-storage-plus 1.0.1", - "cw2 1.0.1", + "cw-multi-test 0.16.5", + "cw-storage-plus 1.1.0", + "cw2 1.1.0", "derivative", "proptest", "schemars", "serde", - "serde-json-wasm 0.5.0", + "serde-json-wasm 0.5.1", "thiserror", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -1243,27 +1947,48 @@ checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] -name = "num-integer" -version = "0.1.45" +name = "num-traits" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", - "num-traits", + "libm", ] [[package]] -name = "num-traits" -version = "0.2.15" +name = "num_cpus" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "autocfg", - "libm", + "hermit-abi 0.3.2", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 2.0.29", ] [[package]] @@ -1275,11 +2000,20 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -1287,15 +2021,27 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "os_str_bytes" +version = "6.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" + [[package]] name = "osmosis-std" -version = "0.12.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b3792977036dc49cfc9af9fd7a6c021fd48dfffc8ebf09324201506c65a47a" +checksum = "2fc0a9075efd64ed5a8be3bf134cbf1080570d68384f2ad58ffaac6c00d063fd" dependencies = [ "chrono", "cosmwasm-std", - "osmosis-std-derive 0.12.0", + "osmosis-std-derive 0.13.2", "prost 0.11.9", "prost-types", "schemars", @@ -1318,16 +2064,32 @@ dependencies = [ "serde-cw-value", ] +[[package]] +name = "osmosis-std" +version = "0.17.0-rc0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b022b748710ecdf1adc6a124c3bef29f17ef05e7fa1260a08889d1d53f9cc5" +dependencies = [ + "chrono", + "cosmwasm-std", + "osmosis-std-derive 0.16.2", + "prost 0.11.9", + "prost-types", + "schemars", + "serde", + "serde-cw-value", +] + [[package]] name = "osmosis-std-derive" -version = "0.12.0" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c501f2b8ff88b1c60ab671d7b808e947f384fa2524fe4ec8c06f63ef4be29979" +checksum = "a455e262a6fdfd3914f3a4e11e6bc0ce491901cb9d507d7856d7ef6e129e90c6" dependencies = [ "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1339,14 +2101,124 @@ dependencies = [ "proc-macro2", "prost-types", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "osmosis-std-derive" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47f0b2f22adb341bb59e5a3a1b464dde033181954bd055b9ae86d6511ba465b" +dependencies = [ + "itertools", + "proc-macro2", + "prost-types", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "osmosis-test-tube" +version = "17.0.0-rc0" +source = "git+https://github.com/osmosis-labs/test-tube.git#9d53e5e6487d288c5f64e09d015908b6f20eb43e" +dependencies = [ + "base64", + "bindgen", + "cosmrs", + "cosmwasm-std", + "osmosis-std 0.17.0-rc0", + "prost 0.11.9", + "serde", + "serde_json", + "test-tube", + "thiserror", ] [[package]] name = "paste" -version = "1.0.12" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "peg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" + +[[package]] +name = "percent-encoding" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs8" @@ -1373,31 +2245,40 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29f1b898011ce9595050a68e60f90bad083ff2987a695a42357134c8381fba70" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" dependencies = [ "bit-set", - "bitflags", + "bitflags 1.3.2", "byteorder", "lazy_static", "num-traits", - "quick-error 2.0.1", "rand", "rand_chacha", "rand_xorshift", - "regex-syntax", + "regex-syntax 0.6.29", "rusty-fork", "tempfile", "unarray", @@ -1433,7 +2314,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1446,7 +2327,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1475,43 +2356,7 @@ checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "qoracle-bindings-test" -version = "0.1.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cosmwasm-storage", - "cw-multi-test 0.16.2-alpha.0", - "cw-storage-plus 0.13.4", - "cw2 0.13.4", - "quasar-bindings", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "quasar-bindings" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", - "serde_json", -] - -[[package]] -name = "quasar-traits" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "cw20 0.13.4", - "schemars", - "serde", + "syn 1.0.109", ] [[package]] @@ -1520,7 +2365,7 @@ version = "0.1.0" dependencies = [ "cosmos-sdk-proto 0.15.0", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw20 0.13.4", "derive_more", "osmosis-std 0.16.1", @@ -1540,24 +2385,24 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quote" -version = "1.0.23" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] -name = "rand" -version = "0.8.5" +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ @@ -1606,7 +2451,30 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax 0.7.4", +] + +[[package]] +name = "regex-automata" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.4", ] [[package]] @@ -1615,6 +2483,12 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + [[package]] name = "rend" version = "0.4.0" @@ -1635,41 +2509,77 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[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 = "rkyv" -version = "0.7.40" +version = "0.7.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c30f1d45d9aa61cbc8cd1eb87705470892289bb2d01943e7803b873a57404dc3" +checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58" dependencies = [ + "bitvec", "bytecheck", "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", "seahash", + "tinyvec", + "uuid 1.4.1", ] [[package]] name = "rkyv_derive" -version = "0.7.40" +version = "0.7.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff26ed6c7c4dfc2aa9480b86a60e3c7233543a270a680e10758a507c5a4ce476" +checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "rust_decimal" -version = "1.28.1" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13cf35f7140155d02ba4ec3294373d513a3c7baa8364c162b030e33c61520a8" +checksum = "a4c4216490d5a413bc6d10fa4742bd7d4955941d062c0ef873141d6b0e7b30fd" dependencies = [ "arrayvec", "borsh", - "bytecheck", - "byteorder", "bytes", "num-traits", "rand", @@ -1678,18 +2588,54 @@ dependencies = [ "serde_json", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustix" -version = "0.37.13" +version = "0.38.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0" +checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" dependencies = [ - "bitflags", + "bitflags 2.4.0", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys", +] + +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +dependencies = [ + "openssl-probe", + "rustls", + "schannel", + "security-framework", ] [[package]] @@ -1699,16 +2645,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", - "quick-error 1.2.3", + "quick-error", "tempfile", "wait-timeout", ] [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys", +] [[package]] name = "schemars" @@ -1731,7 +2695,17 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", ] [[package]] @@ -1754,17 +2728,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.154" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cdd151213925e7f1ab45a9bbfb129316bd00799784b174b7cc7bcd16961c49e" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" dependencies = [ "serde_derive", ] @@ -1789,31 +2786,31 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15bee9b04dd165c3f4e142628982ddde884c2022a89e8ddf99c4829bf2c3a58" +checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" dependencies = [ "serde", ] [[package]] name = "serde_bytes" -version = "0.11.9" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.154" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc80d722935453bcafdc2c9a73cd6fac4dc1938f0346035d84bf99fa9e33217" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.29", ] [[package]] @@ -1824,29 +2821,51 @@ checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "serde_json" -version = "1.0.94" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "serde_test" -version = "1.0.154" +version = "1.0.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2d2d6cb2f0ae0ee570534adaa64db18d2f50cfa433c5afcbe3263f98cfb010" +checksum = "5a2f49ace1498612d14f7e0b8245519584db8299541dfe31a06374a828d620ab" dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sha2" version = "0.9.9" @@ -1862,22 +2881,38 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + [[package]] name = "signature" version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -1887,6 +2922,41 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spki" version = "0.6.0" @@ -1898,16 +2968,16 @@ dependencies = [ ] [[package]] -name = "static_assertions" -version = "1.1.0" +name = "strsim" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "subtle-encoding" @@ -1929,17 +2999,79 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" -version = "3.5.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", "redox_syscall", "rustix", - "windows-sys 0.45.0", + "windows-sys", +] + +[[package]] +name = "tendermint" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467f82178deeebcd357e1273a0c0b77b9a8a0313ef7c07074baebe99d87851f4" +dependencies = [ + "async-trait", + "bytes", + "ed25519", + "ed25519-dalek", + "flex-error", + "futures", + "k256", + "num-traits", + "once_cell", + "prost 0.11.9", + "prost-types", + "ripemd160", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.9.9", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto 0.23.9", + "time", + "zeroize", +] + +[[package]] +name = "tendermint-config" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d42ee0abc27ef5fc34080cce8d43c189950d331631546e7dfb983b6274fa327" +dependencies = [ + "flex-error", + "serde", + "serde_json", + "tendermint", + "toml", + "url", ] [[package]] @@ -1978,24 +3110,87 @@ dependencies = [ "time", ] +[[package]] +name = "tendermint-rpc" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f14aafe3528a0f75e9f3f410b525617b2de16c4b7830a21f717eee62882ec60" +dependencies = [ + "async-trait", + "bytes", + "flex-error", + "futures", + "getrandom", + "http", + "hyper", + "hyper-proxy", + "hyper-rustls", + "peg", + "pin-project", + "serde", + "serde_bytes", + "serde_json", + "subtle-encoding", + "tendermint", + "tendermint-config", + "tendermint-proto 0.23.9", + "thiserror", + "time", + "tokio", + "tracing", + "url", + "uuid 0.8.2", + "walkdir", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "test-tube" +version = "0.1.6" +source = "git+https://github.com/osmosis-labs/test-tube.git#9d53e5e6487d288c5f64e09d015908b6f20eb43e" +dependencies = [ + "base64", + "cosmrs", + "cosmwasm-std", + "osmosis-std 0.17.0-rc0", + "prost 0.11.9", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + [[package]] name = "thiserror" -version = "1.0.39" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.39" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.29", ] [[package]] @@ -2015,6 +3210,74 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2 0.5.3", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml" version = "0.5.11" @@ -2025,40 +3288,115 @@ dependencies = [ ] [[package]] -name = "typenum" -version = "1.16.0" +name = "toml_datetime" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" [[package]] -name = "uint" -version = "0.9.5" +name = "toml_edit" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", + "indexmap 2.0.0", + "toml_datetime", + "winnow", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + [[package]] name = "unarray" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] [[package]] name = "uuid" -version = "1.3.0" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" + +[[package]] +name = "uuid" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "getrandom", "wasm-bindgen", @@ -2071,9 +3409,9 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-asset", - "cw-storage-plus 1.0.1", - "cw2 1.0.1", - "cw20 1.0.1", + "cw-storage-plus 1.1.0", + "cw2 1.1.0", + "cw20 1.1.0", "itertools", "schemars", "serde", @@ -2095,6 +3433,25 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2103,9 +3460,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2113,24 +3470,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.29", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2138,157 +3495,194 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.29", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] -name = "windows-sys" -version = "0.45.0" +name = "web-sys" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ - "windows-targets 0.42.2", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "windows-sys" -version = "0.48.0" +name = "webpki" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" dependencies = [ - "windows-targets 0.48.0", + "ring", + "untrusted", ] [[package]] -name = "windows-targets" -version = "0.42.2" +name = "webpki-roots" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "webpki", ] [[package]] -name = "windows-targets" -version = "0.48.0" +name = "which" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "either", + "libc", + "once_cell", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" +name = "winapi-util" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] [[package]] -name = "windows_aarch64_msvc" +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 = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] [[package]] -name = "windows_i686_gnu" -version = "0.42.2" +name = "windows-targets" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "d1eeca1c172a285ee6c2c84c341ccea837e7c01b12fbb2d0fe3c9e550ce49ec8" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] [[package]] -name = "windows_i686_gnu" -version = "0.48.0" +name = "windows_aarch64_gnullvm" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "b10d0c968ba7f6166195e13d593af609ec2e3d24f916f081690695cf5eaffb2f" [[package]] -name = "windows_i686_msvc" -version = "0.42.2" +name = "windows_aarch64_msvc" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "571d8d4e62f26d4932099a9efe89660e8bd5087775a2ab5cdd8b747b811f1058" [[package]] -name = "windows_i686_msvc" -version = "0.48.0" +name = "windows_i686_gnu" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "2229ad223e178db5fbbc8bd8d3835e51e566b8474bfca58d2e6150c48bb723cd" [[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" +name = "windows_i686_msvc" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "600956e2d840c194eedfc5d18f8242bc2e17c7775b6684488af3a9fff6fe3287" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "ea99ff3f8b49fb7a8e0d305e5aec485bd068c2ba691b6e277d29eaeac945868a" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "8f1a05a1ece9a7a0d5a7ccf30ba2c33e3a61a30e042ffd247567d1de1d94120d" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" +name = "windows_x86_64_msvc" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "d419259aba16b663966e29e6d7c6ecfa0bb8425818bb96f6f1f3c3eb71a6e7b9" [[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" +name = "winnow" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "83817bbecf72c73bad717ee86820ebf286203d2e04c3951f3cd538869c897364" +dependencies = [ + "memchr", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" +name = "wyz" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] [[package]] name = "zeroize" -version = "1.5.7" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] diff --git a/smart-contracts/Cargo.toml b/smart-contracts/Cargo.toml index fa7f1bb24..6403b8d19 100644 --- a/smart-contracts/Cargo.toml +++ b/smart-contracts/Cargo.toml @@ -1,15 +1,15 @@ [workspace] members = [ - "contracts/icq", + # "contracts/icq", "contracts/lp-strategy", - "contracts/ibc-transfer", + # "contracts/ibc-transfer", "contracts/basic-vault", "contracts/vault-rewards", - "contracts/intergamm-bindings-test", - "contracts/qoracle-bindings-test", + # "contracts/intergamm-bindings-test", "contracts/multihop-router", - "packages/intergamm-bindings", + "contracts/cl-vault", + # "packages/intergamm-bindings", "packages/quasar-types", ] diff --git a/smart-contracts/contracts/cl-vault/.cargo/config b/smart-contracts/contracts/cl-vault/.cargo/config new file mode 100644 index 000000000..411e3415e --- /dev/null +++ b/smart-contracts/contracts/cl-vault/.cargo/config @@ -0,0 +1,6 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --bin schema" +test-tube = "test --package cl-vault --lib -- --include-ignored test_tube:: --nocapture" +test-tube-build = "build --release --lib --target wasm32-unknown-unknown --target-dir ./test-tube-build" \ No newline at end of file diff --git a/smart-contracts/contracts/qoracle-bindings-test/.circleci/config.yml b/smart-contracts/contracts/cl-vault/.circleci/config.yml similarity index 100% rename from smart-contracts/contracts/qoracle-bindings-test/.circleci/config.yml rename to smart-contracts/contracts/cl-vault/.circleci/config.yml diff --git a/smart-contracts/contracts/qoracle-bindings-test/.editorconfig b/smart-contracts/contracts/cl-vault/.editorconfig similarity index 100% rename from smart-contracts/contracts/qoracle-bindings-test/.editorconfig rename to smart-contracts/contracts/cl-vault/.editorconfig diff --git a/smart-contracts/contracts/qoracle-bindings-test/.github/workflows/Basic.yml b/smart-contracts/contracts/cl-vault/.github/workflows/Basic.yml similarity index 96% rename from smart-contracts/contracts/qoracle-bindings-test/.github/workflows/Basic.yml rename to smart-contracts/contracts/cl-vault/.github/workflows/Basic.yml index 0aa419313..3890a07cf 100644 --- a/smart-contracts/contracts/qoracle-bindings-test/.github/workflows/Basic.yml +++ b/smart-contracts/contracts/cl-vault/.github/workflows/Basic.yml @@ -17,7 +17,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.58.1 + toolchain: 1.60.0 target: wasm32-unknown-unknown override: true @@ -48,7 +48,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.58.1 + toolchain: 1.60.0 override: true components: rustfmt, clippy diff --git a/smart-contracts/contracts/cl-vault/.github/workflows/Release.yml b/smart-contracts/contracts/cl-vault/.github/workflows/Release.yml new file mode 100644 index 000000000..a9d597941 --- /dev/null +++ b/smart-contracts/contracts/cl-vault/.github/workflows/Release.yml @@ -0,0 +1,35 @@ +name: release wasm + +on: + release: + types: [created] + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + - name: Install cargo-run-script + uses: actions-rs/cargo@v1 + with: + command: install + args: cargo-run-script + - name: Run cargo optimize + uses: actions-rs/cargo@v1 + with: + command: run-script + args: optimize + - name: Get release ID + id: get_release + uses: bruceadams/get-release@v1.2.3 + env: + GITHUB_TOKEN: ${{ github.token }} + - name: Upload optimized wasm + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: ./artifacts/*.wasm + tag: ${{ github.ref }} + overwrite: true + file_glob: true diff --git a/smart-contracts/contracts/qoracle-bindings-test/.gitignore b/smart-contracts/contracts/cl-vault/.gitignore similarity index 89% rename from smart-contracts/contracts/qoracle-bindings-test/.gitignore rename to smart-contracts/contracts/cl-vault/.gitignore index dfdaaa6bc..e8bbaa8d3 100644 --- a/smart-contracts/contracts/qoracle-bindings-test/.gitignore +++ b/smart-contracts/contracts/cl-vault/.gitignore @@ -1,5 +1,7 @@ # Build results /target +/schema +/test-tube-build # Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) .cargo-ok diff --git a/smart-contracts/contracts/qoracle-bindings-test/Cargo.toml b/smart-contracts/contracts/cl-vault/Cargo.toml similarity index 50% rename from smart-contracts/contracts/qoracle-bindings-test/Cargo.toml rename to smart-contracts/contracts/cl-vault/Cargo.toml index 0dd35960f..21c32ca00 100644 --- a/smart-contracts/contracts/qoracle-bindings-test/Cargo.toml +++ b/smart-contracts/contracts/cl-vault/Cargo.toml @@ -1,7 +1,7 @@ [package] -authors = ["njerschow "] -edition = "2018" -name = "qoracle-bindings-test" +authors = ["LaurensKubat <32776056+LaurensKubat@users.noreply.github.com>"] +edition = "2021" +name = "cl-vault" version = "0.1.0" exclude = [ @@ -36,19 +36,36 @@ library = [] optimize = """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.12.6 + cosmwasm/rust-optimizer:0.12.10 """ [dependencies] -cosmwasm-std = {version = "1.0.0"} -cosmwasm-storage = {version = "1.0.0"} -cw-storage-plus = "0.13.2" -cw2 = "0.13.2" -quasar-bindings = {path = "../../packages/quasar-bindings", version = "0.1.0"} -schemars = "0.8.8" -serde = {version = "1.0.137", default-features = false, features = ["derive"]} +apollo-cw-asset = "0.1.0" +cosmwasm-schema = "1.1.3" +cosmwasm-std = "1.1.3" +cosmwasm-storage = "1.1.3" +cw-controllers = "1.0.1" +cw-dex = "0.1.1" +cw-dex-router = {version = "0.1.0", features = ["library"]} +cw-storage-plus = "1.0.1" +cw-utils = "0.16.0" +cw-vault-multi-standard = {git = "https://github.com/quasar-finance/cw-vault-standard", features = ["lockup", "force-unlock"]} +cw-vault-token = "0.1.0" +cw2 = "1.0.1" +cw20 = "1.0.1" +cw20-base = "1.0.1" +derive_builder = "0.11.2" +liquidity-helper = "0.1.0" +num_enum = "0.6.1" +osmosis-std = "0.17.0-rc0" +prost = {version = "0.11", default-features = false} +schemars = "0.8.10" +semver = "1" +serde = {version = "1.0.145", default-features = false, features = ["derive"]} thiserror = {version = "1.0.31"} [dev-dependencies] -cosmwasm-schema = "1.0.0" -cw-multi-test = {workspace = true} +cw-multi-test = "0.16.2" +osmosis-test-tube = {git = 'https://github.com/osmosis-labs/test-tube.git', version = "17.0.0-rc0"} +prost = {version = "0.11", default-features = false} +proptest = "1.2.0" diff --git a/smart-contracts/contracts/qoracle-bindings-test/LICENSE b/smart-contracts/contracts/cl-vault/LICENSE similarity index 100% rename from smart-contracts/contracts/qoracle-bindings-test/LICENSE rename to smart-contracts/contracts/cl-vault/LICENSE diff --git a/smart-contracts/contracts/qoracle-bindings-test/NOTICE b/smart-contracts/contracts/cl-vault/NOTICE similarity index 87% rename from smart-contracts/contracts/qoracle-bindings-test/NOTICE rename to smart-contracts/contracts/cl-vault/NOTICE index 1270a9c2f..1d8984f55 100644 --- a/smart-contracts/contracts/qoracle-bindings-test/NOTICE +++ b/smart-contracts/contracts/cl-vault/NOTICE @@ -1,4 +1,4 @@ -Copyright 2022 LaurensKubat +Copyright 2023 LaurensKubat <32776056+LaurensKubat@users.noreply.github.com> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/smart-contracts/contracts/cl-vault/README.md b/smart-contracts/contracts/cl-vault/README.md new file mode 100644 index 000000000..b5d7e59dc --- /dev/null +++ b/smart-contracts/contracts/cl-vault/README.md @@ -0,0 +1,157 @@ +# CL-Vault + +## Intro + +The CL-vault is a contract that users to move their funds in accordance to given signals by an offchain actor. The user deposits it's funds into the contract. Whenever the range admin submits a new range, the vaults creates a position using that range and deposits all funds in that range. + +# Testing + +The main effort for testing is done using test tube, this means that any code coverage tools won't pick up on lines covered. +Some functions should still have their own unit tests/prop tests ofcourse, especially if the intended behaviour might not be clear later. + +## [Test-tube](https://github.com/osmosis-labs/test-tube) + +### Running tests + +To run any test tube tests, a wasm file of the contract needs to be built for test tube. After which the tests can be ran. +To build the wasm file, run `cargo test-tube-build`. after the build succeeds, tests can be ran using `cargo test-tube`. + +### Writing tests + +To make use of our current setup, all tests using test-tube need to go in `src/test_tube`. `test_tube/initialize.rs` contains some helpers to setup +a Concentrated Liqudity pool and instantiate the cl-vault contract. Any further behaviour tests need to be written potentially in a new file depending on the size of the test + +## Unit testing + +For unit testing coverage, it is up to the reviewers to decide where that level of granularity is needed, or where just test tube provides adequate testing + +## Single sided deposit + +Assume you want to enter a concentrated liquidity pool at a certain range. The pool is an OSMO/DAI pool and you want to enter the pool by just depositing OSMO. How will the contract know how much OSMO to swap for DAI before depositing into the pool? + +The current chain-side concentrated liquidity deposits support lopsided deposits, and will refund any extra tokens, but will only support one-sided deposits on extreme tick ranges. This routine solves this problem. + +### Swapping when we have excess token0 + +We start with the two concentrated liquidity-needed formulas: + +We define the following variables: +$$P_{uc} = min(P_u, P_c)$$ +$$P_{lc} = max(P_l, P_c)$$ + +token0: +$$L=\frac{\Delta x\sqrt{P_{uc}}\sqrt{P_l}}{\sqrt{P_{uc}}-\sqrt{P_l}}$$ +token1: +$$L=\frac{\Delta y}{\sqrt{P_u}-\sqrt{P_{lc}}}$$ + +Let's also explore the three scenarios we can have: $P_c$ is above the range, $P_c$ is below the range, and $P_c$ is within the range. + +When $P_c$ is above the range (greater than $P_u$) then we implicitly know that we must swap all token1 for token0 to create a position. We can ignore this case for now. + +When $P_c$ is below the range (less than $P_l$) then we implicitly know that we must swap all token0 for token1 to create a position. We can ignore this case for now. + +within the range, we can simplify by substituting $P_c$ for $P_{uc}$ and $P_{lc}$ in both liquidity formulas: + +Where $P_c$ is the current price, $P_u$ is the upper price, and $P_l$ is the lower price. + +token0: +$$L=\frac{\Delta x\sqrt{P_{c}}\sqrt{P_u}}{\sqrt{P_{u}}-\sqrt{P_c}}$$ +token1: +$$L=\frac{\Delta y}{\sqrt{P_c}-\sqrt{P_{l}}}$$ + +By setting these two liquidity formulas equal to each other, we are implicitly stating that the amount of token0 awe are depositiing, will match up correctly with the amount of token1 we are depositing. That gives us this equation: + +$$\frac{\Delta x\sqrt{P_{c}}\sqrt{P_u}}{\sqrt{P_{u}}-\sqrt{P_c}}=\frac{\Delta y}{\sqrt{P_c}-\sqrt{P_{l}}}$$ + +which we can rearrange and solve for $\Delta y$: + +$$ +\Delta y=\Delta x(\sqrt{P_{c}}-\sqrt{P_l}) +\frac{\sqrt{P_u}\sqrt{P_{c}}}{\sqrt{P_{l}}-\sqrt{P_c}} +$$ + +lets define a pool metadata variable $K$: + +$$ +K=(\sqrt{P_{c}}-\sqrt{P_l}) +\frac{\sqrt{P_u}\sqrt{P_{c}}}{\sqrt{P_{l}}-\sqrt{P_c}} +$$ + +which gives us +$$\Delta y=\Delta xK$$ + +Now that we have a relationship between what an even deposit would contain (in terms of token0 and token1) we can use this to determine how much token1 we need to swap for token0 to create an even deposit (or vice-versa). + +We can use the spot price formula to get the spot price of the pool: + +$$P_c=\frac{y}{x}$$ + +which we can rearrange to get the amount of y tokens we need in terms of x tokens: + +$$y=xP_c$$ + +We now introduce two new variables: $x'$ and $x''$, where $x'$ is the amount of x tokens we will NOT swap, and $x''$ is the amount of x tokens we WILL swap. Of course, implicitly this means we have + +$$x=x'+x''$$ +which can also be written as +$$x'=x-x''$$ + +We are now looking to swap such that the following relationship is satisfied: + +$$\Delta x=\frac{\Delta y}{K}$$ + +We will replace $\Delta x$ with the amount of tokens we are NOT swapping ($x'$). But how do we replace $\Delta y$? We know that $\Delta y$ is just $\Delta x$ multiplied by the spot price, so we can replace $\Delta y$ with $\Delta xP_c$: and we know that in order to get to $\Delta y$ from $\Delta x$, we will need to swap these tokens. Ah! but we already have a variable that tells us how many tokens we will swap: $x''$. So we can replace $\Delta x$ with $x''$: + +$$x'=\frac{x''P_c}{K}$$ + +We are interested in finding the exact amount of tokens to swap, so let's substitute and solve for $x''$ in terms of x: + +$$x-x''=\frac{x''P_c}{K}$$ + +After some clever algebra, one can verify that we get: + +$$x''=\frac{x}{1+\frac{P_c}{K}}$$ + +Where $x''$ is the amount of tokens we will swap. and $x$ is the total amount of tokens we have. + +### Swapping when we have excess token1 + +Instead of introducing $x'$ and $x''$, we will introduce $y'$ and $y''$, where $y'$ is the amount of y tokens we will NOT swap, and $y''$ is the amount of y tokens we WILL swap. Of course, implicitly this means we have + +$$y=y'+y''$$ +which can also be written as +$$y'=y-y''$$ + +The inverse spot price for x in terms of y is: + +$$x=\frac{y}{P_c}$$ + +We are now looking to swap such that the following relationship is satisfied: + +$$\Delta y=\Delta xK$$ + +Where K is the same as above. + +We will replace $\Delta y$ with the amount of tokens we are NOT swapping ($y'$). But how do we replace $\Delta x$? We know that $\Delta x$ is just some amount of y tokens over by the spot price, so we can replace $\Delta x$ with $\frac{\Delta{y}}{P_c}$. This is equivalent to saying, swap these tokens, but how do we know how many $\Delta{y}$ tokens we need to swap?, Ah! but we already have a variable that tells us how many tokens we will swap: $y''$. So we can replace $\Delta{y}$ with $y''$: + +$$y'=\frac{y''K}{P_c}$$ + +substituting and solving for the amount of tokens to swap given our total amount of y tokens gives us: + +$$y-y''=\frac{y''K}{P_c}$$ + +After some clever algebra, it is trivial for one to verify that we get: + +$$y''=\frac{y}{1+\frac{K}{P_c}}$$ + +Where $y''$ is the amount of tokens we will swap. and $y$ is the total amount of tokens we have. + +and let us not forget that + +$$K=\sqrt{P_c}\sqrt{P_l}\frac{\sqrt{P_u}-\sqrt{P_{c}}}{\sqrt{P_{c}}-\sqrt{P_l}}$$ + +### Further/Future optimizations + +This current math assumes an ideal pool with infinite liquidity. In reality, we will need to account for slippage. + +We can actually create further optimizations here by increasing the amount of tokens to swap by half of the expected slippage, which will lead to returning (not being able to deposit) less tokens. diff --git a/smart-contracts/contracts/cl-vault/src/bin/schema.rs b/smart-contracts/contracts/cl-vault/src/bin/schema.rs new file mode 100644 index 000000000..1028ad208 --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; + +use cl_vault::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} diff --git a/smart-contracts/contracts/cl-vault/src/contract.rs b/smart-contracts/contracts/cl-vault/src/contract.rs new file mode 100644 index 000000000..2a2d54064 --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/contract.rs @@ -0,0 +1,154 @@ +use crate::error::{ContractError, ContractResult}; +use crate::instantiate::{ + handle_create_denom_reply, handle_instantiate, handle_instantiate_create_position_reply, +}; +use crate::msg::{ExecuteMsg, InstantiateMsg, ModifyRangeMsg, QueryMsg}; +use crate::query::{ + query_info, query_metadata, query_pool, query_position, query_total_assets, + query_total_vault_token_supply, query_user_balance, query_user_rewards, +}; +use crate::reply::Replies; +use crate::rewards::{ + execute_distribute_rewards, handle_collect_incentives_reply, + handle_collect_spread_rewards_reply, +}; +use crate::vault::admin::execute_admin; +use crate::vault::claim::execute_claim_user_rewards; +use crate::vault::deposit::{execute_exact_deposit, handle_deposit_create_position_reply}; +use crate::vault::merge::{ + execute_merge, handle_merge_create_position_reply, handle_merge_withdraw_reply, +}; +use crate::vault::range::{ + execute_update_range, handle_initial_create_position_reply, + handle_iteration_create_position_reply, handle_merge_response, handle_swap_reply, + handle_withdraw_position_reply, +}; +use crate::vault::withdraw::{execute_withdraw, handle_withdraw_user_reply}; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response}; +use cw2::set_contract_version; + +// version info for migration info +const CONTRACT_NAME: &str = "crates.io:cl-vault"; +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)?; + handle_instantiate(deps, env, info, msg) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + cw_vault_multi_standard::VaultStandardExecuteMsg::AnyDeposit { + amount: _, + asset: _, + recipient: _, + } => unimplemented!(), + cw_vault_multi_standard::VaultStandardExecuteMsg::ExactDeposit { recipient } => { + execute_exact_deposit(deps, env, info, recipient) + } + cw_vault_multi_standard::VaultStandardExecuteMsg::Redeem { recipient, amount } => { + execute_withdraw(deps, env, info, recipient, amount) + } + cw_vault_multi_standard::VaultStandardExecuteMsg::VaultExtension(vault_msg) => { + match vault_msg { + crate::msg::ExtensionExecuteMsg::Admin(admin_msg) => { + execute_admin(deps, info, admin_msg) + } + crate::msg::ExtensionExecuteMsg::Merge(msg) => execute_merge(deps, env, info, msg), + crate::msg::ExtensionExecuteMsg::ModifyRange(ModifyRangeMsg { + lower_price, + upper_price, + max_slippage, + }) => execute_update_range(deps, env, info, lower_price, upper_price, max_slippage), + crate::msg::ExtensionExecuteMsg::DistributeRewards {} => { + execute_distribute_rewards(deps, env) + } + crate::msg::ExtensionExecuteMsg::ClaimRewards {} => { + execute_claim_user_rewards(deps, info.sender.as_str()) + } + } + } + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { + match msg { + cw_vault_multi_standard::VaultStandardQueryMsg::VaultStandardInfo {} => todo!(), + cw_vault_multi_standard::VaultStandardQueryMsg::Info {} => { + Ok(to_binary(&query_info(deps)?)?) + } + cw_vault_multi_standard::VaultStandardQueryMsg::PreviewDeposit { assets: _ } => todo!(), + cw_vault_multi_standard::VaultStandardQueryMsg::DepositRatio => todo!(), + cw_vault_multi_standard::VaultStandardQueryMsg::PreviewRedeem { amount: _ } => todo!(), + cw_vault_multi_standard::VaultStandardQueryMsg::TotalAssets {} => { + Ok(to_binary(&query_total_assets(deps, env)?)?) + } + cw_vault_multi_standard::VaultStandardQueryMsg::TotalVaultTokenSupply {} => { + Ok(to_binary(&query_total_vault_token_supply(deps)?)?) + } + cw_vault_multi_standard::VaultStandardQueryMsg::ConvertToShares { amount: _ } => todo!(), + cw_vault_multi_standard::VaultStandardQueryMsg::ConvertToAssets { amount: _ } => todo!(), + cw_vault_multi_standard::VaultStandardQueryMsg::VaultExtension(msg) => match msg { + crate::msg::ExtensionQueryMsg::Metadata {} => Ok(to_binary(&query_metadata(deps)?)?), + crate::msg::ExtensionQueryMsg::Balances(msg) => match msg { + crate::msg::UserBalanceQueryMsg::UserSharesBalance { user } => { + Ok(to_binary(&query_user_balance(deps, user)?)?) + } + crate::msg::UserBalanceQueryMsg::UserRewards { user } => { + Ok(to_binary(&query_user_rewards(deps, user)?)?) + } + }, + crate::msg::ExtensionQueryMsg::ConcentratedLiquidity(msg) => match msg { + crate::msg::ClQueryMsg::Pool {} => Ok(to_binary(&query_pool(deps)?)?), + crate::msg::ClQueryMsg::Position {} => Ok(to_binary(&query_position(deps)?)?), + crate::msg::ClQueryMsg::RangeAdmin {} => todo!(), + }, + }, + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { + match msg.id.into() { + Replies::InstantiateCreatePosition => { + handle_instantiate_create_position_reply(deps, env, msg.result) + } + Replies::DepositCreatePosition => { + handle_deposit_create_position_reply(deps, env, msg.result) + } + Replies::CollectIncentives => handle_collect_incentives_reply(deps, env, msg.result), + Replies::CollectSpreadRewards => handle_collect_spread_rewards_reply(deps, env, msg.result), + Replies::WithdrawPosition => handle_withdraw_position_reply(deps, env, msg.result), + Replies::RangeInitialCreatePosition => { + handle_initial_create_position_reply(deps, env, msg.result) + } + Replies::RangeIterationCreatePosition => { + handle_iteration_create_position_reply(deps, env, msg.result) + } + Replies::Swap => handle_swap_reply(deps, env, msg.result), + Replies::Merge => handle_merge_response(deps, msg.result), + Replies::CreateDenom => handle_create_denom_reply(deps, msg.result), + Replies::WithdrawUser => handle_withdraw_user_reply(deps, msg.result), + Replies::WithdrawMerge => handle_merge_withdraw_reply(deps, env, msg.result), + Replies::CreatePositionMerge => handle_merge_create_position_reply(deps, env, msg.result), + Replies::Unknown => unimplemented!(), + } +} + +#[cfg(test)] +mod tests {} diff --git a/smart-contracts/contracts/cl-vault/src/error.rs b/smart-contracts/contracts/cl-vault/src/error.rs new file mode 100644 index 000000000..e997bf1cf --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/error.rs @@ -0,0 +1,120 @@ +use cosmwasm_std::{ + CheckedFromRatioError, CheckedMultiplyRatioError, Coin, ConversionOverflowError, Decimal256, + Decimal256RangeExceeded, DivideByZeroError, OverflowError, StdError, Uint128, +}; + +use cw_utils::PaymentError; +use thiserror::Error; + +use std::num::ParseIntError; + +pub type ContractResult = Result; + +/// AutocompoundingVault errors +#[allow(missing_docs)] +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Pool-id {pool_id} not found")] + PoolNotFound { pool_id: u64 }, + + #[error("Position Not Found")] + PositionNotFound, + + #[error("Sent the wrong amount of denoms")] + IncorrectAmountFunds, + + #[error("Modify range state item not found")] + ModifyRangeStateNotFound, + + #[error("Cannot do two swaps at the same time")] + SwapInProgress, + + #[error("Swap deposit merge state item not found")] + SwapDepositMergeStateNotFound, + + #[error("Swap failed: {message}")] + SwapFailed { message: String }, + + #[error("Vault shares sent in does not equal amount requested")] + IncorrectShares, + + #[error("{0}")] + DivideByZeroError(#[from] DivideByZeroError), + + #[error("{0}")] + CheckedMultiplyRatioError(#[from] CheckedMultiplyRatioError), + + #[error("{0}")] + Decimal256RangeExceededError(#[from] Decimal256RangeExceeded), + + #[error("Overflow")] + Overflow {}, + + #[error("{0}")] + OverflowError(#[from] OverflowError), + + #[error("{0}")] + ConversionOverflowError(#[from] ConversionOverflowError), + + #[error("{0}")] + DecodeError(#[from] prost::DecodeError), + + #[error("{0}")] + MultiplyRatioError(#[from] CheckedFromRatioError), + + #[error("This message does no accept funds")] + NonPayable {}, + + #[error("{0}")] + PaymentError(#[from] PaymentError), + + #[error("{0}")] + ParseIntError(#[from] ParseIntError), + + // Add any other custom errors you like here. + // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. + + // todo: add apollo errors one by one and see what gives us type errors + // apollo errors below (remove from above when you add) + #[error("Unexpected funds sent. Expected: {expected:?}, Actual: {actual:?}")] + UnexpectedFunds { + expected: Vec, + actual: Vec, + }, + + #[error("Bad token out requested for swap, must be one of: {base_token:?}, {quote_token:?}")] + BadTokenForSwap { + base_token: String, + quote_token: String, + }, + + #[error("Insufficient funds for swap. Have: {balance}, Need: {needed}")] + InsufficientFundsForSwap { balance: Uint128, needed: Uint128 }, + + #[error("Insufficient funds")] + InsufficientFunds, + + #[error("Cannot merge positions that are in different ticks")] + DifferentTicksInMerge, + + #[error("Tick index minimum error")] + TickIndexMinError {}, + + #[error("Tick index maximum error")] + TickIndexMaxError {}, + + #[error("Price must be between 0.000000000001 and 100000000000000000000000000000000000000. Got {:?}", price)] + PriceBoundError { price: Decimal256 }, + + #[error("Cannot handle negative powers in uints")] + CannotHandleNegativePowersInUint {}, + + #[error("Invalid current tick and deposit token combination")] + InvalidCurrentTick {}, +} diff --git a/smart-contracts/contracts/cl-vault/src/helpers.rs b/smart-contracts/contracts/cl-vault/src/helpers.rs new file mode 100644 index 000000000..d39abb3c3 --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/helpers.rs @@ -0,0 +1,453 @@ +use std::str::FromStr; + +use crate::math::tick::tick_to_price; +use crate::state::ADMIN_ADDRESS; +use crate::{error::ContractResult, state::POOL_CONFIG, ContractError}; +use cosmwasm_std::{ + coin, Addr, Coin, Decimal, Decimal256, Deps, Fraction, MessageInfo, QuerierWrapper, Storage, + Uint128, Uint256, +}; +use osmosis_std::types::osmosis::poolmanager::v1beta1::PoolmanagerQuerier; + +/// returns the Coin of the needed denoms in the order given in denoms + +pub(crate) fn must_pay_one_or_two( + info: &MessageInfo, + denoms: (String, String), +) -> ContractResult<(Coin, Coin)> { + if info.funds.len() != 2 && info.funds.len() != 1 { + return Err(ContractError::IncorrectAmountFunds); + } + + let token0 = info + .funds + .clone() + .into_iter() + .find(|coin| coin.denom == denoms.0) + .unwrap_or(coin(0, denoms.0)); + + let token1 = info + .funds + .clone() + .into_iter() + .find(|coin| coin.denom == denoms.1) + .unwrap_or(coin(0, denoms.1)); + + Ok((token0, token1)) +} + +/// get_spot_price +/// +/// gets the spot price of the pool which this vault is managing funds in. This will always return token0 in terms of token1 (or would it be the other way around?) +pub fn get_spot_price( + storage: &dyn Storage, + querier: &QuerierWrapper, +) -> Result { + let pool_config = POOL_CONFIG.load(storage)?; + + let pm_querier = PoolmanagerQuerier::new(querier); + let spot_price = + pm_querier.spot_price(pool_config.pool_id, pool_config.token0, pool_config.token1)?; + + Ok(Decimal::from_str(&spot_price.spot_price)?) +} + +// /// get_liquidity_needed_for_tokens +// /// +// /// this function calculates the liquidity needed for depositing token0 and quote token amounts respectively and returns both. +// /// depositing both tokens would result in a refund of the token with higher needed liquidity +// /// +// /// thanks: https://github.com/osmosis-labs/osmosis/blob/main/x/concentrated-liquidity/README.md#adding-liquidity +// pub fn get_liquidity_needed_for_tokens( +// delta_token0: String, +// delta_token1: String, +// lower_tick: i64, +// upper_tick: i64, +// ) -> Result<(Uint128, Uint128), ContractError> { +// // todo check that decimal casts are correct +// let delta_x = Decimal256::from_atomics(Uint128::from_str(&delta_token0)?, 18)?; +// let delta_y = Decimal256::from_atomics(Uint128::from_str(&delta_token1)?, 18)?; +// // calc liquidity needed for token + +// // save gas and read easier by calcing ahead (gas savings prob already done by compiler) +// let price_lower = tick_to_price(lower_tick)?; +// let price_upper = tick_to_price(upper_tick)?; +// let sqrt_price_lower = price_lower.sqrt(); +// let sqrt_price_upper = price_upper.sqrt(); +// let denominator = sqrt_price_upper.checked_sub(sqrt_price_lower)?; + +// // liquidity follows the formula found in the link above this function. basically this: +// // liquidity_x = (delta_x * sqrt(price_lower) * sqrt(price_upper))/(sqrt(price_upper) - sqrt(price_lower)) +// // liquidity_7 = (delta_x)/(sqrt(price_upper) - sqrt(price_lower)) +// // overflow city? +// let liquidity_x = delta_x +// .checked_mul(sqrt_price_lower)? +// .checked_mul(sqrt_price_upper)? +// .checked_div(denominator)?; + +// let liquidity_y = delta_y.checked_div(denominator)?; + +// // todo: check this is what we want +// Ok(( +// liquidity_x.atomics().try_into()?, +// liquidity_y.atomics().try_into()?, +// )) +// } + +// pub fn get_deposit_amounts_for_liquidity_needed( +// liquidity_needed_token0: Uint128, +// liquidity_needed_token1: Uint128, +// token0_amount: String, +// token1_amount: String, +// // i hate this type but it's arguably a good way to write this +// ) -> Result<((Uint128, Uint128), (Uint128, Uint128)), ContractError> { +// // calc deposit amounts for liquidity needed +// let amount_0 = Uint128::from_str(&token0_amount)?; +// let amount_1 = Uint128::from_str(&token1_amount)?; + +// // one of these will be zero +// let mut remainder_0 = Uint128::zero(); +// let mut remainder_1 = Uint128::zero(); + +// let (deposit_amount_0, deposit_amount_1) = if liquidity_needed_token0 > liquidity_needed_token1 +// { +// // scale base token amount down by L1/L0, take full amount of quote token +// let new_amount_0 = +// amount_0.multiply_ratio(liquidity_needed_token1, liquidity_needed_token0); +// remainder_0 = amount_0.checked_sub(new_amount_0).unwrap(); + +// (new_amount_0, amount_1) +// } else { +// // scale quote token amount down by L0/L1, take full amount of base token +// let new_amount_1 = +// amount_1.multiply_ratio(liquidity_needed_token0, liquidity_needed_token1); +// remainder_1 = amount_1.checked_sub(new_amount_1).unwrap(); + +// (amount_0, new_amount_1) +// }; + +// Ok(( +// (deposit_amount_0, deposit_amount_1), +// (remainder_0, remainder_1), +// )) +// } + +// this math is straight from the readme +pub fn get_single_sided_deposit_0_to_1_swap_amount( + token0_balance: Uint128, + lower_tick: i64, + current_tick: i64, + upper_tick: i64, +) -> Result { + // TODO error here if this condition holds + // if current_tick < lower_tick { + // return ; + // } + + let lower_price = tick_to_price(lower_tick)?; + let current_price = tick_to_price(current_tick)?; + let upper_price = tick_to_price(upper_tick)?; + + let cur_price_sqrt = current_price.sqrt(); + let lower_price_sqrt = lower_price.sqrt(); + let upper_price_sqrt = upper_price.sqrt(); + + // let pool_metadata_constant: Decimal256 = cur_price_sqrt + // .checked_mul(lower_price_sqrt)? + // .checked_mul(cur_price_sqrt.checked_sub(lower_price_sqrt)?)? + // .checked_div(upper_price_sqrt.checked_sub(cur_price_sqrt)?)?; + + let pool_metadata_constant: Decimal256 = (upper_price_sqrt + .checked_mul(cur_price_sqrt)? + .checked_mul(cur_price_sqrt.checked_sub(lower_price_sqrt)?))? + .checked_div(upper_price_sqrt.checked_sub(cur_price_sqrt)?)?; + + let spot_price_over_pool_metadata_constant = + current_price.checked_div(pool_metadata_constant)?; + + let denominator = Decimal256::one().checked_add(spot_price_over_pool_metadata_constant)?; + + let swap_amount: Uint128 = Uint256::from(token0_balance) + .multiply_ratio(denominator.denominator(), denominator.numerator()) + .try_into()?; + + Ok(swap_amount) +} + +pub fn get_single_sided_deposit_1_to_0_swap_amount( + token1_balance: Uint128, + lower_tick: i64, + current_tick: i64, + upper_tick: i64, +) -> Result { + let lower_price = tick_to_price(lower_tick)?; + let current_price = tick_to_price(current_tick)?; + let upper_price = tick_to_price(upper_tick)?; + + let cur_price_sqrt = current_price.sqrt(); + let lower_price_sqrt = lower_price.sqrt(); + let upper_price_sqrt = upper_price.sqrt(); + + let pool_metadata_constant: Decimal256 = (upper_price_sqrt + .checked_mul(cur_price_sqrt)? + .checked_mul(cur_price_sqrt.checked_sub(lower_price_sqrt)?))? + .checked_div(upper_price_sqrt.checked_sub(cur_price_sqrt)?)?; + + let pool_metadata_constant_over_spot_price: Decimal256 = + pool_metadata_constant.checked_div(current_price)?; + + let denominator = Decimal256::one().checked_add(pool_metadata_constant_over_spot_price)?; + + let swap_amount: Uint128 = Uint256::from(token1_balance) + .multiply_ratio(denominator.denominator(), denominator.numerator()) + .try_into()?; + + Ok(swap_amount) +} + +pub fn with_slippage(amount: Uint128, slippage: Decimal) -> Result { + let slippage_multiplier = Decimal::one().checked_sub(slippage)?; + + let adjusted_amount = amount.checked_multiply_ratio( + slippage_multiplier.numerator(), + slippage_multiplier.denominator(), + )?; + + Ok(adjusted_amount) +} + +/// This function compares the address of the message sender (caller) with the current admin +/// address stored in the state. This provides a convenient way to verify if the caller +/// is the admin in a single line. +pub fn assert_admin(deps: Deps, caller: &Addr) -> Result { + if ADMIN_ADDRESS.load(deps.storage)? != caller { + Err(ContractError::Unauthorized {}) + } else { + Ok(caller.clone()) + } +} + +pub fn round_up_to_nearest_multiple(amount: i64, multiple: i64) -> i64 { + let remainder = amount % multiple; + if remainder == 0 { + amount + } else if amount < 0 { + amount - remainder + } else { + amount + multiple - remainder + } +} + +#[cfg(test)] +mod tests { + + use std::collections::HashMap; + + use cosmwasm_std::{coin, testing::mock_dependencies, Addr}; + + use crate::math::tick::price_to_tick; + + use super::*; + + #[test] + fn must_pay_one_or_two_works_ordered() { + let expected0 = coin(100, "uatom"); + let expected1 = coin(200, "uosmo"); + let info = MessageInfo { + sender: Addr::unchecked("sender"), + funds: vec![expected0.clone(), expected1.clone()], + }; + let (token0, token1) = + must_pay_one_or_two(&info, ("uatom".to_string(), "uosmo".to_string())).unwrap(); + assert_eq!(expected0, token0); + assert_eq!(expected1, token1); + } + + #[test] + fn must_pay_one_or_two_works_unordered() { + let expected0 = coin(100, "uatom"); + let expected1 = coin(200, "uosmo"); + let info = MessageInfo { + sender: Addr::unchecked("sender"), + funds: vec![expected1.clone(), expected0.clone()], + }; + let (token0, token1) = + must_pay_one_or_two(&info, ("uatom".to_string(), "uosmo".to_string())).unwrap(); + assert_eq!(expected0, token0); + assert_eq!(expected1, token1); + } + + #[test] + fn must_pay_one_or_two_rejects_three() { + let expected0 = coin(100, "uatom"); + let expected1 = coin(200, "uosmo"); + let info = MessageInfo { + sender: Addr::unchecked("sender"), + funds: vec![expected1, expected0, coin(200, "uqsr")], + }; + let _err = + must_pay_one_or_two(&info, ("uatom".to_string(), "uosmo".to_string())).unwrap_err(); + } + + #[test] + fn must_pay_one_or_two_accepts_second_token() { + let info = MessageInfo { + sender: Addr::unchecked("sender"), + funds: vec![coin(200, "uosmo")], + }; + let res = must_pay_one_or_two(&info, ("uatom".to_string(), "uosmo".to_string())).unwrap(); + assert_eq!((coin(0, "uatom"), coin(200, "uosmo")), res) + } + + #[test] + fn must_pay_one_or_two_accepts_first_token() { + let info = MessageInfo { + sender: Addr::unchecked("sender"), + funds: vec![coin(200, "uatom")], + }; + let res = must_pay_one_or_two(&info, ("uatom".to_string(), "uosmo".to_string())).unwrap(); + assert_eq!((coin(200, "uatom"), coin(0, "uosmo")), res) + } + + #[test] + fn test_0_to_1_swap() { + let mut deps = mock_dependencies(); + + let lower_price = "4500"; + let upper_price = "5500"; + let token0amt = 200000u128; + + // prices and expected amounts taken from https://docs.google.com/spreadsheets/d/1xPsKsQkM0apTZQPBBwVlEyB5Sk31sw6eE8U0FgnTWUQ/edit?usp=sharing + let mut prices = HashMap::new(); + prices.insert("4501", Uint128::new(232)); + prices.insert("4600", Uint128::new(22674)); + prices.insert("4700", Uint128::new(44304)); + prices.insert("4800", Uint128::new(65099)); + prices.insert("4900", Uint128::new(85241)); + prices.insert("5000", Uint128::new(104884)); + prices.insert("5100", Uint128::new(124166)); + prices.insert("5200", Uint128::new(143210)); + prices.insert("5300", Uint128::new(162128)); + prices.insert("5400", Uint128::new(181025)); + prices.insert("5499", Uint128::new(199809)); + + let lower_tick = price_to_tick( + deps.as_mut().storage, + Decimal256::from_str(lower_price).unwrap(), + ) + .unwrap() + .try_into() + .unwrap(); + + let upper_tick = price_to_tick( + deps.as_mut().storage, + Decimal256::from_str(upper_price).unwrap(), + ) + .unwrap() + .try_into() + .unwrap(); + + for (price, result) in prices.into_iter() { + let curr_tick = + price_to_tick(deps.as_mut().storage, Decimal256::from_str(price).unwrap()) + .unwrap() + .try_into() + .unwrap(); + + let swap_amount = get_single_sided_deposit_0_to_1_swap_amount( + token0amt.into(), + lower_tick, + curr_tick, + upper_tick, + ) + .unwrap(); + + assert_eq!(swap_amount, result); + } + } + + #[test] + fn test_1_to_0_swap() { + let mut deps = mock_dependencies(); + + let lower_price = "4500"; + let upper_price = "5500"; + + let token1amt = 200000u128; + + // prices and expected amounts taken from https://docs.google.com/spreadsheets/d/1xPsKsQkM0apTZQPBBwVlEyB5Sk31sw6eE8U0FgnTWUQ/edit?usp=sharing + let mut prices = HashMap::new(); + prices.insert("4501", Uint128::new(199767)); + prices.insert("4600", Uint128::new(177325)); + prices.insert("4700", Uint128::new(155695)); + prices.insert("4800", Uint128::new(134900)); + prices.insert("4900", Uint128::new(114758)); + prices.insert("5000", Uint128::new(95115)); + prices.insert("5100", Uint128::new(75833)); + prices.insert("5200", Uint128::new(56789)); + prices.insert("5300", Uint128::new(37871)); + prices.insert("5400", Uint128::new(18974)); + prices.insert("5499", Uint128::new(190)); + + let lower_tick = price_to_tick( + deps.as_mut().storage, + Decimal256::from_str(lower_price).unwrap(), + ) + .unwrap() + .try_into() + .unwrap(); + + let upper_tick: i64 = price_to_tick( + deps.as_mut().storage, + Decimal256::from_str(upper_price).unwrap(), + ) + .unwrap() + .try_into() + .unwrap(); + + for (price, result) in prices.into_iter() { + let curr_tick = + price_to_tick(deps.as_mut().storage, Decimal256::from_str(price).unwrap()) + .unwrap() + .try_into() + .unwrap(); + + let swap_amount = get_single_sided_deposit_1_to_0_swap_amount( + token1amt.into(), + lower_tick, + curr_tick, + upper_tick, + ) + .unwrap(); + + assert_eq!(swap_amount, result); + } + } + + #[test] + fn test_round_up_to_nearest_multiple() { + assert_eq!(round_up_to_nearest_multiple(10, 5), 10); + assert_eq!(round_up_to_nearest_multiple(11, 5), 15); + assert_eq!(round_up_to_nearest_multiple(12, 5), 15); + assert_eq!(round_up_to_nearest_multiple(13, 5), 15); + assert_eq!(round_up_to_nearest_multiple(14, 5), 15); + assert_eq!(round_up_to_nearest_multiple(15, 5), 15); + assert_eq!(round_up_to_nearest_multiple(16, 5), 20); + assert_eq!(round_up_to_nearest_multiple(17, 5), 20); + assert_eq!(round_up_to_nearest_multiple(18, 5), 20); + assert_eq!(round_up_to_nearest_multiple(19, 5), 20); + assert_eq!(round_up_to_nearest_multiple(20, 5), 20); + // does it also work for negative inputs? + assert_eq!(round_up_to_nearest_multiple(-10, 5), -10); + assert_eq!(round_up_to_nearest_multiple(-11, 5), -10); + assert_eq!(round_up_to_nearest_multiple(-12, 5), -10); + assert_eq!(round_up_to_nearest_multiple(-13, 5), -10); + assert_eq!(round_up_to_nearest_multiple(-14, 5), -10); + assert_eq!(round_up_to_nearest_multiple(-15, 5), -15); + assert_eq!(round_up_to_nearest_multiple(-16, 5), -15); + assert_eq!(round_up_to_nearest_multiple(-17, 5), -15); + assert_eq!(round_up_to_nearest_multiple(-18, 5), -15); + assert_eq!(round_up_to_nearest_multiple(-19, 5), -15); + assert_eq!(round_up_to_nearest_multiple(-20, 5), -20); + } +} diff --git a/smart-contracts/contracts/cl-vault/src/instantiate.rs b/smart-contracts/contracts/cl-vault/src/instantiate.rs new file mode 100644 index 000000000..51b1ab8da --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/instantiate.rs @@ -0,0 +1,139 @@ +use cosmwasm_std::{ + coin, CosmosMsg, Decimal, DepsMut, Env, MessageInfo, Response, StdError, SubMsg, SubMsgResult, + Uint128, +}; +use osmosis_std::types::osmosis::concentratedliquidity::v1beta1::{ + MsgCreatePositionResponse, Pool, +}; +use osmosis_std::types::osmosis::poolmanager::v1beta1::PoolmanagerQuerier; +use osmosis_std::types::osmosis::tokenfactory::v1beta1::{ + MsgCreateDenom, MsgCreateDenomResponse, MsgMint, +}; + +use crate::helpers::must_pay_one_or_two; +use crate::msg::InstantiateMsg; +use crate::reply::Replies; +use crate::rewards::Rewards; +use crate::state::{ + Metadata, PoolConfig, Position, ADMIN_ADDRESS, METADATA, POOL_CONFIG, POSITION, RANGE_ADMIN, + STRATEGIST_REWARDS, VAULT_CONFIG, VAULT_DENOM, +}; +use crate::vault::concentrated_liquidity::create_position; +use crate::ContractError; + +pub fn handle_instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + // a performance fee of more than 1 means that the performance fee is more than 100% + if msg.config.performance_fee > Decimal::one() { + return Err(ContractError::Std(StdError::generic_err( + "performance fee cannot be more than 1.0", + ))); + } + + VAULT_CONFIG.save(deps.storage, &msg.config)?; + + let pool: Pool = PoolmanagerQuerier::new(&deps.querier) + .pool(msg.pool_id)? + .pool + .ok_or(ContractError::PoolNotFound { + pool_id: msg.pool_id, + })? + .try_into() + .unwrap(); + + POOL_CONFIG.save( + deps.storage, + &PoolConfig { + pool_id: pool.id, + token0: pool.token0.clone(), + token1: pool.token1.clone(), + }, + )?; + + STRATEGIST_REWARDS.save(deps.storage, &Rewards::new())?; + + METADATA.save( + deps.storage, + &Metadata { + thesis: msg.thesis, + name: msg.name, + }, + )?; + + let admin = deps.api.addr_validate(&msg.admin)?; + + ADMIN_ADDRESS.save(deps.storage, &admin)?; + RANGE_ADMIN.save(deps.storage, &deps.api.addr_validate(&msg.range_admin)?)?; + + let create_denom_msg: CosmosMsg = MsgCreateDenom { + sender: env.contract.address.to_string(), + subdenom: msg.vault_token_subdenom, + } + .into(); + + // in order to create the initial position, we need some funds to throw in there, these funds should be seen as burned + let (initial0, initial1) = must_pay_one_or_two(&info, (pool.token0, pool.token1))?; + + let create_position_msg = create_position( + deps.storage, + &env, + msg.initial_lower_tick, + msg.initial_upper_tick, + vec![initial0, initial1], + Uint128::zero(), + Uint128::zero(), + )?; + + Ok(Response::new() + .add_submessage(SubMsg::reply_on_success( + create_denom_msg, + Replies::CreateDenom as u64, + )) + .add_submessage(SubMsg::reply_on_success( + create_position_msg, + Replies::InstantiateCreatePosition as u64, + ))) +} + +pub fn handle_create_denom_reply( + deps: DepsMut, + data: SubMsgResult, +) -> Result { + let response: MsgCreateDenomResponse = data.try_into()?; + VAULT_DENOM.save(deps.storage, &response.new_token_denom)?; + + Ok(Response::new().add_attribute("vault_denom", response.new_token_denom)) +} + +pub fn handle_instantiate_create_position_reply( + deps: DepsMut, + env: Env, + data: SubMsgResult, +) -> Result { + let response: MsgCreatePositionResponse = data.try_into()?; + POSITION.save( + deps.storage, + &Position { + position_id: response.position_id, + }, + )?; + + let liquidity_amount = Decimal::raw(response.liquidity_created.parse()?); + let vault_denom = VAULT_DENOM.load(deps.storage)?; + + // todo do we want to mint the initial mint to the instantiater, or just not care? + let mint_msg = MsgMint { + sender: env.contract.address.to_string(), + amount: Some(coin(liquidity_amount.atomics().u128(), vault_denom).into()), + mint_to_address: env.contract.address.to_string(), + }; + + Ok(Response::new() + .add_message(mint_msg) + .add_attribute("initial_position", response.position_id.to_string()) + .add_attribute("initial_liquidity", response.liquidity_created)) +} diff --git a/smart-contracts/contracts/cl-vault/src/lib.rs b/smart-contracts/contracts/cl-vault/src/lib.rs new file mode 100644 index 000000000..2991f66c2 --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/lib.rs @@ -0,0 +1,26 @@ +pub mod contract; +mod error; +pub mod helpers; +mod instantiate; +mod math; +pub mod msg; +mod query; +mod reply; +mod rewards; +pub mod state; +mod vault; + +pub use crate::error::ContractError; + +#[cfg(test)] +mod test_tube; + +#[cfg(test)] +mod test_helpers; + +#[macro_export] +macro_rules! debug { + ($deps: ident, $tag:literal, $($arg:tt)*) => { + $deps.api.debug(format!(concat!($tag, " :{:?}"), $($arg)*).as_str()) + }; +} diff --git a/smart-contracts/contracts/cl-vault/src/math/liquidity.rs b/smart-contracts/contracts/cl-vault/src/math/liquidity.rs new file mode 100644 index 000000000..8fcf4051e --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/math/liquidity.rs @@ -0,0 +1,121 @@ +use crate::ContractError; +use cosmwasm_std::{Decimal, Decimal256, StdError, Uint256, Uint512}; + +/// liquidity0 calculates the amount of liquitiy gained from adding an amount of token0 to a position +pub fn _liquidity0( + amount: Decimal, + sqrt_price_a: Decimal, + sqrt_price_b: Decimal, +) -> Result { + let mut sqrt_price_a: Uint512 = sqrt_price_a.atomics().into(); + let mut sqrt_price_b: Uint512 = sqrt_price_b.atomics().into(); + let amount: Uint512 = amount.atomics().into(); + + if sqrt_price_a > sqrt_price_b { + std::mem::swap(&mut sqrt_price_a, &mut sqrt_price_b); + } + + let product = sqrt_price_a.checked_mul(sqrt_price_b)?; + // let product = Uint256::from(sqrt_price_a.atomics().u128()).checked_mul(Uint256::from(sqrt_price_b.atomics().u128()))?; + let diff = sqrt_price_b.checked_sub(sqrt_price_a)?; + + if diff.is_zero() { + return Err(ContractError::Std(StdError::generic_err( + "liquidity0 diff is zero", + ))); + } + + // during this check mul, the result is being truncated and giving is a different final result than expected + let result = amount.checked_mul(product)?.checked_div(diff)?; + // convert the Uint512 back to a decimal, we want to place the decimal at decimal_place 36 + // to do this, we truncate the first 18 digits, and then call Decimal::new + // Should we check here that the leftover bytes are zero? that is technically an overflow + let result_bytes: [u8; 64] = result.to_le_bytes(); + for b in result_bytes[32..64].iter() { + if b != &0_u8 { + return Err(ContractError::Overflow {}); + } + } + let intermediate = Uint256::from_le_bytes(result_bytes[..32].try_into().unwrap()); + // we use Decimal256 to + let intermediate_2 = Decimal256::from_atomics(intermediate, 36).unwrap(); + + // since we start with Decimal and multiply with big_factor, we expect to be able to convert back here + Ok(Decimal::new(intermediate_2.atomics().try_into()?)) +} + +// TODO figure out if liquidity1 need to be Uint512's aswell, currently I (Laurens) don't believe so since we should only need more precision if we multiply decimals +/// liquidity1 calculates the amount of liquitiy gained from adding an amount of token1 to a position +pub fn _liquidity1( + amount: Decimal, + sqrt_price_a: Decimal, + sqrt_price_b: Decimal, +) -> Result { + let mut sqrt_price_a = sqrt_price_a; + let mut sqrt_price_b = sqrt_price_b; + + if sqrt_price_a > sqrt_price_b { + std::mem::swap(&mut sqrt_price_a, &mut sqrt_price_b); + } + + let diff = sqrt_price_b + .checked_sub(sqrt_price_a) + .map_err(|err| StdError::generic_err(err.to_string()))?; + if diff.is_zero() { + return Err(ContractError::Std(StdError::generic_err( + "liquidity1 diff is zero", + ))); + } + + let result = amount.checked_div(diff)?; + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_liquidity0() { + // from the osmosis math tests + // current_sqrt_p: sqrt5000BigDec, // 5000 + // sqrtPHigh: sqrt5500BigDec, // 5500 + // amount0Desired: sdk.NewInt(1000000), + // expectedLiquidity: "1519437308.014768571720923239", + let amount0_desired: Decimal = Decimal::from_ratio(1000000_u128, 1_u128); + let current_sqrt_p = Decimal::from_atomics(70710678118654752440_u128, 18).unwrap(); + let sqrt_p_high = Decimal::from_atomics(74161984870956629487_u128, 18).unwrap(); + + let result = _liquidity0(amount0_desired, current_sqrt_p, sqrt_p_high).unwrap(); + // TODO our amount is slightly different 10 digits behind the comma, do we care about that? + assert_eq!(result.to_string(), "1519437308.014768571720923239") + } + + #[test] + fn test_liquidity1() { + let amount1_desired = Decimal::from_atomics(5000000000_u128, 0).unwrap(); + let current_sqrt_p = Decimal::from_atomics(70710678118654752440_u128, 18).unwrap(); + let sqrt_p_low = Decimal::from_atomics(67416615162732695594_u128, 18).unwrap(); + + let result = _liquidity1(amount1_desired, current_sqrt_p, sqrt_p_low).unwrap(); + assert_eq!(result.to_string(), "1517882343.751510418088349649"); + } + + #[test] + fn test_max_liquidity0() { + let max_sqrt_price = Decimal::raw(10000000000000000000000000000000000000_u128); + let max_sqrt_price_low = Decimal::raw(300000000000000000000000000000000_u128); + let amount0_desired: Decimal = Decimal::from_ratio(1000000_u128, 1_u128); + // we only care about overflows here + let _ = _liquidity0(amount0_desired, max_sqrt_price, max_sqrt_price_low).unwrap(); + } + + #[test] + fn test_max_liquidity1() { + let max_sqrt_price = Decimal::raw(10000000000000000000000000000000000000_u128); + let max_sqrt_price_low = Decimal::raw(1000000000000000000000000000000000000_u128); + let amount0_desired: Decimal = Decimal::from_ratio(1000000_u128, 1_u128); + // we only care about overflows here + let _ = _liquidity1(amount0_desired, max_sqrt_price, max_sqrt_price_low).unwrap(); + } +} diff --git a/smart-contracts/contracts/cl-vault/src/math/mod.rs b/smart-contracts/contracts/cl-vault/src/math/mod.rs new file mode 100644 index 000000000..b9284683b --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/math/mod.rs @@ -0,0 +1,2 @@ +mod liquidity; +pub mod tick; diff --git a/smart-contracts/contracts/cl-vault/src/math/tick.rs b/smart-contracts/contracts/cl-vault/src/math/tick.rs new file mode 100644 index 000000000..e194c7292 --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/math/tick.rs @@ -0,0 +1,481 @@ +use std::str::FromStr; + +use cosmwasm_std::{Decimal, Decimal256, Storage, Uint128}; + +use crate::{ + state::{TickExpIndexData, TICK_EXP_CACHE}, + ContractError, +}; + +const MAX_SPOT_PRICE: &str = "100000000000000000000000000000000000000"; // 10^35 +const MIN_SPOT_PRICE: &str = "0.000000000001"; // 10^-12 +const EXPONENT_AT_PRICE_ONE: i64 = -6; +const MIN_INITIALIZED_TICK: i64 = -108000000; +const MAX_TICK: i128 = 342000000; + +// TODO: exponent_at_current_price_one is fixed at -6? We assume exp is always neg? +pub fn tick_to_price(tick_index: i64) -> Result { + if tick_index == 0 { + return Ok(Decimal256::one()); + } + + let geometric_exponent_increment_distance_in_ticks = Decimal::from_str("9")? + .checked_mul(_pow_ten_internal_dec(-EXPONENT_AT_PRICE_ONE)?)? + .to_string() + .parse::()?; + + // Check that the tick index is between min and max value + if tick_index < MIN_INITIALIZED_TICK { + return Err(ContractError::TickIndexMinError {}); + } + + if tick_index > MAX_TICK as i64 { + return Err(ContractError::TickIndexMaxError {}); + } + + // Use floor division to determine what the geometricExponent is now (the delta) + let geometric_exponent_delta = tick_index / geometric_exponent_increment_distance_in_ticks; + + // Calculate the exponentAtCurrentTick from the starting exponentAtPriceOne and the geometricExponentDelta + let mut exponent_at_current_tick = EXPONENT_AT_PRICE_ONE + geometric_exponent_delta; + + if tick_index < 0 { + // We must decrement the exponentAtCurrentTick when entering the negative tick range in order to constantly step up in precision when going further down in ticks + // Otherwise, from tick 0 to tick -(geometricExponentIncrementDistanceInTicks), we would use the same exponent as the exponentAtPriceOne + exponent_at_current_tick -= 1 + } + + // Knowing what our exponentAtCurrentTick is, we can then figure out what power of 10 this exponent corresponds to + // We need to utilize bigDec here since increments can go beyond the 10^-18 limits set by the sdk + let current_additive_increment_in_ticks = pow_ten_internal_dec_256(exponent_at_current_tick)?; + + // Now, starting at the minimum tick of the current increment, we calculate how many ticks in the current geometricExponent we have passed + let num_additive_ticks = + tick_index - (geometric_exponent_delta * geometric_exponent_increment_distance_in_ticks); + + // Finally, we can calculate the price + + let price: Decimal256 = if num_additive_ticks < 0 { + _pow_ten_internal_dec(geometric_exponent_delta)? + .checked_sub( + Decimal::from_str(&num_additive_ticks.abs().to_string())?.checked_mul( + Decimal::from_str(¤t_additive_increment_in_ticks.to_string())?, + )?, + )? + .into() + } else { + pow_ten_internal_dec_256(geometric_exponent_delta)?.checked_add( + Decimal256::from_str(&num_additive_ticks.to_string())? + .checked_mul(current_additive_increment_in_ticks)?, + )? + }; + + // defense in depth, this logic would not be reached due to use having checked if given tick is in between + // min tick and max tick. + if price > Decimal256::from_str(MAX_SPOT_PRICE)? + || price < Decimal256::from_str(MIN_SPOT_PRICE)? + { + return Err(ContractError::PriceBoundError { price }); + } + Ok(price) +} + +// TODO: hashmaps vs CW maps? +pub fn price_to_tick(storage: &mut dyn Storage, price: Decimal256) -> Result { + if price > Decimal256::from_str(MAX_SPOT_PRICE)? + || price < Decimal256::from_str(MIN_SPOT_PRICE)? + { + return Err(ContractError::PriceBoundError { price }); + } + if price == Decimal256::one() { + // return Ok(0i128); + return Ok(0i128); + } + + // TODO: move this to instantiate? + build_tick_exp_cache(storage)?; + + let mut geo_spacing; + if price > Decimal256::one() { + let mut index = 0i64; + geo_spacing = TICK_EXP_CACHE.load(storage, index)?; + while geo_spacing.max_price < price { + index += 1; + geo_spacing = TICK_EXP_CACHE.load(storage, index)?; + } + } else { + let mut index = -1; + geo_spacing = TICK_EXP_CACHE.load(storage, index)?; + while geo_spacing.initial_price > price { + index -= 1; + geo_spacing = TICK_EXP_CACHE.load(storage, index)?; + } + } + let price_in_this_exponent = price - geo_spacing.initial_price; + + let ticks_filled_by_current_spacing = + price_in_this_exponent / geo_spacing.additive_increment_per_tick; + + // TODO: Optimize this type conversion + let ticks_filled_uint_floor = ticks_filled_by_current_spacing.to_uint_floor(); + let ticks_filled_int: i128 = Uint128::try_from(ticks_filled_uint_floor)? + .u128() + .try_into() + .unwrap(); + + let tick_index = geo_spacing.initial_tick as i128 + ticks_filled_int; + + Ok(tick_index) +} + +// due to pow restrictions we need to use unsigned integers; i.e. 10.pow(-exp: u32) +// so if the resulting power is positive, we take 10**exp; +// and if it is negative, we take 1/10**exp. +fn pow_ten_internal_u128(exponent: i64) -> Result { + if exponent >= 0 { + 10u128 + .checked_pow(exponent.unsigned_abs() as u32) + .ok_or(ContractError::Overflow {}) + } else { + // TODO: write tests for negative exponents as it looks like this will always be 0 + Err(ContractError::CannotHandleNegativePowersInUint {}) + } +} + +// same as pow_ten_internal but returns a Decimal to work with negative exponents +fn _pow_ten_internal_dec(exponent: i64) -> Result { + let p = 10u128 + .checked_pow(exponent.unsigned_abs() as u32) + .ok_or(ContractError::Overflow {})?; + if exponent >= 0 { + Ok(Decimal::from_ratio(p, 1u128)) + } else { + Ok(Decimal::from_ratio(1u128, p)) + } +} + +// same as pow_ten_internal but returns a Decimal to work with negative exponents +fn pow_ten_internal_dec_256(exponent: i64) -> Result { + let p = Decimal256::from_str("10")?.checked_pow(exponent.unsigned_abs() as u32)?; + // let p = 10_u128.pow(exponent as u32); + if exponent >= 0 { + Ok(p) + } else { + Ok(Decimal256::one() / p) + } +} + +fn build_tick_exp_cache(storage: &mut dyn Storage) -> Result<(), ContractError> { + // Build positive indices + let mut max_price = Decimal256::one(); + let mut cur_exp_index = 0i64; + + while max_price < Decimal256::from_str(MAX_SPOT_PRICE)? { + let tick_exp_index_data = TickExpIndexData { + initial_price: pow_ten_internal_dec_256(cur_exp_index)?, + max_price: pow_ten_internal_dec_256(cur_exp_index + 1)?, + additive_increment_per_tick: pow_ten_internal_dec_256( + EXPONENT_AT_PRICE_ONE + cur_exp_index, + )?, + initial_tick: (9u128 + .checked_mul(pow_ten_internal_u128(-EXPONENT_AT_PRICE_ONE)?) + .ok_or(ContractError::Overflow {})? as i64) + .checked_mul(cur_exp_index) + .ok_or(ContractError::Overflow {})?, + }; + TICK_EXP_CACHE.save(storage, cur_exp_index, &tick_exp_index_data)?; + + max_price = tick_exp_index_data.max_price; + cur_exp_index += 1; + } + + // Build negative indices + let mut min_price = Decimal256::one(); + cur_exp_index = -1; + while min_price > Decimal256::from_str(MIN_SPOT_PRICE)? { + let initial_price = pow_ten_internal_dec_256(cur_exp_index)?; + let max_price = pow_ten_internal_dec_256(cur_exp_index + 1)?; + let additive_increment_per_tick = + pow_ten_internal_dec_256(EXPONENT_AT_PRICE_ONE + cur_exp_index)?; + let initial_tick = (9u128 + .checked_mul(pow_ten_internal_u128(-EXPONENT_AT_PRICE_ONE)?) + .ok_or(ContractError::Overflow {})? as i64) + .checked_mul(cur_exp_index) + .ok_or(ContractError::Overflow {})?; + + let tick_exp_index_data = TickExpIndexData { + initial_price, + max_price, + additive_increment_per_tick, + initial_tick, + }; + TICK_EXP_CACHE.save(storage, cur_exp_index, &tick_exp_index_data)?; + + min_price = tick_exp_index_data.initial_price; + cur_exp_index -= 1; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::testing::mock_dependencies; + + #[test] + fn test_test_tube_tick_to_price() { + let mut deps = mock_dependencies(); + // example1 + let tick_index = 27445000_i128; + let _expected_price = Decimal256::from_str("30352").unwrap(); + let price = tick_to_price(tick_index.try_into().unwrap()).unwrap(); + println!("{:?}", price.to_string()); + // assert_eq!(price, expected_price); + let tick = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, tick) + } + + #[test] + fn test_tick_to_price() { + // example1 + let tick_index = 38035200; + let expected_price = Decimal256::from_str("30352").unwrap(); + let price = tick_to_price(tick_index).unwrap(); + assert_eq!(price, expected_price); + + // example2 + let tick_index = 38035300; + let expected_price = Decimal256::from_str("30353").unwrap(); + let price = tick_to_price(tick_index).unwrap(); + assert_eq!(price, expected_price); + + // example3 + let tick_index = -44821000; + let expected_price = Decimal256::from_str("0.000011790").unwrap(); + let price = tick_to_price(tick_index).unwrap(); + assert_eq!(price, expected_price); + + // example4 + let tick_index = -44820900; + let expected_price = Decimal256::from_str("0.000011791").unwrap(); + let price = tick_to_price(tick_index).unwrap(); + assert_eq!(price, expected_price); + + // example5 + let tick_index = -12104000; + let expected_price = Decimal256::from_str("0.068960").unwrap(); + let price = tick_to_price(tick_index).unwrap(); + assert_eq!(price, expected_price); + + // example6 + let tick_index = -12103900; + let expected_price = Decimal256::from_str("0.068961").unwrap(); + let price = tick_to_price(tick_index).unwrap(); + assert_eq!(price, expected_price); + + // example7 + let tick_index = MAX_TICK as i64 - 100; + let expected_price = + Decimal256::from_str("99999000000000000000000000000000000000").unwrap(); + let price = tick_to_price(tick_index).unwrap(); + assert_eq!(price, expected_price); + + // example8 + let tick_index = MAX_TICK as i64; + let expected_price = Decimal256::from_str(MAX_SPOT_PRICE).unwrap(); + let price = tick_to_price(tick_index).unwrap(); + assert_eq!(price, expected_price); + + // example9 + let tick_index = -20594000; + let expected_price = Decimal256::from_str("0.007406").unwrap(); + let price = tick_to_price(tick_index).unwrap(); + assert_eq!(price, expected_price); + + // example10 + let tick_index = -20593900; + let expected_price = Decimal256::from_str("0.0074061").unwrap(); + let price = tick_to_price(tick_index).unwrap(); + assert_eq!(price, expected_price); + + // example11 + let tick_index = -29204000; + let expected_price = Decimal256::from_str("0.00077960").unwrap(); + let price = tick_to_price(tick_index).unwrap(); + assert_eq!(price, expected_price); + + // example12 + let tick_index = -29203900; + let expected_price = Decimal256::from_str("0.00077961").unwrap(); + let price = tick_to_price(tick_index).unwrap(); + assert_eq!(price, expected_price); + + // example13 + let tick_index = -12150000; + let expected_price = Decimal256::from_str("0.068500").unwrap(); + let price = tick_to_price(tick_index).unwrap(); + assert_eq!(price, expected_price); + + // example14 + let tick_index = -12149900; + let expected_price = Decimal256::from_str("0.068501").unwrap(); + let price = tick_to_price(tick_index).unwrap(); + assert_eq!(price, expected_price); + + // example15 + let tick_index = 64576000; + let expected_price = Decimal256::from_str("25760000").unwrap(); + let price = tick_to_price(tick_index).unwrap(); + assert_eq!(price, expected_price); + + // example16 + let tick_index = 64576100; + let expected_price = Decimal256::from_str("25761000").unwrap(); + let price = tick_to_price(tick_index).unwrap(); + assert_eq!(price, expected_price); + + // example17 + let tick_index = 0; + let expected_price = Decimal256::from_str("1").unwrap(); + let price = tick_to_price(tick_index).unwrap(); + assert_eq!(price, expected_price); + + // example19 + assert!(tick_to_price(MAX_TICK as i64 + 1).is_err()); + + // example20 + assert!(tick_to_price(MIN_INITIALIZED_TICK - 1).is_err()); + } + + #[test] + fn test_price_to_tick() { + let mut deps = mock_dependencies(); + // example1 + let mut price = Decimal256::from_str("30352").unwrap(); + let mut expected_tick_index = 38035200; + let mut tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + // example2 + price = Decimal256::from_str("30353").unwrap(); + expected_tick_index = 38035300; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(expected_tick_index, tick_index); + + // example3 + price = Decimal256::from_str("0.000011790").unwrap(); + expected_tick_index = -44821000; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(expected_tick_index, tick_index); + + // example4 + price = Decimal256::from_str("0.000011791").unwrap(); + expected_tick_index = -44820900; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + // example5 + price = Decimal256::from_str("0.068960").unwrap(); + expected_tick_index = -12104000; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + // example6 + price = Decimal256::from_str("0.068961").unwrap(); + expected_tick_index = -12103900; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + // example7 + price = Decimal256::from_str("99999000000000000000000000000000000000").unwrap(); + expected_tick_index = MAX_TICK - 100; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + // example8 + price = Decimal256::from_str(MAX_SPOT_PRICE).unwrap(); + expected_tick_index = MAX_TICK; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + // example9 + price = Decimal256::from_str("0.007406").unwrap(); + expected_tick_index = -20594000; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + // example10 + price = Decimal256::from_str("0.0074061").unwrap(); + expected_tick_index = -20593900; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + // example11 + price = Decimal256::from_str("0.00077960").unwrap(); + expected_tick_index = -29204000; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + // example12 + price = Decimal256::from_str("0.00077961").unwrap(); + expected_tick_index = -29203900; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + // example13 + price = Decimal256::from_str("0.068500").unwrap(); + expected_tick_index = -12150000; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + // example14 + price = Decimal256::from_str("0.068501").unwrap(); + expected_tick_index = -12149900; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + // example15 + price = Decimal256::from_str("25760000").unwrap(); + expected_tick_index = 64576000; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + // example16 + price = Decimal256::from_str("25761000").unwrap(); + expected_tick_index = 64576100; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + // example17 + price = Decimal256::from_str("1").unwrap(); + expected_tick_index = 0; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + // example18: (won't work)... Decimal256 cannot be negative + assert!(Decimal256::from_str("-1").is_err()); + + price = Decimal256::from_str("4.169478164938714112").unwrap(); + expected_tick_index = 3169478; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + price = Decimal256::from_str("2.101924006248355072").unwrap(); + expected_tick_index = 1101924; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + price = Decimal256::from_str("0.007406").unwrap(); + expected_tick_index = -20594000; + tick_index = price_to_tick(deps.as_mut().storage, price).unwrap(); + assert_eq!(tick_index, expected_tick_index); + + // example19 + price = Decimal256::from_str(MAX_SPOT_PRICE).unwrap() + Decimal256::one(); + assert!(price_to_tick(deps.as_mut().storage, price).is_err()); + + // example20 + price = Decimal256::from_str(MIN_SPOT_PRICE).unwrap() / Decimal256::from_str("10").unwrap(); + assert!(price_to_tick(deps.as_mut().storage, price).is_err()); + } +} diff --git a/smart-contracts/contracts/cl-vault/src/msg.rs b/smart-contracts/contracts/cl-vault/src/msg.rs new file mode 100644 index 000000000..c46c2d33a --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/msg.rs @@ -0,0 +1,124 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Decimal; +use cw_vault_multi_standard::{VaultStandardExecuteMsg, VaultStandardQueryMsg}; + +use crate::{ + query::{PoolResponse, PositionResponse, RangeAdminResponse}, + state::VaultConfig, +}; + +/// Extension execute messages for an apollo autocompounding vault +#[cw_serde] +pub enum ExtensionExecuteMsg { + /// Execute Admin operations. + Admin(AdminExtensionExecuteMsg), + /// Rebalance our liquidity range based on an off-chain message + /// given to us by RANGE_ADMIN + ModifyRange(ModifyRangeMsg), + /// provides a fungify callback interface for the contract to use + Merge(MergePositionMsg), + /// Distribute any rewards over all users + DistributeRewards {}, + /// Claim rewards belonging to a single user + ClaimRewards {}, +} + +/// Apollo extension messages define functionality that is part of all apollo +/// vaults, but not part of the standard. +#[cw_serde] +pub enum AdminExtensionExecuteMsg { + /// Update the vault admin. + UpdateAdmin { + /// The new admin address. + address: String, + }, + /// Update the range adming, + UpdateRangeAdmin { + /// the new range admin + address: String, + }, + /// Update the configuration of the vault. + UpdateConfig { + /// The config updates. + updates: VaultConfig, + }, + ClaimStrategistRewards {}, +} + +#[cw_serde] +pub struct ModifyRangeMsg { + /// The new lower bound of the range, this is converted to an 18 precision digit decimal + pub lower_price: Decimal, + /// The new upper bound of the range, this is converted to an 18 precision digit decimal + pub upper_price: Decimal, + /// max position slippage + pub max_slippage: Decimal, +} + +#[cw_serde] +pub struct MergePositionMsg { + pub position_ids: Vec, +} + +/// Extension query messages for an apollo autocompounding vault +#[cw_serde] +pub enum ExtensionQueryMsg { + /// Metadata surrounding the vault + Metadata {}, + /// Queries related to the lockup extension. + Balances(UserBalanceQueryMsg), + /// Queries related to Concentrated Liquidity + ConcentratedLiquidity(ClQueryMsg), +} + +/// Extension query messages for user balance related queries +#[cw_serde] +pub enum UserBalanceQueryMsg { + UserSharesBalance { user: String }, + UserRewards { user: String }, +} + +/// Extension query messages for related concentrated liquidity +#[cw_serde] +#[derive(QueryResponses)] +pub enum ClQueryMsg { + /// Get the underlying pool of the vault + #[returns(PoolResponse)] + Pool {}, + #[returns(PositionResponse)] + Position {}, + #[returns(RangeAdminResponse)] + RangeAdmin {}, +} + +/// ExecuteMsg for an Autocompounding Vault. +pub type ExecuteMsg = VaultStandardExecuteMsg; + +/// QueryMsg for an Autocompounding Vault. +pub type QueryMsg = VaultStandardQueryMsg; + +#[cw_serde] +pub struct InstantiateMsg { + /// The general thesis of the vault + pub thesis: String, + /// the name of the vault + pub name: String, + /// Address that is allowed to update config. + pub admin: String, + /// Address that is allowed to update range. + pub range_admin: String, + /// The ID of the pool that this vault will autocompound. + pub pool_id: u64, + /// Configurable parameters for the contract. + pub config: VaultConfig, + /// The subdenom that will be used for the native vault token, e.g. + /// the denom of the vault token will be: + /// "factory/{vault_contract}/{vault_token_subdenom}". + pub vault_token_subdenom: String, + // create a position upon initialization + pub initial_lower_tick: i64, + pub initial_upper_tick: i64, +} + +#[cw_serde] +pub struct MigrateMsg {} diff --git a/smart-contracts/contracts/cl-vault/src/query.rs b/smart-contracts/contracts/cl-vault/src/query.rs new file mode 100644 index 000000000..c582f6324 --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/query.rs @@ -0,0 +1,148 @@ +use crate::vault::concentrated_liquidity::get_position; +use crate::{ + error::ContractResult, + state::{ + PoolConfig, ADMIN_ADDRESS, METADATA, POOL_CONFIG, POSITION, SHARES, USER_REWARDS, + VAULT_DENOM, + }, +}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{coin, Coin, Deps, Env, Uint128}; +use cw_vault_multi_standard::VaultInfoResponse; +use osmosis_std::types::cosmos::bank::v1beta1::BankQuerier; + +#[cw_serde] +pub struct MetadataResponse { + // thesis -> actual metadata + pub thesis: String, + // name -> actual metadata + pub name: String, + // total_supply -> from denom + pub total_supply: Uint128, + // symbol -> tokenfactory denom + pub symbol: String, + // decimals -> hardcoded since native denom + pub decimals: u8, + // owner -> admin + pub admin: String, +} + +#[cw_serde] +pub struct PoolResponse { + pub pool_config: PoolConfig, +} + +#[cw_serde] +pub struct PositionResponse { + pub position_ids: Vec, +} + +#[cw_serde] +pub struct UserBalanceResponse { + pub balance: Uint128, +} + +#[cw_serde] +pub struct UserRewardsResponse { + pub rewards: Vec, +} + +#[cw_serde] +pub struct TotalAssetsResponse { + pub token0: Coin, + pub token1: Coin, +} + +#[cw_serde] +pub struct RangeAdminResponse { + pub address: String, +} + +#[cw_serde] +pub struct TotalVaultTokenSupplyResponse { + pub total: Uint128, +} + +pub fn query_metadata(deps: Deps) -> ContractResult { + let metadata = METADATA.load(deps.storage)?; + let vault_denom = VAULT_DENOM.load(deps.storage)?; + let total_supply = BankQuerier::new(&deps.querier) + .supply_of(vault_denom.clone())? + .amount + .unwrap() + .amount + .parse::()? + .into(); + let admin = ADMIN_ADDRESS.load(deps.storage)?.to_string(); + + Ok(MetadataResponse { + thesis: metadata.thesis, + name: metadata.name, + total_supply, + symbol: vault_denom, + decimals: 6, + admin, + }) +} + +pub fn query_info(deps: Deps) -> ContractResult { + let pool_config = POOL_CONFIG.load(deps.storage)?; + let vault_token = VAULT_DENOM.load(deps.storage)?; + Ok(VaultInfoResponse { + tokens: vec![pool_config.token0, pool_config.token1], + vault_token, + }) +} + +pub fn query_pool(deps: Deps) -> ContractResult { + let pool_config = POOL_CONFIG.load(deps.storage)?; + Ok(PoolResponse { pool_config }) +} + +pub fn query_position(deps: Deps) -> ContractResult { + let position_id = POSITION.load(deps.storage)?.position_id; + Ok(PositionResponse { + position_ids: vec![position_id], + }) +} +pub fn query_user_balance(deps: Deps, user: String) -> ContractResult { + let balance = SHARES + .may_load(deps.storage, deps.api.addr_validate(&user)?)? + .unwrap_or(Uint128::zero()); + Ok(UserBalanceResponse { balance }) +} + +pub fn query_user_rewards(deps: Deps, user: String) -> ContractResult { + let rewards = USER_REWARDS + .load(deps.storage, deps.api.addr_validate(&user)?)? + .coins(); + Ok(UserRewardsResponse { rewards }) +} + +pub fn query_total_assets(deps: Deps, env: Env) -> ContractResult { + let position = get_position(deps.storage, &deps.querier, &env)?; + let pool = POOL_CONFIG.load(deps.storage)?; + Ok(TotalAssetsResponse { + token0: position + .asset0 + .map(|c| c.try_into().unwrap()) + .unwrap_or(coin(0, pool.token0)), + token1: position + .asset1 + .map(|c| c.try_into().unwrap()) + .unwrap_or(coin(0, pool.token1)), + }) +} + +pub fn query_total_vault_token_supply(deps: Deps) -> ContractResult { + let bq = BankQuerier::new(&deps.querier); + let vault_denom = VAULT_DENOM.load(deps.storage)?; + let total = bq + .supply_of(vault_denom)? + .amount + .unwrap() + .amount + .parse::()? + .into(); + Ok(TotalVaultTokenSupplyResponse { total }) +} diff --git a/smart-contracts/contracts/cl-vault/src/reply.rs b/smart-contracts/contracts/cl-vault/src/reply.rs new file mode 100644 index 000000000..6e17b286f --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/reply.rs @@ -0,0 +1,36 @@ +use num_enum::{FromPrimitive, IntoPrimitive}; + +#[derive(FromPrimitive, IntoPrimitive)] +#[repr(u64)] +pub enum Replies { + // handles position creation for a user deposit + DepositCreatePosition = 1, + // create the initial position while instantiating the contract + InstantiateCreatePosition, + // when handling rewards, we first collect incentives, then collect rewards + CollectIncentives, + // after gathering rewards, we divide them over share holders + CollectSpreadRewards, + + // withdraw position + WithdrawPosition, + // create position in the modify range inital step + RangeInitialCreatePosition, + // create position in the modify range iteration step + RangeIterationCreatePosition, + // swap + Swap, + /// Merge positions, used to merge positions + Merge, + + // handle user withdraws after liquidity is removed from the position + WithdrawUser, + // after creating a denom in initialization, register the created denom + CreateDenom, + /// to merge positions, we need to withdraw positions, used internally for merging + WithdrawMerge, + // create a new singular position in the merge, used internally for merging + CreatePositionMerge, + #[default] + Unknown, +} diff --git a/smart-contracts/contracts/cl-vault/src/rewards/distribution.rs b/smart-contracts/contracts/cl-vault/src/rewards/distribution.rs new file mode 100644 index 000000000..06689159a --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/rewards/distribution.rs @@ -0,0 +1,513 @@ +use cosmwasm_std::{ + Addr, Attribute, Decimal, Deps, DepsMut, Env, Order, Response, StdError, SubMsg, SubMsgResult, + Uint128, +}; + +use crate::{ + debug, + error::ContractResult, + reply::Replies, + state::{ + CURRENT_REWARDS, POSITION, SHARES, STRATEGIST_REWARDS, USER_REWARDS, VAULT_CONFIG, + VAULT_DENOM, + }, + ContractError, +}; +use osmosis_std::types::{ + cosmos::bank::v1beta1::BankQuerier, + osmosis::concentratedliquidity::v1beta1::{ + MsgCollectIncentives, MsgCollectIncentivesResponse, MsgCollectSpreadRewards, + MsgCollectSpreadRewardsResponse, + }, +}; + +use super::helpers::Rewards; + +/// claim_rewards claims rewards from Osmosis and update the rewards map to reflect each users rewards +pub fn execute_distribute_rewards(deps: DepsMut, env: Env) -> Result { + CURRENT_REWARDS.save(deps.storage, &Rewards::new())?; + let msg = collect_incentives(deps.as_ref(), env)?; + + Ok(Response::new().add_submessage(SubMsg::reply_on_success( + msg, + Replies::CollectIncentives as u64, + ))) +} + +pub fn handle_collect_incentives_reply( + deps: DepsMut, + env: Env, + data: SubMsgResult, +) -> Result { + // save the response from the collect incentives + debug!(deps, "here2", data); + // If we do not have data here, we treat this as an empty MsgCollectIncentivesResponse, this seems to be a bug somewhere between cosmwasm and osmosis + let data: Result = data + .into_result() + .map_err(StdError::generic_err)? + .data + .map(|b| Ok(b.try_into()?)) + .unwrap_or(Ok(MsgCollectIncentivesResponse { + collected_incentives: vec![], + forfeited_incentives: vec![], + })); + + let response: MsgCollectIncentivesResponse = data?; + CURRENT_REWARDS.update( + deps.storage, + |mut rewards| -> Result { + rewards.update_rewards(response.collected_incentives)?; + Ok(rewards) + }, + )?; + + debug!(deps, "here4", ""); + // collect the spread rewards + let msg = collect_spread_rewards(deps.as_ref(), env)?; + Ok(Response::new().add_submessage(SubMsg::reply_on_success( + msg, + Replies::CollectSpreadRewards as u64, + ))) +} + +pub fn handle_collect_spread_rewards_reply( + deps: DepsMut, + _env: Env, + data: SubMsgResult, +) -> Result { + debug!(deps, "here3", ""); + // after we have collected both the spread rewards and the incentives, we can distribute them over the share holders + // we don't need to save the rewards here again, just pass it to update rewards + let data: Result = data + .into_result() + .map_err(StdError::generic_err)? + .data + .map(|b| Ok(b.try_into()?)) + .unwrap_or(Ok(MsgCollectSpreadRewardsResponse { + collected_spread_rewards: vec![], + })); + + let response: MsgCollectSpreadRewardsResponse = data?; + let mut rewards = CURRENT_REWARDS.load(deps.storage)?; + rewards.update_rewards(response.collected_spread_rewards)?; + + // update the rewards map against all user's locked up vault shares + distribute_rewards(deps, rewards)?; + + // TODO add a nice response + Ok(Response::new()) +} + +fn distribute_rewards( + mut deps: DepsMut, + mut rewards: Rewards, +) -> Result, ContractError> { + if rewards.is_empty() { + return Ok(vec![Attribute::new("total_rewards_amount", "0")]); + } + + let vault_config = VAULT_CONFIG.load(deps.storage)?; + + // calculate the strategist fee + let strategist_fee = rewards.sub_ratio(vault_config.performance_fee)?; + STRATEGIST_REWARDS.update(deps.storage, |old| old.add(strategist_fee))?; + + let bq = BankQuerier::new(&deps.querier); + let vault_denom = VAULT_DENOM.load(deps.storage)?; + + let total_shares: Uint128 = bq + .supply_of(vault_denom)? + .amount + .unwrap() + .amount + .parse::()? + .into(); + + // for each user with locked tokens, we distribute some part of the rewards to them + // get all users and their current pre-distribution rewards + let user_rewards: Result, ContractError> = SHARES + .range(deps.branch().storage, None, None, Order::Ascending) + .map(|v| -> Result<(Addr, Rewards), ContractError> { + let (address, user_shares) = v?; + // calculate the amount of each asset the user should get in rewards + // we need to always round down here, so we never expect more rewards than we have + let user_rewards = rewards.ratio(Decimal::from_ratio(user_shares, total_shares)); + Ok((address, user_rewards)) + }) + .collect(); + + // add or create a new entry for the user to get rewards + user_rewards? + .into_iter() + .try_for_each(|(addr, reward)| -> ContractResult<()> { + USER_REWARDS.update(deps.storage, addr, |old| -> ContractResult { + if let Some(old_user_rewards) = old { + Ok(reward.add(old_user_rewards)?) + } else { + Ok(reward) + } + })?; + Ok(()) + })?; + + Ok(vec![Attribute::new( + "total_rewards_amount", + format!("{:?}", rewards.coins()), + )]) +} + +fn collect_incentives(deps: Deps, env: Env) -> Result { + let position = POSITION.load(deps.storage)?; + Ok(MsgCollectIncentives { + position_ids: vec![position.position_id], + sender: env.contract.address.into(), + }) +} + +fn collect_spread_rewards(deps: Deps, env: Env) -> Result { + let position = POSITION.load(deps.storage)?; + Ok(MsgCollectSpreadRewards { + position_ids: vec![position.position_id], + sender: env.contract.address.into(), + }) +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::{ + coin, + testing::{mock_dependencies, mock_env}, + }; + + use crate::{state::Position, test_helpers::QuasarQuerier}; + use osmosis_std::types::osmosis::concentratedliquidity::v1beta1::{ + FullPositionBreakdown, Position as OsmoPosition, + }; + + use super::*; + + #[test] + fn test_claim_rewards() { + let position_id = 2; + let mut deps = mock_dependencies(); + let _qq = QuasarQuerier::new( + FullPositionBreakdown { + position: Some(OsmoPosition { + position_id, + address: "bob".to_string(), + pool_id: 1, + lower_tick: 1, + upper_tick: 100, + join_time: None, + liquidity: "123.214".to_string(), + }), + asset0: Some(coin(1000, "uosmo").into()), + asset1: Some(coin(1000, "uosmo").into()), + claimable_spread_rewards: vec![coin(1000, "uosmo").into()], + claimable_incentives: vec![coin(123, "uatom").into()], + forfeited_incentives: vec![], + }, + 100, + ); + let env = mock_env(); + let position = Position { position_id }; + POSITION.save(deps.as_mut().storage, &position).unwrap(); + + let resp = execute_distribute_rewards(deps.as_mut(), env.clone()).unwrap(); + assert_eq!( + resp.messages[0].msg, + collect_incentives(deps.as_ref(), env).unwrap().into() + ) + } + + // #[test] + // fn test_handle_collect_rewards() { + // let mut deps = mock_dependencies(); + // let env = mock_env(); + // let position = Position { position_id: 1 }; + // POSITION.save(deps.as_mut().storage, &position).unwrap(); + + // CURRENT_REWARDS + // .save(deps.as_mut().storage, &Rewards::new()) + // .unwrap(); + + // let msg: Binary = MsgCollectIncentivesResponse { + // collected_incentives: vec![ + // OsmoCoin { + // denom: "uosmo".into(), + // amount: "1234".into(), + // }, + // OsmoCoin { + // denom: "uqsr".into(), + // amount: "2345".into(), + // }, + // ], + // forfeited_incentives: vec![], + // } + // .try_into() + // .unwrap(); + + // let resp = handle_collect_incentives_reply( + // deps.as_mut(), + // env.clone(), + // SubMsgResult::Ok(SubMsgResponse { + // events: vec![], + // data: Some(msg), + // }), + // ) + // .unwrap(); + + // assert_eq!( + // resp.messages[0].msg, + // collect_spread_rewards(deps.as_ref(), env.clone()) + // .unwrap() + // .into() + // ); + + // let msg: Binary = MsgCollectSpreadRewardsResponse { + // collected_spread_rewards: vec![OsmoCoin { + // denom: "uatom".into(), + // amount: "3456".into(), + // }], + // } + // .try_into() + // .unwrap(); + + // // we need a vault config to distribute the rewards in the vault config + // let vault_config = VaultConfig { + // performance_fee: Decimal::percent(20), + // treasury: Addr::unchecked("strategy_man"), + // swap_max_slippage: Decimal::from_ratio(1u128, 100u128), + // }; + + // VAULT_CONFIG + // .save(deps.as_mut().storage, &vault_config) + // .unwrap(); + + // // mock a vec of user shares + // let user_shares = vec![(Addr::unchecked("user1"), Uint128::new(1000))]; + // let total = user_shares + // .iter() + // .fold(Uint128::zero(), |acc, (_, shares)| acc + shares); + // LOCKED_TOTAL.save(deps.as_mut().storage, &total).unwrap(); + // user_shares + // .into_iter() + // .for_each(|(addr, shares)| SHARES.save(deps.as_mut().storage, addr, &shares).unwrap()); + + // // mock some previous rewards + // let strategist_rewards = Rewards::from_coins(vec![coin(50, "uosmo")]); + // STRATEGIST_REWARDS + // .save(deps.as_mut().storage, &strategist_rewards) + // .unwrap(); + + // let _resp = handle_collect_spread_rewards_reply( + // deps.as_mut(), + // env, + // SubMsgResult::Ok(SubMsgResponse { + // events: vec![], + // data: Some(msg), + // }), + // ) + // .unwrap(); + + // // we have collected vec![coin(1234, "uosmo"), coin(2345, "uqsr"), coin(3456, "uatom")] at this point + // let rewards = Rewards::from_coins(vec![ + // coin(1234, "uosmo"), + // coin(2345, "uqsr"), + // coin(3456, "uatom"), + // ]); + + // assert_eq!( + // STRATEGIST_REWARDS.load(deps.as_ref().storage).unwrap(), + // strategist_rewards + // .add( + // rewards + // .clone() + // .sub_percentage( + // vault_config.performance_fee.numerator(), + // vault_config.performance_fee.denominator() + // ) + // .unwrap() + // ) + // .unwrap() + // ); + + // // verify that the distributed rewards make sense + // let strategist_fee_percentage = VAULT_CONFIG + // .load(deps.as_ref().storage) + // .unwrap() + // .performance_fee; + // let total_shares = LOCKED_TOTAL.load(deps.as_ref().storage).unwrap(); + + // USER_REWARDS + // .range(deps.as_ref().storage, None, None, Order::Ascending) + // .for_each(|val| { + // let (user, user_rewards) = val.unwrap(); + // let user_shares = SHARES.load(deps.as_ref().storage, user).unwrap(); + // let mut tmp_rewards = rewards.clone(); + + // tmp_rewards + // .sub_percentage( + // strategist_fee_percentage.numerator(), + // strategist_fee_percentage.denominator(), + // ) + // .unwrap(); + + // assert_eq!(user_rewards, tmp_rewards.ratio(user_shares, total_shares)) + // }) + // } + + // #[test] + // fn distribute_rewards_works() { + // let mut deps = mock_dependencies(); + // let mut mut_deps = deps.as_mut(); + + // let qq = QuasarQuerier::new_with_balances( + // FullPositionBreakdown { + // position: Some(OsmoPosition { + // position_id: 1, + // address: "bob".to_string(), + // pool_id: 1, + // lower_tick: 100, + // upper_tick: 1000, + // join_time: None, + // liquidity: "1000000.2".to_string(), + // }), + // asset0: Some(OsmoCoin { + // denom: "token0".to_string(), + // amount: "1000000".to_string(), + // }), + // asset1: Some(OsmoCoin { + // denom: "token1".to_string(), + // amount: "1000000".to_string(), + // }), + // claimable_spread_rewards: vec![ + // OsmoCoin { + // denom: "token0".to_string(), + // amount: "100".to_string(), + // }, + // OsmoCoin { + // denom: "token1".to_string(), + // amount: "100".to_string(), + // }, + // ], + // claimable_incentives: vec![], + // forfeited_incentives: vec![], + // }, + // 500, + // &[] + // ); + // mut_deps.querier = QuerierWrapper::new(&qq); + // // let qq = QuasarQuerier::new() + // // we need a vault config to distribute the rewards in the vault config + // VAULT_CONFIG + // .save( + // mut_deps.storage, + // &VaultConfig { + // performance_fee: Decimal::percent(20), + // treasury: Addr::unchecked("strategy_man"), + // swap_max_slippage: Decimal::from_ratio(1u128, 100u128), + // }, + // ) + // .unwrap(); + + // VAULT_DENOM.save(mut_deps.storage, &"share_denom".to_string()).unwrap(); + + // // mock a vec of user shares + // let user_shares = vec![(Addr::unchecked("user1"), Uint128::new(1000))]; + // let total = user_shares + // .iter() + // .fold(Uint128::zero(), |acc, (_, shares)| acc + shares); + // user_shares.into_iter().for_each(|(addr, shares)| { + // SHARES + // .save(mut_deps.storage, addr, &shares) + // .unwrap() + // }); + + // let strategist_rewards = Rewards::from_coins(vec![coin(50, "uosmo")]); + // STRATEGIST_REWARDS + // .save(mut_deps.storage, &strategist_rewards) + // .unwrap(); + + // let rewards = Rewards::from_coins(vec![coin(10000, "uosmo"), coin(1000000, "uatom")]); + // distribute_rewards(mut_deps, rewards.clone()).unwrap(); + + // // each entry in USER_REWARDS should be equal to rewards.sub_percentage(strategist_fee_percentage).percentage(user_shares, total_shares) + // // we can get the user shares from SHARES + // let strategist_fee_percentage = VAULT_CONFIG + // .load(mut_deps.storage) + // .unwrap() + // .performance_fee; + + // assert_eq!( + // STRATEGIST_REWARDS.load(mut_deps.storage).unwrap(), + // strategist_rewards + // .add( + // rewards + // .clone() + // .sub_ratio( + // strategist_fee_percentage + // ) + // .unwrap() + // ) + // .unwrap() + // ); + + // USER_REWARDS + // .range(mut_deps.branch().storage, None, None, Order::Ascending) + // .for_each(|val| { + // let (user, user_rewards) = val.unwrap(); + // let user_shares = SHARES.load(mut_deps.branch().storage, user).unwrap(); + // let mut tmp_rewards = rewards.clone(); + + // tmp_rewards + // .sub_ratio( + // strategist_fee_percentage + // ) + // .unwrap(); + + // assert_eq!( + // user_rewards, + // tmp_rewards.ratio(Decimal::from_ratio(user_shares, total)) + // ) + // }) + // } + + // #[test] + // fn test_collect_incentives() { + // let mut deps = mock_dependencies(); + // let position = Position { position_id: 1 }; + // POSITION.save(deps.as_mut().storage, &position).unwrap(); + // let env = mock_env(); + + // let res = collect_incentives(deps.as_ref(), env.clone()).unwrap(); + + // // Check that the correct message type is returned + // assert_eq!( + // res, + // MsgCollectIncentives { + // position_ids: vec![1], // Check that the correct position_id is included in the message + // sender: env.contract.address.into(), + // } + // ); + // } + + // #[test] + // fn test_collect_spread_rewards() { + // let mut deps = mock_dependencies(); + // let position = Position { position_id: 1 }; + // POSITION.save(deps.as_mut().storage, &position).unwrap(); + // let env = mock_env(); + + // let res = collect_spread_rewards(deps.as_ref(), env.clone()).unwrap(); + + // // Check that the correct message type is returned + // assert_eq!( + // res, + // MsgCollectSpreadRewards { + // position_ids: vec![1], // Check that the correct position_id is included in the message + // sender: env.contract.address.into(), + // } + // ); + // } +} diff --git a/smart-contracts/contracts/cl-vault/src/rewards/helpers.rs b/smart-contracts/contracts/cl-vault/src/rewards/helpers.rs new file mode 100644 index 000000000..f1952bb29 --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/rewards/helpers.rs @@ -0,0 +1,350 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{coin, Attribute, BankMsg, Coin, CosmosMsg, Decimal, Fraction}; + +use crate::error::ContractResult; +use osmosis_std::types::cosmos::base::v1beta1::Coin as OsmoCoin; +#[cw_serde] +#[derive(Default)] +pub struct Rewards(Vec); + +impl Rewards { + pub fn new() -> Rewards { + Rewards::default() + } + + /// calculates the ratio of the current rewards + pub fn ratio(&self, ratio: Decimal) -> Rewards { + Rewards( + self.0 + .iter() + .map(|c| { + coin( + c.amount + .multiply_ratio(ratio.numerator(), ratio.denominator()) + .u128(), + c.denom.clone(), + ) + }) + .collect(), + ) + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// merge any values already in Rewards and append any others + pub fn update_rewards(&mut self, rewards: Vec) -> ContractResult<()> { + let parsed_rewards: ContractResult> = rewards + .into_iter() + .map(|c| Ok(coin(c.amount.parse()?, c.denom))) + .collect(); + + // Append and merge to + self.merge(parsed_rewards?)?; + Ok(()) + } + + /// add rewards to self and mutate self + pub fn add(mut self, rewards: Rewards) -> ContractResult { + self.merge(rewards.coins())?; + Ok(self) + } + + fn merge(&mut self, coins: Vec) -> ContractResult<()> { + for c in coins { + let same = self.0.iter_mut().find(|c2| c.denom == c2.denom); + if let Some(c2) = same { + c2.amount = c.amount + c2.amount + } else { + self.0.push(c) + } + } + Ok(()) + } + + /// substract a percentage from self, mutate self and return the subtracted rewards + pub fn sub_ratio(&mut self, ratio: Decimal) -> ContractResult { + let to_sub = self.ratio(ratio); + + // actually subtract the funds + self.sub(&to_sub)?; + Ok(to_sub) + } + + /// subtract to_sub from self, ignores any coins in to_sub that don't exist in self and vice versa + /// every item in self is expected to be greater or equal to the amount of the coin with the same denom + /// in to_sub + pub fn sub(&mut self, to_sub: &Rewards) -> ContractResult<()> { + to_sub + .0 + .iter() + .try_for_each(|sub_coin| -> ContractResult<()> { + let coin = self.0.iter_mut().find(|coin| sub_coin.denom == coin.denom); + if let Some(c) = coin { + c.amount = c.amount.checked_sub(sub_coin.amount)?; + } + Ok(()) + }) + } + + pub fn claim(&mut self, recipient: &str) -> ContractResult { + let rewards = self.coins(); + self.0.clear(); + + Ok(BankMsg::Send { + to_address: recipient.into(), + amount: rewards, + } + .into()) + } + + pub fn into_attributes(self) -> Vec { + self.0 + .iter() + .map(|c| Attribute { + key: c.denom.clone(), + value: c.amount.to_string(), + }) + .collect() + } + + pub fn coins(&self) -> Vec { + self.0.clone() + } + + pub fn from_coins(coins: Vec) -> Self { + Rewards(coins) + } +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::Uint128; + + use super::*; + + #[test] + fn sub_works() { + let mut rewards = Rewards::new(); + rewards + .update_rewards(vec![ + OsmoCoin { + denom: "uosmo".into(), + amount: "1000".into(), + }, + OsmoCoin { + denom: "uatom".into(), + amount: "2000".into(), + }, + OsmoCoin { + denom: "uqsr".into(), + amount: "3000".into(), + }, + ]) + .unwrap(); + + assert_eq!( + rewards, + Rewards(vec![ + coin(1000, "uosmo"), + coin(2000, "uatom"), + coin(3000, "uqsr") + ]) + ); + + rewards + .sub(&Rewards::from_coins(vec![coin(1500, "uqsr")])) + .unwrap(); + + assert_eq!( + rewards, + Rewards(vec![ + coin(1000, "uosmo"), + coin(2000, "uatom"), + coin(1500, "uqsr") + ]) + ); + + rewards + .sub(&Rewards::from_coins(vec![coin(2000, "uqsr")])) + .unwrap_err(); + + rewards + .sub(&Rewards::from_coins(vec![ + coin(999, "uqsr"), + coin(999, "uosmo"), + ])) + .unwrap(); + + assert_eq!( + rewards, + Rewards(vec![ + coin(1, "uosmo"), + coin(2000, "uatom"), + coin(501, "uqsr") + ]) + ); + } + + #[test] + fn percentage_works() { + let mut rewards = Rewards::new(); + rewards + .update_rewards(vec![ + OsmoCoin { + denom: "uosmo".into(), + amount: "1000".into(), + }, + OsmoCoin { + denom: "uatom".into(), + amount: "2000".into(), + }, + OsmoCoin { + denom: "uqsr".into(), + amount: "3000".into(), + }, + ]) + .unwrap(); + + let ratio = rewards.ratio(Decimal::from_ratio(Uint128::new(10), Uint128::new(100))); + assert_eq!( + ratio, + Rewards(vec![ + coin(100, "uosmo"), + coin(200, "uatom"), + coin(300, "uqsr") + ]) + ) + } + + #[test] + fn sub_percentage_works() { + let mut rewards = Rewards::new(); + rewards + .update_rewards(vec![ + OsmoCoin { + denom: "uosmo".into(), + amount: "1000".into(), + }, + OsmoCoin { + denom: "uatom".into(), + amount: "2000".into(), + }, + OsmoCoin { + denom: "uqsr".into(), + amount: "3000".into(), + }, + ]) + .unwrap(); + + let ratio = rewards + .sub_ratio(Decimal::from_ratio(Uint128::new(10), Uint128::new(100))) + .unwrap(); + assert_eq!( + ratio, + Rewards(vec![ + coin(100, "uosmo"), + coin(200, "uatom"), + coin(300, "uqsr") + ]) + ); + assert_eq!( + rewards, + Rewards(vec![ + coin(900, "uosmo"), + coin(1800, "uatom"), + coin(2700, "uqsr") + ]) + ) + } + + #[test] + fn merge_works() {} + + #[test] + fn add_works() { + let mut rewards = Rewards::new(); + rewards + .update_rewards(vec![ + OsmoCoin { + denom: "uosmo".into(), + amount: "1000".into(), + }, + OsmoCoin { + denom: "uatom".into(), + amount: "2000".into(), + }, + OsmoCoin { + denom: "uqsr".into(), + amount: "3000".into(), + }, + ]) + .unwrap(); + rewards = rewards + .add(Rewards::from_coins(vec![ + coin(2000, "uosmo"), + coin(2000, "uatom"), + coin(6000, "uqsr"), + coin(1234, "umars"), + ])) + .unwrap(); + assert_eq!( + rewards, + Rewards::from_coins(vec![ + coin(3000, "uosmo"), + coin(4000, "uatom"), + coin(9000, "uqsr"), + coin(1234, "umars") + ]) + ) + } + + #[test] + fn update_rewards_works() { + let mut rewards = Rewards::new(); + rewards + .update_rewards(vec![ + OsmoCoin { + denom: "uosmo".into(), + amount: "1000".into(), + }, + OsmoCoin { + denom: "uatom".into(), + amount: "2000".into(), + }, + OsmoCoin { + denom: "uqsr".into(), + amount: "3000".into(), + }, + ]) + .unwrap(); + + rewards + .update_rewards(vec![ + OsmoCoin { + denom: "uosmo".into(), + amount: "1000".into(), + }, + OsmoCoin { + denom: "umars".into(), + amount: "1234".into(), + }, + OsmoCoin { + denom: "uqsr".into(), + amount: "3000".into(), + }, + ]) + .unwrap(); + + assert_eq!( + rewards, + Rewards::from_coins(vec![ + coin(2000, "uosmo"), + coin(2000, "uatom"), + coin(6000, "uqsr"), + coin(1234, "umars") + ]) + ); + } +} diff --git a/smart-contracts/contracts/cl-vault/src/rewards/mod.rs b/smart-contracts/contracts/cl-vault/src/rewards/mod.rs new file mode 100644 index 000000000..b8f46a499 --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/rewards/mod.rs @@ -0,0 +1,5 @@ +mod distribution; +mod helpers; + +pub use distribution::*; +pub use helpers::*; diff --git a/smart-contracts/contracts/cl-vault/src/state.rs b/smart-contracts/contracts/cl-vault/src/state.rs new file mode 100644 index 000000000..4a300d21a --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/state.rs @@ -0,0 +1,145 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Decimal, Decimal256, Uint128}; +use cw_storage_plus::{Deque, Item, Map}; + +use crate::rewards::Rewards; +use crate::vault::merge::CurrentMergeWithdraw; +use crate::vault::range::SwapDirection; + +/// metadata useful for display purposes +#[cw_serde] +pub struct Metadata { + /// the underlying thesis of the vault's positions, eg aggresive + pub thesis: String, + /// the name of the vault + pub name: String, +} + +pub const METADATA: Item = Item::new("metadata"); + +pub const ADMIN_ADDRESS: Item = Item::new("admin_address"); +pub const RANGE_ADMIN: Item = Item::new("range_admin"); + +/// VAULT_CONFIG: Base config struct for the contract. +#[cw_serde] +pub struct VaultConfig { + /// Percentage of profit to be charged as performance fee + pub performance_fee: Decimal, + /// Account to receive fee payments + pub treasury: Addr, + /// swap max slippage + pub swap_max_slippage: Decimal, +} + +pub const VAULT_CONFIG: Item = Item::new("vault_config"); + +pub const VAULT_DENOM: Item = Item::new("vault_denom"); + +/// POOL_CONFIG +#[cw_serde] +pub struct PoolConfig { + pub pool_id: u64, + pub token0: String, + // todo: Verify in instantiate message + pub token1: String, // todo: Verify in instantiate message +} + +impl PoolConfig { + pub fn pool_contains_token(&self, token: impl Into) -> bool { + vec![&self.token0, &self.token1].contains(&&token.into()) + } +} + +pub const POOL_CONFIG: Item = Item::new("pool_config"); + +/// POSITION +#[cw_serde] +pub struct Position { + pub position_id: u64, +} + +pub const POSITION: Item = Item::new("position"); + +pub const SHARES: Map = Map::new("shares"); + +/// The merge of positions currently being executed +pub const CURRENT_MERGE: Deque = Deque::new("current_merge"); + +#[cw_serde] +pub struct CurrentMergePosition { + pub lower_tick: i64, + pub upper_tick: i64, +} + +pub const CURRENT_MERGE_POSITION: Item = Item::new("current_merge_position"); + +#[cw_serde] +pub struct CurrentDeposit { + pub token0_in: Uint128, + pub token1_in: Uint128, + pub sender: Addr, +} + +pub const CURRENT_DEPOSIT: Item = Item::new("current_deposit"); + +/// REWARDS: Current rewards are the rewards being gathered, these can be both spread rewards as well as incentives +pub const CURRENT_REWARDS: Item = Item::new("current_rewards"); +pub const USER_REWARDS: Map = Map::new("user_rewards"); +pub const STRATEGIST_REWARDS: Item = Item::new("strategist_rewards"); + +/// CURRENT_REMAINDERS is a tuple of Uin128 containing the current remainder amount before performing a swap +pub const CURRENT_REMAINDERS: Item<(Uint128, Uint128)> = Item::new("current_remainders"); +pub const CURRENT_BALANCE: Item<(Uint128, Uint128)> = Item::new("current_balance"); +pub const CURRENT_SWAP: Item<(SwapDirection, Uint128)> = Item::new("current_swap"); + +#[cw_serde] +pub struct ModifyRangeState { + // pre-withdraw state items + pub lower_tick: i64, + pub upper_tick: i64, + // the max slippage for modifying the range + pub max_slippage: Decimal, + // pre-deposit state items + pub new_range_position_ids: Vec, +} + +pub const MODIFY_RANGE_STATE: Item> = Item::new("modify_range_state"); + +#[cw_serde] +pub struct SwapDepositMergeState { + pub target_lower_tick: i64, + pub target_upper_tick: i64, + pub target_range_position_ids: Vec, +} + +pub const SWAP_DEPOSIT_MERGE_STATE: Item = + Item::new("swap_deposit_merge_state"); + +#[cw_serde] +pub struct TickExpIndexData { + pub initial_price: Decimal256, + pub max_price: Decimal256, + pub additive_increment_per_tick: Decimal256, + pub initial_tick: i64, +} + +pub const TICK_EXP_CACHE: Map = Map::new("tick_exp_cache"); +pub const CURRENT_WITHDRAWER: Item = Item::new("current_withdrawer"); + +#[cfg(test)] +mod tests { + use super::PoolConfig; + + #[test] + fn test_pool_contains_token() { + let pool_config = PoolConfig { + pool_id: 1, + token0: "token1".to_string(), + token1: "token2".to_string(), + }; + + assert!(pool_config.pool_contains_token("token1")); + assert!(pool_config.pool_contains_token("token2")); + assert!(!pool_config.pool_contains_token("token3")); + } +} diff --git a/smart-contracts/contracts/cl-vault/src/test_helpers.rs b/smart-contracts/contracts/cl-vault/src/test_helpers.rs new file mode 100644 index 000000000..dd244da5a --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/test_helpers.rs @@ -0,0 +1,128 @@ +use cosmwasm_std::testing::BankQuerier; +use cosmwasm_std::{ + from_binary, to_binary, Binary, Coin, ContractResult as CwContractResult, Empty, Querier, + QuerierResult, QueryRequest, +}; +use osmosis_std::types::cosmos::bank::v1beta1::{QuerySupplyOfRequest, QuerySupplyOfResponse}; + +use osmosis_std::types::osmosis::concentratedliquidity::v1beta1::Pool; +use osmosis_std::types::osmosis::poolmanager::v1beta1::{PoolResponse, SpotPriceResponse}; +use osmosis_std::types::{ + cosmos::base::v1beta1::Coin as OsmoCoin, + osmosis::concentratedliquidity::v1beta1::{ + FullPositionBreakdown, PositionByIdRequest, PositionByIdResponse, + }, +}; + +use crate::math::tick::tick_to_price; +pub struct QuasarQuerier { + position: FullPositionBreakdown, + current_tick: i64, + bank: BankQuerier, +} + +impl QuasarQuerier { + pub fn new(position: FullPositionBreakdown, current_tick: i64) -> QuasarQuerier { + QuasarQuerier { + position, + current_tick, + bank: BankQuerier::new(&[]), + } + } + + pub fn new_with_balances( + position: FullPositionBreakdown, + current_tick: i64, + balances: &[(&str, &[Coin])], + ) -> QuasarQuerier { + QuasarQuerier { + position, + current_tick, + bank: BankQuerier::new(balances), + } + } +} + +impl Querier for QuasarQuerier { + fn raw_query(&self, bin_request: &[u8]) -> cosmwasm_std::QuerierResult { + let request: QueryRequest = from_binary(&Binary::from(bin_request)).unwrap(); + match request { + QueryRequest::Stargate { path, data } => { + println!("{}", path.as_str()); + match path.as_str() { + "/osmosis.concentratedliquidity.v1beta1.Query/PositionById" => { + let position_by_id_request: PositionByIdRequest = + prost::Message::decode(data.as_slice()).unwrap(); + let position_id = position_by_id_request.position_id; + if position_id == self.position.position.clone().unwrap().position_id { + QuerierResult::Ok(CwContractResult::Ok( + to_binary(&PositionByIdResponse { + position: Some(self.position.clone()), + }) + .unwrap(), + )) + } else { + QuerierResult::Err(cosmwasm_std::SystemError::UnsupportedRequest { + kind: format!("position id not found: {position_id:?}"), + }) + } + } + "/cosmos.bank.v1beta1.Query/SupplyOf" => { + let query_supply_of_request: QuerySupplyOfRequest = + prost::Message::decode(data.as_slice()).unwrap(); + let denom = query_supply_of_request.denom; + QuerierResult::Ok(CwContractResult::Ok( + to_binary(&QuerySupplyOfResponse { + amount: Some(OsmoCoin { + denom, + amount: 100.to_string(), + }), + }) + .unwrap(), + )) + } + "/osmosis.poolmanager.v1beta1.Query/Pool" => { + QuerierResult::Ok(CwContractResult::Ok( + to_binary(&PoolResponse { + pool: Some( + Pool { + address: "idc".to_string(), + incentives_address: "not being used".to_string(), + spread_rewards_address: "not being used".to_string(), + id: 1, + current_tick_liquidity: "100".to_string(), + token0: "uosmo".to_string(), + token1: "uion".to_string(), + current_sqrt_price: "not used".to_string(), + current_tick: self.current_tick, + tick_spacing: 100, + exponent_at_price_one: -6, + spread_factor: "not used".to_string(), + last_liquidity_update: None, + } + .to_any(), + ), + }) + .unwrap(), + )) + } + "/osmosis.poolmanager.v1beta1.Query/SpotPrice" => { + QuerierResult::Ok(CwContractResult::Ok( + to_binary(&SpotPriceResponse { + spot_price: tick_to_price(self.current_tick).unwrap().to_string(), + }) + .unwrap(), + )) + } + &_ => QuerierResult::Err(cosmwasm_std::SystemError::UnsupportedRequest { + kind: format!("Unmocked stargate query path: {path:?}"), + }), + } + } + _ => QuerierResult::Err(cosmwasm_std::SystemError::UnsupportedRequest { + kind: format!("Unmocked query type: {request:?}"), + }), + } + // QuerierResult::Ok(ContractResult::Ok(to_binary(&"hello").unwrap())) + } +} diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/admin.rs b/smart-contracts/contracts/cl-vault/src/test_tube/admin.rs new file mode 100644 index 000000000..d26e91890 --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/test_tube/admin.rs @@ -0,0 +1,12 @@ +#[cfg(test)] +mod tests { + + use crate::test_tube::default_init; + + #[test] + #[ignore] + fn range_admin_update_works() { + let (_app, _contract_address, _cl_pool_id, _admin) = default_init(); + // change the range admin and verify that it works + } +} diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/deposit_withdraw.rs b/smart-contracts/contracts/cl-vault/src/test_tube/deposit_withdraw.rs new file mode 100644 index 000000000..b867a7988 --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/test_tube/deposit_withdraw.rs @@ -0,0 +1,128 @@ +#[cfg(test)] +mod tests { + use cosmwasm_std::Coin; + + use osmosis_test_tube::{Account, Module, Wasm}; + + use crate::{ + msg::{ExecuteMsg, ExtensionQueryMsg, QueryMsg}, + query::UserBalanceResponse, + test_tube::default_init, + }; + + #[test] + #[ignore] + fn multiple_deposit_withdraw_works() { + let (app, contract_address, _cl_pool_id, _admin) = default_init(); + let alice = app + .init_account(&[ + Coin::new(1_000_000_000_000, "uatom"), + Coin::new(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let wasm = Wasm::new(&app); + + let _ = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::ExactDeposit { recipient: None }, + &[Coin::new(5_000, "uatom"), Coin::new(5_000, "uosmo")], + &alice, + ) + .unwrap(); + + let _ = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::ExactDeposit { recipient: None }, + &[Coin::new(5_000, "uatom"), Coin::new(5_000, "uosmo")], + &alice, + ) + .unwrap(); + + let _ = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::ExactDeposit { recipient: None }, + &[Coin::new(5_000, "uatom"), Coin::new(5_000, "uosmo")], + &alice, + ) + .unwrap(); + + let shares: UserBalanceResponse = wasm + .query( + contract_address.as_str(), + &QueryMsg::VaultExtension(ExtensionQueryMsg::Balances( + crate::msg::UserBalanceQueryMsg::UserSharesBalance { + user: alice.address(), + }, + )), + ) + .unwrap(); + assert!(!shares.balance.is_zero()); + + let _withdraw = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::Redeem { + recipient: None, + amount: shares.balance, + }, + &[], + &alice, + ) + .unwrap(); + // verify the correct execution + } + + #[test] + #[ignore] + fn single_deposit_withdraw_works() { + let (app, contract_address, _cl_pool_id, _admin) = default_init(); + let alice = app + .init_account(&[ + Coin::new(1_000_000_000_000, "uatom"), + Coin::new(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let wasm = Wasm::new(&app); + + let deposit = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::ExactDeposit { recipient: None }, + &[Coin::new(5_000, "uatom"), Coin::new(5_000, "uosmo")], + &alice, + ) + .unwrap(); + + let _mint = deposit.events.iter().find(|e| e.ty == "tf_mint").unwrap(); + + let shares: UserBalanceResponse = wasm + .query( + contract_address.as_str(), + &QueryMsg::VaultExtension(ExtensionQueryMsg::Balances( + crate::msg::UserBalanceQueryMsg::UserSharesBalance { + user: alice.address(), + }, + )), + ) + .unwrap(); + assert!(!shares.balance.is_zero()); + + let _withdraw = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::Redeem { + recipient: None, + amount: shares.balance, + }, + &[], + &alice, + ) + .unwrap(); + // verify the correct execution + } +} diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/initialize.rs b/smart-contracts/contracts/cl-vault/src/test_tube/initialize.rs new file mode 100644 index 000000000..587b19ba6 --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/test_tube/initialize.rs @@ -0,0 +1,295 @@ +#[cfg(test)] +pub mod initialize { + use std::str::FromStr; + + use cosmwasm_std::{coin, Addr, Coin, Decimal, Uint128}; + use cw_vault_multi_standard::VaultInfoResponse; + use osmosis_std::types::cosmos::base::v1beta1; + use osmosis_std::types::osmosis::concentratedliquidity::v1beta1::{ + CreateConcentratedLiquidityPoolsProposal, Pool, PoolRecord, PoolsRequest, + }; + use osmosis_std::types::osmosis::poolmanager::v1beta1::{ + MsgSwapExactAmountIn, SwapAmountInRoute, + }; + use osmosis_std::types::osmosis::tokenfactory::v1beta1::QueryDenomsFromCreatorRequest; + use osmosis_test_tube::{ + cosmrs::proto::traits::Message, + osmosis_std::types::osmosis::concentratedliquidity::{ + poolmodel::concentrated::v1beta1::MsgCreateConcentratedPool, v1beta1::MsgCreatePosition, + }, + Account, ConcentratedLiquidity, GovWithAppAccess, Module, OsmosisTestApp, Wasm, + }; + use osmosis_test_tube::{PoolManager, SigningAccount, TokenFactory}; + + use crate::msg::{ + ClQueryMsg, ExecuteMsg, ExtensionQueryMsg, InstantiateMsg, ModifyRangeMsg, QueryMsg, + }; + use crate::query::PoolResponse; + use crate::state::VaultConfig; + + pub fn default_init() -> (OsmosisTestApp, Addr, u64, SigningAccount) { + init_test_contract( + "./test-tube-build/wasm32-unknown-unknown/release/cl_vault.wasm", + &[ + Coin::new(1_000_000_000_000_000_000, "uatom"), + Coin::new(1_000_000_000_000_000_000, "uosmo"), + ], + MsgCreateConcentratedPool { + sender: "overwritten".to_string(), + denom0: "uatom".to_string(), + denom1: "uosmo".to_string(), + tick_spacing: 100, + spread_factor: Decimal::from_str("0.0001").unwrap().atomics().to_string(), + }, + -2000, + 2000, + vec![ + v1beta1::Coin { + denom: "uatom".to_string(), + amount: "1000000000000".to_string(), + }, + v1beta1::Coin { + denom: "uosmo".to_string(), + amount: "1000000000000".to_string(), + }, + ], + Uint128::zero(), + Uint128::zero(), + ) + } + + // admin should be on accs[0] if this is called to init + /// this returns the testube app, contract_address, pool_id + /// sender is overwritten by the admin + // bad function but it's finnee + pub fn init_test_contract( + filename: &str, + admin_balance: &[Coin], + pool: MsgCreateConcentratedPool, + lower_tick: i64, + upper_tick: i64, + tokens_provided: Vec, + token_min_amount0: Uint128, + token_min_amount1: Uint128, + ) -> (OsmosisTestApp, Addr, u64, SigningAccount) { + // create new osmosis appchain instance. + let app = OsmosisTestApp::new(); + + // create new account with initial funds + let admin = app.init_account(admin_balance).unwrap(); + + // `Wasm` is the module we use to interact with cosmwasm releated logic on the appchain + // it implements `Module` trait which you will see more later. + let wasm = Wasm::new(&app); + + // Load compiled wasm bytecode + let wasm_byte_code = std::fs::read(filename).unwrap(); + let code_id = wasm + .store_code(&wasm_byte_code, None, &admin) + .unwrap() + .data + .code_id; + + // setup a CL pool + let cl = ConcentratedLiquidity::new(&app); + let gov = GovWithAppAccess::new(&app); + gov.propose_and_execute( + CreateConcentratedLiquidityPoolsProposal::TYPE_URL.to_string(), + CreateConcentratedLiquidityPoolsProposal { + title: "Create concentrated uosmo:usdc pool".to_string(), + description: "Create concentrated uosmo:usdc pool, so that we can trade it" + .to_string(), + pool_records: vec![PoolRecord { + denom0: pool.denom0, + denom1: pool.denom1, + tick_spacing: pool.tick_spacing, + spread_factor: pool.spread_factor, + }], + }, + admin.address(), + false, + &admin, + ) + .unwrap(); + + let pools = cl.query_pools(&PoolsRequest { pagination: None }).unwrap(); + let pool: Pool = Pool::decode(pools.pools[0].value.as_slice()).unwrap(); + + // create a basic position on the pool + let initial_position = MsgCreatePosition { + pool_id: pool.id, + sender: admin.address(), + lower_tick, + upper_tick, + tokens_provided, + token_min_amount0: token_min_amount0.to_string(), + token_min_amount1: token_min_amount1.to_string(), + }; + let _position = cl.create_position(initial_position, &admin).unwrap(); + + // move the tick to the initial position, if we don't do this, the position the contract is instantiated + let pm = PoolManager::new(&app); + pm.swap_exact_amount_in( + MsgSwapExactAmountIn { + sender: admin.address(), + routes: vec![SwapAmountInRoute { + pool_id: pool.id, + token_out_denom: pool.token0, + }], + token_in: Some(v1beta1::Coin { + denom: pool.token1, + amount: "1000000000".to_string(), + }), + token_out_min_amount: "1".to_string(), + }, + &admin, + ) + .unwrap(); + + let instantiate_msg = InstantiateMsg { + admin: admin.address(), + pool_id: pool.id, + config: VaultConfig { + performance_fee: Decimal::percent(5), + treasury: Addr::unchecked(admin.address()), + swap_max_slippage: Decimal::percent(5), + }, + vault_token_subdenom: "utestvault".to_string(), + range_admin: admin.address(), + initial_lower_tick: lower_tick, + initial_upper_tick: upper_tick, + thesis: "provide big swap efficiency".to_string(), + name: "good contract".to_string(), + }; + let contract = wasm + .instantiate( + code_id, + &instantiate_msg, + Some(admin.address().as_str()), + Some("cl-vault"), + &[coin(100000, "uatom"), coin(100000, "uosmo")], + &admin, + ) + .unwrap(); + + (app, Addr::unchecked(contract.data.address), pool.id, admin) + } + + #[test] + #[ignore] + fn contract_init_works() { + let (app, contract, cl_pool_id, admin) = init_test_contract( + "./test-tube-build/wasm32-unknown-unknown/release/cl_vault.wasm", + &[ + Coin::new(1_000_000_000_000, "uatom"), + Coin::new(1_000_000_000_000, "uosmo"), + ], + MsgCreateConcentratedPool { + sender: "overwritten".to_string(), + denom0: "uatom".to_string(), + denom1: "uosmo".to_string(), + tick_spacing: 100, + spread_factor: Decimal::from_str("0.0001").unwrap().atomics().to_string(), + }, + -50000, + 50000, + vec![ + v1beta1::Coin { + denom: "uatom".to_string(), + amount: "10000000000".to_string(), + }, + v1beta1::Coin { + denom: "uosmo".to_string(), + amount: "10000000000".to_string(), + }, + ], + Uint128::zero(), + Uint128::zero(), + ); + let alice = app + .init_account(&[ + Coin::new(1_000_000_000_000, "uatom"), + Coin::new(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let wasm = Wasm::new(&app); + let cl = ConcentratedLiquidity::new(&app); + + // do a swap to move the cur tick + let pm = PoolManager::new(&app); + pm.swap_exact_amount_in( + MsgSwapExactAmountIn { + sender: alice.address(), + routes: vec![SwapAmountInRoute { + pool_id: cl_pool_id, + token_out_denom: "uatom".to_string(), + }], + token_in: Some(v1beta1::Coin { + denom: "uosmo".to_string(), + amount: "1000".to_string(), + }), + token_out_min_amount: "1".to_string(), + }, + &alice, + ) + .unwrap(); + + let pools = cl.query_pools(&PoolsRequest { pagination: None }).unwrap(); + let pool = Pool::decode(pools.pools[0].value.as_slice()).unwrap(); + + let _result = wasm + .execute( + contract.as_str(), + &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::ModifyRange( + ModifyRangeMsg { + lower_price: Decimal::from_str("0.993").unwrap(), + upper_price: Decimal::from_str("1.002").unwrap(), + max_slippage: Decimal::permille(5), + }, + )), + &[], + &admin, + ) + .unwrap(); + } + + #[test] + #[ignore] + fn default_init_works() { + let (app, contract_address, _cl_pool_id, _admin) = default_init(); + let wasm = Wasm::new(&app); + let cl = ConcentratedLiquidity::new(&app); + let tf = TokenFactory::new(&app); + + let pools = cl.query_pools(&PoolsRequest { pagination: None }).unwrap(); + let pool = Pool::decode(pools.pools[0].value.as_slice()).unwrap(); + + let resp = wasm + .query::( + contract_address.as_str(), + &QueryMsg::VaultExtension(ExtensionQueryMsg::ConcentratedLiquidity( + ClQueryMsg::Pool {}, + )), + ) + .unwrap(); + + assert_eq!(resp.pool_config.pool_id, pool.id); + assert_eq!(resp.pool_config.token0, pool.token0); + assert_eq!(resp.pool_config.token1, pool.token1); + + let resp = wasm + .query::(contract_address.as_str(), &QueryMsg::Info {}) + .unwrap(); + + assert_eq!(resp.tokens, vec![pool.token0, pool.token1]); + assert_eq!( + resp.vault_token, + tf.query_denoms_from_creator(&QueryDenomsFromCreatorRequest { + creator: contract_address.to_string() + }) + .unwrap() + .denoms[0] + ); + } +} diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/mod.rs b/smart-contracts/contracts/cl-vault/src/test_tube/mod.rs new file mode 100644 index 000000000..451bc8b21 --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/test_tube/mod.rs @@ -0,0 +1,9 @@ +mod admin; +mod deposit_withdraw; +mod initialize; +mod proptest; +mod range; +mod rewards; + +#[cfg(test)] +pub(crate) use crate::test_tube::initialize::initialize::default_init; diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/proptest.rs b/smart-contracts/contracts/cl-vault/src/test_tube/proptest.rs new file mode 100644 index 000000000..c31039c03 --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/test_tube/proptest.rs @@ -0,0 +1,431 @@ +#[cfg(test)] +mod tests { + use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; + use osmosis_std::types::cosmos::bank::v1beta1::{QueryBalanceRequest, QueryBalanceResponse}; + use osmosis_std::types::osmosis::concentratedliquidity::v1beta1::PositionByIdRequest; + use osmosis_std::types::{ + cosmos::base::v1beta1, + osmosis::concentratedliquidity::poolmodel::concentrated::v1beta1::MsgCreateConcentratedPool, + }; + use osmosis_test_tube::{ + Account, Bank, ConcentratedLiquidity, Module, OsmosisTestApp, SigningAccount, Wasm, + }; + use proptest::prelude::*; + use std::collections::HashMap; + + use crate::math::tick::tick_to_price; + use crate::query::PositionResponse; + use crate::{ + msg::{ExecuteMsg, ExtensionQueryMsg, ModifyRangeMsg, QueryMsg}, + query::{TotalAssetsResponse, UserBalanceResponse}, + test_tube::initialize::initialize::init_test_contract, + }; + + const ITERATIONS_NUMBER: usize = 1000; + const ACCOUNTS_NUMBER: u64 = 10; + const ACCOUNTS_INITIAL_BALANCE: u128 = 1_000_000_000_000; + const DENOM_BASE: &str = "uatom"; + const DENOM_QUOTE: &str = "uosmo"; + //const MAX_SPOT_PRICE: &str = "100000000000000000000000000000000000000"; // 10^35 + //const MIN_SPOT_PRICE: &str = "0.000000000001"; // 10^-12 + + #[derive(Clone, Copy, Debug)] + enum Action { + Deposit, + Withdraw, + Swap, + UpdateRange, + } + + fn deposit( + wasm: &Wasm, + bank: &Bank, + contract_address: &Addr, + account: &SigningAccount, + percentage: f64, + _accounts_shares_balance: &HashMap, + ) { + // Get user DENOM_BASE balance + let balance_asset0 = get_user_denom_balance(bank, account, DENOM_BASE); + let balance0_str = balance_asset0.balance.unwrap().amount; + let balance0_f64: f64 = balance0_str + .parse() + .expect("Failed to parse balance to f64"); + let amount0 = (balance0_f64 * (percentage / 100.0)).round() as u128; + + // Get user DENOM_QUOTE balance + let balance_asset1 = get_user_denom_balance(bank, account, DENOM_QUOTE); + let balance1_str = balance_asset1.balance.unwrap().amount; + let balance1_f64: f64 = balance1_str + .parse() + .expect("Failed to parse balance to f64"); + let amount1 = (balance1_f64 * (percentage / 100.0)).round() as u128; + + // Get current pool position to know token0 and token1 amounts + let pos_assets: TotalAssetsResponse = get_position_assets(wasm, contract_address); + + // Calculate the ratio between pos_asset0 and pos_asset1 + let ratio = pos_assets.token0.amount.u128() as f64 / pos_assets.token1.amount.u128() as f64; + + // Calculate the adjusted amounts to deposit + let (adjusted_amount0, adjusted_amount1) = if ratio > 1.0 { + // If ratio is greater than 1, adjust amount1 according to the ratio + (amount0, (amount0 as f64 / ratio).round() as u128) + } else { + // If ratio is less than or equal to 1, adjust amount0 according to the ratio + ((amount1 as f64 * ratio).round() as u128, amount1) + }; + + // Initialize an empty Vec and push only non zero amount coins + let mut coins_to_deposit = Vec::new(); + if adjusted_amount0 > 0 { + coins_to_deposit.push(Coin::new(adjusted_amount0, DENOM_BASE)); + } + if adjusted_amount1 > 0 { + coins_to_deposit.push(Coin::new(adjusted_amount1, DENOM_QUOTE)); + } + + // Check if coins_to_deposit is not empty before proceeding + if coins_to_deposit.is_empty() { + // Handle the case where no coins are to be deposited + } else { + // Execute deposit and get liquidity_created from emitted events + let _deposit = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::ExactDeposit { recipient: None }, // Nice to have: Make recipient random + &coins_to_deposit, + account, + ) + .unwrap(); + } + /* + // TODO: Get liquidity_created value from deposit response + let deposit_resp: MsgCreatePositionResponse = deposit.data.try_into(); + let liquidity_created = deposit_resp.liquidity_created; + + // TODO: Update map to keep track of user shares amount and make further assertions + let mut current_shares_amount = accounts_shares_balance.get(&account.address()).unwrap_or(&0u128); + accounts_shares_balance.insert( + account.address(), + current_shares_amount.checked_add(liquidity_created), + ); + */ + } + + fn withdraw( + wasm: &Wasm, + contract_address: &Addr, + account: &SigningAccount, + percentage: f64, + _accounts_shares_balance: &HashMap, + ) { + let balance = get_user_shares_balance(wasm, contract_address, account); // TODO: get user shares balance + let amount = (balance.balance.u128() as f64 * (percentage / 100.0)).round() as u128; + + // Execute deposit and get liquidity_created from emitted events + let _withdraw = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::Redeem { + recipient: None, + amount: Uint128::new(amount), + }, // Nice to have: Make recipient random + &[], + account, + ) + .unwrap(); + + // TODO: Update map to keep track of user shares amount and make further assertions + /*let mut current_shares_amount = accounts_shares_balance.get(&account.address()).unwrap_or(&0u128); + accounts_shares_balance.insert( + account.address(), + current_shares_amount.checked_sub(amount), + );*/ + } + + fn swap( + _wasm: &Wasm, + bank: &Bank, + _contract_address: &Addr, + account: &SigningAccount, + percentage: f64, + _cl_pool_id: u64, + ) { + let balance_response = get_user_denom_balance(bank, account, DENOM_BASE); + let balance_str = balance_response.balance.unwrap().amount; + let balance_f64: f64 = balance_str.parse().expect("Failed to parse balance to f64"); + let _amount = (balance_f64 * (percentage / 100.0)).round() as u128; + + // TODO: Check user bank denom balance is not zero and enough accordingly to amount_u128 + + // TODO: Implement swap strategy + } + + fn update_range( + wasm: &Wasm, + cl: &ConcentratedLiquidity, + contract_address: &Addr, + percentage: f64, + admin_account: &SigningAccount, + ) { + let (current_lower_tick, current_upper_tick) = + get_position_ticks(wasm, cl, contract_address); + let (current_lower_price, current_upper_price) = ( + tick_to_price(current_lower_tick).unwrap(), + tick_to_price(current_upper_tick).unwrap(), + ); + let clp_u128: Uint128 = current_lower_price.atomics().try_into().unwrap(); + let cup_u128: Uint128 = current_upper_price.atomics().try_into().unwrap(); + + // Create new range ticks based on previous ticks by percentage variation + // TODO: 1. Use also negative values, and maybe a random generated value for the lower and another one for upper instead of the same unique percentage + // TODO: 2. Creating them in a range of min/max accepted by Osmosis CL module + let percentage_factor = percentage / 100.0; + let new_lower_price = (clp_u128.u128() as f64 * (1.0 + percentage_factor)).round() as u128; + let new_upper_price = (cup_u128.u128() as f64 * (1.0 + percentage_factor)).round() as u128; + + // Skip equal ticks test case + if new_lower_price == new_upper_price { + return; + } + + // Execute deposit and get liquidity_created from emitted events + let _update_range = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::ModifyRange( + ModifyRangeMsg { + lower_price: Decimal::new(Uint128::new(new_lower_price)), + upper_price: Decimal::new(Uint128::new(new_upper_price)), + max_slippage: Decimal::new(Uint128::new(5)), // optimize and check how this fits in the strategy as it could trigger organic errors we dont want to test + }, + )), + &[], + admin_account, + ) + .unwrap(); + } + + // GETTERS + + fn get_user_denom_balance( + bank: &Bank, + account: &SigningAccount, + denom: &str, + ) -> QueryBalanceResponse { + bank.query_balance(&QueryBalanceRequest { + address: account.address(), + denom: denom.to_string(), + }) + .unwrap() + } + + fn get_user_shares_balance( + wasm: &Wasm, + contract_address: &Addr, + account: &SigningAccount, + ) -> UserBalanceResponse { + wasm.query( + contract_address.as_str(), + &QueryMsg::VaultExtension(ExtensionQueryMsg::Balances( + crate::msg::UserBalanceQueryMsg::UserSharesBalance { + user: account.address(), + }, + )), + ) + .unwrap() + } + + fn get_position_assets( + wasm: &Wasm, + contract_address: &Addr, + ) -> TotalAssetsResponse { + wasm.query(contract_address.as_str(), &QueryMsg::TotalAssets {}) + .unwrap() + } + + fn get_position_ticks( + wasm: &Wasm, + cl: &ConcentratedLiquidity, + contract_address: &Addr, + ) -> (i64, i64) { + // query_position will return a Vec of position_ids + let position_response: PositionResponse = wasm + .query( + contract_address.as_str(), + &QueryMsg::VaultExtension(ExtensionQueryMsg::ConcentratedLiquidity( + crate::msg::ClQueryMsg::Position {}, + )), + ) + .unwrap(); + + // TODO Use those to take the latest one? or what? + let position = cl + .query_position_by_id(&PositionByIdRequest { + position_id: position_response.position_ids[0], + }) + .unwrap() + .position + .unwrap() + .position; + + match position { + Some(position) => (position.lower_tick, position.upper_tick), + None => panic!("Position not found"), + } + } + + // ASSERT METHODS + + fn assert_deposit_withdraw( + wasm: &Wasm, + contract_address: &Addr, + accounts: &Vec, + accounts_shares_balance: &HashMap, + ) { + // TODO: multi-query foreach user created previously + for account in accounts { + let shares = get_user_shares_balance(wasm, contract_address, account); + + // Check that the current account iterated shares balance is the same we expect from Hashmap + assert_eq!( + shares.balance, + accounts_shares_balance.get(&account.address()).unwrap() + ); + } + } + + /* + fn assert_swap() { + todo!() + } + + fn assert_update_range() { + todo!() + } + */ + + // COMPOSE STRATEGY + + // get_initial_range generates random lower and upper ticks for the initial position + prop_compose! { + // TODO: evaluate if lower_tick and upper_tick are too much arbitrary + fn get_initial_range()(lower_tick in 1i64..1_000_000, upper_tick in 1_000_001i64..2_000_000) -> (i64, i64) { + (lower_tick, upper_tick) + } + } + + // get_strategy_list + prop_compose! { + fn get_strategy_list()(list in prop::collection::vec(prop_oneof![ + Just(Action::Deposit), + Just(Action::Withdraw), + Just(Action::Swap), + Just(Action::UpdateRange), + ], ITERATIONS_NUMBER..ITERATIONS_NUMBER+1)) -> Vec { + list + } + } + + // get_percentage generates a list of random percentages used to calculate deposit_amount, + // withdraw_amount, and newers lower and upper ticks based on the previous values + prop_compose! { + fn get_percentage_list()(list in prop::collection::vec(1.0..100.0, ITERATIONS_NUMBER..ITERATIONS_NUMBER+1)) -> Vec { + list + } + } + + // get_account_index generates a list of random numbers between 0 and the ACCOUNTS_NUMBER to use as accounts[account_index as usize] + prop_compose! { + fn get_account_index_list()(list in prop::collection::vec(0..ACCOUNTS_NUMBER, ITERATIONS_NUMBER..ITERATIONS_NUMBER+1)) -> Vec { + list + } + } + + // TESTS + + proptest! { + #[test] + #[ignore] + fn test_complete_works( + (initial_lower_tick, initial_upper_tick) in get_initial_range(), + actions in get_strategy_list(), + percentages in get_percentage_list(), + account_indexes in get_account_index_list() + ) { + // Creating test var utils + let accounts_shares_balance: HashMap = HashMap::new(); + + // Creating test core + let (app, contract_address, cl_pool_id, admin_account) = init_test_contract( + "./test-tube-build/wasm32-unknown-unknown/release/cl_vault.wasm", + &[ + Coin::new(100_000_000_000_000_000_000_000, "uatom"), + Coin::new(100_000_000_000_000_000_000_000, "uosmo"), + ], + MsgCreateConcentratedPool { + sender: "overwritten".to_string(), + denom0: "uatom".to_string(), + denom1: "uosmo".to_string(), + tick_spacing: 1, + spread_factor: "100000000000000".to_string(), + }, + initial_lower_tick, + initial_upper_tick, + vec![ + v1beta1::Coin { + denom: "uatom".to_string(), + amount: "100000000000000000".to_string(), + }, + v1beta1::Coin { + denom: "uosmo".to_string(), + amount: "100000000000000000".to_string(), + }, + ], + Uint128::zero(), + Uint128::zero(), + ); + let wasm = Wasm::new(&app); + let cl = ConcentratedLiquidity::new(&app); + let bank = Bank::new(&app); + + // Create a fixed number of accounts using app.init_accounts() function from test-tube, and assign a fixed initial balance for all of them + let accounts = app + .init_accounts(&[ + Coin::new(ACCOUNTS_INITIAL_BALANCE, DENOM_BASE), + Coin::new(ACCOUNTS_INITIAL_BALANCE, DENOM_QUOTE), + ], ACCOUNTS_NUMBER) + .unwrap(); + + // Make one arbitrary deposit foreach one of the created accounts using 10.00% of its balance, to avoid complications on withdrawing without any position + for i in 0..ACCOUNTS_NUMBER { + deposit(&wasm, &bank, &contract_address, &accounts[i as usize], 10.00, &accounts_shares_balance); + } + + // Iterate iterations times + for i in 0..ITERATIONS_NUMBER { + match actions[i] { + Action::Deposit => { + deposit(&wasm, &bank, &contract_address, &accounts[account_indexes[i] as usize], percentages[i], &accounts_shares_balance); + //assert_deposit_withdraw(&wasm, &contract_address, &accounts, &accounts_shares_balance); + }, + Action::Withdraw => { + withdraw(&wasm, &contract_address, &accounts[account_indexes[i] as usize], percentages[i], &accounts_shares_balance); + //assert_deposit_withdraw(&wasm, &contract_address, &accounts, &accounts_shares_balance); + }, + Action::Swap => { + swap(&wasm, &bank, &contract_address, &accounts[account_indexes[i] as usize], percentages[i], cl_pool_id); + //assert_swap(); // todo!() + }, + Action::UpdateRange => { + update_range(&wasm, &cl, &contract_address, percentages[i], &admin_account); + //assert_update_range(); // todo!() + }, + } + } + + println!("PASS"); + } + } +} diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/range.rs b/smart-contracts/contracts/cl-vault/src/test_tube/range.rs new file mode 100644 index 000000000..5af0dd532 --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/test_tube/range.rs @@ -0,0 +1,278 @@ +#[cfg(test)] +mod test { + use std::str::FromStr; + + use cosmwasm_std::{coin, Coin, Decimal, Uint128}; + use osmosis_std::types::{ + cosmos::base::v1beta1, + osmosis::{ + concentratedliquidity::{ + poolmodel::concentrated::v1beta1::MsgCreateConcentratedPool, + v1beta1::{MsgCreatePosition, Pool, PoolsRequest, PositionByIdRequest}, + }, + poolmanager::v1beta1::{MsgSwapExactAmountIn, SwapAmountInRoute}, + }, + }; + use osmosis_test_tube::{Account, ConcentratedLiquidity, Module, PoolManager, Wasm}; + + use crate::{ + msg::{ExecuteMsg, ModifyRangeMsg, QueryMsg}, + query::PositionResponse, + test_tube::initialize::initialize::init_test_contract, + }; + + use prost::Message; + + // #[test] + // #[ignore] + fn move_range_works() { + let (app, contract, cl_pool_id, admin) = init_test_contract( + "./test-tube-build/wasm32-unknown-unknown/release/cl_vault.wasm", + &[ + Coin::new(1_000_000_000_000, "uatom"), + Coin::new(1_000_000_000_000, "uosmo"), + ], + MsgCreateConcentratedPool { + sender: "overwritten".to_string(), + denom0: "uatom".to_string(), + denom1: "uosmo".to_string(), + tick_spacing: 100, + spread_factor: Decimal::from_str("0.0001").unwrap().atomics().to_string(), + }, + 21205000, + 27448000, + vec![ + v1beta1::Coin { + denom: "uatom".to_string(), + amount: "10000000000".to_string(), + }, + v1beta1::Coin { + denom: "uosmo".to_string(), + amount: "10000000000".to_string(), + }, + ], + Uint128::zero(), + Uint128::zero(), + ); + let alice = app + .init_account(&[ + Coin::new(1_000_000_000_000, "uatom"), + Coin::new(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let wasm = Wasm::new(&app); + let cl = ConcentratedLiquidity::new(&app); + + // do a swap to move the cur tick + let pm = PoolManager::new(&app); + pm.swap_exact_amount_in( + MsgSwapExactAmountIn { + sender: alice.address(), + routes: vec![SwapAmountInRoute { + pool_id: cl_pool_id, + token_out_denom: "uatom".to_string(), + }], + token_in: Some(v1beta1::Coin { + denom: "uosmo".to_string(), + amount: "1000".to_string(), + }), + token_out_min_amount: "1".to_string(), + }, + &alice, + ) + .unwrap(); + + let pools = cl.query_pools(&PoolsRequest { pagination: None }).unwrap(); + let pool = Pool::decode(pools.pools[0].value.as_slice()).unwrap(); + + let before_position: PositionResponse = wasm + .query( + contract.as_str(), + &QueryMsg::VaultExtension(crate::msg::ExtensionQueryMsg::ConcentratedLiquidity( + crate::msg::ClQueryMsg::Position {}, + )), + ) + .unwrap(); + + let _result = wasm + .execute( + contract.as_str(), + &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::ModifyRange( + ModifyRangeMsg { + lower_price: Decimal::from_str("400").unwrap(), + upper_price: Decimal::from_str("1466").unwrap(), + max_slippage: Decimal::permille(5), + }, + )), + &[], + &admin, + ) + .unwrap(); + + let after_position: PositionResponse = wasm + .query( + contract.as_str(), + &QueryMsg::VaultExtension(crate::msg::ExtensionQueryMsg::ConcentratedLiquidity( + crate::msg::ClQueryMsg::Position {}, + )), + ) + .unwrap(); + } + + #[test] + #[ignore] + fn move_range_same_single_side_works() { + let (app, contract, cl_pool_id, admin) = init_test_contract( + "./test-tube-build/wasm32-unknown-unknown/release/cl_vault.wasm", + &[ + Coin::new(1_000_000_000_000, "uatom"), + Coin::new(1_000_000_000_000, "uosmo"), + ], + MsgCreateConcentratedPool { + sender: "overwritten".to_string(), + denom0: "uatom".to_string(), + denom1: "uosmo".to_string(), + tick_spacing: 100, + spread_factor: Decimal::from_str("0.0001").unwrap().atomics().to_string(), + }, + 21205000, + 27448000, + vec![ + v1beta1::Coin { + denom: "uatom".to_string(), + amount: "10000000000".to_string(), + }, + v1beta1::Coin { + denom: "uosmo".to_string(), + amount: "10000000000".to_string(), + }, + ], + Uint128::zero(), + Uint128::zero(), + ); + let alice = app + .init_account(&[ + Coin::new(1_000_000_000_000, "uatom"), + Coin::new(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let wasm = Wasm::new(&app); + let cl = ConcentratedLiquidity::new(&app); + + // do a swap to move the cur tick + let pm = PoolManager::new(&app); + pm.swap_exact_amount_in( + MsgSwapExactAmountIn { + sender: alice.address(), + routes: vec![SwapAmountInRoute { + pool_id: cl_pool_id, + token_out_denom: "uatom".to_string(), + }], + token_in: Some(v1beta1::Coin { + denom: "uosmo".to_string(), + amount: "1000".to_string(), + }), + token_out_min_amount: "1".to_string(), + }, + &alice, + ) + .unwrap(); + + let pools = cl.query_pools(&PoolsRequest { pagination: None }).unwrap(); + let pool = Pool::decode(pools.pools[0].value.as_slice()).unwrap(); + + println!("{:?}", pool); + + let _result = wasm + .execute( + contract.as_str(), + &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::ModifyRange( + ModifyRangeMsg { + lower_price: Decimal::from_str("20.71").unwrap(), + upper_price: Decimal::from_str("45").unwrap(), + max_slippage: Decimal::permille(5), + }, + )), + &[], + &admin, + ) + .unwrap(); + } + + /* + we try the following position from https://docs.google.com/spreadsheets/d/1xPsKsQkM0apTZQPBBwVlEyB5Sk31sw6eE8U0FgnTWUQ/edit?usp=sharing + lower_price: 4500 + current_price: 4692.937 + upper_price: 5500 + + the spreadsheet says we need to leave 42806.28569 in token x and swap over 157193.7143 + 157193.7143 / 4692.937 = 33.49580749 + both token amounts are used in 5 decimals, since the leftover amount is in 5 decimals + so we want to deposit 4280628569 and 3349580 + */ + #[test] + #[ignore] + fn test_swap_math_poc() { + let (app, _contract, _cl_pool_id, _admin) = init_test_contract( + "./test-tube-build/wasm32-unknown-unknown/release/cl_vault.wasm", + &[ + Coin::new(1_000_000_000_000, "uatom"), + Coin::new(1_000_000_000_000, "uosmo"), + ], + MsgCreateConcentratedPool { + sender: "overwritten".to_string(), + denom0: "uatom".to_string(), //token0 is uatom + denom1: "uosmo".to_string(), //token1 is uosmo + tick_spacing: 100, + spread_factor: Decimal::from_str("0.0001").unwrap().atomics().to_string(), + }, + 30500000, // 4500 + 31500000, // 5500 + vec![ + v1beta1::Coin { + denom: "uatom".to_string(), + amount: "1000000".to_string(), + }, + v1beta1::Coin { + denom: "uosmo".to_string(), + amount: "1000000".to_string(), + }, + ], + Uint128::zero(), + Uint128::zero(), + ); + let alice = app + .init_account(&[ + Coin::new(1_000_000_000_000, "uatom"), + Coin::new(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let cl = ConcentratedLiquidity::new(&app); + + let pools = cl.query_pools(&PoolsRequest { pagination: None }).unwrap(); + let pool: Pool = Pool::decode(pools.pools[0].value.as_slice()).unwrap(); + + println!("pool: {:?}", pool); + + // from the spreadsheet + // create a basic position on the pool + let initial_position = MsgCreatePosition { + pool_id: pool.id, + sender: alice.address(), + lower_tick: 30500000, + upper_tick: 31500000, + tokens_provided: vec![ + coin(3349580, "uatom").into(), + coin(4280628569, "uosmo").into(), + ], + token_min_amount0: "0".to_string(), + token_min_amount1: "0".to_string(), + }; + let position = cl.create_position(initial_position, &alice).unwrap(); + + println!("{:?}", position.events) + } +} diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/rewards.rs b/smart-contracts/contracts/cl-vault/src/test_tube/rewards.rs new file mode 100644 index 000000000..30fc10d51 --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/test_tube/rewards.rs @@ -0,0 +1,160 @@ +#[cfg(test)] +mod tests { + use crate::{msg::ExecuteMsg, test_tube::default_init}; + use cosmwasm_std::Coin; + use osmosis_std::types::cosmos::base::v1beta1::Coin as OsmoCoin; + use osmosis_std::types::osmosis::poolmanager::v1beta1::{ + MsgSwapExactAmountIn, SwapAmountInRoute, + }; + use osmosis_test_tube::{Account, Module, PoolManager, Wasm}; + + #[test] + #[ignore] + fn test_rewards_single_distribute_claim() { + let (app, contract_address, cl_pool_id, _admin) = default_init(); + let alice = app + .init_account(&[ + Coin::new(1_000_000_000_000, "uatom"), + Coin::new(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let bob = app + .init_account(&[ + Coin::new(1_000_000_000_000, "uatom"), + Coin::new(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let wasm = Wasm::new(&app); + + let _ = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::ExactDeposit { recipient: None }, + &[Coin::new(5_000_000, "uatom"), Coin::new(5_000_000, "uosmo")], + &alice, + ) + .unwrap(); + + // do a bunch of swaps to get some swap fees + PoolManager::new(&app) + .swap_exact_amount_in( + MsgSwapExactAmountIn { + sender: bob.address(), + routes: vec![SwapAmountInRoute { + pool_id: cl_pool_id, + token_out_denom: "uatom".to_string(), + }], + token_in: Some(OsmoCoin { + denom: "uosmo".to_string(), + amount: "100".to_string(), + }), + token_out_min_amount: "1".to_string(), + }, + &bob, + ) + .unwrap(); + + PoolManager::new(&app) + .swap_exact_amount_in( + MsgSwapExactAmountIn { + sender: bob.address(), + routes: vec![SwapAmountInRoute { + pool_id: cl_pool_id, + token_out_denom: "uatom".to_string(), + }], + token_in: Some(OsmoCoin { + denom: "uosmo".to_string(), + amount: "100".to_string(), + }), + token_out_min_amount: "1".to_string(), + }, + &bob, + ) + .unwrap(); + + PoolManager::new(&app) + .swap_exact_amount_in( + MsgSwapExactAmountIn { + sender: bob.address(), + routes: vec![SwapAmountInRoute { + pool_id: cl_pool_id, + token_out_denom: "uatom".to_string(), + }], + token_in: Some(OsmoCoin { + denom: "uosmo".to_string(), + amount: "100".to_string(), + }), + token_out_min_amount: "1".to_string(), + }, + &bob, + ) + .unwrap(); + + PoolManager::new(&app) + .swap_exact_amount_in( + MsgSwapExactAmountIn { + sender: bob.address(), + routes: vec![SwapAmountInRoute { + pool_id: cl_pool_id, + token_out_denom: "uosmo".to_string(), + }], + token_in: Some(OsmoCoin { + denom: "uatom".to_string(), + amount: "100".to_string(), + }), + token_out_min_amount: "1".to_string(), + }, + &bob, + ) + .unwrap(); + + PoolManager::new(&app) + .swap_exact_amount_in( + MsgSwapExactAmountIn { + sender: bob.address(), + routes: vec![SwapAmountInRoute { + pool_id: cl_pool_id, + token_out_denom: "uosmo".to_string(), + }], + token_in: Some(OsmoCoin { + denom: "uatom".to_string(), + amount: "100".to_string(), + }), + token_out_min_amount: "1".to_string(), + }, + &bob, + ) + .unwrap(); + + PoolManager::new(&app) + .swap_exact_amount_in( + MsgSwapExactAmountIn { + sender: bob.address(), + routes: vec![SwapAmountInRoute { + pool_id: cl_pool_id, + token_out_denom: "uosmo".to_string(), + }], + token_in: Some(OsmoCoin { + denom: "uatom".to_string(), + amount: "100".to_string(), + }), + token_out_min_amount: "1".to_string(), + }, + &bob, + ) + .unwrap(); + + let res = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::DistributeRewards {}), + &[], + &alice, + ) + .unwrap(); + + println!("{:?}", res.events) + } +} diff --git a/smart-contracts/contracts/cl-vault/src/vault/admin.rs b/smart-contracts/contracts/cl-vault/src/vault/admin.rs new file mode 100644 index 000000000..d481b82bb --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/vault/admin.rs @@ -0,0 +1,424 @@ +use crate::error::ContractResult; +use crate::helpers::assert_admin; +use crate::rewards::Rewards; +use crate::state::{VaultConfig, ADMIN_ADDRESS, RANGE_ADMIN, STRATEGIST_REWARDS, VAULT_CONFIG}; +use crate::{msg::AdminExtensionExecuteMsg, ContractError}; +use cosmwasm_std::{BankMsg, DepsMut, MessageInfo, Response}; +use cw_utils::nonpayable; + +pub(crate) fn execute_admin( + deps: DepsMut, + info: MessageInfo, + admin_msg: AdminExtensionExecuteMsg, +) -> Result { + match admin_msg { + AdminExtensionExecuteMsg::UpdateAdmin { address } => { + execute_update_admin(deps, info, address) + } + AdminExtensionExecuteMsg::UpdateConfig { updates } => { + execute_update_config(deps, info, updates) + } + AdminExtensionExecuteMsg::UpdateRangeAdmin { address } => { + execute_update_range_admin(deps, info, address) + } + AdminExtensionExecuteMsg::ClaimStrategistRewards {} => { + execute_claim_strategist_rewards(deps, info) + } + } +} + +pub fn execute_claim_strategist_rewards( + deps: DepsMut, + info: MessageInfo, +) -> ContractResult { + let range_admin = RANGE_ADMIN.load(deps.storage)?; + if info.sender != range_admin { + return Err(ContractError::Unauthorized {}); + } + + // get the currently attained rewards + let rewards = STRATEGIST_REWARDS.load(deps.storage)?; + // empty the saved rewards + STRATEGIST_REWARDS.save(deps.storage, &Rewards::new())?; + + Ok(Response::new() + .add_attribute("rewards", format!("{:?}", rewards.coins())) + .add_message(BankMsg::Send { + to_address: range_admin.to_string(), + amount: rewards.coins(), + })) +} + +/// Updates the admin of the contract. +/// +/// This function first checks if the message sender is nonpayable. If the sender sent funds, a `ContractError::NonPayable` error is returned. +/// Then, it checks if the message sender is the current admin. If not, a `ContractError::Unauthorized` error is returned. +/// If both checks pass, it saves the new admin address in the state. +pub fn execute_update_admin( + deps: DepsMut, + info: MessageInfo, + address: String, +) -> Result { + nonpayable(&info).map_err(|_| ContractError::NonPayable {})?; + + let previous_admin = assert_admin(deps.as_ref(), &info.sender)?; + let new_admin = deps.api.addr_validate(&address)?; + ADMIN_ADDRESS.save(deps.storage, &new_admin)?; + + Ok(Response::new() + .add_attribute("action", "execute_update_admin") + .add_attribute("previous_admin", previous_admin) + .add_attribute("new_admin", &new_admin)) +} + +/// Updates the range admin of the contract. +/// +/// This function first checks if the message sender is nonpayable. If the sender sent funds, a `ContractError::NonPayable` error is returned. +/// Then, it checks if the message sender is the current admin. If not, a `ContractError::Unauthorized` error is returned. +/// If both checks pass, it saves the new range admin address in the state. +pub fn execute_update_range_admin( + deps: DepsMut, + info: MessageInfo, + address: String, +) -> Result { + nonpayable(&info).map_err(|_| ContractError::NonPayable {})?; + assert_admin(deps.as_ref(), &info.sender)?; + + let previous_admin = RANGE_ADMIN.load(deps.storage)?; + let new_admin = deps.api.addr_validate(&address)?; + RANGE_ADMIN.save(deps.storage, &new_admin)?; + + Ok(Response::new() + .add_attribute("action", "execute_update_admin") + .add_attribute("previous_admin", previous_admin) + .add_attribute("new_admin", &new_admin)) +} + +/// Updates the configuration of the contract. +/// +/// This function first checks if the message sender is nonpayable. If the sender sent funds, a `ContractError::NonPayable` error is returned. +/// Then, it checks if the message sender is the current admin. If not, a `ContractError::Unauthorized` error is returned. +/// If both checks pass, it saves the new configuration in the state. +pub fn execute_update_config( + deps: DepsMut, + info: MessageInfo, + updates: VaultConfig, +) -> Result { + nonpayable(&info).map_err(|_| ContractError::NonPayable {})?; + assert_admin(deps.as_ref(), &info.sender)?; + + VAULT_CONFIG.save(deps.storage, &updates)?; + + Ok(Response::default() + .add_attribute("action", "execute_update_config") + .add_attribute("updates", format!("{:?}", updates))) +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::{ + coin, + testing::{mock_dependencies, mock_info}, + Addr, CosmosMsg, Decimal, Uint128, + }; + + #[test] + fn test_execute_claim_strategist_rewards_success() { + let range_admin = Addr::unchecked("bob"); + let mut deps = mock_dependencies(); + let rewards = vec![coin(12304151, "uosmo"), coin(5415123, "uatom")]; + STRATEGIST_REWARDS + .save(deps.as_mut().storage, &Rewards::from_coins(rewards.clone())) + .unwrap(); + + RANGE_ADMIN + .save(deps.as_mut().storage, &range_admin) + .unwrap(); + + let response = + execute_claim_strategist_rewards(deps.as_mut(), mock_info(range_admin.as_str(), &[])) + .unwrap(); + assert_eq!( + CosmosMsg::Bank(BankMsg::Send { + to_address: range_admin.to_string(), + amount: rewards + }), + response.messages[0].msg + ) + } + + #[test] + fn test_execute_claim_strategist_rewards_not_admin() { + let range_admin = Addr::unchecked("bob"); + let mut deps = mock_dependencies(); + let rewards = vec![coin(12304151, "uosmo"), coin(5415123, "uatom")]; + STRATEGIST_REWARDS + .save(deps.as_mut().storage, &Rewards::from_coins(rewards)) + .unwrap(); + + RANGE_ADMIN + .save(deps.as_mut().storage, &range_admin) + .unwrap(); + + let err = + execute_claim_strategist_rewards(deps.as_mut(), mock_info("alice", &[])).unwrap_err(); + assert_eq!(ContractError::Unauthorized {}, err) + } + + #[test] + fn test_execute_update_admin_success() { + let old_admin = Addr::unchecked("old_admin"); + let mut deps = mock_dependencies(); + ADMIN_ADDRESS + .save(deps.as_mut().storage, &old_admin) + .unwrap(); + + let new_admin = Addr::unchecked("new_admin"); + let info_admin: MessageInfo = mock_info("old_admin", &[]); + + execute_update_admin(deps.as_mut(), info_admin, new_admin.to_string()).unwrap(); + assert_eq!(ADMIN_ADDRESS.load(&deps.storage).unwrap(), new_admin); + } + + #[test] + fn test_execute_update_admin_not_admin() { + let old_admin = Addr::unchecked("old_admin"); + let mut deps = mock_dependencies(); + ADMIN_ADDRESS + .save(deps.as_mut().storage, &old_admin) + .unwrap(); + + let new_admin = Addr::unchecked("new_admin"); + let info_not_admin = mock_info("not_admin", &[]); + + execute_update_admin(deps.as_mut(), info_not_admin, new_admin.to_string()).unwrap_err(); + assert_eq!(ADMIN_ADDRESS.load(&deps.storage).unwrap(), old_admin); + } + + #[test] + fn test_execute_update_admin_with_funds() { + let old_admin = Addr::unchecked("old_admin"); + let mut deps = mock_dependencies(); + ADMIN_ADDRESS + .save(deps.as_mut().storage, &old_admin) + .unwrap(); + + let new_admin = Addr::unchecked("new_admin"); + + let info_admin_with_funds = mock_info("old_admin", &[coin(1, "token")]); + + let result = + execute_update_admin(deps.as_mut(), info_admin_with_funds, new_admin.to_string()); + assert!(result.is_err(), "Expected Err, but got: {:?}", result); + } + + #[test] + fn test_execute_update_admin_same_admin() { + let old_admin = Addr::unchecked("old_admin"); + let mut deps = mock_dependencies(); + ADMIN_ADDRESS + .save(deps.as_mut().storage, &old_admin) + .unwrap(); + + let info_admin: MessageInfo = mock_info("old_admin", &[]); + + let res = execute_update_admin(deps.as_mut(), info_admin, old_admin.to_string()); + assert!(res.is_ok()); + assert_eq!(ADMIN_ADDRESS.load(&deps.storage).unwrap(), old_admin); + } + + #[test] + fn test_execute_update_range_admin_success() { + let admin = Addr::unchecked("admin"); + let mut deps = mock_dependencies(); + ADMIN_ADDRESS.save(deps.as_mut().storage, &admin).unwrap(); + + let old_range_admin = Addr::unchecked("rang_admin1"); + RANGE_ADMIN + .save(deps.as_mut().storage, &old_range_admin) + .unwrap(); + let new_range_admin = Addr::unchecked("rang_admin2"); + let info_admin: MessageInfo = mock_info("admin", &[]); + + execute_update_range_admin(deps.as_mut(), info_admin, new_range_admin.to_string()).unwrap(); + assert_eq!(RANGE_ADMIN.load(&deps.storage).unwrap(), new_range_admin); + } + + #[test] + fn test_execute_update_range_admin_not_admin() { + let admin = Addr::unchecked("admin"); + let mut deps = mock_dependencies(); + ADMIN_ADDRESS.save(deps.as_mut().storage, &admin).unwrap(); + + let old_range_admin = Addr::unchecked("rang_admin1"); + RANGE_ADMIN + .save(deps.as_mut().storage, &old_range_admin) + .unwrap(); + let new_range_admin = Addr::unchecked("rang_admin2"); + let info_not_admin = mock_info("not_admin", &[]); + + execute_update_range_admin(deps.as_mut(), info_not_admin, new_range_admin.to_string()) + .unwrap_err(); + assert_eq!(RANGE_ADMIN.load(&deps.storage).unwrap(), old_range_admin); + } + + #[test] + fn test_execute_update_range_admin_with_funds() { + let admin = Addr::unchecked("admin"); + let mut deps = mock_dependencies(); + ADMIN_ADDRESS.save(deps.as_mut().storage, &admin).unwrap(); + + let old_range_admin = Addr::unchecked("rang_admin1"); + RANGE_ADMIN + .save(deps.as_mut().storage, &old_range_admin) + .unwrap(); + let new_range_admin = Addr::unchecked("rang_admin2"); + + let info_admin_with_funds = mock_info(admin.as_str(), &[coin(1, "token")]); + + let result = execute_update_range_admin( + deps.as_mut(), + info_admin_with_funds, + new_range_admin.to_string(), + ); + assert!(result.is_err(), "Expected Err, but got: {:?}", result); + } + + #[test] + fn test_execute_update_range_admin_same_admin() { + let admin = Addr::unchecked("admin"); + let mut deps = mock_dependencies(); + ADMIN_ADDRESS.save(deps.as_mut().storage, &admin).unwrap(); + + let old_range_admin = Addr::unchecked("rang_admin1"); + RANGE_ADMIN + .save(deps.as_mut().storage, &old_range_admin) + .unwrap(); + let new_range_admin = Addr::unchecked("rang_admin1"); + + let info_admin = mock_info(admin.as_str(), &[]); + + let res = + execute_update_range_admin(deps.as_mut(), info_admin, new_range_admin.to_string()); + assert!(res.is_ok()); + assert_eq!(RANGE_ADMIN.load(&deps.storage).unwrap(), old_range_admin); + } + + #[test] + fn test_execute_update_config_success() { + let admin = Addr::unchecked("admin"); + let old_config = VaultConfig { + treasury: Addr::unchecked("old_treasury"), + performance_fee: Decimal::new(Uint128::from(100u128)), + swap_max_slippage: Decimal::from_ratio(1u128, 100u128), + }; + let mut deps = mock_dependencies(); + ADMIN_ADDRESS.save(deps.as_mut().storage, &admin).unwrap(); + VAULT_CONFIG + .save(deps.as_mut().storage, &old_config) + .unwrap(); + + let new_config = VaultConfig { + treasury: Addr::unchecked("new_treasury"), + performance_fee: Decimal::new(Uint128::from(200u128)), + swap_max_slippage: Decimal::from_ratio(1u128, 100u128), + }; + let info_admin: MessageInfo = mock_info("admin", &[]); + + assert!(execute_update_config(deps.as_mut(), info_admin, new_config.clone()).is_ok()); + assert_eq!( + VAULT_CONFIG.load(deps.as_mut().storage).unwrap(), + new_config + ); + } + + #[test] + fn test_execute_update_config_not_admin() { + let admin = Addr::unchecked("admin"); + let old_config = VaultConfig { + treasury: Addr::unchecked("old_treasury"), + performance_fee: Decimal::new(Uint128::from(100u128)), + swap_max_slippage: Decimal::from_ratio(1u128, 100u128), + }; + let mut deps = mock_dependencies(); + ADMIN_ADDRESS.save(deps.as_mut().storage, &admin).unwrap(); + VAULT_CONFIG + .save(deps.as_mut().storage, &old_config) + .unwrap(); + + let new_config = VaultConfig { + treasury: Addr::unchecked("new_treasury"), + performance_fee: Decimal::new(Uint128::from(200u128)), + swap_max_slippage: Decimal::from_ratio(1u128, 100u128), + }; + let info_not_admin = mock_info("not_admin", &[]); + + assert!(execute_update_config(deps.as_mut(), info_not_admin, new_config).is_err()); + assert_eq!( + VAULT_CONFIG.load(deps.as_mut().storage).unwrap(), + old_config + ); + } + + #[test] + fn test_execute_update_config_with_funds() { + let admin = Addr::unchecked("admin"); + let old_config = VaultConfig { + treasury: Addr::unchecked("old_treasury"), + performance_fee: Decimal::new(Uint128::from(100u128)), + swap_max_slippage: Decimal::from_ratio(1u128, 100u128), + }; + let mut deps = mock_dependencies(); + ADMIN_ADDRESS.save(deps.as_mut().storage, &admin).unwrap(); + VAULT_CONFIG + .save(deps.as_mut().storage, &old_config) + .unwrap(); + + let new_config = VaultConfig { + treasury: Addr::unchecked("new_treasury"), + performance_fee: Decimal::new(Uint128::from(200u128)), + swap_max_slippage: Decimal::from_ratio(1u128, 100u128), + }; + + let info_admin_with_funds = mock_info("admin", &[coin(1, "token")]); + + let result = execute_update_config(deps.as_mut(), info_admin_with_funds, new_config); + assert!(result.is_err(), "Expected Err, but got: {:?}", result); + } + + #[test] + fn test_execute_update_config_same_config() { + let admin = Addr::unchecked("admin"); + let old_config = VaultConfig { + treasury: Addr::unchecked("old_treasury"), + performance_fee: Decimal::new(Uint128::from(100u128)), + swap_max_slippage: Decimal::from_ratio(1u128, 100u128), + }; + let mut deps = mock_dependencies(); + ADMIN_ADDRESS.save(deps.as_mut().storage, &admin).unwrap(); + VAULT_CONFIG + .save(deps.as_mut().storage, &old_config) + .unwrap(); + + let info_admin: MessageInfo = mock_info("admin", &[]); + + let res = execute_update_config(deps.as_mut(), info_admin, old_config.clone()); + assert!(res.is_ok()); + assert_eq!( + VAULT_CONFIG.load(deps.as_mut().storage).unwrap(), + old_config + ); + } + + #[test] + fn test_assert_admin() { + let mut deps = mock_dependencies(); + let admin = Addr::unchecked("admin"); + let not_admin = Addr::unchecked("not_admin"); + + ADMIN_ADDRESS.save(deps.as_mut().storage, &admin).unwrap(); + assert!(assert_admin(deps.as_ref(), &admin).is_ok()); + assert!(assert_admin(deps.as_ref(), ¬_admin).is_err()); + } +} diff --git a/smart-contracts/contracts/cl-vault/src/vault/claim.rs b/smart-contracts/contracts/cl-vault/src/vault/claim.rs new file mode 100644 index 000000000..42d11b92d --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/vault/claim.rs @@ -0,0 +1,30 @@ +use cosmwasm_std::{Addr, DepsMut, Response}; + +use crate::{state::USER_REWARDS, ContractError}; + +pub fn execute_claim_user_rewards( + deps: DepsMut, + recipient: &str, +) -> Result { + // addr unchecked is safe here because we will chekc addresses on save into this map + let mut user_rewards = match USER_REWARDS.may_load(deps.storage, Addr::unchecked(recipient))? { + Some(user_rewards) => user_rewards, + None => { + return Ok(Response::default() + .add_attribute("action", "claim_user_rewards") + .add_attribute("result", "no_rewards")) + } + }; + + let send_rewards_msg = user_rewards.claim(recipient)?; + + // todo: check if user rewards are claimed correctly + USER_REWARDS.save(deps.storage, Addr::unchecked(recipient), &user_rewards)?; + + Ok(Response::new() + .add_message(send_rewards_msg) + .add_attribute("action", "claim_user_rewards") + .add_attribute("result", "success") + .add_attribute("recipient", recipient) + .add_attributes(user_rewards.into_attributes())) +} diff --git a/smart-contracts/contracts/cl-vault/src/vault/concentrated_liquidity.rs b/smart-contracts/contracts/cl-vault/src/vault/concentrated_liquidity.rs new file mode 100644 index 000000000..a12c8ee6a --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/vault/concentrated_liquidity.rs @@ -0,0 +1,179 @@ +use cosmwasm_std::{Coin, Decimal256, Env, QuerierWrapper, Storage, Uint128}; +use osmosis_std::types::osmosis::concentratedliquidity::v1beta1::{ + ConcentratedliquidityQuerier, FullPositionBreakdown, MsgCreatePosition, MsgWithdrawPosition, + Pool, +}; +use osmosis_std::types::osmosis::poolmanager::v1beta1::PoolmanagerQuerier; +use prost::Message; + +use crate::{ + state::{POOL_CONFIG, POSITION}, + ContractError, +}; + +pub fn create_position( + storage: &mut dyn Storage, + env: &Env, + lower_tick: i64, + upper_tick: i64, + tokens_provided: Vec, + token_min_amount0: Uint128, + token_min_amount1: Uint128, +) -> Result { + let pool_config = POOL_CONFIG.load(storage)?; + let sender = env.contract.address.to_string(); + + let create_position = MsgCreatePosition { + pool_id: pool_config.pool_id, + sender, + lower_tick, + upper_tick, + tokens_provided: tokens_provided.into_iter().map(|c| c.into()).collect(), + // An sdk.Int in the Go code + token_min_amount0: token_min_amount0.to_string(), + // An sdk.Int in the Go code + token_min_amount1: token_min_amount1.to_string(), + }; + Ok(create_position) +} + +// TODO verify that liquidity amount should be Decimal256 +pub fn withdraw_from_position( + storage: &dyn Storage, + env: &Env, + liquidity_amount: Decimal256, +) -> Result { + let sender = env.contract.address.to_string(); + let position = POSITION.load(storage)?; + + let withdraw_position = MsgWithdrawPosition { + position_id: position.position_id, + sender, + liquidity_amount: liquidity_amount.atomics().to_string(), + }; + Ok(withdraw_position) +} + +pub fn get_position( + storage: &dyn Storage, + querier: &QuerierWrapper, + _env: &Env, +) -> Result { + let position = POSITION.load(storage)?; + + let cl_querier = ConcentratedliquidityQuerier::new(querier); + let position = cl_querier.position_by_id(position.position_id)?; + position.position.ok_or(ContractError::PositionNotFound) +} + +pub fn get_cl_pool_info(querier: &QuerierWrapper, pool_id: u64) -> Result { + let pm_querier = PoolmanagerQuerier::new(querier); + let pool = pm_querier.pool(pool_id)?; + + match pool.pool { + // Some(pool) => Some(Pool::decode(pool.value.as_slice()).unwrap()), + Some(pool) => { + let decoded_pool = Message::decode(pool.value.as_ref())?; + Ok(decoded_pool) + } + None => Err(ContractError::PoolNotFound { pool_id }), + } +} + +pub fn _may_get_position( + storage: &dyn Storage, + querier: &QuerierWrapper, + _env: &Env, +) -> Result, ContractError> { + let position = POSITION.may_load(storage)?; + if let Some(position) = position { + let cl_querier = ConcentratedliquidityQuerier::new(querier); + let position = cl_querier.position_by_id(position.position_id)?; + Ok(Some( + position.position.ok_or(ContractError::PositionNotFound)?, + )) + } else { + Ok(None) + } +} + +#[cfg(test)] +mod tests { + use crate::state::{PoolConfig, Position}; + use cosmwasm_std::{ + testing::{mock_dependencies, mock_env}, + Coin, Uint128, + }; + + use super::*; + + #[test] + fn test_create_position() { + let mut deps = mock_dependencies(); + let pool_id = 1; + POOL_CONFIG + .save( + deps.as_mut().storage, + &PoolConfig { + pool_id, + token0: "token0".to_string(), + token1: "token1".to_string(), + }, + ) + .unwrap(); + + let env = mock_env(); + let lower_tick = 100; + let upper_tick = 200; + let tokens_provided = vec![Coin::new(100, "token0"), Coin::new(200, "token1")]; + let token_min_amount0 = Uint128::new(1000); + let token_min_amount1 = Uint128::new(2000); + + let result = create_position( + deps.as_mut().storage, + &env, + lower_tick, + upper_tick, + tokens_provided.clone(), + token_min_amount0, + token_min_amount1, + ) + .unwrap(); + + assert_eq!( + result, + MsgCreatePosition { + pool_id, + sender: env.contract.address.into(), + lower_tick, + upper_tick, + tokens_provided: tokens_provided.into_iter().map(|c| c.into()).collect(), + token_min_amount0: token_min_amount0.to_string(), + token_min_amount1: token_min_amount1.to_string() + } + ); + } + + #[test] + fn test_withdraw_from_position() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let liquidity_amount = Decimal256::from_ratio(100_u128, 1_u128); + + let position_id = 1; + POSITION + .save(deps.as_mut().storage, &Position { position_id }) + .unwrap(); + + let result = withdraw_from_position(&mut deps.storage, &env, liquidity_amount).unwrap(); + + assert_eq!( + result, + MsgWithdrawPosition { + position_id, + sender: env.contract.address.into(), + liquidity_amount: liquidity_amount.atomics().to_string() + } + ); + } +} diff --git a/smart-contracts/contracts/cl-vault/src/vault/deposit.rs b/smart-contracts/contracts/cl-vault/src/vault/deposit.rs new file mode 100644 index 000000000..7b2ae192e --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/vault/deposit.rs @@ -0,0 +1,568 @@ +use std::str::FromStr; + +use cosmwasm_std::{ + attr, coin, to_binary, Attribute, BankMsg, Coin, Decimal256, DepsMut, Env, Fraction, + MessageInfo, Response, SubMsg, SubMsgResult, Uint128, Uint256, +}; + +use osmosis_std::types::{ + cosmos::bank::v1beta1::BankQuerier, + osmosis::{ + concentratedliquidity::v1beta1::{ConcentratedliquidityQuerier, MsgCreatePositionResponse}, + tokenfactory::v1beta1::MsgMint, + }, +}; + +use crate::{ + error::ContractResult, + helpers::must_pay_one_or_two, + msg::{ExecuteMsg, MergePositionMsg}, + reply::Replies, + state::{CurrentDeposit, CURRENT_DEPOSIT, POOL_CONFIG, POSITION, SHARES, VAULT_DENOM}, + vault::concentrated_liquidity::{create_position, get_position}, + ContractError, +}; + +// execute_any_deposit is a nice to have feature for the cl vault. +// but left out of the current release. +pub(crate) fn _execute_any_deposit( + _deps: DepsMut, + _env: Env, + _info: &MessageInfo, + _amount: Uint128, + _recipient: Option, +) -> Result { + // Unwrap recipient or use caller's address + unimplemented!() +} + +/// Try to deposit as much user funds as we can into the a position and +/// refund the rest to the caller +pub(crate) fn execute_exact_deposit( + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: Option, +) -> Result { + // Unwrap recipient or use caller's address + let recipient = recipient.map_or(Ok(info.sender.clone()), |x| deps.api.addr_validate(&x))?; + + let position_id = (POSITION.load(deps.storage)?).position_id; + let position = ConcentratedliquidityQuerier::new(&deps.querier) + .position_by_id(position_id)? + .position + .ok_or(ContractError::PositionNotFound)? + .position + .ok_or(ContractError::PositionNotFound)?; + + let pool = POOL_CONFIG.load(deps.storage)?; + let (token0, token1) = must_pay_one_or_two(&info, (pool.token0, pool.token1))?; + + let mut coins_to_send = vec![]; + if !token0.amount.is_zero() { + coins_to_send.push(token0.clone()); + } + if !token1.amount.is_zero() { + coins_to_send.push(token1.clone()); + } + let create_position_msg = create_position( + deps.storage, + &env, + position.lower_tick, + position.upper_tick, + coins_to_send, + Uint128::zero(), + Uint128::zero(), + )?; + + CURRENT_DEPOSIT.save( + deps.storage, + &CurrentDeposit { + token0_in: token0.amount, + token1_in: token1.amount, + sender: recipient, + }, + )?; + + Ok(Response::new() + .add_submessage(SubMsg::reply_on_success( + create_position_msg, + Replies::DepositCreatePosition as u64, + )) + .add_attribute("method", "exact_deposit") + .add_attribute("action", "exact_deposit") + .add_attribute("amount0", token0.amount) + .add_attribute("amount1", token1.amount)) +} + +/// handles the reply to creating a position for a user deposit +/// and calculates the refund for the user +pub fn handle_deposit_create_position_reply( + deps: DepsMut, + env: Env, + data: SubMsgResult, +) -> ContractResult { + let create_deposit_position_resp: MsgCreatePositionResponse = data.try_into()?; + let current_deposit = CURRENT_DEPOSIT.load(deps.storage)?; + let vault_denom = VAULT_DENOM.load(deps.storage)?; + + // we mint shares according to the liquidity created in the position creation + // this return value is a uint128 with 18 decimals, eg: 101017752467168561172212170 + let user_created_liquidity = Decimal256::new(Uint256::from_str( + create_deposit_position_resp.liquidity_created.as_str(), + )?); + + let existing_position = get_position(deps.storage, &deps.querier, &env)? + .position + .ok_or(ContractError::PositionNotFound)?; + + // the total liquidity, an actual decimal, eg: 2020355.049343371223444243" + let existing_liquidity = Decimal256::from_str(existing_position.liquidity.as_str())?; + + let total_vault_shares: Uint256 = BankQuerier::new(&deps.querier) + .supply_of(vault_denom.clone())? + .amount + .unwrap() + .amount + .parse::()? + .into(); + + // total_vault_shares.is_zero() should never be zero. This should ideally always enter the else and we are just sanity checking. + let user_shares: Uint128 = if total_vault_shares.is_zero() { + existing_liquidity.to_uint_floor().try_into()? + } else { + total_vault_shares + .multiply_ratio( + user_created_liquidity.numerator(), + user_created_liquidity.denominator(), + ) + .multiply_ratio( + existing_liquidity.denominator(), + existing_liquidity.numerator(), + ) + .try_into()? + }; + + // TODO the locking of minted shares is a band-aid for giving out rewards to users, + // once tokenfactory has send hooks, we can remove the lockup and have the users + // own the shares in their balance + // we mint shares to the contract address here, so we can lock those shares for the user later in the same call + // this is blocked by Osmosis v17 update + let mint_msg = MsgMint { + sender: env.contract.address.to_string(), + amount: Some(coin(user_shares.into(), vault_denom).into()), + mint_to_address: env.contract.address.to_string(), + }; + + // save the shares in the user map + SHARES.update( + deps.storage, + current_deposit.sender.clone(), + |old| -> Result { + if let Some(existing_user_shares) = old { + Ok(user_shares + existing_user_shares) + } else { + Ok(user_shares) + } + }, + )?; + + // resp.amount0 and resp.amount1 are the amount of tokens used for the position, we want to refund any unused tokens + // thus we calculate which tokens are not used + let pool_config = POOL_CONFIG.load(deps.storage)?; + + // TODOSN: Document the following refund_bank_msg purpose + let bank_msg = refund_bank_msg( + current_deposit.clone(), + &create_deposit_position_resp, + pool_config.token0, + pool_config.token1, + )?; + + let position_ids = vec![ + existing_position.position_id, + create_deposit_position_resp.position_id, + ]; + let merge_msg = + ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::Merge(MergePositionMsg { + position_ids, + })); + // merge our position with the main position + let merge_submsg = SubMsg::reply_on_success( + cosmwasm_std::WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + msg: to_binary(&merge_msg)?, + funds: vec![], + }, + Replies::Merge.into(), + ); + + let mint_attrs = vec![ + attr("mint_shares_amount", user_shares), + attr("receiver", current_deposit.sender.as_str()), + ]; + + // clear out the current deposit since it is no longer needed + CURRENT_DEPOSIT.remove(deps.storage); + + // Merge our positions together and mint the user shares to the cl-vault + let mut response = Response::new() + .add_submessage(merge_submsg) + .add_attribute( + "position_ids", + format!( + "{},{}", + existing_position.position_id, create_deposit_position_resp.position_id + ), + ) + .add_message(mint_msg) + .add_attributes(mint_attrs) + .add_attribute("method", "create_position_reply") + .add_attribute("action", "exact_deposit"); + + // if we have any funds to refund, refund them + if let Some((msg, attr)) = bank_msg { + response = response.add_message(msg).add_attributes(attr); + } + + Ok(response) +} + +fn refund_bank_msg( + current_deposit: CurrentDeposit, + resp: &MsgCreatePositionResponse, + denom0: String, + denom1: String, +) -> Result)>, ContractError> { + let refund0 = current_deposit + .token0_in + .checked_sub(Uint128::new(resp.amount0.parse::()?))?; + + let refund1 = current_deposit + .token1_in + .checked_sub(Uint128::new(resp.amount1.parse::()?))?; + + let mut attributes: Vec = vec![]; + let mut coins: Vec = vec![]; + + // TODOSN: Document this explaining what s happening below + if !refund0.is_zero() { + attributes.push(attr("refund0_amount", refund0)); + attributes.push(attr("refund0_denom", denom0.as_str())); + + coins.push(coin(refund0.u128(), denom0)) + } + if !refund1.is_zero() { + attributes.push(attr("refund1_amount", refund1)); + attributes.push(attr("refund1_denom", denom1.as_str())); + + coins.push(coin(refund1.u128(), denom1)) + } + let result: Option<(BankMsg, Vec)> = if !coins.is_empty() { + Some(( + BankMsg::Send { + to_address: current_deposit.sender.to_string(), + amount: coins, + }, + attributes, + )) + } else { + None + }; + Ok(result) +} + +#[cfg(test)] +mod tests { + use std::marker::PhantomData; + + use cosmwasm_std::{ + testing::{mock_env, MockApi, MockStorage, MOCK_CONTRACT_ADDR}, + to_binary, Addr, Decimal256, Empty, OwnedDeps, SubMsgResponse, Uint256, WasmMsg, + }; + + use osmosis_std::types::{ + cosmos::base::v1beta1::Coin as OsmoCoin, + osmosis::concentratedliquidity::v1beta1::{ + FullPositionBreakdown, Position as OsmoPosition, + }, + }; + + use crate::{ + state::{PoolConfig, Position}, + test_helpers::QuasarQuerier, + }; + + use super::*; + + #[test] + fn handle_deposit_create_position_works() { + let mut deps = mock_deps_with_querier(); + let env = mock_env(); + let sender = Addr::unchecked("alice"); + VAULT_DENOM + .save(deps.as_mut().storage, &"money".to_string()) + .unwrap(); + POSITION + .save(deps.as_mut().storage, &Position { position_id: 1 }) + .unwrap(); + + CURRENT_DEPOSIT + .save( + deps.as_mut().storage, + &CurrentDeposit { + token0_in: Uint128::new(100), + token1_in: Uint128::new(100), + sender: sender.clone(), + }, + ) + .unwrap(); + POOL_CONFIG + .save( + deps.as_mut().storage, + &PoolConfig { + pool_id: 1, + token0: "token0".to_string(), + token1: "token1".to_string(), + }, + ) + .unwrap(); + + let result = SubMsgResult::Ok(SubMsgResponse { + events: vec![], + data: Some( + MsgCreatePositionResponse { + position_id: 2, + amount0: "100".to_string(), + amount1: "100".to_string(), + // MsgCreatePositionResponse returns a uint, which represents an 18 decimal in + // for the liquidity created to be 500000.1, we expect this number to be 500000100000000000000000 + liquidity_created: "500000100000000000000000".to_string(), + lower_tick: 1, + upper_tick: 100, + } + .try_into() + .unwrap(), + ), + }); + + let response = + handle_deposit_create_position_reply(deps.as_mut(), env.clone(), result).unwrap(); + assert_eq!(response.messages.len(), 2); + assert_eq!( + response.messages[0], + SubMsg::reply_on_success( + WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + msg: to_binary(&ExecuteMsg::VaultExtension( + crate::msg::ExtensionExecuteMsg::Merge(MergePositionMsg { + position_ids: vec![1, 2] + }) + )) + .unwrap(), + funds: vec![] + }, + Replies::Merge.into() + ) + ); + // the mint amount is dependent on the liquidity returned by MsgCreatePositionResponse, in this case 50% of current liquidty + assert_eq!( + SHARES.load(deps.as_ref().storage, sender).unwrap(), + Uint128::new(50) + ); + assert_eq!( + response.messages[1], + SubMsg::new(MsgMint { + sender: env.contract.address.to_string(), + amount: Some(OsmoCoin { + denom: "money".to_string(), + amount: 50.to_string() + }), + mint_to_address: env.contract.address.to_string() + }) + ); + } + + #[test] + fn test_shares() { + let total_shares = Uint256::from(1000000000_u128); + let total_liquidity = Decimal256::from_str("1000000000").unwrap(); + let liquidity = Decimal256::from_str("5000000").unwrap(); + + let user_shares: Uint128 = if total_shares.is_zero() && total_liquidity.is_zero() { + liquidity.to_uint_floor().try_into().unwrap() + } else { + let _ratio = liquidity.checked_div(total_liquidity).unwrap(); + total_shares + .multiply_ratio(liquidity.numerator(), liquidity.denominator()) + .multiply_ratio(total_liquidity.denominator(), total_liquidity.numerator()) + .try_into() + .unwrap() + }; + + println!("{}", user_shares); + } + + #[test] + fn refund_bank_msg_2_leftover() { + let _env = mock_env(); + let user = Addr::unchecked("alice"); + + let current_deposit = CurrentDeposit { + token0_in: Uint128::new(200), + token1_in: Uint128::new(400), + sender: user, + }; + let resp = MsgCreatePositionResponse { + position_id: 1, + amount0: 150.to_string(), + amount1: 250.to_string(), + liquidity_created: "100000.000".to_string(), + lower_tick: 1, + upper_tick: 100, + }; + let denom0 = "uosmo".to_string(); + let denom1 = "uatom".to_string(); + + let response = refund_bank_msg(current_deposit.clone(), &resp, denom0, denom1).unwrap(); + assert!(response.is_some()); + assert_eq!( + response.unwrap().0, + BankMsg::Send { + to_address: current_deposit.sender.to_string(), + amount: vec![coin(50, "uosmo"), coin(150, "uatom")], + } + ) + } + + #[test] + fn refund_bank_msg_token1_leftover() { + let _env = mock_env(); + let user = Addr::unchecked("alice"); + + let current_deposit = CurrentDeposit { + token0_in: Uint128::new(200), + token1_in: Uint128::new(400), + sender: user, + }; + let resp = MsgCreatePositionResponse { + position_id: 1, + amount0: 200.to_string(), + amount1: 250.to_string(), + liquidity_created: "100000.000".to_string(), + lower_tick: 1, + upper_tick: 100, + }; + let denom0 = "uosmo".to_string(); + let denom1 = "uatom".to_string(); + + let response = refund_bank_msg(current_deposit.clone(), &resp, denom0, denom1).unwrap(); + assert!(response.is_some()); + assert_eq!( + response.unwrap().0, + BankMsg::Send { + to_address: current_deposit.sender.to_string(), + amount: vec![coin(150, "uatom")] + } + ) + } + + #[test] + fn refund_bank_msg_token0_leftover() { + let _env = mock_env(); + let user = Addr::unchecked("alice"); + + let current_deposit = CurrentDeposit { + token0_in: Uint128::new(200), + token1_in: Uint128::new(400), + sender: user, + }; + let resp = MsgCreatePositionResponse { + position_id: 1, + amount0: 150.to_string(), + amount1: 400.to_string(), + liquidity_created: "100000.000".to_string(), + lower_tick: 1, + upper_tick: 100, + }; + let denom0 = "uosmo".to_string(); + let denom1 = "uatom".to_string(); + + let response = refund_bank_msg(current_deposit.clone(), &resp, denom0, denom1).unwrap(); + assert!(response.is_some()); + assert_eq!( + response.unwrap().0, + BankMsg::Send { + to_address: current_deposit.sender.to_string(), + amount: vec![coin(50, "uosmo")] + } + ) + } + + #[test] + fn refund_bank_msg_none_leftover() { + let _env = mock_env(); + let user = Addr::unchecked("alice"); + + let current_deposit = CurrentDeposit { + token0_in: Uint128::new(200), + token1_in: Uint128::new(400), + sender: user, + }; + let resp = MsgCreatePositionResponse { + position_id: 1, + amount0: 200.to_string(), + amount1: 400.to_string(), + liquidity_created: "100000.000".to_string(), + lower_tick: 1, + upper_tick: 100, + }; + let denom0 = "uosmo".to_string(); + let denom1 = "uatom".to_string(); + + let response = refund_bank_msg(current_deposit, &resp, denom0, denom1).unwrap(); + assert!(response.is_none()); + } + + fn mock_deps_with_querier() -> OwnedDeps { + OwnedDeps { + storage: MockStorage::default(), + api: MockApi::default(), + querier: QuasarQuerier::new( + FullPositionBreakdown { + position: Some(OsmoPosition { + position_id: 1, + address: MOCK_CONTRACT_ADDR.to_string(), + pool_id: 1, + lower_tick: 100, + upper_tick: 1000, + join_time: None, + liquidity: "1000000.2".to_string(), + }), + asset0: Some(OsmoCoin { + denom: "token0".to_string(), + amount: "1000000".to_string(), + }), + asset1: Some(OsmoCoin { + denom: "token1".to_string(), + amount: "1000000".to_string(), + }), + claimable_spread_rewards: vec![ + OsmoCoin { + denom: "token0".to_string(), + amount: "100".to_string(), + }, + OsmoCoin { + denom: "token1".to_string(), + amount: "100".to_string(), + }, + ], + claimable_incentives: vec![], + forfeited_incentives: vec![], + }, + 500, + ), + custom_query_type: PhantomData, + } + } +} diff --git a/smart-contracts/contracts/cl-vault/src/vault/merge.rs b/smart-contracts/contracts/cl-vault/src/vault/merge.rs new file mode 100644 index 000000000..e223936ea --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/vault/merge.rs @@ -0,0 +1,237 @@ +use std::str::FromStr; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + coin, from_binary, to_binary, CosmosMsg, Decimal256, DepsMut, Env, MessageInfo, Response, + StdError, SubMsg, SubMsgResult, Uint128, +}; +use cw_utils::parse_execute_response_data; +use osmosis_std::types::osmosis::concentratedliquidity::v1beta1::{ + ConcentratedliquidityQuerier, MsgCreatePositionResponse, MsgWithdrawPosition, + MsgWithdrawPositionResponse, +}; + +use crate::{ + error::ContractResult, + msg::MergePositionMsg, + reply::Replies, + state::{CurrentMergePosition, CURRENT_MERGE, CURRENT_MERGE_POSITION, POOL_CONFIG}, + vault::concentrated_liquidity::create_position, + ContractError, +}; + +#[cw_serde] +pub struct MergeResponse { + pub new_position_id: u64, +} + +pub fn execute_merge( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: MergePositionMsg, +) -> ContractResult { + //check that the sender is our contract + if env.contract.address != info.sender { + return Err(ContractError::Unauthorized {}); + } + + let mut range: Option = None; + // Withdraw all positions + let withdraw_msgs: ContractResult> = msg + .position_ids + .into_iter() + .map(|position_id| { + let cl_querier = ConcentratedliquidityQuerier::new(&deps.querier); + let position = cl_querier.position_by_id(position_id)?; + let p = position.position.unwrap().position.unwrap(); + + // if we already have queried a range to seen as "canonical", compare the range of the position + // and error if they are not the same else we set the value of range. Thus the first queried position is seen as canonical + if let Some(range) = &range { + if range.lower_tick != p.lower_tick || range.upper_tick != p.upper_tick { + return Err(ContractError::DifferentTicksInMerge); + } + } else { + range = Some(CurrentMergePosition { + lower_tick: p.lower_tick, + upper_tick: p.upper_tick, + }) + } + + // save the position as an ongoing withdraw + // create a withdraw msg to dispatch + let liquidity_amount = Decimal256::from_str(p.liquidity.as_str())?; + + Ok(MsgWithdrawPosition { + position_id, + sender: env.contract.address.to_string(), + liquidity_amount: liquidity_amount.atomics().to_string(), + }) + }) + .collect(); + + CURRENT_MERGE_POSITION.save(deps.storage, &range.unwrap())?; + + // push all items on the queue + for msg in withdraw_msgs? { + CURRENT_MERGE.push_back(deps.storage, &CurrentMergeWithdraw { result: None, msg })?; + } + + // check the first item and dispatch it + let current = CURRENT_MERGE.front(deps.storage)?.unwrap(); + + // let msg: CosmosMsg = current.msg.into(); + Ok(Response::new() + .add_submessage(SubMsg::reply_on_success( + current.msg, + Replies::WithdrawMerge as u64, + )) + .add_attribute("method", "merge") + .add_attribute("action", "merge")) +} + +#[cw_serde] +pub struct CurrentMergeWithdraw { + pub result: Option, + pub msg: MsgWithdrawPosition, +} + +#[cw_serde] +pub struct WithdrawResponse { + pub amount0: Uint128, + pub amount1: Uint128, +} + +pub fn handle_merge_withdraw_reply( + deps: DepsMut, + env: Env, + msg: SubMsgResult, +) -> ContractResult { + let response: MsgWithdrawPositionResponse = msg.try_into()?; + + // get the corresponding withdraw + let last = CURRENT_MERGE.pop_front(deps.storage)?.unwrap(); + + // mark the current response as finished + CURRENT_MERGE.push_back( + deps.storage, + &CurrentMergeWithdraw { + result: Some(WithdrawResponse { + amount0: response.amount0.parse()?, + amount1: response.amount1.parse()?, + }), + msg: last.msg, + }, + )?; + + let next = CURRENT_MERGE.front(deps.storage)?.unwrap(); + + // if next already has a result, we already performed that withdraw + // so then we empty the entire queue, add all results together and create a new position + // under the current range with that + if next.result.is_some() { + let range = CURRENT_MERGE_POSITION.load(deps.storage)?; + let (mut amount0, mut amount1) = (Uint128::zero(), Uint128::zero()); + + // sum all results in the queue while emptying the queue + while !CURRENT_MERGE.is_empty(deps.storage)? { + let w = CURRENT_MERGE + .pop_front(deps.storage)? + .unwrap() + .result + .unwrap(); + amount0 += w.amount0; + amount1 += w.amount1; + } + + let pool: crate::state::PoolConfig = POOL_CONFIG.load(deps.storage)?; + + // amount0 and amount1 can be zero only in the case of a single side position handling + let mut tokens = vec![]; + if !amount0.is_zero() { + tokens.push(coin(amount0.into(), pool.token0)) + } + if !amount1.is_zero() { + tokens.push(coin(amount1.into(), pool.token1)) + } + + // this is expected to panic if tokens is an empty vec![] + // tokens should never be an empty vec![] as this would mean that all the current positions + // are returning zero tokens and this would fail on osmosis side + let position = create_position( + deps.storage, + &env, + range.lower_tick, + range.upper_tick, + tokens, + Uint128::zero(), + Uint128::zero(), + )?; + + Ok(Response::new().add_submessage(SubMsg::reply_on_success( + position, + Replies::CreatePositionMerge as u64, + ))) + } else { + let msg: CosmosMsg = next.msg.into(); + + Ok(Response::new() + .add_submessage(SubMsg::reply_on_success(msg, Replies::WithdrawMerge as u64)) + .add_attribute("method", "withdraw-position-reply") + .add_attribute("action", "merge")) + } +} + +pub fn handle_merge_create_position_reply( + _deps: DepsMut, + _env: Env, + msg: SubMsgResult, +) -> ContractResult { + let response: MsgCreatePositionResponse = msg.try_into()?; + // TODO decide if we want any healthchecks here + Ok(Response::new() + .set_data( + to_binary(&MergeResponse { + new_position_id: response.position_id, + })? + .0, + ) + .add_attribute("method", "create-position-reply") + .add_attribute("action", "merge")) +} + +impl TryFrom for MergeResponse { + type Error = StdError; + + fn try_from(value: SubMsgResult) -> Result { + let data = &value + .into_result() + .map_err(StdError::generic_err)? + .data + .ok_or(StdError::NotFound { + kind: "MergeResponse".to_string(), + })?; + let response = parse_execute_response_data(&data.0).unwrap(); + from_binary(&response.data.unwrap()) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + + #[test] + fn execute_merge_works() {} + + #[test] + fn serde_merge_response_is_inverse() { + let expected = MergeResponse { new_position_id: 5 }; + + let data = &to_binary(&expected).unwrap(); + println!("{:?}", data); + + let result = from_binary(data).unwrap(); + assert_eq!(expected, result) + } +} diff --git a/smart-contracts/contracts/cl-vault/src/vault/mod.rs b/smart-contracts/contracts/cl-vault/src/vault/mod.rs new file mode 100644 index 000000000..b309d2f1d --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/vault/mod.rs @@ -0,0 +1,8 @@ +pub mod admin; +pub mod claim; +pub mod concentrated_liquidity; +pub mod deposit; +pub mod merge; +pub mod range; +mod swap; +pub mod withdraw; diff --git a/smart-contracts/contracts/cl-vault/src/vault/range.rs b/smart-contracts/contracts/cl-vault/src/vault/range.rs new file mode 100644 index 000000000..3592204dc --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/vault/range.rs @@ -0,0 +1,759 @@ +use cosmwasm_schema::cw_serde; +use std::str::FromStr; + +use cosmwasm_std::{ + to_binary, Addr, Coin, Decimal, Decimal256, Deps, DepsMut, Env, Fraction, MessageInfo, + Response, Storage, SubMsg, SubMsgResult, Uint128, +}; + +use osmosis_std::types::{ + cosmos::base::v1beta1::Coin as OsmoCoin, + osmosis::{ + concentratedliquidity::v1beta1::{ + MsgCreatePosition, MsgCreatePositionResponse, MsgWithdrawPosition, + MsgWithdrawPositionResponse, Pool, + }, + gamm::v1beta1::MsgSwapExactAmountInResponse, + poolmanager::v1beta1::PoolmanagerQuerier, + }, +}; + +use crate::helpers::round_up_to_nearest_multiple; +use crate::msg::{ExecuteMsg, MergePositionMsg}; +use crate::state::CURRENT_SWAP; +use crate::vault::concentrated_liquidity::create_position; +use crate::{ + helpers::get_spot_price, + math::tick::price_to_tick, + reply::Replies, + state::{ + ModifyRangeState, Position, SwapDepositMergeState, MODIFY_RANGE_STATE, POOL_CONFIG, + POSITION, RANGE_ADMIN, SWAP_DEPOSIT_MERGE_STATE, VAULT_CONFIG, + }, + vault::concentrated_liquidity::get_position, + vault::merge::MergeResponse, + vault::swap::swap, + ContractError, +}; +use crate::{ + helpers::{ + get_single_sided_deposit_0_to_1_swap_amount, get_single_sided_deposit_1_to_0_swap_amount, + }, + state::CURRENT_BALANCE, +}; + +use super::concentrated_liquidity::get_cl_pool_info; + +fn assert_range_admin(storage: &mut dyn Storage, sender: &Addr) -> Result<(), ContractError> { + let admin = RANGE_ADMIN.load(storage)?; + if admin != sender { + return Err(ContractError::Unauthorized {}); + } + Ok(()) +} + +fn _get_range_admin(deps: Deps) -> Result { + Ok(RANGE_ADMIN.load(deps.storage)?) +} + +pub fn execute_update_range( + deps: DepsMut, + env: Env, + info: MessageInfo, + lower_price: Decimal, + upper_price: Decimal, + max_slippage: Decimal, +) -> Result { + let lower_tick = price_to_tick(deps.storage, Decimal256::from(lower_price))?; + let upper_tick = price_to_tick(deps.storage, Decimal256::from(upper_price))?; + + execute_update_range_ticks( + deps, + env, + info, + lower_tick.try_into().unwrap(), + upper_tick.try_into().unwrap(), + max_slippage, + ) +} + +/// This function is the entrypoint into the dsm routine that will go through the following steps +/// * how much liq do we have in current range +/// * so how much of each asset given liq would we have at current price +/// * how much of each asset do we need to move to get to new range +/// * deposit up to max liq we can right now, then swap remaining over and deposit again +pub fn execute_update_range_ticks( + deps: DepsMut, + env: Env, + info: MessageInfo, + lower_tick: i64, + upper_tick: i64, + max_slippage: Decimal, +) -> Result { + assert_range_admin(deps.storage, &info.sender)?; + + // todo: prevent re-entrancy by checking if we have anything in MODIFY_RANGE_STATE (redundant check but whatever) + + // this will error if we dont have a position anyway + let position_breakdown = get_position(deps.storage, &deps.querier, &env)?; + let position = position_breakdown.position.unwrap(); + + let withdraw_msg = MsgWithdrawPosition { + position_id: position.position_id, + sender: env.contract.address.to_string(), + liquidity_amount: Decimal256::from_str(position.liquidity.as_str())? + .atomics() + .to_string(), + }; + + MODIFY_RANGE_STATE.save( + deps.storage, + // todo: should ModifyRangeState be an enum? + &Some(ModifyRangeState { + lower_tick, + upper_tick, + new_range_position_ids: vec![], + max_slippage, + }), + )?; + + Ok(Response::default() + .add_submessage(SubMsg::reply_on_success( + withdraw_msg, + Replies::WithdrawPosition.into(), + )) + .add_attribute("action", "modify_range") + .add_attribute("method", "withdraw_position") + .add_attribute("position_id", position.position_id.to_string()) + .add_attribute("liquidity_amount", position.liquidity)) +} + +// do create new position +pub fn handle_withdraw_position_reply( + deps: DepsMut, + env: Env, + data: SubMsgResult, +) -> Result { + let msg: MsgWithdrawPositionResponse = data.try_into()?; + + // let msg: MsgWithdrawPositionResponse = data.into_result().unwrap().data.unwrap().try_into()?; + + let modify_range_state = MODIFY_RANGE_STATE.load(deps.storage)?.unwrap(); + let pool_config = POOL_CONFIG.load(deps.storage)?; + // what about funds sent to the vault via banksend, what about airdrops/other ways this would not be the total deposited balance + // todo: Test that one-sided withdraw wouldn't error here (it shouldn't) + let amount0: Uint128 = msg.amount0.parse()?; + let amount1: Uint128 = msg.amount1.parse()?; + + CURRENT_BALANCE.save(deps.storage, &(amount0, amount1))?; + + let mut tokens_provided = vec![]; + if !amount0.is_zero() { + tokens_provided.push(OsmoCoin { + denom: pool_config.token0.clone(), + amount: amount0.to_string(), + }) + } + if !amount1.is_zero() { + tokens_provided.push(OsmoCoin { + denom: pool_config.token1.clone(), + amount: amount1.to_string(), + }) + } + + let pool_details = get_cl_pool_info(&deps.querier, pool_config.pool_id)?; + + // if only one token is being deposited, and we are moving into a position where any amount of the other token is needed, + // creating the position here will fail because liquidityNeeded is calculated as 0 on chain level + // we can fix this by going straight into a swap-deposit-merge before creating any positions + + // todo: Check if needs LTE or just LT + // 0 token0 and current_tick > lower_tick + // 0 token1 and current_tick < upper_tick + // if (lower < current < upper) && amount0 == 0 || amount1 == 0 + // also onesided but wrong token + // bad complexity demon, grug no like + if (amount0.is_zero() && pool_details.current_tick < modify_range_state.upper_tick) + || (amount1.is_zero() && pool_details.current_tick > modify_range_state.lower_tick) + { + do_swap_deposit_merge( + deps, + env, + modify_range_state.lower_tick, + modify_range_state.upper_tick, + (amount0, amount1), + None, // we just withdrew our only position + ) + } else { + // we can naively re-deposit up to however much keeps the proportion of tokens the same. Then swap & re-deposit the proper ratio with the remaining tokens + let create_position_msg = MsgCreatePosition { + pool_id: pool_config.pool_id, + sender: env.contract.address.to_string(), + // round our lower tick and upper tick up to the nearest pool_details.tick_spacing + lower_tick: round_up_to_nearest_multiple( + modify_range_state.lower_tick, + pool_details + .tick_spacing + .try_into() + .expect("tick spacing is too big to fit into u64"), + ), + upper_tick: round_up_to_nearest_multiple( + modify_range_state.upper_tick, + pool_details + .tick_spacing + .try_into() + .expect("tick spacing is too big to fit into u64"), + ), + tokens_provided, + // passing 0 is ok here because currently no swap is done on osmosis side, so we don't actually need to worry about slippage impact + token_min_amount0: "0".to_string(), + token_min_amount1: "0".to_string(), + }; + + Ok(Response::new() + .add_submessage(SubMsg::reply_on_success( + create_position_msg, + Replies::RangeInitialCreatePosition.into(), + )) + .add_attribute("action", "modify_range") + .add_attribute("method", "create_position") + .add_attribute("lower_tick", format!("{:?}", modify_range_state.lower_tick)) + .add_attribute("upper_tick", format!("{:?}", modify_range_state.upper_tick)) + .add_attribute("token0", format!("{:?}{:?}", amount0, pool_config.token0)) + .add_attribute("token1", format!("{:?}{:?}", amount1, pool_config.token1))) + } +} + +// do swap +pub fn handle_initial_create_position_reply( + deps: DepsMut, + env: Env, + data: SubMsgResult, +) -> Result { + let create_position_message: MsgCreatePositionResponse = data.try_into()?; + + // target range for our imminent swap + // taking from response message is important because they may differ from the ones in our request + let target_lower_tick = create_position_message.lower_tick; + let target_upper_tick = create_position_message.upper_tick; + + // get refunded amounts + let current_balance = CURRENT_BALANCE.load(deps.storage)?; + let refunded_amounts = ( + current_balance + .0 + .checked_sub(Uint128::from_str(&create_position_message.amount0)?)?, + current_balance + .1 + .checked_sub(Uint128::from_str(&create_position_message.amount1)?)?, + ); + + do_swap_deposit_merge( + deps, + env, + target_lower_tick, + target_upper_tick, + refunded_amounts, + Some(create_position_message.position_id), + ) +} + +/// this function assumes that we are swapping and depositing into a valid range +/// +/// It also calculates the exact amount we should be swapping based on current balances and the new range +pub fn do_swap_deposit_merge( + deps: DepsMut, + env: Env, + target_lower_tick: i64, + target_upper_tick: i64, + refunded_amounts: (Uint128, Uint128), + position_id: Option, +) -> Result { + if SWAP_DEPOSIT_MERGE_STATE.may_load(deps.storage)?.is_some() { + return Err(ContractError::SwapInProgress {}); + } + + let (balance0, balance1) = refunded_amounts; + + let pool_config = POOL_CONFIG.load(deps.storage)?; + let vault_config = VAULT_CONFIG.load(deps.storage)?; + let pool_details = get_cl_pool_info(&deps.querier, pool_config.pool_id)?; + + let mut target_range_position_ids = vec![]; + if let Some(pos_id) = position_id { + target_range_position_ids.push(pos_id); + } + + SWAP_DEPOSIT_MERGE_STATE.save( + deps.storage, + &SwapDepositMergeState { + target_lower_tick, + target_upper_tick, + target_range_position_ids, + }, + )?; + + //TODO: further optimizations can be made by increasing the swap amount by half of our expected slippage, + // to reduce the total number of non-deposited tokens that we will then need to refund + let (swap_amount, swap_direction) = if !balance0.is_zero() { + ( + // range is above current tick + if pool_details.current_tick > target_upper_tick { + balance0 + } else { + get_single_sided_deposit_0_to_1_swap_amount( + balance0, + target_lower_tick, + pool_details.current_tick, + target_upper_tick, + )? + }, + SwapDirection::ZeroToOne, + ) + } else if !balance1.is_zero() { + ( + // current tick is above range + if pool_details.current_tick < target_lower_tick { + // TODO: Maybe here <= ? + balance1 + } else { + get_single_sided_deposit_1_to_0_swap_amount( + balance1, + target_lower_tick, + pool_details.current_tick, + target_upper_tick, + )? + }, + SwapDirection::OneToZero, + ) + } else { + // if we have not tokens to swap, that means all tokens we correctly used in the create position + // this means we can save the position id of the first create_position + POSITION.save( + deps.storage, + &Position { + // if position not found, then we should panic here anyway ? + position_id: position_id.expect("position id should be set if no swap is needed"), + }, + )?; + + SWAP_DEPOSIT_MERGE_STATE.remove(deps.storage); + + return Ok(Response::new() + .add_attribute("action", "swap_deposit_merge") + .add_attribute("method", "no_swap") + .add_attribute("new_position", position_id.unwrap().to_string())); + }; + // todo check that this math is right with spot price (numerators, denominators) if taken by legacy gamm module instead of poolmanager + let spot_price = get_spot_price(deps.storage, &deps.querier)?; + let (token_in_denom, token_out_ideal_amount, left_over_amount) = match swap_direction { + SwapDirection::ZeroToOne => ( + pool_config.token0, + swap_amount.checked_multiply_ratio(spot_price.numerator(), spot_price.denominator()), + balance0.checked_sub(swap_amount)?, + ), + SwapDirection::OneToZero => ( + pool_config.token1, + swap_amount.checked_multiply_ratio(spot_price.denominator(), spot_price.numerator()), + balance1.checked_sub(swap_amount)?, + ), + }; + + CURRENT_SWAP.save(deps.storage, &(swap_direction, left_over_amount))?; + + let token_out_min_amount = token_out_ideal_amount?.checked_multiply_ratio( + vault_config.swap_max_slippage.numerator(), + vault_config.swap_max_slippage.denominator(), + )?; + + let swap_msg = swap( + deps, + &env, + swap_amount, + &token_in_denom, + token_out_min_amount, + )?; + + Ok(Response::new() + .add_submessage(SubMsg::reply_on_success(swap_msg, Replies::Swap.into())) + .add_attribute("action", "swap_deposit_merge") + .add_attribute("method", "swap") + .add_attribute("token_in", format!("{:?}{:?}", swap_amount, token_in_denom)) + .add_attribute("token_out_min", format!("{:?}", token_out_min_amount))) +} + +// do deposit +pub fn handle_swap_reply( + deps: DepsMut, + env: Env, + data: SubMsgResult, +) -> Result { + // TODO: Remove handling of data. if we keep reply_on_success in the caller function + match data.clone() { + SubMsgResult::Ok(_msg) => handle_swap_success(deps, env, data.try_into()?), + SubMsgResult::Err(msg) => Err(ContractError::SwapFailed { message: msg }), + } +} + +fn handle_swap_success( + deps: DepsMut, + env: Env, + resp: MsgSwapExactAmountInResponse, +) -> Result { + let swap_deposit_merge_state = match SWAP_DEPOSIT_MERGE_STATE.may_load(deps.storage)? { + Some(swap_deposit_merge) => swap_deposit_merge, + None => return Err(ContractError::SwapDepositMergeStateNotFound {}), + }; + let (swap_direction, left_over_amount) = CURRENT_SWAP.load(deps.storage)?; + + let pool_config = POOL_CONFIG.load(deps.storage)?; + let _modify_range_state = MODIFY_RANGE_STATE.load(deps.storage)?.unwrap(); + + // get post swap balances to create positions with + let (balance0, balance1): (Uint128, Uint128) = match swap_direction { + SwapDirection::ZeroToOne => ( + left_over_amount, + Uint128::new(resp.token_out_amount.parse()?), + ), + SwapDirection::OneToZero => ( + Uint128::new(resp.token_out_amount.parse()?), + left_over_amount, + ), + }; + // Create the position after swapped the leftovers based on swap direction + let mut coins_to_send = vec![]; + if !balance0.is_zero() { + coins_to_send.push(Coin { + denom: pool_config.token0.clone(), + amount: balance0, + }); + } + if !balance1.is_zero() { + coins_to_send.push(Coin { + denom: pool_config.token1, + amount: balance1, + }); + } + let create_position_msg = create_position( + deps.storage, + &env, + swap_deposit_merge_state.target_lower_tick, + swap_deposit_merge_state.target_upper_tick, + coins_to_send, + Uint128::zero(), + Uint128::zero(), + )?; + + // get the current pool + let pool_config = POOL_CONFIG.load(deps.storage)?; + + let pm_querier = PoolmanagerQuerier::new(&deps.querier); + let _pool: Pool = pm_querier + .pool(pool_config.pool_id)? + .pool + .unwrap() + .try_into()?; + + Ok(Response::new() + .add_submessage(SubMsg::reply_on_success( + create_position_msg, + Replies::RangeIterationCreatePosition.into(), + )) + .add_attribute("action", "swap_deposit_merge") + .add_attribute("method", "create_position2") + .add_attribute( + "lower_tick", + format!("{:?}", swap_deposit_merge_state.target_lower_tick), + ) + .add_attribute( + "upper_tick", + format!("{:?}", swap_deposit_merge_state.target_upper_tick), + ) + .add_attribute("token0", format!("{:?}{:?}", balance0, pool_config.token0)) + .add_attribute("token1", format!("{:?}{:?}", balance1, pool_config.token1))) +} + +// do merge position & exit +pub fn handle_iteration_create_position_reply( + deps: DepsMut, + env: Env, + data: SubMsgResult, +) -> Result { + let create_position_message: MsgCreatePositionResponse = data.try_into()?; + + let mut swap_deposit_merge_state = match SWAP_DEPOSIT_MERGE_STATE.may_load(deps.storage)? { + Some(swap_deposit_merge) => swap_deposit_merge, + None => return Err(ContractError::SwapDepositMergeStateNotFound {}), + }; + + // add the position id to the ones we need to merge + swap_deposit_merge_state + .target_range_position_ids + .push(create_position_message.position_id); + + // call merge + let merge_msg = + ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::Merge(MergePositionMsg { + position_ids: swap_deposit_merge_state.target_range_position_ids.clone(), + })); + // merge our position with the main position + let merge_submsg = SubMsg::reply_on_success( + cosmwasm_std::WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + msg: to_binary(&merge_msg)?, + funds: vec![], + }, + Replies::Merge.into(), + ); + + // clear state to allow for new liquidity movement operations + SWAP_DEPOSIT_MERGE_STATE.remove(deps.storage); + + Ok(Response::new() + .add_submessage(merge_submsg) + .add_attribute("action", "swap_deposit_merge") + .add_attribute("method", "fungify_positions") + .add_attribute( + "position_ids", + format!("{:?}", swap_deposit_merge_state.target_range_position_ids), + )) +} + +// store new position id and exit +pub fn handle_merge_response(deps: DepsMut, data: SubMsgResult) -> Result { + let merge_response: MergeResponse = data.try_into()?; + + POSITION.save( + deps.storage, + &Position { + position_id: merge_response.new_position_id, + }, + )?; + + Ok(Response::new() + .add_attribute("action", "swap_deposit_merge") + .add_attribute("method", "fungify_positions_success") + .add_attribute("swap_deposit_merge_status", "success") + .add_attribute("status", "success")) +} + +#[cw_serde] +pub enum SwapDirection { + ZeroToOne, + OneToZero, +} + +#[cfg(test)] +mod tests { + use std::{marker::PhantomData, str::FromStr}; + + use cosmwasm_std::{ + testing::{ + mock_dependencies, mock_env, mock_info, MockApi, MockStorage, MOCK_CONTRACT_ADDR, + }, + Addr, Decimal, Empty, MessageInfo, OwnedDeps, SubMsgResponse, SubMsgResult, + }; + use osmosis_std::types::{ + cosmos::base::v1beta1::Coin as OsmoCoin, + osmosis::concentratedliquidity::v1beta1::{ + FullPositionBreakdown, MsgWithdrawPositionResponse, Position as OsmoPosition, + }, + }; + + use crate::{ + state::{ + PoolConfig, VaultConfig, MODIFY_RANGE_STATE, POOL_CONFIG, POSITION, RANGE_ADMIN, + VAULT_CONFIG, + }, + test_helpers::QuasarQuerier, + }; + + fn mock_deps_with_querier( + info: &MessageInfo, + ) -> OwnedDeps { + let mut deps = OwnedDeps { + storage: MockStorage::default(), + api: MockApi::default(), + querier: QuasarQuerier::new( + FullPositionBreakdown { + position: Some(OsmoPosition { + position_id: 1, + address: MOCK_CONTRACT_ADDR.to_string(), + pool_id: 1, + lower_tick: 100, + upper_tick: 1000, + join_time: None, + liquidity: "1000000.1".to_string(), + }), + asset0: Some(OsmoCoin { + denom: "token0".to_string(), + amount: "1000000".to_string(), + }), + asset1: Some(OsmoCoin { + denom: "token1".to_string(), + amount: "1000000".to_string(), + }), + claimable_spread_rewards: vec![ + OsmoCoin { + denom: "token0".to_string(), + amount: "100".to_string(), + }, + OsmoCoin { + denom: "token1".to_string(), + amount: "100".to_string(), + }, + ], + claimable_incentives: vec![], + forfeited_incentives: vec![], + }, + 500, + ), + custom_query_type: PhantomData, + }; + + let storage = &mut deps.storage; + + RANGE_ADMIN.save(storage, &info.sender).unwrap(); + POOL_CONFIG + .save( + storage, + &PoolConfig { + pool_id: 1, + token0: "token0".to_string(), + token1: "token1".to_string(), + }, + ) + .unwrap(); + VAULT_CONFIG + .save( + storage, + &VaultConfig { + performance_fee: Decimal::zero(), + treasury: Addr::unchecked("treasure"), + swap_max_slippage: Decimal::from_ratio(1u128, 20u128), + }, + ) + .unwrap(); + POSITION + .save(storage, &crate::state::Position { position_id: 1 }) + .unwrap(); + + deps + } + + #[test] + fn test_assert_range_admin() { + let mut deps = mock_dependencies(); + let info = mock_info("addr0000", &[]); + + RANGE_ADMIN.save(&mut deps.storage, &info.sender).unwrap(); + + super::assert_range_admin(&mut deps.storage, &info.sender).unwrap(); + + let info = mock_info("addr0001", &[]); + super::assert_range_admin(&mut deps.storage, &info.sender).unwrap_err(); + + let info = mock_info("addr0000", &[]); + RANGE_ADMIN.save(&mut deps.storage, &info.sender).unwrap(); + + super::assert_range_admin(&mut deps.storage, &Addr::unchecked("someoneelse")).unwrap_err(); + } + + #[test] + fn test_get_range_admin() { + let mut deps = mock_dependencies(); + let info = mock_info("addr0000", &[]); + + RANGE_ADMIN.save(&mut deps.storage, &info.sender).unwrap(); + + assert_eq!(super::_get_range_admin(deps.as_ref()).unwrap(), info.sender); + } + + #[test] + fn test_execute_update_range() { + let info = mock_info("addr0000", &[]); + let mut deps = mock_deps_with_querier(&info); + + let env = mock_env(); + let lower_price = Decimal::from_str("100").unwrap(); + let upper_price = Decimal::from_str("100.20").unwrap(); + let max_slippage = Decimal::from_str("0.5").unwrap(); + + let res = super::execute_update_range( + deps.as_mut(), + env, + info, + lower_price, + upper_price, + max_slippage, + ) + .unwrap(); + + assert_eq!(res.messages.len(), 1); + assert_eq!(res.attributes[0].value, "modify_range"); + assert_eq!(res.attributes[1].value, "withdraw_position"); + assert_eq!(res.attributes[2].value, "1"); + assert_eq!(res.attributes[3].value, "1000000.1"); + } + + #[test] + fn test_handle_withdraw_position_reply_selects_correct_next_step_for_new_range() { + let info = mock_info("addr0000", &[]); + let mut deps = mock_deps_with_querier(&info); + + // moving into a range + MODIFY_RANGE_STATE + .save( + deps.as_mut().storage, + &Some(crate::state::ModifyRangeState { + lower_tick: 100, + upper_tick: 1000, // since both times we are moving into range and in the quasarquerier we configured the current_tick as 500, this would mean we are trying to move into range + new_range_position_ids: vec![], + max_slippage: Decimal::zero(), + }), + ) + .unwrap(); + + // Reply + let env = mock_env(); + //first test fully one-sided withdraw + let data = SubMsgResult::Ok(SubMsgResponse { + events: vec![], + data: Some( + MsgWithdrawPositionResponse { + amount0: "0".to_string(), + amount1: "10000".to_string(), + } + .try_into() + .unwrap(), + ), + }); + + let res = super::handle_withdraw_position_reply(deps.as_mut(), env.clone(), data).unwrap(); + + // verify that we went straight to swap_deposit_merge + assert_eq!(res.messages.len(), 1); + assert_eq!(res.attributes[0].value, "swap_deposit_merge"); + assert_eq!(res.attributes[1].value, "swap"); + + // now test two-sided withdraw + let data = SubMsgResult::Ok(SubMsgResponse { + events: vec![], + data: Some( + MsgWithdrawPositionResponse { + amount0: "10000".to_string(), + amount1: "10000".to_string(), + } + .try_into() + .unwrap(), + ), + }); + + let res = super::handle_withdraw_position_reply(deps.as_mut(), env, data).unwrap(); + + // verify that we did create_position first + assert_eq!(res.messages.len(), 1); + assert_eq!(res.attributes[0].value, "modify_range"); + assert_eq!(res.attributes[1].value, "create_position"); + } +} diff --git a/smart-contracts/contracts/cl-vault/src/vault/swap.rs b/smart-contracts/contracts/cl-vault/src/vault/swap.rs new file mode 100644 index 000000000..aba6e52ed --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/vault/swap.rs @@ -0,0 +1,200 @@ +use std::str::FromStr; + +use cosmwasm_std::{Coin, CosmosMsg, DepsMut, Env, QuerierWrapper, Storage, Uint128}; +use osmosis_std::types::{ + cosmos::base::v1beta1::Coin as OsmoCoin, osmosis::poolmanager::v1beta1::SwapAmountInRoute, +}; + +use crate::{state::POOL_CONFIG, ContractError}; + +/// estimate_swap can be used to pass correct token_out_min_amount values into swap() +/// for now this function can only be used for our pool +/// this will likely be expanded once we allow arbitrary pool swaps +pub fn _estimate_swap( + querier: &QuerierWrapper, + storage: &mut dyn Storage, + _env: &Env, + token_in_amount: Uint128, + token_in_denom: &String, + _token_out_min_amount: Uint128, +) -> Result { + let pool_config = POOL_CONFIG.load(storage)?; + + if !pool_config.pool_contains_token(token_in_denom) { + return Err(ContractError::BadTokenForSwap { + base_token: pool_config.token0, + quote_token: pool_config.token1, + }); + } + + // get token_out_denom + let token_out_denom = if *token_in_denom == pool_config.token0 { + pool_config.token1 + } else { + pool_config.token0 + }; + + // we will only ever have a route length of one, this will likely change once we start selecting different routes + let pool_route = SwapAmountInRoute { + pool_id: pool_config.pool_id, + token_out_denom: token_out_denom.to_string(), + }; + + let pm_querier = + osmosis_std::types::osmosis::poolmanager::v1beta1::PoolmanagerQuerier::new(querier); + + // todo: verify that we should be concatenating amount and denom or if we should just send token in amount as string + let result = pm_querier.estimate_swap_exact_amount_in( + pool_config.pool_id, + token_in_amount.to_string() + token_in_denom, + vec![pool_route], + )?; + + Ok(Coin { + denom: token_out_denom, + amount: Uint128::from_str(&result.token_out_amount)?, + }) +} + +/// swap will always swap over the CL pool. In the future we may expand the +/// feature such that it chooses best swaps over all routes +pub fn swap( + deps: DepsMut, + env: &Env, + token_in_amount: Uint128, + token_in_denom: &String, + token_out_min_amount: Uint128, +) -> Result { + let pool_config = POOL_CONFIG.load(deps.storage)?; + + if !pool_config.pool_contains_token(token_in_denom) { + return Err(ContractError::BadTokenForSwap { + base_token: pool_config.token0, + quote_token: pool_config.token1, + }); + } + + // get token_out_denom + let token_out_denom = if *token_in_denom == pool_config.token0 { + pool_config.token1 + } else { + pool_config.token0 + }; + + // we will only ever have a route length of one, this will likely change once we start selecting different routes + let pool_route = SwapAmountInRoute { + pool_id: pool_config.pool_id, + token_out_denom, + }; + + let swap_msg: CosmosMsg = + osmosis_std::types::osmosis::poolmanager::v1beta1::MsgSwapExactAmountIn { + sender: env.contract.address.to_string(), + routes: vec![pool_route], + token_in: Some(OsmoCoin { + denom: token_in_denom.to_string(), + amount: token_in_amount.to_string(), + }), + token_out_min_amount: token_out_min_amount.to_string(), + } + .into(); + + Ok(swap_msg) +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::{ + testing::{mock_dependencies_with_balance, mock_env}, + Coin, CosmosMsg, Uint128, + }; + + use crate::state::{PoolConfig, POOL_CONFIG}; + + fn mock_pool_config() -> PoolConfig { + PoolConfig { + pool_id: 1, + token0: "token0".to_string(), + token1: "token1".to_string(), + } + } + + #[test] + fn test_proper_swap() { + let mut deps = mock_dependencies_with_balance(&[Coin { + denom: "token0".to_string(), + amount: Uint128::new(1000), + }]); + let deps_mut = deps.as_mut(); + + let env = mock_env(); + + let token_in_amount = Uint128::new(100); + let token_in_denom = "token0".to_string(); + let token_out_min_amount = Uint128::new(100); + + POOL_CONFIG + .save(deps_mut.storage, &mock_pool_config()) + .unwrap(); + + let result = super::swap( + deps_mut, + &env, + token_in_amount, + &token_in_denom, + token_out_min_amount, + ) + .unwrap(); + + if let CosmosMsg::Stargate { type_url: _, value } = result { + let msg_swap = + osmosis_std::types::osmosis::poolmanager::v1beta1::MsgSwapExactAmountIn::try_from( + value, + ) + .unwrap(); + + assert!(msg_swap.sender == env.contract.address); + assert!(msg_swap.routes.len() == 1); + assert!(msg_swap.routes[0].pool_id == 1); + assert!(msg_swap.routes[0].token_out_denom == *"token1"); + assert!(msg_swap.token_in.clone().unwrap().denom == *"token0"); + assert!(msg_swap.token_in.unwrap().amount == *"100"); + assert!(token_out_min_amount.to_string() == *"100"); + } else { + panic!("Unexpected message type: {:?}", result); + } + } + + #[test] + fn test_bad_denom_swap() { + let mut deps = mock_dependencies_with_balance(&[Coin { + denom: "token0".to_string(), + amount: Uint128::new(1000), + }]); + let deps_mut = deps.as_mut(); + + let env = mock_env(); + + let token_in_amount = Uint128::new(100); + let token_in_denom = "token3".to_string(); + let token_out_min_amount = Uint128::new(100); + + POOL_CONFIG + .save(deps_mut.storage, &mock_pool_config()) + .unwrap(); + + let err = super::swap( + deps_mut, + &env, + token_in_amount, + &token_in_denom, + token_out_min_amount, + ) + .unwrap_err(); + + assert_eq!( + err.to_string(), + "Bad token out requested for swap, must be one of: \"token0\", \"token1\"".to_string() + ); + } +} diff --git a/smart-contracts/contracts/cl-vault/src/vault/withdraw.rs b/smart-contracts/contracts/cl-vault/src/vault/withdraw.rs new file mode 100644 index 000000000..a58f1a2a4 --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/vault/withdraw.rs @@ -0,0 +1,177 @@ +use cosmwasm_std::{ + attr, coin, BankMsg, CosmosMsg, Decimal256, DepsMut, Env, MessageInfo, Response, SubMsg, + SubMsgResult, Uint128, +}; +use osmosis_std::types::{ + cosmos::bank::v1beta1::BankQuerier, + osmosis::{ + concentratedliquidity::v1beta1::{MsgWithdrawPosition, MsgWithdrawPositionResponse}, + tokenfactory::v1beta1::MsgBurn, + }, +}; + +use crate::{ + reply::Replies, + state::{CURRENT_WITHDRAWER, POOL_CONFIG, SHARES, VAULT_DENOM}, + vault::concentrated_liquidity::{get_position, withdraw_from_position}, + ContractError, +}; + +// any locked shares are sent in amount, due to a lack of tokenfactory hooks during development +// currently that functions as a bandaid +pub fn execute_withdraw( + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: Option, + amount: Uint128, +) -> Result { + let recipient = recipient.map_or(Ok(info.sender.clone()), |x| deps.api.addr_validate(&x))?; + + let vault_denom = VAULT_DENOM.load(deps.storage)?; + + // get the sent along shares + // let shares = must_pay(&info, vault_denom.as_str())?; + + // get the amount from SHARES state + let user_shares = SHARES.load(deps.storage, info.sender.clone())?; + let left_over = user_shares + .checked_sub(amount) + .map_err(|_| ContractError::InsufficientFunds)?; + SHARES.save(deps.storage, info.sender, &left_over)?; + + // burn the shares + let burn_coin = coin(amount.u128(), vault_denom); + let burn_msg: CosmosMsg = MsgBurn { + sender: env.contract.address.clone().into_string(), + amount: Some(burn_coin.into()), + burn_from_address: env.contract.address.clone().into_string(), + } + .into(); + + CURRENT_WITHDRAWER.save(deps.storage, &recipient)?; + + // withdraw the user's funds from the position + let withdraw_msg = withdraw(deps, &env, amount)?; // TODOSN: Rename this function name to something more explicative + + Ok(Response::new() + .add_attribute("method", "withdraw") + .add_attribute("action", "withdraw") + .add_attribute("liquidity_amount", withdraw_msg.liquidity_amount.as_str()) + .add_attribute("share_amount", amount) + .add_message(burn_msg) + .add_submessage(SubMsg::reply_on_success( + withdraw_msg, + Replies::WithdrawUser as u64, + ))) +} + +fn withdraw( + deps: DepsMut, + env: &Env, + shares: Uint128, +) -> Result { + let existing_position = get_position(deps.storage, &deps.querier, env)?; + let existing_liquidity: Decimal256 = existing_position + .position + .ok_or(ContractError::PositionNotFound)? + .liquidity + .parse()?; + + let bq = BankQuerier::new(&deps.querier); + let vault_denom = VAULT_DENOM.load(deps.storage)?; + + let total_vault_shares: Uint128 = bq + .supply_of(vault_denom)? + .amount + .unwrap() + .amount + .parse::()? + .into(); + + let user_shares = Decimal256::from_ratio(shares, 1_u128) + .checked_mul(existing_liquidity)? + .checked_div(Decimal256::from_ratio(total_vault_shares, 1_u128))?; + + withdraw_from_position(deps.storage, env, user_shares) +} + +pub fn handle_withdraw_user_reply( + deps: DepsMut, + data: SubMsgResult, +) -> Result { + // parse the reply and instantiate the funds we want to send + let response: MsgWithdrawPositionResponse = data.try_into()?; + let user = CURRENT_WITHDRAWER.load(deps.storage)?; + let pool_config = POOL_CONFIG.load(deps.storage)?; + + let coin0 = coin(response.amount0.parse()?, pool_config.token0); + let coin1 = coin(response.amount1.parse()?, pool_config.token1); + + let withdraw_attrs = vec![ + attr("token0_amount", coin0.amount), + attr("token1_amount", coin1.amount), + ]; + // send the funds to the user + let msg = BankMsg::Send { + to_address: user.to_string(), + amount: vec![coin0, coin1], + }; + Ok(Response::new() + .add_message(msg) + .add_attribute("method", "withdraw_position_reply") + .add_attribute("action", "withdraw") + .add_attributes(withdraw_attrs)) +} + +#[cfg(test)] +mod tests { + use crate::state::PoolConfig; + use cosmwasm_std::{testing::mock_dependencies, Addr, CosmosMsg, SubMsgResponse}; + + use super::*; + + // the execute withdraw flow should be easiest to test in test-tube, since it requires quite a bit of Osmsosis specific information + // we just test the handle withdraw implementation here + #[test] + fn handle_withdraw_user_reply_works() { + let mut deps = mock_dependencies(); + let to_address = Addr::unchecked("bolice"); + CURRENT_WITHDRAWER + .save(deps.as_mut().storage, &to_address) + .unwrap(); + POOL_CONFIG + .save( + deps.as_mut().storage, + &PoolConfig { + pool_id: 1, + token0: "uosmo".into(), + token1: "uatom".into(), + }, + ) + .unwrap(); + + let msg = MsgWithdrawPositionResponse { + amount0: "1000".to_string(), + amount1: "1000".to_string(), + } + .try_into() + .unwrap(); + + let response = handle_withdraw_user_reply( + deps.as_mut(), + SubMsgResult::Ok(SubMsgResponse { + events: vec![], + data: Some(msg), + }), + ) + .unwrap(); + assert_eq!( + response.messages[0].msg, + CosmosMsg::Bank(BankMsg::Send { + to_address: to_address.to_string(), + amount: vec![coin(1000, "uosmo"), coin(1000, "uatom")] + }) + ) + } +} diff --git a/smart-contracts/contracts/lp-strategy/src/admin.rs b/smart-contracts/contracts/lp-strategy/src/admin.rs index eaf1cdde5..40fbd6531 100644 --- a/smart-contracts/contracts/lp-strategy/src/admin.rs +++ b/smart-contracts/contracts/lp-strategy/src/admin.rs @@ -9,7 +9,7 @@ use crate::{ // check if sender is the admin pub fn check_depositor(storage: &mut dyn Storage, sender: &Addr) -> Result { let depositor = DEPOSITOR.load(storage)?; - Ok(&depositor == sender) + Ok(depositor == sender) } pub fn add_lock_admin( diff --git a/smart-contracts/contracts/lp-strategy/src/ibc.rs b/smart-contracts/contracts/lp-strategy/src/ibc.rs index 21e6e57d0..31fd8377f 100644 --- a/smart-contracts/contracts/lp-strategy/src/ibc.rs +++ b/smart-contracts/contracts/lp-strategy/src/ibc.rs @@ -956,7 +956,7 @@ mod tests { ); let msg = IbcChannelOpenMsg::OpenTry { - channel: channel, + channel, counterparty_version: "1".to_string(), }; diff --git a/smart-contracts/contracts/multihop-router/src/route.rs b/smart-contracts/contracts/multihop-router/src/route.rs index 7e1d28bbe..0d463f22b 100644 --- a/smart-contracts/contracts/multihop-router/src/route.rs +++ b/smart-contracts/contracts/multihop-router/src/route.rs @@ -226,22 +226,6 @@ impl KeyDeserialize for RouteId { } } -impl<'a> PrimaryKey<'a> for &RouteId { - type Prefix = Destination; - - type SubPrefix = (); - - type Suffix = String; - - type SuperSuffix = (Destination, String); - - fn key(&self) -> Vec { - let mut keys = self.destination.key(); - keys.extend(self.asset.key()); - keys - } -} - impl KeyDeserialize for &RouteId { type Output = RouteId; @@ -323,17 +307,6 @@ impl KeyDeserialize for Destination { } } -impl<'a> PrimaryKey<'a> for &Destination { - type Prefix = (); - type SubPrefix = (); - type Suffix = Self; - type SuperSuffix = Self; - - fn key(&self) -> Vec { - self.0.key() - } -} - impl KeyDeserialize for &Destination { type Output = Destination; diff --git a/smart-contracts/contracts/qoracle-bindings-test/.cargo/config b/smart-contracts/contracts/qoracle-bindings-test/.cargo/config deleted file mode 100644 index 336b618a1..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/.cargo/config +++ /dev/null @@ -1,4 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib" -schema = "run --example schema" diff --git a/smart-contracts/contracts/qoracle-bindings-test/.gitpod.Dockerfile b/smart-contracts/contracts/qoracle-bindings-test/.gitpod.Dockerfile deleted file mode 100644 index bff8bc536..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/.gitpod.Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -### wasmd ### -FROM cosmwasm/wasmd:v0.18.0 as wasmd - -### rust-optimizer ### -FROM cosmwasm/rust-optimizer:0.11.5 as rust-optimizer - -FROM gitpod/workspace-full:latest - -COPY --from=wasmd /usr/bin/wasmd /usr/local/bin/wasmd -COPY --from=wasmd /opt/* /opt/ - -RUN sudo apt-get update \ - && sudo apt-get install -y jq \ - && sudo rm -rf /var/lib/apt/lists/* - -RUN rustup update stable \ - && rustup target add wasm32-unknown-unknown diff --git a/smart-contracts/contracts/qoracle-bindings-test/.gitpod.yml b/smart-contracts/contracts/qoracle-bindings-test/.gitpod.yml deleted file mode 100644 index d03610c21..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/.gitpod.yml +++ /dev/null @@ -1,10 +0,0 @@ -image: cosmwasm/cw-gitpod-base:v0.16 - -vscode: - extensions: - - rust-lang.rust - -tasks: - - name: Dependencies & Build - init: | - cargo build diff --git a/smart-contracts/contracts/qoracle-bindings-test/Cargo.lock b/smart-contracts/contracts/qoracle-bindings-test/Cargo.lock deleted file mode 100644 index 91fb09ff8..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/Cargo.lock +++ /dev/null @@ -1,738 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "anyhow" -version = "1.0.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" - -[[package]] -name = "base16ct" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "base64ct" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea908e7347a8c64e378c17e30ef880ad73e3b4498346b055c2c00ea342f3179" - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "const-oid" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" - -[[package]] -name = "cosmwasm-crypto" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb0afef2325df81aadbf9be1233f522ed8f6e91df870c764bc44cca2b1415bd" -dependencies = [ - "digest", - "ed25519-zebra", - "k256", - "rand_core 0.6.3", - "thiserror", -] - -[[package]] -name = "cosmwasm-derive" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4" -dependencies = [ - "syn", -] - -[[package]] -name = "cosmwasm-schema" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" -dependencies = [ - "schemars", - "serde_json", -] - -[[package]] -name = "cosmwasm-std" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195" -dependencies = [ - "base64", - "cosmwasm-crypto", - "cosmwasm-derive", - "forward_ref", - "schemars", - "serde", - "serde-json-wasm", - "thiserror", - "uint", -] - -[[package]] -name = "cosmwasm-storage" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d18403b07304d15d304dad11040d45bbcaf78d603b4be3fb5e2685c16f9229b5" -dependencies = [ - "cosmwasm-std", - "serde", -] - -[[package]] -name = "cpufeatures" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" -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.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" -dependencies = [ - "generic-array", - "rand_core 0.6.3", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-mac" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" -dependencies = [ - "generic-array", - "subtle", -] - -[[package]] -name = "curve25519-dalek" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" -dependencies = [ - "byteorder", - "digest", - "rand_core 0.5.1", - "subtle", - "zeroize", -] - -[[package]] -name = "cw-multi-test" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbea57e5be4a682268a5eca1a57efece57a54ff216bfd87603d5e864aad40e12" -dependencies = [ - "anyhow", - "cosmwasm-std", - "cosmwasm-storage", - "cw-storage-plus", - "cw-utils", - "derivative", - "itertools", - "prost", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cw-storage-plus" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9336ecef1e19d56cf6e3e932475fc6a3dee35eec5a386e07917a1d1ba6bb0e35" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - -[[package]] -name = "cw-utils" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "babd2c090f39d07ce5bf2556962305e795daa048ce20a93709eb591476e4a29e" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cw2" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993df11574f29574dd443eb0c189484bb91bc0638b6de3e32ab7f9319c92122d" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus", - "schemars", - "serde", -] - -[[package]] -name = "der" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" -dependencies = [ - "const-oid", -] - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - -[[package]] -name = "dyn-clone" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" - -[[package]] -name = "ecdsa" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" -dependencies = [ - "der", - "elliptic-curve", - "rfc6979", - "signature", -] - -[[package]] -name = "ed25519-zebra" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" -dependencies = [ - "curve25519-dalek", - "hex", - "rand_core 0.6.3", - "serde", - "sha2", - "thiserror", - "zeroize", -] - -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "elliptic-curve" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" -dependencies = [ - "base16ct", - "crypto-bigint", - "der", - "ff", - "generic-array", - "group", - "rand_core 0.6.3", - "sec1", - "subtle", - "zeroize", -] - -[[package]] -name = "ff" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" -dependencies = [ - "rand_core 0.6.3", - "subtle", -] - -[[package]] -name = "forward_ref" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" - -[[package]] -name = "generic-array" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.10.2+wasi-snapshot-preview1", -] - -[[package]] -name = "group" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" -dependencies = [ - "ff", - "rand_core 0.6.3", - "subtle", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac", - "digest", -] - -[[package]] -name = "itertools" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" - -[[package]] -name = "k256" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" -dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "sec1", - "sha2", -] - -[[package]] -name = "libc" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "pkcs8" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" -dependencies = [ - "der", - "spki", - "zeroize", -] - -[[package]] -name = "proc-macro2" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" -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", -] - -[[package]] -name = "quote" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom 0.2.6", -] - -[[package]] -name = "rfc6979" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" -dependencies = [ - "crypto-bigint", - "hmac", - "zeroize", -] - -[[package]] -name = "ryu" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" - -[[package]] -name = "schemars" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d21ecb263bf75fc69d5e74b0f2a60b6dd80cfd9fb0eba15c4b9d1104ceffc77" -dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219b924f5b39f25b7d7c9203873a2698fdac8db2b396aaea6fa099b699cc40e9" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn", -] - -[[package]] -name = "sec1" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" -dependencies = [ - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - -[[package]] -name = "serde" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-json-wasm" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_derive_internals" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer", - "cfg-if", - "cpufeatures", - "digest", - "opaque-debug", -] - -[[package]] -name = "signature" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" -dependencies = [ - "digest", - "rand_core 0.6.3", -] - -[[package]] -name = "spki" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - -[[package]] -name = "syn" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "testgen-local" -version = "0.1.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cosmwasm-storage", - "cw-multi-test", - "cw-storage-plus", - "cw2", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "thiserror" -version = "1.0.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "typenum" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "uint" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - -[[package]] -name = "unicode-ident" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" - -[[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.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - -[[package]] -name = "zeroize" -version = "1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94693807d016b2f2d2e14420eb3bfcca689311ff775dcf113d74ea624b7cdf07" diff --git a/smart-contracts/contracts/qoracle-bindings-test/Developing.md b/smart-contracts/contracts/qoracle-bindings-test/Developing.md deleted file mode 100644 index 2dd572cee..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/Developing.md +++ /dev/null @@ -1,104 +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.58.1+). - -## 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.58.1+. - -And you need to have the `wasm32-unknown-unknown` target installed as well. - -You can check that via: - -```sh -rustc --version -cargo --version -rustup target list --installed -# if wasm32 is not listed above, run this -rustup target add wasm32-unknown-unknown -``` - -## Compiling and running tests - -Now that you created your custom contract, make sure you can compile and run it before -making any changes. Go into the repository and do: - -```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 - -# auto-generate json schema -cargo schema -``` - -### 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`. - -We consider testing critical for anything on a blockchain, and recommend to always keep -the tests up to date. - -## Generating JSON Schema - -While the Wasm calls (`instantiate`, `execute`, `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.12.6 -``` - -Or, If you're on an arm64 machine, you should use a docker image built with arm64. -```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-arm64:0.12.6 -``` - -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 an `artifacts` directory with a `PROJECT_NAME.wasm`, as well as -`checksums.txt`, containing the Sha256 hash of the wasm file. -The wasm file is compiled deterministically (anyone else running the same -docker on the same git commit should get the identical file with the same Sha256 hash). -It is also stripped and minimized for upload to a blockchain (we will also -gzip it in the uploading process to make it even smaller). diff --git a/smart-contracts/contracts/qoracle-bindings-test/Importing.md b/smart-contracts/contracts/qoracle-bindings-test/Importing.md deleted file mode 100644 index a572ba05e..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/Importing.md +++ /dev/null @@ -1,62 +0,0 @@ -# Importing - -In [Publishing](quasar-finance/quasar/smart-contracts/contracts/intergamm-bindings-testt/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 execute 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/smart-contracts/contracts/qoracle-bindings-test/Publishing.md b/smart-contracts/contracts/qoracle-bindings-test/Publishing.md deleted file mode 100644 index c94f8675d..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/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 -they 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 = ["artifacts"] -``` - -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/smart-contracts/contracts/qoracle-bindings-test/README.md b/smart-contracts/contracts/qoracle-bindings-test/README.md deleted file mode 100644 index e138bcfcb..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/README.md +++ /dev/null @@ -1,100 +0,0 @@ -# CosmWasm Starter Pack - -This is a template to build smart contracts in Rust to run inside a -[Cosmos SDK](https://github.com/cosmos/cosmos-sdk) module on all chains that enable it. -To understand the framework better, please read the overview in the -[cosmwasm repo](https://github.com/CosmWasm/cosmwasm/blob/master/README.md), -and dig into the [cosmwasm docs](https://www.cosmwasm.com). -This assumes you understand the theory and just want to get coding. - -## Creating a new repo from template - -Assuming you have a recent version of rust and cargo (v1.58.1+) installed -(via [rustup](https://rustup.rs/)), -then the following should get you a new repo to start a contract: - -Install [cargo-generate](https://github.com/ashleygwilliams/cargo-generate) and cargo-run-script. -Unless you did that before, run this line now: - -```sh -cargo install cargo-generate --features vendored-openssl -cargo install cargo-run-script -``` - -Now, use it to create your new contract. -Go to the folder in which you want to place it and run: - - -**Latest: 1.0.0** - -```sh -cargo generate --git https://github.com/CosmWasm/cw-template.git --name PROJECT_NAME -```` - -For cloning minimal code repo: - -```sh -cargo generate --git https://github.com/CosmWasm/cw-template.git --branch 1.0-minimal --name PROJECT_NAME -``` - -**Older Version** - -Pass version as branch flag: - -```sh -cargo generate --git https://github.com/CosmWasm/cw-template.git --branch --name PROJECT_NAME -```` - -Example: - -```sh -cargo generate --git https://github.com/CosmWasm/cw-template.git --branch 0.16 --name PROJECT_NAME -``` - -You will now have a new folder called `PROJECT_NAME` (I hope you changed that to something else) -containing a simple working contract and build system that you can customize. - -## Create a Repo - -After generating, you have a initialized local git repo, but no commits, and no remote. -Go to a server (eg. github) and create a new upstream repo (called `YOUR-GIT-URL` below). -Then run the following: - -```sh -# this is needed to create a valid Cargo.lock file (see below) -cargo check -git branch -M main -git add . -git commit -m 'Initial Commit' -git remote add origin YOUR-GIT-URL -git push -u origin main -``` - -## CI Support - -We have template configurations for both [GitHub Actions](quasar-finance/quasar/smart-contracts/contracts/intergamm-bindings-testt/.github/workflows/Basic.yml) -and [Circle CI](quasar-finance/quasar/smart-contracts/contracts/intergamm-bindings-testt/.circleci/config.yml) in the generated project, so you can -get up and running with CI right away. - -One note is that the CI runs all `cargo` commands -with `--locked` to ensure it uses the exact same versions as you have locally. This also means -you must have an up-to-date `Cargo.lock` file, which is not auto-generated. -The first time you set up the project (or after adding any dep), you should ensure the -`Cargo.lock` file is updated, so the CI will test properly. This can be done simply by -running `cargo check` or `cargo unit-test`. - -## Using your project - -Once you have your custom repo, you should check out [Developing](quasar-finance/quasar/smart-contracts/contracts/intergamm-bindings-testt/Developing.md) to explain -more on how to run tests and develop code. Or go through the -[online tutorial](https://docs.cosmwasm.com/) to get a better feel -of how to develop. - -[Publishing](quasar-finance/quasar/smart-contracts/contracts/intergamm-bindings-testt/Publishing.md) contains useful information on how to publish your contract -to the world, once you are ready to deploy it on a running blockchain. And -[Importing](quasar-finance/quasar/smart-contracts/contracts/intergamm-bindings-testt/Importing.md) contains information about pulling in other contracts or crates -that have been published. - -Please replace this README file with information about your specific project. You can keep -the `Developing.md` and `Publishing.md` files as useful referenced, but please set some -proper description in the README. diff --git a/smart-contracts/contracts/qoracle-bindings-test/examples/schema.rs b/smart-contracts/contracts/qoracle-bindings-test/examples/schema.rs deleted file mode 100644 index b22a64caa..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/examples/schema.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::env::current_dir; -use std::fs::create_dir_all; - -use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; - -use qoracle_bindings_test::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use qoracle_bindings_test::state::State; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InstantiateMsg), &out_dir); - export_schema(&schema_for!(ExecuteMsg), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(State), &out_dir); -} diff --git a/smart-contracts/contracts/qoracle-bindings-test/rustfmt.toml b/smart-contracts/contracts/qoracle-bindings-test/rustfmt.toml deleted file mode 100644 index 11a85e6a9..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/rustfmt.toml +++ /dev/null @@ -1,15 +0,0 @@ -# stable -newline_style = "unix" -hard_tabs = false -tab_spaces = 4 - -# unstable... should we require `rustup run nightly cargo fmt` ? -# or just update the style guide when they are stable? -#fn_single_line = true -#format_code_in_doc_comments = true -#overflow_delimited_expr = true -#reorder_impl_items = true -#struct_field_align_threshold = 20 -#struct_lit_single_line = true -#report_todo = "Always" - diff --git a/smart-contracts/contracts/qoracle-bindings-test/schema/execute_msg.json b/smart-contracts/contracts/qoracle-bindings-test/schema/execute_msg.json deleted file mode 100644 index 6fa0a75db..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/schema/execute_msg.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "increment" - ], - "properties": { - "increment": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "reset" - ], - "properties": { - "reset": { - "type": "object", - "required": [ - "count" - ], - "properties": { - "count": { - "type": "integer", - "format": "int32" - } - } - } - }, - "additionalProperties": false - } - ] -} diff --git a/smart-contracts/contracts/qoracle-bindings-test/schema/get_count_response.json b/smart-contracts/contracts/qoracle-bindings-test/schema/get_count_response.json deleted file mode 100644 index 02619f0a2..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/schema/get_count_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "GetCountResponse", - "type": "object", - "required": [ - "count" - ], - "properties": { - "count": { - "type": "integer", - "format": "int32" - } - } -} diff --git a/smart-contracts/contracts/qoracle-bindings-test/schema/instantiate_msg.json b/smart-contracts/contracts/qoracle-bindings-test/schema/instantiate_msg.json deleted file mode 100644 index e794ec1d8..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/schema/instantiate_msg.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "count" - ], - "properties": { - "count": { - "type": "integer", - "format": "int32" - } - } -} diff --git a/smart-contracts/contracts/qoracle-bindings-test/schema/query_msg.json b/smart-contracts/contracts/qoracle-bindings-test/schema/query_msg.json deleted file mode 100644 index be0245bf8..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/schema/query_msg.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "get_count" - ], - "properties": { - "get_count": { - "type": "object" - } - }, - "additionalProperties": false - } - ] -} diff --git a/smart-contracts/contracts/qoracle-bindings-test/schema/state.json b/smart-contracts/contracts/qoracle-bindings-test/schema/state.json deleted file mode 100644 index e18725d1a..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/schema/state.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "State", - "type": "object", - "required": [ - "count", - "owner" - ], - "properties": { - "count": { - "type": "integer", - "format": "int32" - }, - "owner": { - "$ref": "#/definitions/Addr" - } - }, - "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" - } - } -} diff --git a/smart-contracts/contracts/qoracle-bindings-test/src/contract.rs b/smart-contracts/contracts/qoracle-bindings-test/src/contract.rs deleted file mode 100644 index df59b2b0a..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/src/contract.rs +++ /dev/null @@ -1,47 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; - -use cw2::set_contract_version; -use quasar_bindings::query::QuasarQuery; - -use crate::error::ContractError; -use crate::execute::{demo_fetch_oracle_prices, demo_fetch_pool_info, demo_fetch_pools}; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; - -// version info for migration info -const CONTRACT_NAME: &str = "crates.io:intergamm-bindings-test-2"; -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)?; - Ok(Response::default()) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::DemoOsmosisPools {} => demo_fetch_pools(deps), - ExecuteMsg::DemoOsmosisPoolInfo {} => demo_fetch_pool_info(deps), - ExecuteMsg::DemoOraclePrices {} => demo_fetch_oracle_prices(deps), - } -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg {} -} - -#[cfg(test)] -mod tests {} diff --git a/smart-contracts/contracts/qoracle-bindings-test/src/error.rs b/smart-contracts/contracts/qoracle-bindings-test/src/error.rs deleted file mode 100644 index 3caf0c5c5..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/src/error.rs +++ /dev/null @@ -1,16 +0,0 @@ -use cosmwasm_std::StdError; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("Custom Error val: {val:?}")] - CustomError { val: String }, - // Add any other custom errors you like here. - // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. -} diff --git a/smart-contracts/contracts/qoracle-bindings-test/src/execute.rs b/smart-contracts/contracts/qoracle-bindings-test/src/execute.rs deleted file mode 100644 index dfc6905a7..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/src/execute.rs +++ /dev/null @@ -1,58 +0,0 @@ -use cosmwasm_std::{DepsMut, Response}; - -use quasar_bindings::querier::QuasarQuerier; -use quasar_bindings::query::QuasarQuery; - -use crate::error::ContractError; - -pub fn demo_fetch_pools(deps: DepsMut) -> Result { - let querier = QuasarQuerier::new(&deps.querier); - - let pools_response = querier.osmosis_pools(Option::None)?; - - let pools = pools_response.pools.unwrap_or_default(); - - let first_pool_id = match pools.first() { - Some(pool) => pool.pool_info.id.to_string(), - None => "No pools found".to_string(), - }; - - Ok(Response::new() - .add_attribute("num_pools", pools.len().to_string()) - .add_attribute("first_pool_id", first_pool_id)) -} - -pub fn demo_fetch_pool_info(deps: DepsMut) -> Result { - let querier = QuasarQuerier::new(&deps.querier); - - let pool = match querier.osmosis_pool("1".to_string())?.pool { - Some(pool_info) => pool_info, - None => { - return Err(ContractError::CustomError { - val: "No pool info".into(), - }) - } - }; - - Ok(Response::new().add_attribute("pool_info_id", pool.pool_info.id.to_string())) -} - -pub fn demo_fetch_oracle_prices(deps: DepsMut) -> Result { - let querier = QuasarQuerier::new(&deps.querier); - - let oracle_prices = querier.oracle_prices()?; - - Ok(Response::new() - .add_attribute( - "oracle_prices_length", - oracle_prices.prices.len().to_string(), - ) - .add_attribute( - "updated_at_height", - oracle_prices.updated_at_height.to_string(), - ) - .add_attribute( - "oracle_prices", - oracle_prices.prices.first().unwrap().denom.clone(), - )) -} diff --git a/smart-contracts/contracts/qoracle-bindings-test/src/ibc.rs b/smart-contracts/contracts/qoracle-bindings-test/src/ibc.rs deleted file mode 100644 index fcd94cb04..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/src/ibc.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::state::ACKS; - -fn do_intergamm_msg(msg: IntergammMsg, deps: DepsMut) -> Result, ContractError> { - - todo!() -} \ No newline at end of file diff --git a/smart-contracts/contracts/qoracle-bindings-test/src/integration_tests.rs b/smart-contracts/contracts/qoracle-bindings-test/src/integration_tests.rs deleted file mode 100644 index 68d1a0523..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/src/integration_tests.rs +++ /dev/null @@ -1,70 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::msg::InstantiateMsg; - use cosmwasm_std::{Addr, Coin, Empty, Uint128}; - use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper, Executor}; - - pub fn contract_template() -> Box> { - let contract = ContractWrapper::new( - crate::contract::execute, - crate::contract::instantiate, - crate::contract::query, - ); - Box::new(contract) - } - - const USER: &str = "USER"; - const ADMIN: &str = "ADMIN"; - const NATIVE_DENOM: &str = "denom"; - - fn mock_app() -> App { - AppBuilder::new().build(|router, _, storage| { - router - .bank - .init_balance( - storage, - &Addr::unchecked(USER), - vec![Coin { - denom: NATIVE_DENOM.to_string(), - amount: Uint128::new(1), - }], - ) - .unwrap(); - }) - } - - fn proper_instantiate() -> (App, CwTemplateContract) { - let mut app = mock_app(); - let cw_template_id = app.store_code(contract_template()); - - let msg = InstantiateMsg {}; - let cw_template_contract_addr = app - .instantiate_contract( - cw_template_id, - Addr::unchecked(ADMIN), - &msg, - &[], - "test", - None, - ) - .unwrap(); - - let cw_template_contract = CwTemplateContract(cw_template_contract_addr); - - (app, cw_template_contract) - } - - mod count { - use super::*; - use crate::msg::ExecuteMsg; - - #[test] - fn count() { - let (mut app, cw_template_contract) = proper_instantiate(); - - let msg = ExecuteMsg::Increment {}; - let cosmos_msg = cw_template_contract.call(msg).unwrap(); - app.execute(Addr::unchecked(USER), cosmos_msg).unwrap(); - } - } -} diff --git a/smart-contracts/contracts/qoracle-bindings-test/src/lib.rs b/smart-contracts/contracts/qoracle-bindings-test/src/lib.rs deleted file mode 100644 index f3b050e6d..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod contract; -mod error; -pub mod execute; -pub mod msg; -pub mod state; - -pub use crate::error::ContractError; diff --git a/smart-contracts/contracts/qoracle-bindings-test/src/msg.rs b/smart-contracts/contracts/qoracle-bindings-test/src/msg.rs deleted file mode 100644 index caf8bfc3d..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/src/msg.rs +++ /dev/null @@ -1,17 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct InstantiateMsg {} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - DemoOsmosisPools {}, - DemoOsmosisPoolInfo {}, - DemoOraclePrices {}, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg {} diff --git a/smart-contracts/contracts/qoracle-bindings-test/src/state.rs b/smart-contracts/contracts/qoracle-bindings-test/src/state.rs deleted file mode 100644 index 0d23b7517..000000000 --- a/smart-contracts/contracts/qoracle-bindings-test/src/state.rs +++ /dev/null @@ -1,14 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::Addr; -use cw_storage_plus::Item; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct State { - pub count: i32, - pub owner: Addr, -} - -pub const ACKTRIGGERED: Item = Item::new("ack_triggered"); -pub const STATE: Item = Item::new("state"); diff --git a/smart-contracts/contracts/qoracle-bindings-test/test.sh b/smart-contracts/contracts/qoracle-bindings-test/test.sh deleted file mode 100644 index e69de29bb..000000000 diff --git a/smart-contracts/contracts/strategy/.cargo/config b/smart-contracts/contracts/strategy/.cargo/config deleted file mode 100644 index 336b618a1..000000000 --- a/smart-contracts/contracts/strategy/.cargo/config +++ /dev/null @@ -1,4 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib" -schema = "run --example schema" diff --git a/smart-contracts/contracts/strategy/Cargo.toml b/smart-contracts/contracts/strategy/Cargo.toml deleted file mode 100644 index dbcdc2aca..000000000 --- a/smart-contracts/contracts/strategy/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "strategy" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[features] -library = [] - - -[dependencies] -uuid = { version = "1.1.2", default-features = false, features = ["v4","js"]} -cw-utils = "0.13.4" -cw2 = "0.13.4" -cw20 = "0.13.4" -cw20-base = { version = "0.13.4", features = ["library"]} -cw-storage-plus = "0.13.4" -cosmwasm-std = { version = "1.0.0", features = ["abort"]} -schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -serde_json = { version = "1.0.81" , default-features = false, features = ["alloc"]} -thiserror = { version = "1.0.23" } -quasar-traits = { path = "../../packages/quasar-traits" } -quasar-types = { path = "../../packages/quasar-types"} diff --git a/smart-contracts/contracts/strategy/README.md b/smart-contracts/contracts/strategy/README.md deleted file mode 100644 index 8ca721494..000000000 --- a/smart-contracts/contracts/strategy/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Quasar Strategy -A Quasar strategy is made to work in conjunction with the Quasar vault contract. Theoratically, the strategy contract can be deployed independent of the vault, however it's functionality will be limited. - -## What does a Strategy do? -A strategy accepts funds, places them in some location where yield is earned and keeps tracks of the location of the funds. - -## What does a Strategy not do? -A strategy does not do any bookkeeping or accounting on deposits from specific users, It also does not concern itself with the division of profit. -For this functionality, the vault contract should be used. \ No newline at end of file diff --git a/smart-contracts/contracts/strategy/src/contract.rs b/smart-contracts/contracts/strategy/src/contract.rs deleted file mode 100644 index 8a3b61465..000000000 --- a/smart-contracts/contracts/strategy/src/contract.rs +++ /dev/null @@ -1,195 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - coins, BankMsg, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, -}; -use cw2::set_contract_version; - -use crate::error::ContractError; -use crate::error::ContractError::PaymentError; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::queue::{dequeue, enqueue}; -use crate::state::{WithdrawRequest, OUTSTANDING_FUNDS}; - -// version info for migration info -const CONTRACT_NAME: &str = "crates.io:cw-4626"; -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)?; - // check valid token info - msg.validate()?; - - // TODO fill in the instantiation - Ok(Response::default()) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - // TODO decide if we want to do something with deposit - ExecuteMsg::Deposit { .. } => execute_deposit(deps, env, info), - ExecuteMsg::WithdrawRequest { .. } => { - todo!() - } - } -} - -pub fn execute_deposit( - _deps: DepsMut, - _env: Env, - info: MessageInfo, -) -> Result { - // do things here with the funds. This is where the actual strategy starts placing funds on a new deposit - // in the template version of the contract, all we do is check that funds are present - if info.funds.is_empty() { - return Err(PaymentError(cw_utils::PaymentError::NoFunds {})); - } - // if funds are sent to an outside contract, OUTSTANDING funds should be updated here - // TODO add some more sensible attributes here - Ok(Response::new().add_attribute("deposit", info.sender)) -} - -pub fn execute_withdraw_request( - mut deps: DepsMut, - env: Env, - _info: MessageInfo, - owner: String, - denom: String, - amount: Uint128, -) -> Result { - enqueue( - deps.branch(), - WithdrawRequest { - denom, - amount, - owner, - }, - )?; - let res = try_withdraw(deps, env)?; - Ok(res) -} - -fn try_withdraw(mut deps: DepsMut, env: Env) -> Result { - let withdraw = dequeue(deps.branch()); - if withdraw.is_none() { - return Err(ContractError::QueueError( - "dequeue was none while queue should be some".to_string(), - )); - } - let w = withdraw.unwrap(); - // check the free balance of the contract - let free_balance = deps - .querier - .query_balance(env.contract.address, w.denom.clone()) - .map_err(ContractError::Std)?; - // if the contract has enough free balance, execute the withdraw - if w.amount <= free_balance.amount { - // remove the peeked withdraw request - do_withdraw(w) - } else { - // else we start to unlock funds and return a response - // TODO determine whether we need to dequeue the withdraw at this point or a later point - unlock_funds(deps, w) - } -} - -// do_withdraw sends funds from the contract to the owner of the funds according to the withdraw request -fn do_withdraw(withdraw: WithdrawRequest) -> Result { - let msg = BankMsg::Send { - to_address: withdraw.owner.clone(), - amount: coins(withdraw.amount.u128(), withdraw.denom.clone()), - }; - Ok(Response::new() - .add_message(msg) - .add_attribute("withdraw", "executed") - .add_attribute("amount", withdraw.amount) - .add_attribute("denom", withdraw.denom) - .add_attribute("owner", withdraw.owner)) -} - -fn unlock_funds(deps: DepsMut, withdraw: WithdrawRequest) -> Result { - // TODO this is where funds are locked or not present within the strategy contract. The withdraw happens 'async' here - // the strategy needs to know where the funds are located, unlock the funds there(or do so) - let outstanding = OUTSTANDING_FUNDS.load(deps.storage)?; - if withdraw.amount > outstanding { - return Err(ContractError::InsufficientOutStandingFunds); - } - todo!() -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(_deps: Deps, _env: Env, _msg: QueryMsg) -> StdResult { - todo!() -} - -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cosmwasm_std::SubMsg; - - const DENOM: &str = "satoshi"; - const CREATOR: &str = "creator"; - const INVESTOR: &str = "investor"; - const BUYER: &str = "buyer"; - - fn default_instantiate( - supply_decimals: u8, - reserve_decimals: u8, - reserve_supply: Uint128, - ) -> InstantiateMsg { - InstantiateMsg {} - } - - fn setup_test( - deps: DepsMut, - supply_decimals: u8, - reserve_decimals: u8, - reserve_supply: Uint128, - ) { - } - - #[test] - fn deposit_works() { - let mut deps = mock_dependencies(); - let info = mock_info("alice", &coins(100_000, "uqsar")); - execute_deposit(deps.as_mut(), mock_env(), info).unwrap(); - } - - #[test] - fn withdraw_with_sufficient_funds_works() { - let mut deps = mock_dependencies(); - let env = mock_env(); - deps.querier - .update_balance(env.clone().contract.address, coins(100_000, "uqsar")); - let res = execute_withdraw_request( - deps.as_mut(), - env, - mock_info("alice", &[]), - "alice".into(), - "uqsar".to_string(), - Uint128::new(100_000), - ) - .unwrap(); - assert_eq!(res.messages.len(), 1); - assert_eq!( - res.messages[0], - SubMsg::new(BankMsg::Send { - to_address: "alice".to_string(), - amount: coins(100_000, "uqsar") - }) - ) - } -} diff --git a/smart-contracts/contracts/strategy/src/error.rs b/smart-contracts/contracts/strategy/src/error.rs deleted file mode 100644 index b2345994d..000000000 --- a/smart-contracts/contracts/strategy/src/error.rs +++ /dev/null @@ -1,20 +0,0 @@ -use cosmwasm_std::StdError; -use thiserror::Error; - -#[derive(Error, Debug, PartialEq)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("{0}")] - Base(#[from] cw20_base::ContractError), - - #[error("{0}")] - PaymentError(#[from] cw_utils::PaymentError), - - #[error("{0}")] - QueueError(String), - - #[error("not enough funds in the strategy to withdraw")] - InsufficientOutStandingFunds, -} diff --git a/smart-contracts/contracts/strategy/src/helpers.rs b/smart-contracts/contracts/strategy/src/helpers.rs deleted file mode 100644 index 25c2ad6e1..000000000 --- a/smart-contracts/contracts/strategy/src/helpers.rs +++ /dev/null @@ -1,9 +0,0 @@ -use cosmwasm_std::{Addr, Deps, Order, Uint128}; -use quasar_traits::traits::Curve; -use crate::state::{VAULT_INFO, VAULT_CURVE}; - - -#[cfg(test)] -mod tests { - // TODO write some tests for helpers -} \ No newline at end of file diff --git a/smart-contracts/contracts/strategy/src/ibc.rs b/smart-contracts/contracts/strategy/src/ibc.rs deleted file mode 100644 index 134d6ad70..000000000 --- a/smart-contracts/contracts/strategy/src/ibc.rs +++ /dev/null @@ -1,341 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use crate::error::ContractError; - -use cosmwasm_std::{ - attr, entry_point, from_binary, to_binary, BankMsg, Binary, CosmosMsg, DepsMut, Env, - IbcAcknowledgement, IbcBasicResponse, IbcChannel, IbcEndpoint, IbcOrder, IbcPacket, - IbcReceiveResponse, StdResult, Uint128, WasmMsg, -}; - -pub const STRATEGY_VERSION:&str = "strategy-1"; -pub const ICS20_ORDERING: IbcOrder = IbcOrder::Unordered; - -/// The funds in a StrategyPacket are mean for the contract receiving the packet and should be used -/// from there according to the -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -#[serde(rename_all = "snake_case")] -pub struct StrategyPacket { - /// amount of tokens to transfer is encoded as a string, but limited to u64 max - pub amount: Uint128, - /// the token denomination to be transferred - pub denom: String, - /// the sender address - pub sender: String, - // extend these packets with any additional information needed in your strategy -} - -impl StrategyPacket { - pub fn new>(amount: Uint128, denom: T, sender: &str, receiver: &str) -> Self { - Ics20Packet { - denom: denom.into(), - amount, - sender: sender.to_string(), - receiver: receiver.to_string(), - } - } - - pub fn validate(&self) -> Result<(), ContractError> { - if self.amount.u128() > (u64::MAX as u128) { - Err(ContractError::AmountOverflow {}) - } else { - Ok(()) - } - } -} - - -/// This is a generic ICS acknowledgement format. -/// Proto defined here: https://github.com/cosmos/cosmos-sdk/blob/v0.42.0/proto/ibc/core/channel/v1/channel.proto#L141-L147 -/// This is compatible with the JSON serialization -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -#[serde(rename_all = "snake_case")] -pub enum StrategyAck { - Result(Binary), - Error(String), -} - -// TODO we probably want to ACK with the owners address, at least on withdrawals, so that if a withdraw fails, the burned tokens -// on the host chain are returned to the user correctly -// create a serialized success message -fn ack_success() -> Binary { - let res = Ics20Ack::Result(b"1".into()); - to_binary(&res).unwrap() -} - -// create a serialized error message -fn ack_fail(err: String) -> Binary { - let res = Ics20Ack::Error(err); - to_binary(&res).unwrap() -} - -#[cfg_attr(not(feature = "library"), entry_point)] -/// enforces ordering and versioning constraints -pub fn ibc_channel_open( - _deps: DepsMut, - _env: Env, - channel: IbcChannel, -) -> Result<(), ContractError> { - enforce_order_and_version(&channel)?; - Ok(()) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -/// record the channel in CHANNEL_INFO -pub fn ibc_channel_connect( - deps: DepsMut, - _env: Env, - channel: IbcChannel, -) -> Result { - // we need to check the counter party version in try and ack (sometimes here) - enforce_order_and_version(&channel)?; - - let info = ChannelInfo { - id: channel.endpoint.channel_id, - counterparty_endpoint: channel.counterparty_endpoint, - connection_id: channel.connection_id, - }; - CHANNEL_INFO.save(deps.storage, &info.id, &info)?; - - Ok(IbcBasicResponse::default()) -} - -fn enforce_order_and_version(channel: &IbcChannel) -> Result<(), ContractError> { - if channel.version != ICS20_VERSION { - return Err(ContractError::InvalidIbcVersion { - version: channel.version.clone(), - }); - } - if let Some(version) = &channel.counterparty_version { - if version != ICS20_VERSION { - return Err(ContractError::InvalidIbcVersion { - version: version.clone(), - }); - } - } - if channel.order != ICS20_ORDERING { - return Err(ContractError::OnlyOrderedChannel {}); - } - Ok(()) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -/// Check to see if we have any balance here -/// We should not return an error if possible, but rather an acknowledgement of failure -pub fn ibc_packet_receive( - deps: DepsMut, - _env: Env, - packet: IbcPacket, -) -> Result { - let res = match do_ibc_packet_receive(deps, &packet) { - Ok(msg) => { - // build attributes first so we don't have to clone msg below - // similar event messages like ibctransfer module - - // This cannot fail as we parse it in do_ibc_packet_receive. Best to pass the data somehow? - let denom = parse_voucher_denom(&msg.denom, &packet.src).unwrap(); - - let attributes = vec![ - attr("action", "receive"), - attr("sender", &msg.sender), - attr("receiver", &msg.receiver), - attr("denom", denom), - attr("amount", msg.amount), - attr("success", "true"), - ]; - let to_send = Amount::from_parts(denom.into(), msg.amount); - let msg = send_amount(to_send, msg.receiver); - IbcReceiveResponse { - acknowledgement: ack_success(), - submessages: vec![], - messages: vec![msg], - attributes, - } - } - Err(err) => IbcReceiveResponse { - acknowledgement: ack_fail(err.to_string()), - submessages: vec![], - messages: vec![], - attributes: vec![ - attr("action", "receive"), - attr("success", "false"), - attr("error", err), - ], - }, - }; - - // if we have funds, now send the tokens to the requested recipient - Ok(res) -} - -// Returns local denom if the denom is an encoded voucher from the expected endpoint -// Otherwise, error -fn parse_voucher_denom<'a>( - voucher_denom: &'a str, - remote_endpoint: &IbcEndpoint, -) -> Result<&'a str, ContractError> { - let split_denom: Vec<&str> = voucher_denom.splitn(3, '/').collect(); - if split_denom.len() != 3 { - return Err(ContractError::NoForeignTokens {}); - } - // a few more sanity checks - if split_denom[0] != remote_endpoint.port_id { - return Err(ContractError::FromOtherPort { - port: split_denom[0].into(), - }); - } - if split_denom[1] != remote_endpoint.channel_id { - return Err(ContractError::FromOtherChannel { - channel: split_denom[1].into(), - }); - } - - Ok(split_denom[2]) -} - -// this does the work of ibc_packet_receive, we wrap it to turn errors into acknowledgements -fn do_ibc_packet_receive(deps: DepsMut, packet: &IbcPacket) -> Result { - let msg: Ics20Packet = from_binary(&packet.data)?; - let channel = packet.dest.channel_id.clone(); - - // If the token originated on the remote chain, it looks like "ucosm". - // If it originated on our chain, it looks like "port/channel/ucosm". - let denom = parse_voucher_denom(&msg.denom, &packet.src)?; - - let amount = msg.amount; - CHANNEL_STATE.update( - deps.storage, - (&channel, denom), - |orig| -> Result<_, ContractError> { - // this will return error if we don't have the funds there to cover the request (or no denom registered) - let mut cur = orig.ok_or(ContractError::PaymentError(cw_utils) )?; - cur.outstanding = cur - .outstanding - .checked_sub(amount) - .or(Err(ContractError::InsufficientFunds {}))?; - Ok(cur) - }, - )?; - Ok(msg) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -/// check if success or failure and update balance, or return funds -pub fn ibc_packet_ack( - deps: DepsMut, - _env: Env, - ack: IbcAcknowledgement, -) -> Result { - // TODO: trap error like in receive? - let msg: Ics20Ack = from_binary(&ack.acknowledgement)?; - match msg { - Ics20Ack::Result(_) => on_packet_success(deps, ack.original_packet), - Ics20Ack::Error(err) => on_packet_failure(deps, ack.original_packet, err), - } -} - -#[cfg_attr(not(feature = "library"), entry_point)] -/// return fund to original sender (same as failure in ibc_packet_ack) -pub fn ibc_packet_timeout( - deps: DepsMut, - _env: Env, - packet: IbcPacket, -) -> Result { - // TODO: trap error like in receive? - on_packet_failure(deps, packet, "timeout".to_string()) -} - -// update the balance stored on this (channel, denom) index -fn on_packet_success(deps: DepsMut, packet: IbcPacket) -> Result { - let msg: Ics20Packet = from_binary(&packet.data)?; - // similar event messages like ibctransfer module - let attributes = vec![ - attr("action", "acknowledge"), - attr("sender", &msg.sender), - attr("receiver", &msg.receiver), - attr("denom", &msg.denom), - attr("amount", msg.amount), - attr("success", "true"), - ]; - - let channel = packet.src.channel_id; - let denom = msg.denom; - let amount = msg.amount; - CHANNEL_STATE.update(deps.storage, (&channel, &denom), |orig| -> StdResult<_> { - let mut state = orig.unwrap_or_default(); - state.outstanding += amount; - state.total_sent += amount; - Ok(state) - })?; - - Ok(IbcBasicResponse { - submessages: vec![], - messages: vec![], - attributes, - }) -} - -// return the tokens to sender -fn on_packet_failure( - _deps: DepsMut, - packet: IbcPacket, - err: String, -) -> Result { - let msg: Ics20Packet = from_binary(&packet.data)?; - // similar event messages like ibctransfer module - let attributes = vec![ - attr("action", "acknowledge"), - attr("sender", &msg.sender), - attr("receiver", &msg.receiver), - attr("denom", &msg.denom), - attr("amount", &msg.amount), - attr("success", "false"), - attr("error", err), - ]; - - let amount = Amount::from_parts(msg.denom, msg.amount); - let msg = send_amount(amount, msg.sender); - let res = IbcBasicResponse { - submessages: vec![], - messages: vec![msg], - attributes, - }; - Ok(res) -} - -#[cfg(test)] -mod test { - use super::*; - use crate::test_helpers::*; - - use crate::contract::query_channel; - use cosmwasm_std::testing::mock_env; - use cosmwasm_std::{coins, to_vec, IbcEndpoint}; - - #[test] - fn check_ack_json() { - let success = StrategyAck::Result(b"1".into()); - let fail = StrategyAck::Error("bad coin".into()); - - let success_json = String::from_utf8(to_vec(&success).unwrap()).unwrap(); - assert_eq!(r#"{"result":"MQ=="}"#, success_json.as_str()); - - let fail_json = String::from_utf8(to_vec(&fail).unwrap()).unwrap(); - assert_eq!(r#"{"error":"bad coin"}"#, fail_json.as_str()); - } - - #[test] - fn check_packet_json() { - let packet = StrategyPacket::new( - Uint128(12345), - "ucosm", - "cosmos1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n", - "wasm1fucynrfkrt684pm8jrt8la5h2csvs5cnldcgqc", - ); - // Example message generated from the SDK - let expected = r#"{"amount":"12345","denom":"ucosm","receiver":"wasm1fucynrfkrt684pm8jrt8la5h2csvs5cnldcgqc","sender":"cosmos1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n"}"#; - - let encdoded = String::from_utf8(to_vec(&packet).unwrap()).unwrap(); - assert_eq!(expected, encdoded.as_str()); - } -} \ No newline at end of file diff --git a/smart-contracts/contracts/strategy/src/lib.rs b/smart-contracts/contracts/strategy/src/lib.rs deleted file mode 100644 index 53102a459..000000000 --- a/smart-contracts/contracts/strategy/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub mod contract; -pub mod error; -mod msg; -mod queue; -mod state; - -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); - } -} diff --git a/smart-contracts/contracts/strategy/src/msg.rs b/smart-contracts/contracts/strategy/src/msg.rs deleted file mode 100644 index 07bd7be2a..000000000 --- a/smart-contracts/contracts/strategy/src/msg.rs +++ /dev/null @@ -1,38 +0,0 @@ -use cosmwasm_std::StdResult; -use cw20::Logo; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)] -pub struct InstantiateMarketingInfo { - pub project: Option, - pub description: Option, - pub marketing: Option, - pub logo: Option, -} - -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq, Eq)] -pub struct InstantiateMsg { - // TODO write the instantiate msg -} - -impl InstantiateMsg { - pub fn validate(&self) -> StdResult<()> { - todo!() - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - // TODO add all wanted queries -} - -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - // We can always deposit money into the strategy - Deposit { owner: String }, - // A request for a withdraw, this has to be a request and cannot be an immediate withdraw since funds might be locked - WithdrawRequest {}, -} diff --git a/smart-contracts/contracts/strategy/src/queue.rs b/smart-contracts/contracts/strategy/src/queue.rs deleted file mode 100644 index b572273d9..000000000 --- a/smart-contracts/contracts/strategy/src/queue.rs +++ /dev/null @@ -1,103 +0,0 @@ -use cosmwasm_std::{DepsMut, Order, OverflowError, OverflowOperation, StdError, StdResult}; -use std::collections::VecDeque; - -use crate::state::{WithdrawRequest, WITHDRAW_QUEUE}; - -pub fn enqueue(deps: DepsMut, value: WithdrawRequest) -> StdResult<()> { - // find the last element in the queue and extract key - let queue: VecDeque<_> = WITHDRAW_QUEUE - .range(deps.storage, None, None, Order::Ascending) - .collect::>() - .unwrap(); - let (last, _) = queue - .back() - .unwrap_or(&(0, WithdrawRequest::default())) - .clone(); - let next = last.checked_add(1); - if next.is_none() { - return Err(StdError::overflow(OverflowError { - operation: OverflowOperation::Add, - operand1: last.to_string(), - operand2: "1".to_string(), - })); - } - WITHDRAW_QUEUE.save(deps.storage, next.unwrap(), &value) -} - -pub fn dequeue(deps: DepsMut) -> Option { - handle_dequeue(deps) -} - -#[allow(clippy::unnecessary_wraps)] -fn handle_dequeue(deps: DepsMut) -> Option { - // find the first element in the queue and extract value - let mut queue: VecDeque<_> = WITHDRAW_QUEUE - .range(deps.storage, None, None, Order::Ascending) - .collect::>() - .unwrap(); - match queue.pop_front() { - None => None, - Some((key, value)) => { - // remove the key from the map - WITHDRAW_QUEUE.remove(deps.storage, key); - // return the underlying value - Some(value) - } - } -} - -// pub fn peek(deps: Deps) -> Option { -// let mut queue: VecDeque<_> = WITHDRAW_QUEUE.range(deps.storage, None, None, Order::Ascending).collect::>().unwrap(); -// // we can use pop front since it doesn't remove the item from the underlying Map -// let front = queue.pop_front(); -// match front{ -// None => {None} -// Some((_key, val)) => { -// return Some(val); -// } -// } -// } - -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::{mock_dependencies, mock_dependencies_with_balance}; - use cosmwasm_std::Storage; - use cosmwasm_std::Uint128; - - #[test] - fn enqueue_dequeue_one_works() { - let mut deps = mock_dependencies(); - let req = WithdrawRequest { - denom: "uqsar".to_string(), - amount: Uint128::new(100_000), - owner: "alice".to_string(), - }; - enqueue(deps.as_mut(), req.clone()).unwrap(); - let mem: Vec<_> = deps.storage.range(None, None, Order::Ascending).collect(); - let res = dequeue(deps.as_mut()).unwrap(); - assert_eq!(req, res) - } - - #[test] - fn enqueue_dequeue_multiple_works() { - let mut deps = mock_dependencies(); - let req1 = WithdrawRequest { - denom: "uqsar".to_string(), - amount: Uint128::new(100_000), - owner: "alice".to_string(), - }; - let req2 = WithdrawRequest { - denom: "uqsar".to_string(), - amount: Uint128::new(100_000), - owner: "bobbyb".to_string(), - }; - enqueue(deps.as_mut(), req1.clone()).unwrap(); - enqueue(deps.as_mut(), req2.clone()).unwrap(); - let mem: Vec<_> = deps.storage.range(None, None, Order::Ascending).collect(); - let res1 = dequeue(deps.as_mut()).unwrap(); - let res2 = dequeue(deps.as_mut()).unwrap(); - assert_eq!(req1, res1); - assert_eq!(req2, res2) - } -} diff --git a/smart-contracts/contracts/strategy/src/state.rs b/smart-contracts/contracts/strategy/src/state.rs deleted file mode 100644 index 1e487653d..000000000 --- a/smart-contracts/contracts/strategy/src/state.rs +++ /dev/null @@ -1,18 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::fmt::Debug; - -use cosmwasm_std::Uint128; -use cw_storage_plus::{Item, Map}; - -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug, Default)] -#[serde(rename_all = "snake_case")] -pub struct WithdrawRequest { - pub denom: String, - pub amount: Uint128, - pub owner: String, -} - -// TODO is u128 too much/ insufficient?, this might cause errors on overlapping keys, could also be handled as a full queue error -pub(crate) const WITHDRAW_QUEUE: Map = Map::new("withdraw_queue"); -pub(crate) const OUTSTANDING_FUNDS: Item = Item::new("outstanding_funds"); diff --git a/smart-contracts/contracts/strategy/src/strategy.rs b/smart-contracts/contracts/strategy/src/strategy.rs deleted file mode 100644 index 78c418223..000000000 --- a/smart-contracts/contracts/strategy/src/strategy.rs +++ /dev/null @@ -1,17 +0,0 @@ -use cosmwasm_std::Coin; - -pub trait strategy { - fn on_deposit(funds: Vec); -} - - - -struct dummy { -} - - -impl strategy for dummy{ - fn on_deposit(funds: Vec) { - - } -} \ No newline at end of file diff --git a/smart-contracts/test/yarn.lock b/smart-contracts/test/yarn.lock new file mode 100644 index 000000000..7885bbed1 --- /dev/null +++ b/smart-contracts/test/yarn.lock @@ -0,0 +1,478 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@confio/ics23@^0.6.8": + version "0.6.8" + resolved "https://registry.yarnpkg.com/@confio/ics23/-/ics23-0.6.8.tgz#2a6b4f1f2b7b20a35d9a0745bb5a446e72930b3d" + integrity sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w== + dependencies: + "@noble/hashes" "^1.0.0" + protobufjs "^6.8.8" + +"@cosmjs/amino@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.30.1.tgz#7c18c14627361ba6c88e3495700ceea1f76baace" + integrity sha512-yNHnzmvAlkETDYIpeCTdVqgvrdt1qgkOXwuRVi8s27UKI5hfqyE9fJ/fuunXE6ZZPnKkjIecDznmuUOMrMvw4w== + dependencies: + "@cosmjs/crypto" "^0.30.1" + "@cosmjs/encoding" "^0.30.1" + "@cosmjs/math" "^0.30.1" + "@cosmjs/utils" "^0.30.1" + +"@cosmjs/cosmwasm-stargate@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.30.1.tgz#6f9ca310f75433a3e30d683bc6aa24eadb345d79" + integrity sha512-W/6SLUCJAJGBN+sJLXouLZikVgmqDd9LCdlMzQaxczcCHTWeJAmRvOiZGSZaSy3shw/JN1qc6g6PKpvTVgj10A== + dependencies: + "@cosmjs/amino" "^0.30.1" + "@cosmjs/crypto" "^0.30.1" + "@cosmjs/encoding" "^0.30.1" + "@cosmjs/math" "^0.30.1" + "@cosmjs/proto-signing" "^0.30.1" + "@cosmjs/stargate" "^0.30.1" + "@cosmjs/tendermint-rpc" "^0.30.1" + "@cosmjs/utils" "^0.30.1" + cosmjs-types "^0.7.1" + long "^4.0.0" + pako "^2.0.2" + +"@cosmjs/crypto@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.30.1.tgz#21e94d5ca8f8ded16eee1389d2639cb5c43c3eb5" + integrity sha512-rAljUlake3MSXs9xAm87mu34GfBLN0h/1uPPV6jEwClWjNkAMotzjC0ab9MARy5FFAvYHL3lWb57bhkbt2GtzQ== + dependencies: + "@cosmjs/encoding" "^0.30.1" + "@cosmjs/math" "^0.30.1" + "@cosmjs/utils" "^0.30.1" + "@noble/hashes" "^1" + bn.js "^5.2.0" + elliptic "^6.5.4" + libsodium-wrappers "^0.7.6" + +"@cosmjs/encoding@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.30.1.tgz#b5c4e0ef7ceb1f2753688eb96400ed70f35c6058" + integrity sha512-rXmrTbgqwihORwJ3xYhIgQFfMSrwLu1s43RIK9I8EBudPx3KmnmyAKzMOVsRDo9edLFNuZ9GIvysUCwQfq3WlQ== + dependencies: + base64-js "^1.3.0" + bech32 "^1.1.4" + readonly-date "^1.0.0" + +"@cosmjs/json-rpc@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.30.1.tgz#16f21305fc167598c8a23a45549b85106b2372bc" + integrity sha512-pitfC/2YN9t+kXZCbNuyrZ6M8abnCC2n62m+JtU9vQUfaEtVsgy+1Fk4TRQ175+pIWSdBMFi2wT8FWVEE4RhxQ== + dependencies: + "@cosmjs/stream" "^0.30.1" + xstream "^11.14.0" + +"@cosmjs/math@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.30.1.tgz#8b816ef4de5d3afa66cb9fdfb5df2357a7845b8a" + integrity sha512-yaoeI23pin9ZiPHIisa6qqLngfnBR/25tSaWpkTm8Cy10MX70UF5oN4+/t1heLaM6SSmRrhk3psRkV4+7mH51Q== + dependencies: + bn.js "^5.2.0" + +"@cosmjs/proto-signing@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.30.1.tgz#f0dda372488df9cd2677150b89b3e9c72b3cb713" + integrity sha512-tXh8pPYXV4aiJVhTKHGyeZekjj+K9s2KKojMB93Gcob2DxUjfKapFYBMJSgfKPuWUPEmyr8Q9km2hplI38ILgQ== + dependencies: + "@cosmjs/amino" "^0.30.1" + "@cosmjs/crypto" "^0.30.1" + "@cosmjs/encoding" "^0.30.1" + "@cosmjs/math" "^0.30.1" + "@cosmjs/utils" "^0.30.1" + cosmjs-types "^0.7.1" + long "^4.0.0" + +"@cosmjs/socket@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.30.1.tgz#00b22f4b5e2ab01f4d82ccdb7b2e59536bfe5ce0" + integrity sha512-r6MpDL+9N+qOS/D5VaxnPaMJ3flwQ36G+vPvYJsXArj93BjgyFB7BwWwXCQDzZ+23cfChPUfhbINOenr8N2Kow== + dependencies: + "@cosmjs/stream" "^0.30.1" + isomorphic-ws "^4.0.1" + ws "^7" + xstream "^11.14.0" + +"@cosmjs/stargate@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.30.1.tgz#e1b22e1226cffc6e93914a410755f1f61057ba04" + integrity sha512-RdbYKZCGOH8gWebO7r6WvNnQMxHrNXInY/gPHPzMjbQF6UatA6fNM2G2tdgS5j5u7FTqlCI10stNXrknaNdzog== + dependencies: + "@confio/ics23" "^0.6.8" + "@cosmjs/amino" "^0.30.1" + "@cosmjs/encoding" "^0.30.1" + "@cosmjs/math" "^0.30.1" + "@cosmjs/proto-signing" "^0.30.1" + "@cosmjs/stream" "^0.30.1" + "@cosmjs/tendermint-rpc" "^0.30.1" + "@cosmjs/utils" "^0.30.1" + cosmjs-types "^0.7.1" + long "^4.0.0" + protobufjs "~6.11.3" + xstream "^11.14.0" + +"@cosmjs/stream@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.30.1.tgz#ba038a2aaf41343696b1e6e759d8e03a9516ec1a" + integrity sha512-Fg0pWz1zXQdoxQZpdHRMGvUH5RqS6tPv+j9Eh7Q953UjMlrwZVo0YFLC8OTf/HKVf10E4i0u6aM8D69Q6cNkgQ== + dependencies: + xstream "^11.14.0" + +"@cosmjs/tendermint-rpc@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.30.1.tgz#c16378892ba1ac63f72803fdf7567eab9d4f0aa0" + integrity sha512-Z3nCwhXSbPZJ++v85zHObeUggrEHVfm1u18ZRwXxFE9ZMl5mXTybnwYhczuYOl7KRskgwlB+rID0WYACxj4wdQ== + dependencies: + "@cosmjs/crypto" "^0.30.1" + "@cosmjs/encoding" "^0.30.1" + "@cosmjs/json-rpc" "^0.30.1" + "@cosmjs/math" "^0.30.1" + "@cosmjs/socket" "^0.30.1" + "@cosmjs/stream" "^0.30.1" + "@cosmjs/utils" "^0.30.1" + axios "^0.21.2" + readonly-date "^1.0.0" + xstream "^11.14.0" + +"@cosmjs/utils@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.30.1.tgz#6d92582341be3c2ec8d82090253cfa4b7f959edb" + integrity sha512-KvvX58MGMWh7xA+N+deCfunkA/ZNDvFLw4YbOmX3f/XBIkqrVY7qlotfy2aNb1kgp6h4B6Yc8YawJPDTfvWX7g== + +"@noble/hashes@^1", "@noble/hashes@^1.0.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" + integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== + +"@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== + +"@types/long@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== + +"@types/node@*", "@types/node@>=13.7.0": + version "20.4.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9" + integrity sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw== + +"@types/prompts@^2.4.3": + version "2.4.4" + resolved "https://registry.yarnpkg.com/@types/prompts/-/prompts-2.4.4.tgz#dd5a1d41cb1bcd0fc4464bf44a0c8354f36ea735" + integrity sha512-p5N9uoTH76lLvSAaYSZtBCdEXzpOOufsRjnhjVSrZGXikVGHX9+cc9ERtHRV4hvBKHyZb1bg4K+56Bd2TqUn4A== + dependencies: + "@types/node" "*" + kleur "^3.0.3" + +axios@^0.21.2: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== + dependencies: + follow-redirects "^1.14.0" + +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@^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== + +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== + +cosmjs-types@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.7.2.tgz#a757371abd340949c5bd5d49c6f8379ae1ffd7e2" + integrity sha512-vf2uLyktjr/XVAgEq0DjMxeAWh1yYREe7AMHDKd7EiHVqxBPCaBS+qEEQUkXbR9ndnckqr1sUG8BQhazh4X5lA== + dependencies: + long "^4.0.0" + protobufjs "~6.11.2" + +define-properties@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" + integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== + dependencies: + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +elliptic@^6.5.4: + 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" + +follow-redirects@^1.14.0: + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + +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== + +get-intrinsic@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-proto "^1.0.1" + has-symbols "^1.0.3" + +globalthis@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +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.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.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== + +isomorphic-ws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" + integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== + +javascript-time-ago@^2.5.9: + version "2.5.9" + resolved "https://registry.yarnpkg.com/javascript-time-ago/-/javascript-time-ago-2.5.9.tgz#3c5d8012cd493d764c6b26a0ffe6e8b20afcf1fe" + integrity sha512-pQ8mNco/9g9TqWXWWjP0EWl6i/lAQScOyEeXy5AB+f7MfLSdgyV9BJhiOD1zrIac/lrxPYOWNbyl/IW8CW5n0A== + dependencies: + relative-time-format "^1.1.6" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +libsodium-wrappers@^0.7.6: + version "0.7.11" + resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.11.tgz#53bd20606dffcc54ea2122133c7da38218f575f7" + integrity sha512-SrcLtXj7BM19vUKtQuyQKiQCRJPgbpauzl3s0rSwD+60wtHqSUuqcoawlMDheCJga85nKOQwxNYQxf/CKAvs6Q== + dependencies: + libsodium "^0.7.11" + +libsodium@^0.7.11: + version "0.7.11" + resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.11.tgz#cd10aae7bcc34a300cc6ad0ac88fcca674cfbc2e" + integrity sha512-WPfJ7sS53I2s4iM58QxY3Inb83/6mjlYgcmZs7DJsvDlnmVUwNinBCi5vBT43P6bHRy01O4zsMU2CoVR6xJ40A== + +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + +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== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +pako@^2.0.2: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== + +prompts@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: + version "6.11.3" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" + integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== + 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/long" "^4.0.1" + "@types/node" ">=13.7.0" + long "^4.0.0" + +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== + +relative-time-format@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/relative-time-format/-/relative-time-format-1.1.6.tgz#724a5fbc3794b8e0471b6b61419af2ce699eb9f1" + integrity sha512-aCv3juQw4hT1/P/OrVltKWLlp15eW1GRcwP1XdxHrPdZE9MtgqFpegjnTjLhi2m2WI9MT/hQQtE+tjEWG1hgkQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +symbol-observable@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-2.0.3.tgz#5b521d3d07a43c351055fa43b8355b62d33fd16a" + integrity sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA== + +time-ago@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/time-ago/-/time-ago-0.2.1.tgz#4abcb7fb8c8cbb1efd9575ab8b30ec699e8e05a9" + integrity sha512-fQ3WQ5yPBoNefBgITR+kMnd5aWiKYhBNSgQH3FwpJgDCaVEmju7rWyP+Rk52KyQbRwQEnw3ox2yxcS4yMxgP+g== + +typescript@^5.0.2: + version "5.1.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" + integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== + +ws@^7: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + +xstream@^11.14.0: + version "11.14.0" + resolved "https://registry.yarnpkg.com/xstream/-/xstream-11.14.0.tgz#2c071d26b18310523b6877e86b4e54df068a9ae5" + integrity sha512-1bLb+kKKtKPbgTK6i/BaoAn03g47PpFstlbe1BA+y3pNS/LfvcaghS5BFf9+EE1J+KwSQsEpfJvFN5GqFtiNmw== + dependencies: + globalthis "^1.0.1" + symbol-observable "^2.0.3"