diff --git a/.github/workflows/check-state-compatibility.yml b/.github/workflows/check-state-compatibility.yml index 38ba5d8464d..64fbe207c11 100644 --- a/.github/workflows/check-state-compatibility.yml +++ b/.github/workflows/check-state-compatibility.yml @@ -1,17 +1,17 @@ -# This workflow checks that a specific commit / branch / tag is state compatible +# This workflow checks that a specific commit / branch / tag is state compatible # with the latest osmosis version by: # - building the new `osmosisd` binary with the latest changes # - replaying a configurable number of previous blocks from chain history -# Currently, the node starts from a snapshot taken some blocks before the last epoch +# Currently, the node starts from a snapshot taken some blocks before the last epoch # and waits `DELTA_HALT_HEIGHT` blocks after epoch before finally halting. # Important Caveat: -# The fact that this workflow succeeds and the binary doesn't fail doesn't +# The fact that this workflow succeeds and the binary doesn't fail doesn't # directly imply that the new binary is state-compatible. -# It could be that the binary is not state-compatible, but the condition +# It could be that the binary is not state-compatible, but the condition # which would break state compatibility was not present in the chunk of block history used. # On the other hand, if the workflow fails, the binary is not state-compatible. @@ -19,19 +19,19 @@ name: Check state compatibility # ************************************ NOTE ************************************ -# +# # DO NOT TRIGGER THIS WORKFLOW ON PUBLIC FORKS # -# This workflow runs on a self-hosted runner and forks to this repository -# can potentially run dangerous code on the self-hosted runner machine +# This workflow runs on a self-hosted runner and forks to this repository +# can potentially run dangerous code on the self-hosted runner machine # by creating a pull request that executes the code in a workflow. -# +# # ****************************************************************************** on: pull_request: branches: - - 'v[0-9]+.x' + - "v[0-9]+.x" env: GOLANG_VERSION: 1.18 @@ -43,29 +43,26 @@ env: DELTA_HALT_HEIGHT: 50 jobs: - compare_versions: - # Compare current mainnet osmosis major version with the major version of github branch - # Skip the next job if the current github major is greater than the current mainnet major + # Compare current mainnet osmosis major version with the major version of github branch + # Skip the next job if the current github major is greater than the current mainnet major runs-on: self-hosted outputs: should_i_run: ${{ steps.compare_versions.outputs.should_i_run }} mainnet_major_version: ${{ steps.mainnet_version.outputs.mainnet_major_version }} steps: - - - name: Get mainnet major version + - name: Get mainnet major version id: mainnet_version - run: | + run: | # Find current major version via rpc.osmosis.zone/abci_info RPC_ABCI_INFO=$(curl -s --retry 5 --retry-delay 5 --connect-timeout 30 -H "Accept: application/json" ${{ env.RPC_ENDPOINT }}/abci_info) MAINNET_MAJOR_VERSION=$(echo $RPC_ABCI_INFO | dasel --plain -r json 'result.response.version' | cut -f 1 -d '.') echo "MAINNET_MAJOR_VERSION=$MAINNET_MAJOR_VERSION" >> $GITHUB_ENV echo "mainnet_major_version=$MAINNET_MAJOR_VERSION" >> $GITHUB_OUTPUT - - - name: Get GitHub branch major version + - name: Get GitHub branch major version id: compare_versions - run: | + run: | CURRENT_BRANCH_MAJOR_VERSION=$(echo ${{ github.event.pull_request.base.ref }} | tr -dc '0-9') SHOULD_I_RUN=$(( $CURRENT_BRANCH_MAJOR_VERSION <= $MAINNET_MAJOR_VERSION )) @@ -85,24 +82,20 @@ jobs: needs: compare_versions runs-on: self-hosted steps: - - - name: Checkout branch + - name: Checkout branch uses: actions/checkout@v3 with: fetch-depth: 0 - - - name: 🐿 Setup Golang + - name: 🐿 Setup Golang uses: actions/setup-go@v3 with: - go-version: '^${{ env.GOLANG_VERSION }}' - - - name: 🔨 Build the osmosisd binary + go-version: "^${{ env.GOLANG_VERSION }}" + - name: 🔨 Build the osmosisd binary run: | make build build/osmosisd version - - - name: 🧪 Initialize Osmosis Node - run: | + - name: 🧪 Initialize Osmosis Node + run: | rm -rf $HOME/.osmosisd/ || true build/osmosisd init runner -o @@ -114,10 +107,9 @@ jobs: # Copy genesis to config folder cp /mnt/data/genesis/osmosis-1/genesis.json $HOME/.osmosisd/config/genesis.json - - - name: ⏬ Download last pre-epoch snapshot - run: | - REPO_MAJOR_VERSION=$(echo $(git describe --tags) | cut -f 1 -d '.') # with v prefix + - name: ⏬ Download last pre-epoch snapshot + run: | + REPO_MAJOR_VERSION=v14 SNAPSHOT_INFO_URL=${{ env.SNAPSHOT_BUCKET }}/$REPO_MAJOR_VERSION/snapshots.json # Get the latest pre-epoch snapshot information from bucket @@ -135,13 +127,12 @@ jobs: # Copy snapshot in Data folder cp -R /mnt/data/snapshots/$REPO_MAJOR_VERSION/$SNAPSHOT_ID/* $HOME/.osmosisd/ - - - name: 🧪 Configure Osmosis Node - run: | + - name: 🧪 Configure Osmosis Node + run: | CONFIG_FOLDER=$HOME/.osmosisd/config - + # Find last epoch block comparing repo version to current chain version - REPO_MAJOR_VERSION=$(echo $(git describe --tags) | sed 's/^v//' | cut -f 1 -d '.') # without v prefix + REPO_MAJOR_VERSION=14 if [ $REPO_MAJOR_VERSION == $MAINNET_MAJOR_VERSION ]; then # I'm in the latest major, fetch the epoch info from the lcd endpoint @@ -174,10 +165,8 @@ jobs: # Download addrbook wget -q -O $CONFIG_FOLDER/addrbook.json ${{ env.ADDRBOOK_URL }} - - - name: 🧪 Start Osmosis Node + - name: 🧪 Start Osmosis Node run: build/osmosisd start - - - name: 🧹 Clean up Osmosis Home + - name: 🧹 Clean up Osmosis Home if: always() run: rm -rf $HOME/.osmosisd/ || true diff --git a/.github/workflows/push-dev-docker-images.yml b/.github/workflows/push-dev-docker-images.yml index 365d1cef8ed..b7a26245881 100644 --- a/.github/workflows/push-dev-docker-images.yml +++ b/.github/workflows/push-dev-docker-images.yml @@ -79,4 +79,15 @@ jobs: GIT_COMMIT=${{ github.sha }} tags: | ${{ env.OSMOSIS_DEV_IMAGE_REPOSITORY }}:${{ env.DOCKER_IMAGE_TAG }} + - + name: Build and Push E2E Init Docker Images + uses: docker/build-push-action@v3 + with: + file: tests/e2e/initialization/init.Dockerfile + context: . + push: true + platforms: linux/amd64,linux/arm64 + build-args: | + E2E_SCRIPT_NAME=chain + tags: | ${{ env.OSMOSIS_INIT_CHAIN_IMAGE_REPOSITORY }}:${{ env.DOCKER_IMAGE_TAG }} diff --git a/.github/workflows/push-docker-images.yml b/.github/workflows/push-docker-images.yml index 86867f3caa8..f365154184b 100644 --- a/.github/workflows/push-docker-images.yml +++ b/.github/workflows/push-docker-images.yml @@ -136,42 +136,9 @@ jobs: ${{ env.DOCKER_REPOSITORY }}:${{ env.MAJOR_VERSION }}-alpine ${{ env.DOCKER_REPOSITORY }}:${{ env.MAJOR_VERSION }}.${{ env.MINOR_VERSION }}-alpine ${{ env.DOCKER_REPOSITORY }}:${{ env.MAJOR_VERSION }}.${{ env.MINOR_VERSION }}.${{ env.PATCH_VERSION }}-alpine - e2e-init-chain-images: - if: startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '.0') - runs-on: ubuntu-latest - steps: - - - name: Check out the repo - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to DockerHub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Parse tag - id: tag - run: | - VERSION=$(echo ${{ github.ref_name }} | sed "s/v//") - MAJOR_VERSION=$(echo $VERSION | cut -d '.' -f 1) - MINOR_VERSION=$(echo $VERSION | cut -d '.' -f 2) - PATCH_VERSION=$(echo $VERSION | cut -d '.' -f 3) - echo "VERSION=$VERSION" >> $GITHUB_ENV - echo "MAJOR_VERSION=$MAJOR_VERSION" >> $GITHUB_ENV - echo "MINOR_VERSION=$MINOR_VERSION" >> $GITHUB_ENV - echo "PATCH_VERSION=$PATCH_VERSION" >> $GITHUB_ENV - - name: Build and push - id: build_push_e2e_init_image + if: startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '.0') + name: Build and push (e2e-chain-init) uses: docker/build-push-action@v3 with: file: tests/e2e/initialization/init.Dockerfile diff --git a/CHANGELOG.md b/CHANGELOG.md index fed951c2319..f18223bfb64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,10 +69,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [#3715](https://github.com/osmosis-labs/osmosis/pull/3715) Fix x/gamm (golang API) CalculateSpotPrice, balancer.SpotPrice and Stableswap.SpotPrice base and quote asset. * [#3746](https://github.com/osmosis-labs/osmosis/pull/3746) Make ApplyFuncIfNoErr logic preserve panics for OutOfGas behavior. +* [#4306](https://github.com/osmosis-labs/osmosis/pull/4306) Prevent adding more tokens to an already finished gauge +## v14.0.1 +### Bug fixes + +* [#4132](https://github.com/osmosis-labs/osmosis/pull/4132) Fix CLI for EstimateSwapExactAmountIn and EstimateSwapExactAmountOut in x/gamm. +* [#4262](https://github.com/osmosis-labs/osmosis/pull/4262) Fix geometric twap genesis validation. -## v14 +## v14.0.0 This release's main features are utility helpers for smart contract developers. This release contains: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5b38909db18..c98ff592493 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ Contributions come in the form of writing documentation, raising issues / PRs, a The first step is to find an issue you want to fix. To identify issues we think are good for first-time contributors, we add the **good first issue** label. [You can see a list of issues to contribute here](https://github.com/osmosis-labs/osmosis/contribute). -We recommend setting up your IDE as per our [recommended IDE setup](https://docs.osmosis.zone/developing/osmosis-core/ide-guide.html) before proceeding. +We recommend setting up your IDE as per our [recommended IDE setup](https://docs.osmosis.zone/osmosis-core/ide-guide) before proceeding. If you have a feature request, please use the [feature-request repo](https://github.com/osmosis-labs/feature-requests). We also welcome you to [make an issue](https://github.com/osmosis-labs/osmosis/issues/new/choose) for anything of substance, or posting an issue if you want to work on it. @@ -74,7 +74,7 @@ To simplify (and speed up) the process of writing unit tests that fit our standa #### 1. Setup -Note: this section assumes you already have the Go plugin for Vscode installed. Please refer to our [IDE setup docs](https://docs.osmosis.zone/developing/osmosis-core/ide-guide.html) if you haven't done any IDE setup yet. +Note: this section assumes you already have the Go plugin for Vscode installed. Please refer to our [IDE setup docs](https:/docs.osmosis.zone/osmosis-core/ide-guide) if you haven't done any IDE setup yet. Copy the `templates` folder into your `.vscode` folder from our main repo [here](https://github.com/osmosis-labs/osmosis/tree/main/.vscode). This folder has our custom templates for generating tests that fit our testing standards as accurately as possible. diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 14bb3cb50c6..cbd5b6742c6 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -365,7 +365,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers( appKeepers.SuperfluidKeeper = superfluidkeeper.NewKeeper( appKeepers.keys[superfluidtypes.StoreKey], appKeepers.GetSubspace(superfluidtypes.ModuleName), *appKeepers.AccountKeeper, appKeepers.BankKeeper, appKeepers.StakingKeeper, appKeepers.DistrKeeper, appKeepers.EpochsKeeper, appKeepers.LockupKeeper, appKeepers.GAMMKeeper, appKeepers.IncentivesKeeper, - lockupkeeper.NewMsgServerImpl(appKeepers.LockupKeeper)) + lockupkeeper.NewMsgServerImpl(appKeepers.LockupKeeper), appKeepers.ConcentratedLiquidityKeeper) mintKeeper := mintkeeper.NewKeeper( appKeepers.keys[minttypes.StoreKey], diff --git a/app/modules.go b/app/modules.go index 2909635fda5..f78139d6458 100644 --- a/app/modules.go +++ b/app/modules.go @@ -167,6 +167,7 @@ func appModules( app.LockupKeeper, app.GAMMKeeper, app.EpochsKeeper, + app.ConcentratedLiquidityKeeper, ), tokenfactory.NewAppModule(*app.TokenFactoryKeeper, app.AccountKeeper, app.BankKeeper), valsetprefmodule.NewAppModule(appCodec, *app.ValidatorSetPreferenceKeeper), diff --git a/proto/osmosis/gamm/pool-models/balancer/tx/tx.proto b/proto/osmosis/gamm/pool-models/balancer/tx/tx.proto index 4f5de4cddea..878dc09c4d2 100644 --- a/proto/osmosis/gamm/pool-models/balancer/tx/tx.proto +++ b/proto/osmosis/gamm/pool-models/balancer/tx/tx.proto @@ -42,8 +42,6 @@ message MsgMigrateSharesToFullRangeConcentratedPosition { (gogoproto.moretags) = "yaml:\"shares_to_migrate\"", (gogoproto.nullable) = false ]; - // temporary field, eventually gamm pool should be linked to cl pool - uint64 pool_id_entering = 3; } message MsgMigrateSharesToFullRangeConcentratedPositionResponse { diff --git a/proto/osmosis/superfluid/tx.proto b/proto/osmosis/superfluid/tx.proto index e49e620db11..4eabc7b119c 100644 --- a/proto/osmosis/superfluid/tx.proto +++ b/proto/osmosis/superfluid/tx.proto @@ -37,6 +37,11 @@ service Msg { rpc UnPoolWhitelistedPool(MsgUnPoolWhitelistedPool) returns (MsgUnPoolWhitelistedPoolResponse); + + rpc UnlockAndMigrateSharesToFullRangeConcentratedPosition( + MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) + returns ( + MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse); } message MsgSuperfluidDelegate { @@ -103,3 +108,32 @@ message MsgUnPoolWhitelistedPool { message MsgUnPoolWhitelistedPoolResponse { repeated uint64 exited_lock_ids = 1; } + +// ===================== +// MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition +message MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition { + string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; + uint64 lock_id = 2 [ (gogoproto.moretags) = "yaml:\"lock_id\"" ]; + cosmos.base.v1beta1.Coin shares_to_migrate = 3 [ + (gogoproto.moretags) = "yaml:\"shares_to_migrate\"", + (gogoproto.nullable) = false + ]; +} + +message MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse { + string amount0 = 1 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.moretags) = "yaml:\"amount0\"", + (gogoproto.nullable) = false + ]; + string amount1 = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.moretags) = "yaml:\"amount1\"", + (gogoproto.nullable) = false + ]; + string liquidity_created = 3 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.moretags) = "yaml:\"liquidity_created\"", + (gogoproto.nullable) = false + ]; +} \ No newline at end of file diff --git a/simulation/simtypes/txbuilder.go b/simulation/simtypes/txbuilder.go index 65adfb1df5c..5a997e792e0 100644 --- a/simulation/simtypes/txbuilder.go +++ b/simulation/simtypes/txbuilder.go @@ -36,7 +36,7 @@ func (sim *SimCtx) defaultTxBuilder( txConfig := params.MakeEncodingConfig().TxConfig // TODO: unhardcode // TODO: Consider making a default tx builder that charges some random fees // Low value for amount of work right now though. - fees := sdk.Coins{} + fees := sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 25000)) tx, err := genTx( txConfig, []sdk.Msg{msg}, diff --git a/tests/e2e/README.md b/tests/e2e/README.md index e752a9d401a..50f391e35b7 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -238,6 +238,32 @@ This section contains common "gotchas" that is sometimes very good to know when To fix that, check the docker container and see if you node is working. Or remove all related container then rerun e2e test +3. Transaction fees. + + Consensus min fee is set to "0.0025". This is how much is charged for 1 unit of gas. By default, we specify the gas limit + of `40000`. Therefore, each transaction should take `fee = 0.0025 uosmo / gas * 400000 gas = 1000 fee token. + The fee denom is set in `tests/e2e/initialization/config.go` by the value of `E2EFeeToken`. + See "Hermes Relayer - Consensus Min Fee" section for the relevant relayer configuration. + +## Hermes Relayer + +The configuration is built using the script in `tests/e2e/scripts/hermes_bootstrap.sh` + +Repository: + +### Consensus Min Fee + +We set the following parameters Hermes configs to enable the consensus min fee in Osmosis: + + - `gas_price` - Specifies the price per gas used of the fee to submit a transaction and + the denomination of the fee. The specified gas price should always be greater or equal to the `min-gas-price` + configured on the chain. This is to ensure that at least some minimal price is + paid for each unit of gas per transaction. + In Osmosis, we set consensus min fee = .0025 uosmo / gas * 400000 gas = 1000 + See ConsensusMinFee in x/txfees/types/constants.go + + - `default_gas` - the gas amount to use when simulation fails. + ## Debugging This section contains information about debugging osmosis's `e2e` tests. diff --git a/tests/e2e/configurer/chain/chain.go b/tests/e2e/configurer/chain/chain.go index 891cf5feaf7..542f5518ab1 100644 --- a/tests/e2e/configurer/chain/chain.go +++ b/tests/e2e/configurer/chain/chain.go @@ -137,9 +137,18 @@ func (c *Config) SendIBC(dstChain *Config, recipient string, token sdk.Coin) { dstNode, err := dstChain.GetDefaultNode() require.NoError(c.t, err) - balancesDstPre, err := dstNode.QueryBalances(recipient) + // removes the fee token from balances for calculating the difference in other tokens + // before and after the IBC send. + removeFeeTokenFromBalance := func(balance sdk.Coins) sdk.Coins { + feeTokenBalance := balance.FilterDenoms([]string{initialization.E2EFeeToken}) + return balance.Sub(feeTokenBalance) + } + + balancesDstPreWithTxFeeBalance, err := dstNode.QueryBalances(recipient) require.NoError(c.t, err) + balancesDstPre := removeFeeTokenFromBalance(balancesDstPreWithTxFeeBalance) + cmd := []string{"hermes", "tx", "raw", "ft-transfer", dstChain.Id, c.Id, "transfer", "channel-0", token.Amount.String(), fmt.Sprintf("--denom=%s", token.Denom), fmt.Sprintf("--receiver=%s", recipient), "--timeout-height-offset=1000"} _, _, err = c.containerManager.ExecHermesCmd(c.t, cmd, "Success") require.NoError(c.t, err) @@ -147,8 +156,11 @@ func (c *Config) SendIBC(dstChain *Config, recipient string, token sdk.Coin) { require.Eventually( c.t, func() bool { - balancesDstPost, err := dstNode.QueryBalances(recipient) + balancesDstPostWithTxFeeBalance, err := dstNode.QueryBalances(recipient) require.NoError(c.t, err) + + balancesDstPost := removeFeeTokenFromBalance(balancesDstPostWithTxFeeBalance) + ibcCoin := balancesDstPost.Sub(balancesDstPre) if ibcCoin.Len() == 1 { tokenPre := balancesDstPre.AmountOfNoDenomValidation(ibcCoin[0].Denom) diff --git a/tests/e2e/configurer/chain/commands.go b/tests/e2e/configurer/chain/commands.go index c3217f1d5ee..4c7e7595acc 100644 --- a/tests/e2e/configurer/chain/commands.go +++ b/tests/e2e/configurer/chain/commands.go @@ -21,10 +21,11 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - app "github.com/osmosis-labs/osmosis/v14/app" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/p2p" coretypes "github.com/tendermint/tendermint/rpc/core/types" + + app "github.com/osmosis-labs/osmosis/v14/app" ) func (n *NodeConfig) CreateBalancerPool(poolFile, from string) uint64 { @@ -127,7 +128,7 @@ func (n *NodeConfig) SendIBCTransfer(from, recipient, amount, memo string) { cmd := []string{"osmosisd", "tx", "ibc-transfer", "transfer", "transfer", "channel-0", recipient, amount, fmt.Sprintf("--from=%s", from), "--memo", memo} - _, _, err := n.containerManager.ExecTxCmdWithSuccessString(n.t, n.chainId, n.Name, cmd, "code: 0") + _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) n.LogActionF("successfully submitted sent IBC transfer") @@ -277,6 +278,8 @@ func (n *NodeConfig) BankSend(amount string, sendAddress string, receiveAddress n.LogActionF("successfully sent bank sent %s from address %s to %s", amount, sendAddress, receiveAddress) } +// This method also funds fee tokens from the `initialization.ValidatorWalletName` account. +// TODO: Abstract this to be a fee token provider account. func (n *NodeConfig) CreateWallet(walletName string) string { n.LogActionF("creating wallet %s", walletName) cmd := []string{"osmosisd", "keys", "add", walletName, "--keyring-backend=test"} @@ -285,19 +288,25 @@ func (n *NodeConfig) CreateWallet(walletName string) string { re := regexp.MustCompile("osmo1(.{38})") walletAddr := fmt.Sprintf("%s\n", re.FindString(outBuf.String())) walletAddr = strings.TrimSuffix(walletAddr, "\n") - n.LogActionF("created wallet %s, waller address - %s", walletName, walletAddr) + n.LogActionF("created wallet %s, wallet address - %s", walletName, walletAddr) + n.BankSend(initialization.WalletFeeTokens.String(), initialization.ValidatorWalletName, walletAddr) + n.LogActionF("Sent fee tokens from %s", initialization.ValidatorWalletName) return walletAddr } func (n *NodeConfig) CreateWalletAndFund(walletName string, tokensToFund []string) string { - n.LogActionF("Sending tokens to %s", walletName) + return n.CreateWalletAndFundFrom(walletName, initialization.ValidatorWalletName, tokensToFund) +} + +func (n *NodeConfig) CreateWalletAndFundFrom(newWalletName string, fundingWalletName string, tokensToFund []string) string { + n.LogActionF("Sending tokens to %s", newWalletName) - walletAddr := n.CreateWallet(walletName) + walletAddr := n.CreateWallet(newWalletName) for _, tokenToFund := range tokensToFund { - n.BankSend(tokenToFund, initialization.ValidatorWalletName, walletAddr) + n.BankSend(tokenToFund, fundingWalletName, walletAddr) } - n.LogActionF("Successfully sent tokens to %s", walletName) + n.LogActionF("Successfully sent tokens to %s", newWalletName) return walletAddr } diff --git a/tests/e2e/configurer/config/constants.go b/tests/e2e/configurer/config/constants.go index de9df8ecd68..cefee9aa4a3 100644 --- a/tests/e2e/configurer/config/constants.go +++ b/tests/e2e/configurer/config/constants.go @@ -26,4 +26,13 @@ var ( InitialMinDeposit = MinDepositValue / 4 // Minimum expedited deposit value for proposal to be submitted. InitialMinExpeditedDeposit = MinExpeditedDepositValue / 4 + // The first id of a pool create via CLI before starting an + // upgrade. + // Note: that we create a pool with id 1 via genesis + // in the initialization package. As a result, the first + // pre-upgrade should have id 2. + // This value gets mutated during the pre-upgrade pool + // creation in case more pools are added to genesis + // in the future + PreUpgradePoolId uint64 = 2 ) diff --git a/tests/e2e/configurer/upgrade.go b/tests/e2e/configurer/upgrade.go index 5dd7eadc270..bfe270e5c1f 100644 --- a/tests/e2e/configurer/upgrade.go +++ b/tests/e2e/configurer/upgrade.go @@ -125,19 +125,20 @@ func (uc *UpgradeConfigurer) CreatePreUpgradeState() error { chainA.SendIBC(chainB, chainB.NodeConfigs[0].PublicAddress, initialization.StakeToken) chainB.SendIBC(chainA, chainA.NodeConfigs[0].PublicAddress, initialization.StakeToken) - chainANode.CreateBalancerPool("pool1A.json", initialization.ValidatorWalletName) + config.PreUpgradePoolId = chainANode.CreateBalancerPool("pool1A.json", initialization.ValidatorWalletName) + poolShareDenom := fmt.Sprintf("gamm/pool/%d", config.PreUpgradePoolId) chainBNode.CreateBalancerPool("pool1B.json", initialization.ValidatorWalletName) // enable superfluid assets on chainA - chainA.EnableSuperfluidAsset("gamm/pool/1") + chainA.EnableSuperfluidAsset(poolShareDenom) // setup wallets and send gamm tokens to these wallets (only chainA) lockupWalletAddrA, lockupWalletSuperfluidAddrA := chainANode.CreateWallet(lockupWallet), chainANode.CreateWallet(lockupWalletSuperfluid) - chainANode.BankSend("10000000000000000000gamm/pool/1", chainA.NodeConfigs[0].PublicAddress, lockupWalletAddrA) - chainANode.BankSend("10000000000000000000gamm/pool/1", chainA.NodeConfigs[0].PublicAddress, lockupWalletSuperfluidAddrA) + chainANode.BankSend("10000000000000000000"+poolShareDenom, chainA.NodeConfigs[0].PublicAddress, lockupWalletAddrA) + chainANode.BankSend("10000000000000000000"+poolShareDenom, chainA.NodeConfigs[0].PublicAddress, lockupWalletSuperfluidAddrA) // test lock and add to existing lock for both regular and superfluid lockups (only chainA) - chainA.LockAndAddToExistingLock(sdk.NewInt(1000000000000000000), "gamm/pool/1", lockupWalletAddrA, lockupWalletSuperfluidAddrA) + chainA.LockAndAddToExistingLock(sdk.NewInt(1000000000000000000), poolShareDenom, lockupWalletAddrA, lockupWalletSuperfluidAddrA) return nil } diff --git a/tests/e2e/containers/config.go b/tests/e2e/containers/config.go index 62c4061a644..9ba26d2a61f 100644 --- a/tests/e2e/containers/config.go +++ b/tests/e2e/containers/config.go @@ -24,10 +24,10 @@ const ( // It should be uploaded to Docker Hub. OSMOSIS_E2E_SKIP_UPGRADE should be unset // for this functionality to be used. previousVersionOsmoRepository = "osmolabs/osmosis-dev" - previousVersionOsmoTag = "v14.x-6b742c84-1675172347" + previousVersionOsmoTag = "v14.x-4d4583fa-1676370337" // Pre-upgrade repo/tag for osmosis initialization (this should be one version below upgradeVersion) previousVersionInitRepository = "osmolabs/osmosis-e2e-init-chain" - previousVersionInitTag = "v14.x-f6813bcb-1674140667-manual" + previousVersionInitTag = "v14.x-4d4583fa-1676370337-manual" // Hermes repo/version for relayer relayerRepository = "osmolabs/hermes" relayerTag = "0.13.0" diff --git a/tests/e2e/containers/containers.go b/tests/e2e/containers/containers.go index de656780b72..461388b8929 100644 --- a/tests/e2e/containers/containers.go +++ b/tests/e2e/containers/containers.go @@ -13,6 +13,11 @@ import ( "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/osmosis-labs/osmosis/v14/tests/e2e/initialization" + txfeestypes "github.com/osmosis-labs/osmosis/v14/x/txfees/types" ) const ( @@ -20,9 +25,21 @@ const ( // The maximum number of times debug logs are printed to console // per CLI command. maxDebugLogsPerCommand = 3 + + GasLimit = 400000 ) -var defaultErrRegex = regexp.MustCompile(`(E|e)rror`) +var ( + // We set consensus min fee = .0025 uosmo / gas * 400000 gas = 1000 + Fees = txfeestypes.ConsensusMinFee.Mul(sdk.NewDec(GasLimit)).Ceil().TruncateInt64() + + defaultErrRegex = regexp.MustCompile(`(E|e)rror`) + + txArgs = []string{"-b=block", "--yes", "--keyring-backend=test", "--log_format=json"} + + // See ConsensusMinFee in x/txfees/types/constants.go + txDefaultGasArgs = []string{fmt.Sprintf("--gas=%d", GasLimit), fmt.Sprintf("--fees=%d", Fees) + initialization.E2EFeeToken} +) // Manager is a wrapper around all Docker instances, and the Docker API. // It provides utilities to run and interact with all Docker containers used within e2e testing. @@ -59,10 +76,21 @@ func (m *Manager) ExecTxCmd(t *testing.T, chainId string, containerName string, } // ExecTxCmdWithSuccessString Runs ExecCmd, with flags for txs added. -// namely adding flags `--chain-id={chain-id} -b=block --yes --keyring-backend=test "--log_format=json"`, +// namely adding flags `--chain-id={chain-id} -b=block --yes --keyring-backend=test "--log_format=json" --gas=400000`, // and searching for `successStr` func (m *Manager) ExecTxCmdWithSuccessString(t *testing.T, chainId string, containerName string, command []string, successStr string) (bytes.Buffer, bytes.Buffer, error) { - allTxArgs := []string{fmt.Sprintf("--chain-id=%s", chainId), "-b=block", "--yes", "--keyring-backend=test", "--log_format=json"} + allTxArgs := []string{fmt.Sprintf("--chain-id=%s", chainId)} + allTxArgs = append(allTxArgs, txArgs...) + // parse to see if command has gas flags. If not, add default gas flags. + addGasFlags := true + for _, cmd := range command { + if strings.HasPrefix(cmd, "--gas") || strings.HasPrefix(cmd, "--fees") { + addGasFlags = false + } + } + if addGasFlags { + allTxArgs = append(allTxArgs, txDefaultGasArgs...) + } txCommand := append(command, allTxArgs...) return m.ExecCmd(t, containerName, txCommand, successStr) } diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 699cb2df2d1..0d155c42237 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -3,9 +3,6 @@ package e2e import ( "encoding/json" "fmt" - transfertypes "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types" - "github.com/iancoleman/orderedmap" - "github.com/osmosis-labs/osmosis/v14/tests/e2e/configurer/chain" "io" "os" "path/filepath" @@ -13,6 +10,11 @@ import ( "strings" "time" + transfertypes "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types" + "github.com/iancoleman/orderedmap" + + "github.com/osmosis-labs/osmosis/v14/tests/e2e/configurer/chain" + packetforwardingtypes "github.com/strangelove-ventures/packet-forward-middleware/v4/router/types" ibchookskeeper "github.com/osmosis-labs/osmosis/x/ibc-hooks/keeper" @@ -156,7 +158,6 @@ func (s *IntegrationTestSuite) TestGeometricTwapMigration() { const ( // Configurations for tests/e2e/scripts/pool1A.json // This pool gets initialized pre-upgrade. - oldPoolId = 1 minAmountOut = "1" otherDenom = "ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518" migrationWallet = "migration" @@ -173,7 +174,7 @@ func (s *IntegrationTestSuite) TestGeometricTwapMigration() { node.BankSend(uosmoIn, chainA.NodeConfigs[0].PublicAddress, swapWalletAddr) // Swap to create new twap records on the pool that was created pre-upgrade. - node.SwapExactAmountIn(uosmoIn, minAmountOut, fmt.Sprintf("%d", oldPoolId), otherDenom, swapWalletAddr) + node.SwapExactAmountIn(uosmoIn, minAmountOut, fmt.Sprintf("%d", config.PreUpgradePoolId), otherDenom, swapWalletAddr) } // TestIBCTokenTransfer tests that IBC token transfers work as expected. @@ -212,11 +213,11 @@ func (s *IntegrationTestSuite) TestSuperfluidVoting() { chainA.EnableSuperfluidAsset(fmt.Sprintf("gamm/pool/%d", poolId)) // setup wallets and send gamm tokens to these wallets (both chains) - superfluildVotingWallet := chainANode.CreateWallet("TestSuperfluidVoting") - chainANode.BankSend(fmt.Sprintf("10000000000000000000gamm/pool/%d", poolId), chainA.NodeConfigs[0].PublicAddress, superfluildVotingWallet) - chainANode.LockTokens(fmt.Sprintf("%v%s", sdk.NewInt(1000000000000000000), fmt.Sprintf("gamm/pool/%d", poolId)), "240s", superfluildVotingWallet) + superfluidVotingWallet := chainANode.CreateWallet("TestSuperfluidVoting") + chainANode.BankSend(fmt.Sprintf("10000000000000000000gamm/pool/%d", poolId), chainA.NodeConfigs[0].PublicAddress, superfluidVotingWallet) + chainANode.LockTokens(fmt.Sprintf("%v%s", sdk.NewInt(1000000000000000000), fmt.Sprintf("gamm/pool/%d", poolId)), "240s", superfluidVotingWallet) chainA.LatestLockNumber += 1 - chainANode.SuperfluidDelegate(chainA.LatestLockNumber, chainA.NodeConfigs[1].OperatorAddress, superfluildVotingWallet) + chainANode.SuperfluidDelegate(chainA.LatestLockNumber, chainA.NodeConfigs[1].OperatorAddress, superfluidVotingWallet) // create a text prop, deposit and vote yes chainANode.SubmitTextProposal("superfluid vote overwrite test", sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(config.InitialMinDeposit)), false) @@ -227,7 +228,7 @@ func (s *IntegrationTestSuite) TestSuperfluidVoting() { } // set delegator vote to no - chainANode.VoteNoProposal(superfluildVotingWallet, chainA.LatestProposalNumber) + chainANode.VoteNoProposal(superfluidVotingWallet, chainA.LatestProposalNumber) s.Eventually( func() bool { @@ -514,9 +515,11 @@ func (s *IntegrationTestSuite) TestAddToExistingLockPostUpgrade() { s.NoError(err) // ensure we can add to existing locks and superfluid locks that existed pre upgrade on chainA // we use the hardcoded gamm/pool/1 and these specific wallet names to match what was created pre upgrade + preUpgradePoolShareDenom := fmt.Sprintf("gamm/pool/%d", config.PreUpgradePoolId) + lockupWalletAddr, lockupWalletSuperfluidAddr := chainANode.GetWallet("lockup-wallet"), chainANode.GetWallet("lockup-wallet-superfluid") - chainANode.AddToExistingLock(sdk.NewInt(1000000000000000000), "gamm/pool/1", "240s", lockupWalletAddr) - chainANode.AddToExistingLock(sdk.NewInt(1000000000000000000), "gamm/pool/1", "240s", lockupWalletSuperfluidAddr) + chainANode.AddToExistingLock(sdk.NewInt(1000000000000000000), preUpgradePoolShareDenom, "240s", lockupWalletAddr) + chainANode.AddToExistingLock(sdk.NewInt(1000000000000000000), preUpgradePoolShareDenom, "240s", lockupWalletSuperfluidAddr) } // TestAddToExistingLock tests lockups to both regular and superfluid locks. @@ -524,15 +527,17 @@ func (s *IntegrationTestSuite) TestAddToExistingLock() { chainA := s.configurer.GetChainConfig(0) chainANode, err := chainA.GetDefaultNode() s.NoError(err) + funder := chainA.NodeConfigs[0].PublicAddress // ensure we can add to new locks and superfluid locks // create pool and enable superfluid assets - poolId := chainANode.CreateBalancerPool("nativeDenomPool.json", chainA.NodeConfigs[0].PublicAddress) + poolId := chainANode.CreateBalancerPool("nativeDenomPool.json", funder) chainA.EnableSuperfluidAsset(fmt.Sprintf("gamm/pool/%d", poolId)) // setup wallets and send gamm tokens to these wallets on chainA - lockupWalletAddr, lockupWalletSuperfluidAddr := chainANode.CreateWallet("TestAddToExistingLock"), chainANode.CreateWallet("TestAddToExistingLockSuperfluid") - chainANode.BankSend(fmt.Sprintf("10000000000000000000gamm/pool/%d", poolId), chainA.NodeConfigs[0].PublicAddress, lockupWalletAddr) - chainANode.BankSend(fmt.Sprintf("10000000000000000000gamm/pool/%d", poolId), chainA.NodeConfigs[0].PublicAddress, lockupWalletSuperfluidAddr) + gammShares := fmt.Sprintf("10000000000000000000gamm/pool/%d", poolId) + fundTokens := []string{gammShares, initialization.WalletFeeTokens.String()} + lockupWalletAddr := chainANode.CreateWalletAndFundFrom("TestAddToExistingLock", funder, fundTokens) + lockupWalletSuperfluidAddr := chainANode.CreateWalletAndFundFrom("TestAddToExistingLockSuperfluid", funder, fundTokens) // ensure we can add to new locks and superfluid locks on chainA chainA.LockAndAddToExistingLock(sdk.NewInt(1000000000000000000), fmt.Sprintf("gamm/pool/%d", poolId), lockupWalletAddr, lockupWalletSuperfluidAddr) @@ -566,19 +571,29 @@ func (s *IntegrationTestSuite) TestArithmeticTWAP() { // Triggers the creation of TWAP records. poolId := chainANode.CreateBalancerPool(poolFile, initialization.ValidatorWalletName) - swapWalletAddr := chainANode.CreateWallet(walletName) + swapWalletAddr := chainANode.CreateWalletAndFund(walletName, []string{initialization.WalletFeeTokens.String()}) timeBeforeSwap := chainANode.QueryLatestBlockTime() // Wait for the next height so that the requested twap // start time (timeBeforeSwap) is not equal to the block time. + chainA.WaitForNumHeights(2) + + timeBeforeSwapEnd := chainANode.QueryLatestBlockTime() + + // timeBeforeSwapStartTwo = right in betweeen timeBeforeSwap and timeBeforeSwapEnd + timeBeforeSwapStartTwo := timeBeforeSwap.Add(timeBeforeSwapEnd.Sub(timeBeforeSwap) / 2) + + // Wait longer to make sure that in both series of queries, both do not land + // on the current block time with twap end time. chainA.WaitForNumHeights(1) s.T().Log("querying for the first TWAP to now before swap") - twapFromBeforeSwapToBeforeSwapOneAB, err := chainANode.QueryArithmeticTwapToNow(poolId, denomA, denomB, timeBeforeSwap) + fmt.Println("timeBeforeSwapEnd ", timeBeforeSwapEnd) + twapFromBeforeSwapToBeforeSwapOneAB, err := chainANode.QueryArithmeticTwap(poolId, denomA, denomB, timeBeforeSwap, timeBeforeSwapEnd) s.Require().NoError(err) - twapFromBeforeSwapToBeforeSwapOneBC, err := chainANode.QueryArithmeticTwapToNow(poolId, denomB, denomC, timeBeforeSwap) + twapFromBeforeSwapToBeforeSwapOneBC, err := chainANode.QueryArithmeticTwap(poolId, denomB, denomC, timeBeforeSwap, timeBeforeSwapEnd) s.Require().NoError(err) - twapFromBeforeSwapToBeforeSwapOneCA, err := chainANode.QueryArithmeticTwapToNow(poolId, denomC, denomA, timeBeforeSwap) + twapFromBeforeSwapToBeforeSwapOneCA, err := chainANode.QueryArithmeticTwap(poolId, denomC, denomA, timeBeforeSwap, timeBeforeSwapEnd) s.Require().NoError(err) chainANode.BankSend(coinAIn, chainA.NodeConfigs[0].PublicAddress, swapWalletAddr) @@ -586,11 +601,11 @@ func (s *IntegrationTestSuite) TestArithmeticTWAP() { chainANode.BankSend(coinCIn, chainA.NodeConfigs[0].PublicAddress, swapWalletAddr) s.T().Log("querying for the second TWAP to now before swap, must equal to first") - twapFromBeforeSwapToBeforeSwapTwoAB, err := chainANode.QueryArithmeticTwapToNow(poolId, denomA, denomB, timeBeforeSwap.Add(50*time.Millisecond)) + twapFromBeforeSwapToBeforeSwapTwoAB, err := chainANode.QueryArithmeticTwap(poolId, denomA, denomB, timeBeforeSwapStartTwo, timeBeforeSwapEnd) s.Require().NoError(err) - twapFromBeforeSwapToBeforeSwapTwoBC, err := chainANode.QueryArithmeticTwapToNow(poolId, denomB, denomC, timeBeforeSwap.Add(50*time.Millisecond)) + twapFromBeforeSwapToBeforeSwapTwoBC, err := chainANode.QueryArithmeticTwap(poolId, denomB, denomC, timeBeforeSwapStartTwo, timeBeforeSwapEnd) s.Require().NoError(err) - twapFromBeforeSwapToBeforeSwapTwoCA, err := chainANode.QueryArithmeticTwapToNow(poolId, denomC, denomA, timeBeforeSwap.Add(50*time.Millisecond)) + twapFromBeforeSwapToBeforeSwapTwoCA, err := chainANode.QueryArithmeticTwap(poolId, denomC, denomA, timeBeforeSwapStartTwo, timeBeforeSwapEnd) s.Require().NoError(err) // Since there were no swaps between the two queries, the TWAPs should be the same. @@ -844,6 +859,8 @@ func (s *IntegrationTestSuite) TestExpeditedProposals() { // Upon swapping 1_000_000 uosmo for stake, supply changes, making uosmo less expensive. // As a result of the swap, twap changes to 0.5. func (s *IntegrationTestSuite) TestGeometricTWAP() { + s.T().Skip("TODO: investigate further: https://github.com/osmosis-labs/osmosis/issues/4342") + const ( // This pool contains 1_000_000 uosmo and 2_000_000 stake. // Equals weights. @@ -864,20 +881,25 @@ func (s *IntegrationTestSuite) TestGeometricTWAP() { // Triggers the creation of TWAP records. poolId := chainANode.CreateBalancerPool(poolFile, initialization.ValidatorWalletName) - swapWalletAddr := chainANode.CreateWallet(walletName) + swapWalletAddr := chainANode.CreateWalletAndFund(walletName, []string{initialization.WalletFeeTokens.String()}) // We add 5 ms to avoid landing directly on block time in twap. If block time // is provided as start time, the latest spot price is used. Otherwise // interpolation is done. timeBeforeSwapPlus5ms := chainANode.QueryLatestBlockTime().Add(5 * time.Millisecond) + s.T().Log("geometric twap, start time ", timeBeforeSwapPlus5ms.Unix()) + // Wait for the next height so that the requested twap // start time (timeBeforeSwap) is not equal to the block time. - chainA.WaitForNumHeights(1) + chainA.WaitForNumHeights(2) s.T().Log("querying for the first geometric TWAP to now (before swap)") // Assume base = uosmo, quote = stake // At pool creation time, the twap should be: // quote assset supply / base asset supply = 2_000_000 / 1_000_000 = 2 + curBlockTime := chainANode.QueryLatestBlockTime().Unix() + s.T().Log("geometric twap, end time ", curBlockTime) + initialTwapBOverA, err := chainANode.QueryGeometricTwapToNow(poolId, denomA, denomB, timeBeforeSwapPlus5ms) s.Require().NoError(err) s.Require().Equal(sdk.NewDec(2), initialTwapBOverA) diff --git a/tests/e2e/initialization/config.go b/tests/e2e/initialization/config.go index 54450822122..71e0da75ebe 100644 --- a/tests/e2e/initialization/config.go +++ b/tests/e2e/initialization/config.go @@ -19,6 +19,7 @@ import ( tmjson "github.com/tendermint/tendermint/libs/json" epochtypes "github.com/osmosis-labs/osmosis/v14/x/epochs/types" + "github.com/osmosis-labs/osmosis/v14/x/gamm/pool-models/balancer" gammtypes "github.com/osmosis-labs/osmosis/v14/x/gamm/types" incentivestypes "github.com/osmosis-labs/osmosis/v14/x/incentives/types" minttypes "github.com/osmosis-labs/osmosis/v14/x/mint/types" @@ -27,6 +28,8 @@ import ( twaptypes "github.com/osmosis-labs/osmosis/v14/x/twap/types" txfeestypes "github.com/osmosis-labs/osmosis/v14/x/txfees/types" + types1 "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/osmosis-labs/osmosis/v14/tests/e2e/util" ) @@ -52,6 +55,7 @@ const ( AtomDenom = "uatom" OsmoIBCDenom = "ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518" StakeIBCDenom = "ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B7787" + E2EFeeToken = "e2e-default-feetoken" MinGasPrice = "0.000" IbcSendAmount = 3300000000 ValidatorWalletName = "val" @@ -62,11 +66,13 @@ const ( StakeBalanceA = 110000000000 StakeAmountA = 100000000000 // chainB - ChainBID = "osmo-test-b" - OsmoBalanceB = 500000000000 - IonBalanceB = 100000000000 - StakeBalanceB = 440000000000 - StakeAmountB = 400000000000 + ChainBID = "osmo-test-b" + OsmoBalanceB = 500000000000 + IonBalanceB = 100000000000 + StakeBalanceB = 440000000000 + StakeAmountB = 400000000000 + GenesisFeeBalance = 100000000000 + WalletFeeBalance = 100000000 EpochDuration = time.Second * 60 TWAPPruningKeepPeriod = EpochDuration / 4 @@ -84,6 +90,7 @@ var ( StakeToken = sdk.NewInt64Coin(StakeDenom, IbcSendAmount) // 3,300ustake tenOsmo = sdk.Coins{sdk.NewInt64Coin(OsmoDenom, 10_000_000)} fiftyOsmo = sdk.Coins{sdk.NewInt64Coin(OsmoDenom, 50_000_000)} + WalletFeeTokens = sdk.NewCoin(E2EFeeToken, sdk.NewInt(WalletFeeBalance)) ) func addAccount(path, moniker, amountStr string, accAddr sdk.AccAddress, forkHeight int) error { @@ -97,6 +104,7 @@ func addAccount(path, moniker, amountStr string, accAddr sdk.AccAddress, forkHei if err != nil { return fmt.Errorf("failed to parse coins: %w", err) } + coins = coins.Add(sdk.NewCoin(E2EFeeToken, sdk.NewInt(GenesisFeeBalance))) balances := banktypes.Balance{Address: accAddr.String(), Coins: coins.Sort()} genAccount := authtypes.NewBaseAccount(accAddr, nil, 0, 0) @@ -347,10 +355,35 @@ func updateMintGenesis(mintGenState *minttypes.GenesisState) { func updateTxfeesGenesis(txfeesGenState *txfeestypes.GenesisState) { txfeesGenState.Basedenom = OsmoDenom + txfeesGenState.Feetokens = []txfeestypes.FeeToken{ + {Denom: E2EFeeToken, PoolID: 1}, + } } func updateGammGenesis(gammGenState *gammtypes.GenesisState) { gammGenState.Params.PoolCreationFee = tenOsmo + // setup fee pool, between "e2e_default_fee_token" and "uosmo" + feePoolParams := balancer.NewPoolParams(sdk.MustNewDecFromStr("0.01"), sdk.ZeroDec(), nil) + feePoolAssets := []balancer.PoolAsset{ + { + Weight: sdk.NewInt(100), + Token: sdk.NewCoin("uosmo", sdk.NewInt(100000000000)), + }, + { + Weight: sdk.NewInt(100), + Token: sdk.NewCoin(E2EFeeToken, sdk.NewInt(100000000000)), + }, + } + pool1, err := balancer.NewBalancerPool(1, feePoolParams, feePoolAssets, "", time.Unix(0, 0)) + if err != nil { + panic(err) + } + anyPool, err := types1.NewAnyWithValue(&pool1) + if err != nil { + panic(err) + } + gammGenState.Pools = []*types1.Any{anyPool} + gammGenState.NextPoolNumber = 2 } func updatePoolManagerGenesis(appGenState map[string]json.RawMessage) func(*poolmanagertypes.GenesisState) { diff --git a/tests/e2e/scripts/hermes_bootstrap.sh b/tests/e2e/scripts/hermes_bootstrap.sh index ce7d2f8dc94..1eddab31e81 100644 --- a/tests/e2e/scripts/hermes_bootstrap.sh +++ b/tests/e2e/scripts/hermes_bootstrap.sh @@ -42,7 +42,8 @@ account_prefix = 'osmo' key_name = 'val01-osmosis-a' store_prefix = 'ibc' max_gas = 6000000 -gas_price = { price = 0.000, denom = 'uosmo' } +default_gas = 400000 +gas_price = { price = 0.0025, denom = 'e2e-default-feetoken' } gas_adjustment = 1.0 clock_drift = '1m' # to accomdate docker containers trusting_period = '239seconds' @@ -57,7 +58,8 @@ account_prefix = 'osmo' key_name = 'val01-osmosis-b' store_prefix = 'ibc' max_gas = 6000000 -gas_price = { price = 0.000, denom = 'uosmo' } +default_gas = 400000 +gas_price = { price = 0.0025, denom = 'e2e-default-feetoken' } gas_adjustment = 1.0 clock_drift = '1m' # to accomdate docker containers trusting_period = '239seconds' diff --git a/tests/ibc-hooks/ibc_middleware_test.go b/tests/ibc-hooks/ibc_middleware_test.go index 6587042254b..d112dd23c46 100644 --- a/tests/ibc-hooks/ibc_middleware_test.go +++ b/tests/ibc-hooks/ibc_middleware_test.go @@ -14,6 +14,7 @@ import ( "github.com/osmosis-labs/osmosis/v14/x/gamm/pool-models/balancer" gammtypes "github.com/osmosis-labs/osmosis/v14/x/gamm/types" minttypes "github.com/osmosis-labs/osmosis/v14/x/mint/types" + txfeetypes "github.com/osmosis-labs/osmosis/v14/x/txfees/types" "github.com/osmosis-labs/osmosis/v14/app/apptesting" @@ -42,7 +43,11 @@ type HooksTestSuite struct { path *ibctesting.Path } +var oldConsensusMinFee = txfeetypes.ConsensusMinFee + func (suite *HooksTestSuite) SetupTest() { + // TODO: This needs to get removed. Waiting on https://github.com/cosmos/ibc-go/issues/3123 + txfeetypes.ConsensusMinFee = sdk.ZeroDec() suite.Setup() ibctesting.DefaultTestingAppInit = osmosisibctesting.SetupTestingApp suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) @@ -60,6 +65,11 @@ func (suite *HooksTestSuite) SetupTest() { suite.coordinator.Setup(suite.path) } +// TODO: This needs to get removed. Waiting on https://github.com/cosmos/ibc-go/issues/3123 +func (suite *HooksTestSuite) TearDownSuite() { + txfeetypes.ConsensusMinFee = oldConsensusMinFee +} + func TestIBCHooksTestSuite(t *testing.T) { suite.Run(t, new(HooksTestSuite)) } diff --git a/tests/osmosisibctesting/chain.go b/tests/osmosisibctesting/chain.go index cf56047882c..98a9110caaf 100644 --- a/tests/osmosisibctesting/chain.go +++ b/tests/osmosisibctesting/chain.go @@ -24,7 +24,7 @@ func SetupTestingApp() (ibctesting.TestingApp, map[string]json.RawMessage) { return osmosisApp, app.NewDefaultGenesisState() } -// SendMsgsNoCheck overrides ibctesting.TestChain.SendMsgs so that it doesn't check for errors. That should be handled by the caller +// SendMsgsNoCheck is an alternative to ibctesting.TestChain.SendMsgs so that it doesn't check for errors. That should be handled by the caller func (chain *TestChain) SendMsgsNoCheck(msgs ...sdk.Msg) (*sdk.Result, error) { // ensure the chain has the latest time chain.Coordinator.UpdateTimeForChain(chain.TestChain) diff --git a/tests/simulator/sim_test.go b/tests/simulator/sim_test.go index 5db9e062f0e..7915116fba5 100644 --- a/tests/simulator/sim_test.go +++ b/tests/simulator/sim_test.go @@ -11,9 +11,11 @@ import ( dbm "github.com/tendermint/tm-db" "github.com/cosmos/cosmos-sdk/simapp/helpers" + sdk "github.com/cosmos/cosmos-sdk/types" osmosim "github.com/osmosis-labs/osmosis/v14/simulation/executor" "github.com/osmosis-labs/osmosis/v14/simulation/simtypes/simlogger" + txfeetypes "github.com/osmosis-labs/osmosis/v14/x/txfees/types" ) // Profile with: @@ -45,6 +47,8 @@ func TestFullAppSimulation(t *testing.T) { } func fullAppSimulation(tb testing.TB, is_testing bool) { + // TODO: Get SDK simulator fixed to have min fees possible + txfeetypes.ConsensusMinFee = sdk.ZeroDec() config, db, logger, cleanup, err := osmosim.SetupSimulation("goleveldb-app-sim", "Simulation") if err != nil { tb.Fatalf("simulation setup failed: %s", err.Error()) @@ -79,6 +83,8 @@ func TestAppStateDeterminism(t *testing.T) { // if !osmosim.FlagEnabledValue { // t.Skip("skipping application simulation") // } + // TODO: Get SDK simulator fixed to have min fees possible + txfeetypes.ConsensusMinFee = sdk.ZeroDec() config := osmosim.NewConfigFromFlags() config.ExportConfig.ExportParamsPath = "" diff --git a/x/concentrated-liquidity/architecture.md b/x/concentrated-liquidity/architecture.md index b0822318312..789237768e2 100644 --- a/x/concentrated-liquidity/architecture.md +++ b/x/concentrated-liquidity/architecture.md @@ -1106,7 +1106,12 @@ We will use the following terms throughout the document: - `Tick` - TODO -- `Position` - TODO +- `FullPosition` - A single user's liquidity in a single pool spread out between two ticks with a frozenUntil timestamp. Unlike Position, FullPosition can +only describe a single instance of liquidity. If a user adds liquidity to the same pool between the same two ticks, but with a different frozenUntil timestamp, then it will be a different FullPosition. + +- `Position` - A single user's liquidity in a single pool spread out between two ticks. Unlike FullPosition, position does not +take into consideration the frozenUntil timestamp. Therefore, a position can describe multiple instances of liquidity +between the same two ticks in the same pool, but with different frozenUntil timestamps. - `Range` - TODO diff --git a/x/concentrated-liquidity/clmodule/module.go b/x/concentrated-liquidity/clmodule/module.go index 9875dfaf6ee..ea49131138e 100644 --- a/x/concentrated-liquidity/clmodule/module.go +++ b/x/concentrated-liquidity/clmodule/module.go @@ -154,8 +154,10 @@ func (am AppModule) GenerateGenesisState(simState *module.SimulationState, s *si func (am AppModule) Actions() []simtypes.Action { return []simtypes.Action{ simtypes.NewMsgBasedAction("CreateConcentratedPool", am.keeper, simulation.RandomMsgCreateConcentratedPool), - // simtypes.NewMsgBasedAction("CreatePosition", am.keeper, simulation.RandMsgCreatePosition), - // simtypes.NewMsgBasedAction("WithdrawPosition", am.keeper, simulation.RandMsgWithdrawPosition), - // simtypes.NewMsgBasedAction("CollectFees", am.keeper, simulation.RandMsgCollectFees), + simtypes.NewMsgBasedAction("CreatePosition", am.keeper, simulation.RandMsgCreatePosition), + //simtypes.NewMsgBasedAction("CLSwapExactAmountIn", am.keeper, simulation.RandomSwapExactAmountIn), + //simtypes.NewMsgBasedAction("CLSwapExactAmountOut", am.keeper, simulation.RandomSwapExactAmountOut), + simtypes.NewMsgBasedAction("WithdrawPosition", am.keeper, simulation.RandMsgWithdrawPosition), + simtypes.NewMsgBasedAction("CollectFees", am.keeper, simulation.RandMsgCollectFees), } } diff --git a/x/concentrated-liquidity/export_test.go b/x/concentrated-liquidity/export_test.go index ea5ba25f707..8e1a897f910 100644 --- a/x/concentrated-liquidity/export_test.go +++ b/x/concentrated-liquidity/export_test.go @@ -136,6 +136,10 @@ func (k Keeper) ChargeFee(ctx sdk.Context, poolId uint64, feeUpdate sdk.DecCoin) return k.chargeFee(ctx, poolId, feeUpdate) } +func ValidateTickInRangeIsValid(tickSpacing uint64, exponentAtPriceOne sdk.Int, lowerTick int64, upperTick int64) error { + return validateTickRangeIsValid(tickSpacing, exponentAtPriceOne, lowerTick, upperTick) +} + func FormatPositionAccumulatorKey(poolId uint64, owner sdk.AccAddress, lowerTick, upperTick int64) string { return formatFeePositionAccumulatorKey(poolId, owner, lowerTick, upperTick) } diff --git a/x/concentrated-liquidity/internal/math/math.go b/x/concentrated-liquidity/internal/math/math.go index 4ae77c6de8e..f57febbc348 100644 --- a/x/concentrated-liquidity/internal/math/math.go +++ b/x/concentrated-liquidity/internal/math/math.go @@ -96,22 +96,15 @@ func CalcAmount1Delta(liq, sqrtPriceA, sqrtPriceB sdk.Dec, roundUp bool) sdk.Dec // getNextSqrtPriceFromAmount0RoundingUp utilizes the current squareRootPrice, liquidity of denom0, and amount of denom0 that still needs // to be swapped in order to determine the next squareRootPrice -// TODO: make an issue to determine if we can remove the less precise formula +// Note: we are using only using the precise formula here. func GetNextSqrtPriceFromAmount0RoundingUp(sqrtPriceCurrent, liquidity, amountRemaining sdk.Dec) (sqrtPriceNext sdk.Dec) { if amountRemaining.Equal(sdk.ZeroDec()) { return sqrtPriceCurrent } product := amountRemaining.Mul(sqrtPriceCurrent) - // use precise formula if product doesn't overflow - if (product.Quo(amountRemaining)).Equal(sqrtPriceCurrent) { - denominator := liquidity.Add(product) - if denominator.GTE(liquidity) { - return liquidity.Mul(sqrtPriceCurrent).QuoRoundUp(denominator) - } - } - // if the product does overflow, use less precise formula - return liquidity.QuoRoundUp(liquidity.Quo(sqrtPriceCurrent).Add(amountRemaining)) + denominator := liquidity.Add(product) + return liquidity.Mul(sqrtPriceCurrent).QuoRoundUp(denominator) } // getNextSqrtPriceFromAmount1RoundingDown utilizes the current squareRootPrice, liquidity of denom1, and amount of denom1 that still needs diff --git a/x/concentrated-liquidity/pool.go b/x/concentrated-liquidity/pool.go index 4bf8c0974ae..2f46395da67 100644 --- a/x/concentrated-liquidity/pool.go +++ b/x/concentrated-liquidity/pool.go @@ -129,6 +129,14 @@ func convertPoolInterfaceToConcentrated(poolI poolmanagertypes.PoolI) (types.Con return concentratedPool, nil } +func (k Keeper) GetPoolFromPoolIdAndConvertToConcentrated(ctx sdk.Context, poolId uint64) (types.ConcentratedPoolExtension, error) { + poolI, err := k.GetPool(ctx, poolId) + if err != nil { + return nil, err + } + return convertPoolInterfaceToConcentrated(poolI) +} + // validateTickSpacing returns true if the given tick spacing is one of the authorized tick spacings set in the func (k Keeper) validateTickSpacing(ctx sdk.Context, tickSpacing uint64) bool { params := k.GetParams(ctx) diff --git a/x/concentrated-liquidity/position.go b/x/concentrated-liquidity/position.go index 9c764fdf91d..a4d1aaeda54 100644 --- a/x/concentrated-liquidity/position.go +++ b/x/concentrated-liquidity/position.go @@ -163,3 +163,18 @@ func (k Keeper) deletePosition(ctx sdk.Context, store.Delete(key) return nil } + +// CreateFullRangePosition creates a full range (min to max tick) concentrated liquidity position for the given pool ID, owner, coins, and frozen until time. +// The function returns the amounts of token 0 and token 1, and the liquidity created from the position. +func (k Keeper) CreateFullRangePosition(ctx sdk.Context, concentratedPool types.ConcentratedPoolExtension, owner sdk.AccAddress, coins sdk.Coins, frozenUntil time.Time) (amount0, amount1 sdk.Int, liquidity sdk.Dec, err error) { + // Determine the max and min ticks for the concentrated pool we are migrating to. + minTick, maxTick := GetMinAndMaxTicksFromExponentAtPriceOne(concentratedPool.GetPrecisionFactorAtPriceOne()) + + // Create a full range (min to max tick) concentrated liquidity position. + amount0, amount1, liquidity, err = k.CreatePosition(ctx, concentratedPool.GetId(), owner, coins.AmountOf(concentratedPool.GetToken0()), coins.AmountOf(concentratedPool.GetToken1()), sdk.ZeroInt(), sdk.ZeroInt(), minTick, maxTick, frozenUntil) + if err != nil { + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, err + } + + return amount0, amount1, liquidity, nil +} diff --git a/x/concentrated-liquidity/simulation/sim_msgs.go b/x/concentrated-liquidity/simulation/sim_msgs.go index a05947d992b..494ac57dda3 100644 --- a/x/concentrated-liquidity/simulation/sim_msgs.go +++ b/x/concentrated-liquidity/simulation/sim_msgs.go @@ -3,7 +3,6 @@ package simulation import ( "errors" "fmt" - "math/rand" sdk "github.com/cosmos/cosmos-sdk/types" @@ -18,6 +17,7 @@ import ( var PoolCreationFee = sdk.NewInt64Coin("stake", 10_000_000) func RandomMsgCreateConcentratedPool(k clkeeper.Keeper, sim *osmosimtypes.SimCtx, ctx sdk.Context) (*clmodeltypes.MsgCreateConcentratedPool, error) { + rand := sim.GetRand() minExponentAtOneValue := cltypes.ExponentAtPriceOneMin.Int64() maxExponentAtOneValue := cltypes.ExponentAtPriceOneMax.Int64() @@ -61,6 +61,121 @@ func RandomMsgCreateConcentratedPool(k clkeeper.Keeper, sim *osmosimtypes.SimCtx }, nil } +func RandMsgCreatePosition(k clkeeper.Keeper, sim *osmosimtypes.SimCtx, ctx sdk.Context) (*cltypes.MsgCreatePosition, error) { + // get random pool + clPool, poolDenoms, err := getRandCLPool(k, sim, ctx) + if err != nil { + return nil, err + } + + // get random user address with the pool denoms + sender, tokens, senderExists := sim.SelAddrWithDenoms(ctx, poolDenoms) + if !senderExists { + return nil, fmt.Errorf("no sender with denoms %s exists", poolDenoms) + } + + // ensure that we always have 2 tokens + // Note: tokens returns a random subset of poolDenoms, so had to add this assertion + if len(tokens) < 2 { + return nil, fmt.Errorf("user doesnot have pool tokens") + } + + // Retrieve minTick and maxTick from precision factor + minTick, maxTick := clkeeper.GetMinAndMaxTicksFromExponentAtPriceOne(clPool.GetPrecisionFactorAtPriceOne()) + + // Randomize lowerTick and upperTick from max values to create position + lowerTick, upperTick, err := getRandomTickPositions(sim, minTick, maxTick, clPool.GetTickSpacing()) + if err != nil { + return nil, err + } + + return &cltypes.MsgCreatePosition{ + PoolId: clPool.GetId(), + Sender: sender.Address.String(), + LowerTick: lowerTick, + UpperTick: upperTick, + TokenDesired0: tokens[0], + TokenDesired1: tokens[1], + // TODO: Randomize TokenMinAmount0 and TokenMinAmount1 in next iteration + TokenMinAmount0: sdk.NewInt(0), + TokenMinAmount1: sdk.NewInt(0), + }, nil +} + +func RandMsgWithdrawPosition(k clkeeper.Keeper, sim *osmosimtypes.SimCtx, ctx sdk.Context) (*cltypes.MsgWithdrawPosition, error) { + rand := sim.GetRand() + // get random pool + _, poolDenoms, err := getRandCLPool(k, sim, ctx) + if err != nil { + return nil, err + } + + // get random user address with the pool denoms + sender, _, senderExists := sim.SelAddrWithDenoms(ctx, poolDenoms) + if !senderExists { + return nil, fmt.Errorf("no sender with denoms %s exists", poolDenoms) + } + + positions, err := k.GetUserPositions(ctx, sender.Address) + if err != nil { + return nil, fmt.Errorf("position does not exist") + } + + if len(positions) == 0 { + return nil, fmt.Errorf("user does not have any position") + } + + // pick a random position + randPosition := positions[rand.Intn(len(positions))] + + // get percentage amount from 1 to 100 to withdraw liquidity + randPerc := sim.RandomDecAmount(sdk.OneDec()) + + withdrawAmountInt := randPosition.Liquidity.Mul(randPerc) + + return &cltypes.MsgWithdrawPosition{ + PoolId: randPosition.PoolId, + Sender: sender.Address.String(), + LowerTick: randPosition.LowerTick, + UpperTick: randPosition.UpperTick, + LiquidityAmount: withdrawAmountInt, + }, nil +} + +func RandMsgCollectFees(k clkeeper.Keeper, sim *osmosimtypes.SimCtx, ctx sdk.Context) (*cltypes.MsgCollectFees, error) { + rand := sim.GetRand() + // get random pool + _, poolDenoms, err := getRandCLPool(k, sim, ctx) + if err != nil { + return nil, err + } + + // get random user address with the pool denoms + sender, _, senderExists := sim.SelAddrWithDenoms(ctx, poolDenoms) + if !senderExists { + return nil, fmt.Errorf("no sender with denoms %s exists", poolDenoms) + } + + positions, err := k.GetUserPositions(ctx, sender.Address) + if err != nil { + return nil, fmt.Errorf("position does not exist") + } + + if len(positions) == 0 { + return nil, fmt.Errorf("user does not have any position") + } + + // pick a random position + randPosition := positions[rand.Intn(len(positions))] + + return &cltypes.MsgCollectFees{ + PoolId: randPosition.PoolId, + Sender: sender.Address.String(), + LowerTick: randPosition.LowerTick, + UpperTick: randPosition.UpperTick, + }, nil +} + // createPoolRestriction creates specific restriction for the creation of a pool. func createPoolRestriction(k clkeeper.Keeper, sim *osmosimtypes.SimCtx, ctx sdk.Context) osmosimtypes.SimAccountConstraint { return func(acc legacysimulationtype.Account) bool { @@ -70,3 +185,70 @@ func createPoolRestriction(k clkeeper.Keeper, sim *osmosimtypes.SimCtx, ctx sdk. return hasTwoCoins && hasPoolCreationFee } } + +// getRandCLPool gets a concentrated liquidity pool with its pool denoms. +func getRandCLPool(k clkeeper.Keeper, sim *osmosimtypes.SimCtx, ctx sdk.Context) (cltypes.ConcentratedPoolExtension, []string, error) { + rand := sim.GetRand() + + // get all pools + clPools, err := k.GetAllPools(ctx) + if err != nil { + return nil, nil, err + } + + numPools := len(clPools) + if numPools == 0 { + return nil, nil, fmt.Errorf("no pools created") + } + + randConcentratedPool := clPools[rand.Intn(numPools)] + poolDenoms := []string{randConcentratedPool.GetToken0(), randConcentratedPool.GetToken1()} + + return randConcentratedPool, poolDenoms, err +} + +// getRandomTickPositions returns random lowerTick and upperTick divisible by tickSpacing value. +func getRandomTickPositions(sim *osmosimtypes.SimCtx, minTick, maxTick int64, tickSpacing uint64) (int64, int64, error) { + lowerTick, err := RandomTickDivisibility(sim, minTick, maxTick, tickSpacing) + if err != nil { + return 0, 0, err + } + + if lowerTick == -1 { + return 0, 0, fmt.Errorf("random lowertick divisible by tickSpacing not found") + } + + upperTick, err := RandomTickDivisibility(sim, lowerTick, maxTick, tickSpacing) + if err != nil { + return 0, 0, err + } + + if upperTick == -1 { + return 0, 0, fmt.Errorf("random lowertick divisible by tickSpacing not found") + } + + if lowerTick == upperTick { + return 0, 0, fmt.Errorf("lower tick and upper tick cannot be the same") + } + + return lowerTick, upperTick, nil +} + +// RandomTickDivisibility calculates a random number between minTick - maxTick (inclusive) that is divisible by tickSpacing +func RandomTickDivisibility(sim *osmosimtypes.SimCtx, minTick int64, maxTick int64, tickSpacing uint64) (int64, error) { + rand := sim.GetRand() + + // Generate a random number in the range [minTick, maxTick] + randomNumber := rand.Int63n(maxTick-minTick+1) + minTick + + // Find the next multiple of x that is greater than or equal to the random number + nextMultiple := ((randomNumber + int64(tickSpacing) - 1) / int64(tickSpacing)) * int64(tickSpacing) + + // If the next multiple is within the range [a, b], return it + if nextMultiple >= minTick && nextMultiple <= maxTick { + return nextMultiple, nil + } + + // If the next multiple is not within the range [a, b], return -1 + return int64(-1), nil +} diff --git a/x/concentrated-liquidity/swaps_test.go b/x/concentrated-liquidity/swaps_test.go index 1aa19ce2ebd..facf6d31871 100644 --- a/x/concentrated-liquidity/swaps_test.go +++ b/x/concentrated-liquidity/swaps_test.go @@ -1674,6 +1674,175 @@ func (s *KeeperTestSuite) TestSwapExactAmountOut() { } } +// TestCalcOutAmtGivenInWriteCtx tests that writeCtx successfully performs state changes as expected. +// We expect writeCtx to only change fee accum state, since pool state change is not handled via writeCtx function. +func (s *KeeperTestSuite) TestCalcOutAmtGivenInWriteCtx() { + // we only use fee cases here since write Ctx only takes effect in the fee accumulator + tests := make(map[string]SwapTest, len(swapOutGivenInFeeCases)) + + for name, test := range swapOutGivenInFeeCases { + tests[name] = test + } + + for name, test := range tests { + test := test + s.Run(name, func() { + s.Setup() + s.FundAcc(s.TestAccs[0], sdk.NewCoins(sdk.NewCoin("eth", sdk.NewInt(10000000000000)), sdk.NewCoin("usdc", sdk.NewInt(1000000000000)))) + s.FundAcc(s.TestAccs[1], sdk.NewCoins(sdk.NewCoin("eth", sdk.NewInt(10000000000000)), sdk.NewCoin("usdc", sdk.NewInt(1000000000000)))) + + // Create default CL pool + pool := s.PrepareConcentratedPool() + + // add default position + s.SetupDefaultPosition(pool.GetId()) + + // add second position depending on the test + if !test.secondPositionLowerPrice.IsNil() { + newLowerTick, err := math.PriceToTick(test.secondPositionLowerPrice, DefaultExponentAtPriceOne) + s.Require().NoError(err) + newUpperTick, err := math.PriceToTick(test.secondPositionUpperPrice, DefaultExponentAtPriceOne) + s.Require().NoError(err) + + _, _, _, err = s.App.ConcentratedLiquidityKeeper.CreatePosition(s.Ctx, pool.GetId(), s.TestAccs[1], DefaultAmt0, DefaultAmt1, sdk.ZeroInt(), sdk.ZeroInt(), newLowerTick.Int64(), newUpperTick.Int64(), s.Ctx.BlockTime().Add(DefaultFreezeDuration)) + s.Require().NoError(err) + } + + poolBeforeCalc, err := s.App.ConcentratedLiquidityKeeper.GetPoolById(s.Ctx, pool.GetId()) + s.Require().NoError(err) + + // perform calc + writeCtx, _, _, _, _, _, err := s.App.ConcentratedLiquidityKeeper.CalcOutAmtGivenInInternal( + s.Ctx, + test.tokenIn, test.tokenOutDenom, + test.swapFee, test.priceLimit, pool.GetId()) + s.Require().NoError(err) + + // check that the pool has not been modified after performing calc + poolAfterCalc, err := s.App.ConcentratedLiquidityKeeper.GetPoolById(s.Ctx, pool.GetId()) + s.Require().NoError(err) + + s.Require().Equal(poolBeforeCalc.GetCurrentSqrtPrice(), poolAfterCalc.GetCurrentSqrtPrice()) + s.Require().Equal(poolBeforeCalc.GetCurrentTick(), poolAfterCalc.GetCurrentTick()) + s.Require().Equal(poolBeforeCalc.GetTotalShares(), poolAfterCalc.GetTotalShares()) + s.Require().Equal(poolBeforeCalc.GetLiquidity(), poolAfterCalc.GetLiquidity()) + s.Require().Equal(poolBeforeCalc.GetTickSpacing(), poolAfterCalc.GetTickSpacing()) + + feeAccum, err := s.App.ConcentratedLiquidityKeeper.GetFeeAccumulator(s.Ctx, 1) + s.Require().NoError(err) + + feeAccumValue := feeAccum.GetValue() + s.Require().Equal(0, feeAccumValue.Len()) + s.Require().Equal(1, + additiveFeeGrowthGlobalErrTolerance.CompareBigDec( + osmomath.BigDecFromSDKDec(test.expectedFeeGrowthAccumulatorValue), + osmomath.BigDecFromSDKDec(feeAccum.GetValue().AmountOf(test.tokenIn.Denom)), + ), + ) + + // System under test + writeCtx() + + // now we check that fee accum has been correctly updated upon writeCtx + feeAccum, err = s.App.ConcentratedLiquidityKeeper.GetFeeAccumulator(s.Ctx, 1) + s.Require().NoError(err) + + feeAccumValue = feeAccum.GetValue() + s.Require().Equal(1, feeAccumValue.Len()) + s.Require().Equal(0, + additiveFeeGrowthGlobalErrTolerance.CompareBigDec( + osmomath.BigDecFromSDKDec(test.expectedFeeGrowthAccumulatorValue), + osmomath.BigDecFromSDKDec(feeAccum.GetValue().AmountOf(test.tokenIn.Denom)), + ), + ) + }) + } +} + +// TestCalcInAmtGivenOutWriteCtx tests that writeCtx succesfully perfroms state changes as expected. +// We expect writeCtx to only change fee accum state, since pool state change is not handled via writeCtx function. +func (s *KeeperTestSuite) TestCalcInAmtGivenOutWriteCtx() { + // we only use fee cases here since write Ctx only takes effect in the fee accumulator + tests := make(map[string]SwapTest, len(swapInGivenOutFeeTestCases)) + + for name, test := range swapInGivenOutFeeTestCases { + tests[name] = test + } + + for name, test := range tests { + test := test + s.Run(name, func() { + s.Setup() + s.FundAcc(s.TestAccs[0], sdk.NewCoins(sdk.NewCoin("eth", sdk.NewInt(10000000000000)), sdk.NewCoin("usdc", sdk.NewInt(1000000000000)))) + s.FundAcc(s.TestAccs[1], sdk.NewCoins(sdk.NewCoin("eth", sdk.NewInt(10000000000000)), sdk.NewCoin("usdc", sdk.NewInt(1000000000000)))) + + // Create default CL pool + pool := s.PrepareConcentratedPool() + + // add default position + s.SetupDefaultPosition(pool.GetId()) + + // add second position depending on the test + if !test.secondPositionLowerPrice.IsNil() { + newLowerTick, err := math.PriceToTick(test.secondPositionLowerPrice, DefaultExponentAtPriceOne) + s.Require().NoError(err) + newUpperTick, err := math.PriceToTick(test.secondPositionUpperPrice, DefaultExponentAtPriceOne) + s.Require().NoError(err) + + _, _, _, err = s.App.ConcentratedLiquidityKeeper.CreatePosition(s.Ctx, pool.GetId(), s.TestAccs[1], DefaultAmt0, DefaultAmt1, sdk.ZeroInt(), sdk.ZeroInt(), newLowerTick.Int64(), newUpperTick.Int64(), s.Ctx.BlockTime().Add(DefaultFreezeDuration)) + s.Require().NoError(err) + } + + poolBeforeCalc, err := s.App.ConcentratedLiquidityKeeper.GetPoolById(s.Ctx, pool.GetId()) + s.Require().NoError(err) + + // perform calc + writeCtx, _, _, _, _, _, err := s.App.ConcentratedLiquidityKeeper.CalcInAmtGivenOutInternal( + s.Ctx, + test.tokenOut, test.tokenInDenom, + test.swapFee, test.priceLimit, pool.GetId()) + s.Require().NoError(err) + + // check that the pool has not been modified after performing calc + poolAfterCalc, err := s.App.ConcentratedLiquidityKeeper.GetPoolById(s.Ctx, pool.GetId()) + s.Require().NoError(err) + + s.Require().Equal(poolBeforeCalc.GetCurrentSqrtPrice(), poolAfterCalc.GetCurrentSqrtPrice()) + s.Require().Equal(poolBeforeCalc.GetCurrentTick(), poolAfterCalc.GetCurrentTick()) + s.Require().Equal(poolBeforeCalc.GetTotalShares(), poolAfterCalc.GetTotalShares()) + s.Require().Equal(poolBeforeCalc.GetLiquidity(), poolAfterCalc.GetLiquidity()) + s.Require().Equal(poolBeforeCalc.GetTickSpacing(), poolAfterCalc.GetTickSpacing()) + + feeAccum, err := s.App.ConcentratedLiquidityKeeper.GetFeeAccumulator(s.Ctx, 1) + s.Require().NoError(err) + + feeAccumValue := feeAccum.GetValue() + s.Require().Equal(0, feeAccumValue.Len()) + s.Require().Equal(1, + additiveFeeGrowthGlobalErrTolerance.CompareBigDec( + osmomath.BigDecFromSDKDec(test.expectedFeeGrowthAccumulatorValue), + osmomath.BigDecFromSDKDec(feeAccum.GetValue().AmountOf(test.tokenInDenom)), + ), + ) + + // System under test + writeCtx() + + // now we check that fee accum has been correctly updated upon writeCtx + feeAccum, err = s.App.ConcentratedLiquidityKeeper.GetFeeAccumulator(s.Ctx, 1) + s.Require().NoError(err) + + feeAccumValue = feeAccum.GetValue() + s.Require().Equal(1, feeAccumValue.Len()) + s.Require().Equal(0, + additiveFeeGrowthGlobalErrTolerance.CompareBigDec( + osmomath.BigDecFromSDKDec(test.expectedFeeGrowthAccumulatorValue), + osmomath.BigDecFromSDKDec(feeAccum.GetValue().AmountOf(test.tokenInDenom)), + ), + ) + }) + } +} func (s *KeeperTestSuite) TestInverseRelationshipSwapOutAmtGivenIn() { tests := swapOutGivenInCases diff --git a/x/concentrated-liquidity/tick.go b/x/concentrated-liquidity/tick.go index 37bc5f4d13a..a8412dbfe0a 100644 --- a/x/concentrated-liquidity/tick.go +++ b/x/concentrated-liquidity/tick.go @@ -110,7 +110,6 @@ func (k Keeper) SetTickInfo(ctx sdk.Context, poolId uint64, tickIndex int64, tic // That is, both lower and upper ticks are within MinTick and MaxTick range for the given exponentAtPriceOne. // Also, lower tick must be less than upper tick. // Returns error if validation fails. Otherwise, nil. -// TODO: test func validateTickRangeIsValid(tickSpacing uint64, exponentAtPriceOne sdk.Int, lowerTick int64, upperTick int64) error { minTick, maxTick := GetMinAndMaxTicksFromExponentAtPriceOne(exponentAtPriceOne) // Check if the lower and upper tick values are divisible by the tick spacing. diff --git a/x/concentrated-liquidity/tick_test.go b/x/concentrated-liquidity/tick_test.go index 4d829bb7031..0bf801cb4ff 100644 --- a/x/concentrated-liquidity/tick_test.go +++ b/x/concentrated-liquidity/tick_test.go @@ -553,3 +553,110 @@ func (s *KeeperTestSuite) TestGetLiquidityDepthFromIterator() { }) } } + +func (s *KeeperTestSuite) TestValidateTickRangeIsValid() { + // use 2 as default tick spacing + defaultTickSpacing := uint64(2) + + tests := []struct { + name string + lowerTick int64 + upperTick int64 + tickSpacing uint64 + expectedError error + }{ + { + name: "lower tick is not divisible by deafult tick spacing", + lowerTick: 3, + upperTick: 2, + expectedError: types.TickSpacingError{LowerTick: 3, UpperTick: 2, TickSpacing: defaultTickSpacing}, + }, + { + name: "upper tick is not divisible by default tick spacing", + lowerTick: 2, + upperTick: 3, + expectedError: types.TickSpacingError{LowerTick: 2, UpperTick: 3, TickSpacing: defaultTickSpacing}, + }, + { + name: "lower tick is not divisible by tick spacing", + lowerTick: 4, + upperTick: 3, + tickSpacing: 3, + expectedError: types.TickSpacingError{LowerTick: 4, UpperTick: 3, TickSpacing: 3}, + }, + { + name: "upper tick is not divisible by tick spacing", + lowerTick: 3, + upperTick: 4, + tickSpacing: 3, + expectedError: types.TickSpacingError{LowerTick: 3, UpperTick: 4, TickSpacing: 3}, + }, + { + name: "lower tick is smaller than min tick", + lowerTick: DefaultMinTick - 2, + upperTick: 2, + expectedError: types.InvalidTickError{Tick: DefaultMinTick - 2, IsLower: true, MinTick: DefaultMinTick, MaxTick: DefaultMaxTick}, + }, + { + name: "lower tick is greater than max tick", + lowerTick: DefaultMaxTick + 2, + upperTick: DefaultMaxTick + 4, + expectedError: types.InvalidTickError{Tick: DefaultMaxTick + 2, IsLower: true, MinTick: DefaultMinTick, MaxTick: DefaultMaxTick}, + }, + { + name: "upper tick is smaller than min tick", + lowerTick: 2, + upperTick: DefaultMinTick - 2, + expectedError: types.InvalidTickError{Tick: DefaultMinTick - 2, IsLower: false, MinTick: DefaultMinTick, MaxTick: DefaultMaxTick}, + }, + { + + name: "upper tick is greater than max tick", + lowerTick: 2, + upperTick: DefaultMaxTick + 2, + expectedError: types.InvalidTickError{Tick: DefaultMaxTick + 2, IsLower: false, MinTick: DefaultMinTick, MaxTick: DefaultMaxTick}, + }, + { + name: "lower tick is greater than upper tick", + lowerTick: 2, + upperTick: 0, + + expectedError: types.InvalidLowerUpperTickError{LowerTick: 2, UpperTick: 0}, + }, + { + name: "happy path with default tick spacing", + lowerTick: 2, + upperTick: 4, + }, + { + name: "happy path with non default tick spacing", + tickSpacing: 3, + lowerTick: 3, + upperTick: 6, + }, + } + + for _, test := range tests { + s.Run(test.name, func() { + s.Setup() + + // use default exponent at price one + exponentAtPriceOne := DefaultExponentAtPriceOne + + tickSpacing := defaultTickSpacing + if test.tickSpacing != uint64(0) { + tickSpacing = test.tickSpacing + } + + // System Under Test + err := cl.ValidateTickInRangeIsValid(tickSpacing, exponentAtPriceOne, test.lowerTick, test.upperTick) + + if test.expectedError != nil { + s.Require().Error(err) + s.Require().ErrorContains(err, test.expectedError.Error()) + } else { + s.Require().NoError(err) + } + }) + } +} diff --git a/x/gamm/keeper/genesis_test.go b/x/gamm/keeper/genesis_test.go index 5207db8cd34..b186733822b 100644 --- a/x/gamm/keeper/genesis_test.go +++ b/x/gamm/keeper/genesis_test.go @@ -18,10 +18,10 @@ import ( ) var ( - defaultMigrationRecords = types.MigrationRecords{BalancerToConcentratedPoolLinks: []types.BalancerToConcentratedPoolLink{ - {BalancerPoolId: 1, ClPoolId: 50}, - {BalancerPoolId: 2, ClPoolId: 51}, - {BalancerPoolId: 3, ClPoolId: 52}, + DefaultMigrationRecords = types.MigrationRecords{BalancerToConcentratedPoolLinks: []types.BalancerToConcentratedPoolLink{ + {BalancerPoolId: 1, ClPoolId: 4}, + {BalancerPoolId: 2, ClPoolId: 5}, + {BalancerPoolId: 3, ClPoolId: 6}, }} ) @@ -53,7 +53,7 @@ func TestGammInitGenesis(t *testing.T) { Params: types.Params{ PoolCreationFee: sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000_000_000)}, }, - MigrationRecords: &defaultMigrationRecords, + MigrationRecords: &DefaultMigrationRecords, }, app.AppCodec()) require.Equal(t, app.PoolManagerKeeper.GetNextPoolId(ctx), uint64(1)) @@ -75,7 +75,7 @@ func TestGammInitGenesis(t *testing.T) { require.Equal(t, liquidity, sdk.Coins{sdk.NewInt64Coin("nodetoken", 10), sdk.NewInt64Coin(sdk.DefaultBondDenom, 10)}) postInitGenMigrationRecords := app.GAMMKeeper.GetMigrationInfo(ctx) - require.Equal(t, defaultMigrationRecords, postInitGenMigrationRecords) + require.Equal(t, DefaultMigrationRecords, postInitGenMigrationRecords) } func TestGammExportGenesis(t *testing.T) { @@ -116,7 +116,7 @@ func TestGammExportGenesis(t *testing.T) { _, err = app.PoolManagerKeeper.CreatePool(ctx, msg) require.NoError(t, err) - app.GAMMKeeper.SetMigrationInfo(ctx, defaultMigrationRecords) + app.GAMMKeeper.SetMigrationInfo(ctx, DefaultMigrationRecords) genesis := app.GAMMKeeper.ExportGenesis(ctx) // Note: the next pool number index has been migrated to @@ -126,7 +126,7 @@ func TestGammExportGenesis(t *testing.T) { // in a subsequent upgrade. require.Equal(t, genesis.NextPoolNumber, uint64(1)) require.Len(t, genesis.Pools, 2) - require.Equal(t, genesis.MigrationRecords, &defaultMigrationRecords) + require.Equal(t, genesis.MigrationRecords, &DefaultMigrationRecords) } func TestMarshalUnmarshalGenesis(t *testing.T) { @@ -157,7 +157,7 @@ func TestMarshalUnmarshalGenesis(t *testing.T) { _, err = app.PoolManagerKeeper.CreatePool(ctx, msg) require.NoError(t, err) - app.GAMMKeeper.SetMigrationInfo(ctx, defaultMigrationRecords) + app.GAMMKeeper.SetMigrationInfo(ctx, DefaultMigrationRecords) genesis := am.ExportGenesis(ctx, appCodec) assert.NotPanics(t, func() { diff --git a/x/gamm/keeper/migrate.go b/x/gamm/keeper/migrate.go index 5849f9f0b9b..f0ae82527f2 100644 --- a/x/gamm/keeper/migrate.go +++ b/x/gamm/keeper/migrate.go @@ -6,7 +6,6 @@ import ( "time" "github.com/osmosis-labs/osmosis/osmoutils" - cl "github.com/osmosis-labs/osmosis/v14/x/concentrated-liquidity" cltypes "github.com/osmosis-labs/osmosis/v14/x/concentrated-liquidity/types" "github.com/osmosis-labs/osmosis/v14/x/gamm/types" poolmanagertypes "github.com/osmosis-labs/osmosis/v14/x/poolmanager/types" @@ -14,56 +13,43 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) Migrate(ctx sdk.Context, sender sdk.AccAddress, sharesToMigrate sdk.Coin, poolIdEntering uint64) (amount0, amount1 sdk.Int, liquidity sdk.Dec, poolIdLeaving uint64, err error) { +// MigrateFromBalancerToConcentrated migrates unlocked lp tokens from a balancer pool to a concentrated liquidity pool. +// Fails if the lp tokens are locked (must utilize UnlockAndMigrate function in the superfluid module) +func (k Keeper) MigrateFromBalancerToConcentrated(ctx sdk.Context, sender sdk.AccAddress, sharesToMigrate sdk.Coin) (amount0, amount1 sdk.Int, liquidity sdk.Dec, poolIdLeaving, poolIdEntering uint64, err error) { // Get the balancer poolId by parsing the gamm share denom. - poolIdLeaving, err = getPoolIdFromSharesDenom(sharesToMigrate.Denom) + poolIdLeaving, err = types.GetPoolIdFromShareDenom(sharesToMigrate.Denom) if err != nil { - return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, err + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, err } - // Ensure a governance sanctioned link exists between the balancer pool and the concentrated pool. - migrationInfo := k.GetMigrationInfo(ctx) - matchFound := false - for _, info := range migrationInfo.BalancerToConcentratedPoolLinks { - if info.BalancerPoolId == poolIdLeaving { - if info.ClPoolId != poolIdEntering { - return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, types.InvalidPoolMigrationLinkError{PoolIdEntering: poolIdEntering, CanonicalId: info.ClPoolId} - } - matchFound = true - break - } - } - if !matchFound { - return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, types.PoolMigrationLinkNotFoundError{PoolIdLeaving: poolIdLeaving} + // Find the governance sanctioned link between the balancer pool and a concentrated pool. + poolIdEntering, err = k.GetLinkedConcentratedPoolID(ctx, poolIdLeaving) + if err != nil { + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, err } // Get the concentrated pool from the message and type cast it to ConcentratedPoolExtension. - poolI, err := k.clKeeper.GetPool(ctx, poolIdEntering) + concentratedPool, err := k.clKeeper.GetPoolFromPoolIdAndConvertToConcentrated(ctx, poolIdEntering) if err != nil { - return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, err - } - concentratedPool, ok := poolI.(cltypes.ConcentratedPoolExtension) - if !ok { - // If the conversion fails, return an error. - return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, fmt.Errorf("given pool does not implement ConcentratedPoolExtension, implements %T", poolI) + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, err } // Exit the balancer pool position. exitCoins, err := k.ExitPool(ctx, sender, poolIdLeaving, sharesToMigrate.Amount, sdk.NewCoins()) if err != nil { - return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, err + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, err + } + // Defense in depth, ensuring we are returning exactly two coins. + if len(exitCoins) != 2 { + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, fmt.Errorf("Balancer pool must have exactly two tokens") } - - // Determine the max and min ticks for the concentrated pool we are migrating to. - minTick, maxTick := cl.GetMinAndMaxTicksFromExponentAtPriceOne(concentratedPool.GetPrecisionFactorAtPriceOne()) // Create a full range (min to max tick) concentrated liquidity position. - // TODO: Will need to implement lock breaking logic and add the corresponding freeze duration here. - amount0, amount1, liquidity, err = k.clKeeper.CreatePosition(ctx, poolIdEntering, sender, exitCoins.AmountOf(concentratedPool.GetToken0()), exitCoins.AmountOf(concentratedPool.GetToken1()), sdk.ZeroInt(), sdk.ZeroInt(), minTick, maxTick, time.Time{}) + amount0, amount1, liquidity, err = k.clKeeper.CreateFullRangePosition(ctx, concentratedPool, sender, exitCoins, time.Time{}) if err != nil { - return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, err + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, err } - return amount0, amount1, liquidity, poolIdLeaving, nil + return amount0, amount1, liquidity, poolIdLeaving, poolIdEntering, nil } // GetMigrationInfo returns the balancer to gamm pool migration info from the store @@ -133,10 +119,10 @@ func (k Keeper) validateRecords(ctx sdk.Context, records []types.BalancerToConce } // If clPoolID is 0, this signals a removal, so we skip this check. - var clPool poolmanagertypes.PoolI + var clPool cltypes.ConcentratedPoolExtension if record.ClPoolId != 0 { // Ensure the provided ClPoolId exists and that it is of type concentrated. - clPool, err = k.clKeeper.GetPool(ctx, record.ClPoolId) + clPool, err = k.clKeeper.GetPoolFromPoolIdAndConvertToConcentrated(ctx, record.ClPoolId) if err != nil { return err } @@ -152,16 +138,11 @@ func (k Keeper) validateRecords(ctx sdk.Context, records []types.BalancerToConce return fmt.Errorf("Balancer pool ID #%d does not contain exactly 2 tokens", record.BalancerPoolId) } - clPoolExt, ok := clPool.(cltypes.ConcentratedPoolExtension) - if !ok { - return fmt.Errorf("pool type (%T) cannot be cast to ConcentratedPoolExtension", clPool) + if balancerPoolAssets.AmountOf(clPool.GetToken0()).IsZero() { + return fmt.Errorf("Balancer pool ID #%d does not contain token %s", record.BalancerPoolId, clPool.GetToken0()) } - - if balancerPoolAssets.AmountOf(clPoolExt.GetToken0()).IsZero() { - return fmt.Errorf("Balancer pool ID #%d does not contain token %s", record.BalancerPoolId, clPoolExt.GetToken0()) - } - if balancerPoolAssets.AmountOf(clPoolExt.GetToken1()).IsZero() { - return fmt.Errorf("Balancer pool ID #%d does not contain token %s", record.BalancerPoolId, clPoolExt.GetToken1()) + if balancerPoolAssets.AmountOf(clPool.GetToken1()).IsZero() { + return fmt.Errorf("Balancer pool ID #%d does not contain token %s", record.BalancerPoolId, clPool.GetToken1()) } } @@ -229,3 +210,16 @@ func (k Keeper) UpdateMigrationRecords(ctx sdk.Context, records []types.Balancer }) return nil } + +// GetLinkedConcentratedPoolID checks if a governance sanctioned link exists between the provided balancer pool and a concentrated pool. +// If a link exists, it returns the concentrated pool ID. +// If a link does not exist, it returns a 0 pool ID an error. +func (k Keeper) GetLinkedConcentratedPoolID(ctx sdk.Context, poolIdLeaving uint64) (poolIdEntering uint64, err error) { + migrationInfo := k.GetMigrationInfo(ctx) + for _, info := range migrationInfo.BalancerToConcentratedPoolLinks { + if info.BalancerPoolId == poolIdLeaving { + return info.ClPoolId, nil + } + } + return 0, types.PoolMigrationLinkNotFoundError{PoolIdLeaving: poolIdLeaving} +} diff --git a/x/gamm/keeper/migrate_test.go b/x/gamm/keeper/migrate_test.go index aec0c152341..da30a1b0d95 100644 --- a/x/gamm/keeper/migrate_test.go +++ b/x/gamm/keeper/migrate_test.go @@ -27,7 +27,6 @@ func (suite *KeeperTestSuite) TestMigrate() { sender sdk.AccAddress sharesToMigrateDenom string sharesToMigrateAmount sdk.Int - poolIdEntering uint64 } tests := []struct { @@ -45,7 +44,6 @@ func (suite *KeeperTestSuite) TestMigrate() { sender: defaultAccount, sharesToMigrateDenom: defaultGammShares.Denom, sharesToMigrateAmount: defaultGammShares.Amount, - poolIdEntering: 2, }, sharesToCreate: defaultGammShares.Amount, expectedPosition: &model.Position{Liquidity: sdk.MustNewDecFromStr("100000000000.000000010000000000")}, @@ -58,7 +56,6 @@ func (suite *KeeperTestSuite) TestMigrate() { sender: defaultAccount, sharesToMigrateDenom: defaultGammShares.Denom, sharesToMigrateAmount: defaultGammShares.Amount, - poolIdEntering: 2, }, sharesToCreate: defaultGammShares.Amount, expectedPosition: &model.Position{Liquidity: sdk.MustNewDecFromStr("100000000000.000000010000000000")}, @@ -72,7 +69,6 @@ func (suite *KeeperTestSuite) TestMigrate() { sender: defaultAccount, sharesToMigrateDenom: defaultGammShares.Denom, sharesToMigrateAmount: defaultGammShares.Amount.Quo(sdk.NewInt(2)), - poolIdEntering: 2, }, sharesToCreate: defaultGammShares.Amount, expectedPosition: &model.Position{Liquidity: sdk.MustNewDecFromStr("50000000000.000000005000000000")}, @@ -85,7 +81,6 @@ func (suite *KeeperTestSuite) TestMigrate() { sender: defaultAccount, sharesToMigrateDenom: defaultGammShares.Denom, sharesToMigrateAmount: defaultGammShares.Amount.Quo(sdk.NewInt(2)), - poolIdEntering: 2, }, sharesToCreate: defaultGammShares.Amount.Mul(sdk.NewInt(2)), expectedPosition: &model.Position{Liquidity: sdk.MustNewDecFromStr("49999999999.000000004999999999")}, @@ -98,25 +93,12 @@ func (suite *KeeperTestSuite) TestMigrate() { sender: defaultAccount, sharesToMigrateDenom: defaultGammShares.Denom, sharesToMigrateAmount: invalidGammShares.Amount, - poolIdEntering: 2, }, sharesToCreate: defaultGammShares.Amount, expectedPosition: &model.Position{Liquidity: sdk.MustNewDecFromStr("100000000000.000000010000000000")}, setupPoolMigrationLink: true, expectedErr: sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, fmt.Sprintf("%s is smaller than %s", defaultGammShares, invalidGammShares)), }, - { - name: "error: poolIdEntering is not the canonical link", - param: param{ - sender: defaultAccount, - sharesToMigrateDenom: defaultGammShares.Denom, - sharesToMigrateAmount: defaultGammShares.Amount, - poolIdEntering: 1, - }, - sharesToCreate: defaultGammShares.Amount, - setupPoolMigrationLink: true, - expectedErr: types.InvalidPoolMigrationLinkError{PoolIdEntering: 1, CanonicalId: 2}, - }, } for _, test := range tests { @@ -162,12 +144,16 @@ func (suite *KeeperTestSuite) TestMigrate() { // Migrate the user's gamm shares to a full range concentrated liquidity position userBalancesBeforeMigration := suite.App.BankKeeper.GetAllBalances(suite.Ctx, test.param.sender) - amount0, amount1, _, _, err := keeper.Migrate(suite.Ctx, test.param.sender, sharesToMigrate, test.param.poolIdEntering) + amount0, amount1, _, poolIdLeaving, poolIdEntering, err := keeper.MigrateFromBalancerToConcentrated(suite.Ctx, test.param.sender, sharesToMigrate) userBalancesAfterMigration := suite.App.BankKeeper.GetAllBalances(suite.Ctx, test.param.sender) if test.expectedErr != nil { suite.Require().Error(err) suite.Require().ErrorContains(err, test.expectedErr.Error()) + // Expect zero values for both pool ids + suite.Require().Zero(poolIdLeaving) + suite.Require().Zero(poolIdEntering) + // Assure the user's gamm shares still exist userGammBalanceAfterFailedMigration := suite.App.BankKeeper.GetBalance(suite.Ctx, test.param.sender, "gamm/pool/1") suite.Require().Equal(userGammBalancePostJoin.String(), userGammBalanceAfterFailedMigration.String()) @@ -186,6 +172,11 @@ func (suite *KeeperTestSuite) TestMigrate() { } suite.Require().NoError(err) + // Expect the poolIdLeaving to be the balancer pool id + // Expect the poolIdEntering to be the concentrated liquidity pool id + suite.Require().Equal(balancerPoolId, poolIdLeaving) + suite.Require().Equal(clPool.GetId(), poolIdEntering) + // Determine how much of the user's balance was not used in the migration // This amount should be returned to the user. expectedUserFinalEthBalanceDiff := expectedCoinsOut.AmountOf(ETH).Sub(amount0) @@ -674,3 +665,56 @@ func (suite *KeeperTestSuite) TestUpdateMigrationRecords() { }) } } + +func (suite *KeeperTestSuite) TestGetLinkedConcentratedPoolID() { + tests := []struct { + name string + poolIdLeaving []uint64 + expectedPoolIdEntering []uint64 + expectErr bool + }{ + { + name: "Happy path", + poolIdLeaving: []uint64{1, 2, 3}, + expectedPoolIdEntering: []uint64{4, 5, 6}, + expectErr: false, + }, + { + name: "error: set poolIdLeaving to a concentrated pool ID", + poolIdLeaving: []uint64{4}, + expectErr: true, + }, + { + name: "error: set poolIdLeaving to a non existent pool ID", + poolIdLeaving: []uint64{7}, + expectErr: true, + }, + } + + for _, test := range tests { + test := test + suite.Run(test.name, func() { + suite.SetupTest() + keeper := suite.App.GAMMKeeper + + // Our testing environment is as follows: + // Balancer pool IDs: 1, 2, 3 + // Concentrated pool IDs: 3, 4, 5 + suite.PrepareMultipleBalancerPools(3) + suite.PrepareMultipleConcentratedPools(3) + + keeper.SetMigrationInfo(suite.Ctx, DefaultMigrationRecords) + + for i, poolIdLeaving := range test.poolIdLeaving { + poolIdEntering, err := keeper.GetLinkedConcentratedPoolID(suite.Ctx, poolIdLeaving) + if test.expectErr { + suite.Require().Error(err) + suite.Require().Zero(poolIdEntering) + } else { + suite.Require().NoError(err) + suite.Require().Equal(test.expectedPoolIdEntering[i], poolIdEntering) + } + } + }) + } +} diff --git a/x/gamm/keeper/msg_server.go b/x/gamm/keeper/msg_server.go index d06732ec75c..26e8efbc4b6 100644 --- a/x/gamm/keeper/msg_server.go +++ b/x/gamm/keeper/msg_server.go @@ -318,7 +318,7 @@ func (server msgServer) MigrateSharesToFullRangeConcentratedPosition(goCtx conte return nil, err } - amount0, amount1, liquidity, poolIdLeaving, err := server.keeper.Migrate(ctx, sender, msg.SharesToMigrate, msg.PoolIdEntering) + amount0, amount1, liquidity, poolIdLeaving, poolIdEntering, err := server.keeper.MigrateFromBalancerToConcentrated(ctx, sender, msg.SharesToMigrate) if err != nil { return nil, err } @@ -326,7 +326,7 @@ func (server msgServer) MigrateSharesToFullRangeConcentratedPosition(goCtx conte ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( types.TypeEvtMigrateShares, - sdk.NewAttribute(types.AttributeKeyPoolIdEntering, strconv.FormatUint(msg.PoolIdEntering, 10)), + sdk.NewAttribute(types.AttributeKeyPoolIdEntering, strconv.FormatUint(poolIdEntering, 10)), sdk.NewAttribute(types.AttributeKeyPoolIdLeaving, strconv.FormatUint(poolIdLeaving, 10)), ), sdk.NewEvent( diff --git a/x/gamm/keeper/msg_server_test.go b/x/gamm/keeper/msg_server_test.go index 4a14c3c9719..d215cd6e26e 100644 --- a/x/gamm/keeper/msg_server_test.go +++ b/x/gamm/keeper/msg_server_test.go @@ -367,7 +367,6 @@ func (suite *KeeperTestSuite) TestMsgMigrateShares_Events() { sender sdk.AccAddress sharesToMigrateDenom string sharesToMigrateAmount sdk.Int - poolIdEntering uint64 } tests := []struct { @@ -384,7 +383,6 @@ func (suite *KeeperTestSuite) TestMsgMigrateShares_Events() { sender: defaultAccount, sharesToMigrateDenom: defaultGammShares.Denom, sharesToMigrateAmount: defaultGammShares.Amount, - poolIdEntering: 2, }, sharesToCreate: defaultGammShares.Amount, expectedMigrateShareEvents: 1, @@ -396,7 +394,6 @@ func (suite *KeeperTestSuite) TestMsgMigrateShares_Events() { sender: defaultAccount, sharesToMigrateDenom: defaultGammShares.Denom, sharesToMigrateAmount: defaultGammShares.Amount.Quo(sdk.NewInt(2)), - poolIdEntering: 2, }, sharesToCreate: defaultGammShares.Amount, expectedMigrateShareEvents: 1, @@ -408,7 +405,6 @@ func (suite *KeeperTestSuite) TestMsgMigrateShares_Events() { sender: defaultAccount, sharesToMigrateDenom: defaultGammShares.Denom, sharesToMigrateAmount: defaultGammShares.Amount.Quo(sdk.NewInt(2)), - poolIdEntering: 2, }, sharesToCreate: defaultGammShares.Amount.Mul(sdk.NewInt(2)), expectedMigrateShareEvents: 1, @@ -420,18 +416,6 @@ func (suite *KeeperTestSuite) TestMsgMigrateShares_Events() { sender: defaultAccount, sharesToMigrateDenom: "gamm/pool/1000", sharesToMigrateAmount: defaultGammShares.Amount, - poolIdEntering: 2, - }, - sharesToCreate: defaultGammShares.Amount, - expectError: true, - }, - { - name: "error: attempt to migrate shares to non-existent pool", - param: param{ - sender: defaultAccount, - sharesToMigrateDenom: defaultGammShares.Denom, - sharesToMigrateAmount: defaultGammShares.Amount, - poolIdEntering: 3, }, sharesToCreate: defaultGammShares.Amount, expectError: true, @@ -442,7 +426,6 @@ func (suite *KeeperTestSuite) TestMsgMigrateShares_Events() { sender: defaultAccount, sharesToMigrateDenom: defaultGammShares.Denom, sharesToMigrateAmount: defaultGammShares.Amount.Add(sdk.NewInt(1)), - poolIdEntering: 2, }, sharesToCreate: defaultGammShares.Amount, expectedMessageEvents: 1, // 1 exitPool. @@ -473,7 +456,6 @@ func (suite *KeeperTestSuite) TestMsgMigrateShares_Events() { msg := &balancer.MsgMigrateSharesToFullRangeConcentratedPosition{ Sender: test.param.sender.String(), SharesToMigrate: sharesToMigrate, - PoolIdEntering: test.param.poolIdEntering, } // Reset event counts to 0 by creating a new manager. @@ -481,10 +463,16 @@ func (suite *KeeperTestSuite) TestMsgMigrateShares_Events() { suite.Require().Equal(0, len(suite.Ctx.EventManager().Events())) // Migrate the user's gamm shares to a full range concentrated liquidity position - _, err = msgServer.MigrateSharesToFullRangeConcentratedPosition(sdk.WrapSDKContext(suite.Ctx), msg) - - // Assert events are emitted - suite.AssertEventEmitted(suite.Ctx, types.TypeEvtMigrateShares, test.expectedMigrateShareEvents) - suite.AssertEventEmitted(suite.Ctx, sdk.EventTypeMessage, test.expectedMessageEvents) + response, err := msgServer.MigrateSharesToFullRangeConcentratedPosition(sdk.WrapSDKContext(suite.Ctx), msg) + + if !test.expectError { + suite.NoError(err) + suite.NotNil(response) + suite.AssertEventEmitted(suite.Ctx, types.TypeEvtMigrateShares, test.expectedMigrateShareEvents) + suite.AssertEventEmitted(suite.Ctx, sdk.EventTypeMessage, test.expectedMessageEvents) + } else { + suite.Require().Error(err) + suite.Require().Nil(response) + } } } diff --git a/x/gamm/keeper/pool.go b/x/gamm/keeper/pool.go index 4ecfd97912d..08791b45770 100644 --- a/x/gamm/keeper/pool.go +++ b/x/gamm/keeper/pool.go @@ -2,8 +2,6 @@ package keeper import ( "fmt" - "strconv" - "strings" gogotypes "github.com/gogo/protobuf/types" @@ -297,12 +295,3 @@ func convertToCFMMPool(pool poolmanagertypes.PoolI) (types.CFMMPoolI, error) { } return cfmmPool, nil } - -// getPoolIdFromSharesDenom takes in a string representing a pool share denom and extracts the pool ID. -// It returns the pool ID as a uint64 and an error if the denom is invalid. -func getPoolIdFromSharesDenom(denom string) (uint64, error) { - if !strings.HasPrefix(denom, "gamm/pool/") { - return 0, fmt.Errorf("invalid pool share denom %s", denom) - } - return strconv.ParseUint(denom[len("gamm/pool/"):], 10, 64) -} diff --git a/x/gamm/pool-models/balancer/msgs_test.go b/x/gamm/pool-models/balancer/msgs_test.go index d18593f6835..de63b6e2f23 100644 --- a/x/gamm/pool-models/balancer/msgs_test.go +++ b/x/gamm/pool-models/balancer/msgs_test.go @@ -302,7 +302,6 @@ func TestMsgMigrateSharesToFullRangeConcentratedPosition(t *testing.T) { properMsg := balancer.MsgMigrateSharesToFullRangeConcentratedPosition{ Sender: addr1, SharesToMigrate: gammShares, - PoolIdEntering: 2, } return after(properMsg) } diff --git a/x/gamm/pool-models/balancer/tx.pb.go b/x/gamm/pool-models/balancer/tx.pb.go index fcb1fb44358..53d3e053b57 100644 --- a/x/gamm/pool-models/balancer/tx.pb.go +++ b/x/gamm/pool-models/balancer/tx.pb.go @@ -148,8 +148,6 @@ func (m *MsgCreateBalancerPoolResponse) GetPoolID() uint64 { type MsgMigrateSharesToFullRangeConcentratedPosition struct { Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` SharesToMigrate types.Coin `protobuf:"bytes,2,opt,name=shares_to_migrate,json=sharesToMigrate,proto3" json:"shares_to_migrate" yaml:"shares_to_migrate"` - // temporary field, eventually gamm pool should be linked to cl pool - PoolIdEntering uint64 `protobuf:"varint,3,opt,name=pool_id_entering,json=poolIdEntering,proto3" json:"pool_id_entering,omitempty"` } func (m *MsgMigrateSharesToFullRangeConcentratedPosition) Reset() { @@ -203,13 +201,6 @@ func (m *MsgMigrateSharesToFullRangeConcentratedPosition) GetSharesToMigrate() t return types.Coin{} } -func (m *MsgMigrateSharesToFullRangeConcentratedPosition) GetPoolIdEntering() uint64 { - if m != nil { - return m.PoolIdEntering - } - return 0 -} - type MsgMigrateSharesToFullRangeConcentratedPositionResponse struct { Amount0 github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=amount0,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"amount0" yaml:"amount0"` Amount1 github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,2,opt,name=amount1,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"amount1" yaml:"amount1"` @@ -265,50 +256,48 @@ func init() { } var fileDescriptor_0647ee155de97433 = []byte{ - // 680 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x95, 0x4f, 0x6b, 0x13, 0x4f, - 0x18, 0xc7, 0xb3, 0x4d, 0x49, 0xe9, 0x94, 0x5f, 0x7f, 0xed, 0x52, 0x25, 0x46, 0xdc, 0x0d, 0x2b, - 0x48, 0x04, 0x33, 0x63, 0x6a, 0x41, 0xf0, 0x52, 0xdd, 0xd6, 0x96, 0x0a, 0x81, 0xba, 0x7a, 0x69, - 0x2f, 0x61, 0xb2, 0x3b, 0x6e, 0x17, 0x77, 0x67, 0xe2, 0xce, 0xa4, 0xb6, 0xef, 0xc2, 0x8b, 0x57, - 0x5f, 0x87, 0x2f, 0xa1, 0xc7, 0x1e, 0xc5, 0xc3, 0x22, 0xe9, 0x4d, 0x04, 0x21, 0xaf, 0x40, 0xe6, - 0x4f, 0xd2, 0xaa, 0x29, 0x1a, 0x8a, 0xa7, 0x6e, 0x9f, 0xf9, 0x3e, 0x9f, 0xe7, 0xf9, 0x3e, 0xf3, - 0xec, 0x06, 0x34, 0x19, 0xcf, 0x18, 0x4f, 0x38, 0x8a, 0x71, 0x96, 0xa1, 0x1e, 0x63, 0x69, 0x33, - 0x63, 0x11, 0x49, 0x39, 0xea, 0xe2, 0x14, 0xd3, 0x90, 0xe4, 0x48, 0x1c, 0x21, 0x71, 0x04, 0x7b, - 0x39, 0x13, 0xcc, 0x6e, 0x18, 0x39, 0x94, 0x72, 0x28, 0xe5, 0x5a, 0x0d, 0x47, 0x6a, 0x78, 0xd8, - 0xea, 0x12, 0x81, 0x5b, 0xb5, 0x95, 0x98, 0xc5, 0x4c, 0x25, 0x21, 0xf9, 0xa4, 0xf3, 0x6b, 0x6b, - 0x7f, 0x2e, 0x37, 0x7a, 0xd8, 0x65, 0x2c, 0x35, 0x59, 0x4e, 0xa8, 0xd2, 0x50, 0x17, 0x73, 0x82, - 0x4c, 0x01, 0x14, 0xb2, 0x84, 0xea, 0x73, 0xef, 0xe3, 0x0c, 0xb8, 0xd6, 0xe6, 0xf1, 0x46, 0x4e, - 0xb0, 0x20, 0xfe, 0x85, 0x7c, 0xfb, 0x2e, 0xa8, 0x70, 0x42, 0x23, 0x92, 0x57, 0xad, 0xba, 0xd5, - 0x98, 0xf7, 0x97, 0x87, 0x85, 0xfb, 0xdf, 0x31, 0xce, 0xd2, 0x47, 0x9e, 0x8e, 0x7b, 0x81, 0x11, - 0xd8, 0x7b, 0x60, 0x41, 0xf6, 0xd3, 0xe9, 0xe1, 0x1c, 0x67, 0xbc, 0x3a, 0x53, 0xb7, 0x1a, 0x0b, - 0xab, 0x75, 0xf8, 0x93, 0x61, 0x53, 0x1b, 0x4a, 0xf6, 0xae, 0xd2, 0xf9, 0xd7, 0x87, 0x85, 0x6b, - 0x6b, 0xe2, 0x85, 0x74, 0x2f, 0x00, 0xbd, 0xb1, 0xc6, 0xde, 0x32, 0x68, 0xcc, 0x39, 0x11, 0xbc, - 0x5a, 0xae, 0x97, 0x1b, 0x0b, 0xab, 0xee, 0xe5, 0xe8, 0x27, 0x52, 0xe7, 0xcf, 0x9e, 0x14, 0x6e, - 0x49, 0x73, 0x54, 0x80, 0xdb, 0xcf, 0xc1, 0xca, 0xab, 0xbe, 0xe8, 0xe7, 0xa4, 0xa3, 0x70, 0x31, - 0x3b, 0x24, 0x39, 0x65, 0x79, 0x75, 0x56, 0x79, 0x73, 0x87, 0x85, 0x7b, 0x53, 0x77, 0x32, 0x49, - 0xe5, 0x05, 0xb6, 0x0e, 0xcb, 0x0a, 0xdb, 0xa3, 0xe0, 0x26, 0xb8, 0x35, 0x71, 0x72, 0x01, 0xe1, - 0x3d, 0x46, 0x39, 0xb1, 0x6f, 0x83, 0x39, 0x85, 0x49, 0x22, 0x35, 0xc2, 0x59, 0x1f, 0x0c, 0x0a, - 0xb7, 0x22, 0x25, 0x3b, 0x9b, 0x41, 0x45, 0x1e, 0xed, 0x44, 0xde, 0x77, 0x0b, 0xa0, 0x36, 0x8f, - 0xdb, 0x49, 0x9c, 0x63, 0x41, 0x5e, 0x1c, 0xe0, 0x9c, 0xf0, 0x97, 0x6c, 0xab, 0x9f, 0xa6, 0x01, - 0xa6, 0x31, 0xd9, 0x60, 0x34, 0x24, 0x54, 0xc8, 0xb3, 0x68, 0x97, 0xf1, 0x44, 0x24, 0x8c, 0x4e, - 0x73, 0x35, 0x31, 0x58, 0xe6, 0x8a, 0xd9, 0x11, 0xac, 0x93, 0xe9, 0x22, 0xe6, 0x82, 0x6e, 0x40, - 0xbd, 0x1b, 0x50, 0xee, 0xc6, 0x78, 0x88, 0x1b, 0x2c, 0xa1, 0x7e, 0x5d, 0xce, 0x6f, 0x58, 0xb8, - 0x55, 0x03, 0xfd, 0x95, 0xe0, 0x05, 0xff, 0x73, 0xd3, 0xa9, 0x69, 0xdc, 0x6e, 0x80, 0x25, 0x63, - 0xb6, 0x43, 0xa8, 0x20, 0x79, 0x42, 0xe3, 0x6a, 0x59, 0xba, 0x0e, 0x16, 0xb5, 0xd3, 0xa7, 0x26, - 0xea, 0x7d, 0x9d, 0x01, 0x0f, 0xa7, 0x74, 0x3c, 0x1e, 0xe9, 0x3e, 0x98, 0xc3, 0x19, 0xeb, 0x53, - 0x71, 0xdf, 0x58, 0x7f, 0x2c, 0x3b, 0xfd, 0x5c, 0xb8, 0x77, 0xe2, 0x44, 0x1c, 0xf4, 0xbb, 0x30, - 0x64, 0x19, 0x32, 0x2b, 0xaf, 0xff, 0x34, 0x79, 0xf4, 0x1a, 0x89, 0xe3, 0x1e, 0xe1, 0x70, 0x87, - 0x8a, 0x61, 0xe1, 0x2e, 0x6a, 0x4f, 0x06, 0xe3, 0x05, 0x23, 0xe0, 0x39, 0xbb, 0xa5, 0x06, 0x74, - 0x65, 0x76, 0x6b, 0xcc, 0x6e, 0xd9, 0x6f, 0xc1, 0x72, 0x9a, 0xbc, 0xe9, 0x27, 0x51, 0x22, 0x8e, - 0x3b, 0xa1, 0x5a, 0x99, 0x48, 0x8d, 0x67, 0xde, 0x7f, 0x36, 0x45, 0x95, 0x4d, 0x12, 0x9e, 0xdf, - 0xca, 0x6f, 0x40, 0x2f, 0x58, 0x1a, 0xc7, 0xf4, 0x5a, 0x46, 0xab, 0xef, 0xcb, 0xa0, 0xdc, 0xe6, - 0xb1, 0xfd, 0xc1, 0x02, 0xf6, 0x84, 0x97, 0x7c, 0x1d, 0xfe, 0xed, 0x57, 0x09, 0x4e, 0xdc, 0xf5, - 0xda, 0xf6, 0x15, 0x01, 0xe3, 0x9b, 0xfd, 0x66, 0x81, 0x7b, 0x53, 0xbd, 0x04, 0x7b, 0x53, 0x55, - 0x9e, 0x06, 0x5d, 0xc3, 0xff, 0x0c, 0x3d, 0xb2, 0xeb, 0xef, 0x9d, 0x0c, 0x1c, 0xeb, 0x74, 0xe0, - 0x58, 0x5f, 0x06, 0x8e, 0xf5, 0xee, 0xcc, 0x29, 0x9d, 0x9e, 0x39, 0xa5, 0x4f, 0x67, 0x4e, 0x69, - 0x7f, 0xfd, 0xc2, 0x1e, 0x98, 0x36, 0x9a, 0x29, 0xee, 0xf2, 0xd1, 0x3f, 0xe8, 0xb0, 0xb5, 0x86, - 0x8e, 0x2e, 0xff, 0x15, 0xe8, 0x56, 0xd4, 0x97, 0xfd, 0xc1, 0x8f, 0x00, 0x00, 0x00, 0xff, 0xff, - 0x24, 0xf2, 0xad, 0xbb, 0xa0, 0x06, 0x00, 0x00, + // 656 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x94, 0x4f, 0x6b, 0x13, 0x4f, + 0x18, 0xc7, 0xb3, 0x4d, 0x49, 0xf9, 0x4d, 0xf9, 0xa9, 0x5d, 0xaa, 0xc4, 0x88, 0xbb, 0x61, 0x05, + 0x89, 0x60, 0x66, 0x4c, 0x2d, 0x08, 0x5e, 0xaa, 0xdb, 0xd2, 0x52, 0x21, 0x50, 0x57, 0x2f, 0xed, + 0x25, 0x4c, 0x76, 0xc7, 0xed, 0xe2, 0xee, 0x4c, 0xdc, 0x99, 0xd4, 0xf6, 0x5d, 0x78, 0xf1, 0xea, + 0xeb, 0xf0, 0x0d, 0x08, 0x3d, 0xf6, 0x28, 0x1e, 0x16, 0x49, 0x6f, 0xe2, 0x29, 0xaf, 0x40, 0xe6, + 0x4f, 0xd2, 0xaa, 0x29, 0xba, 0x14, 0x4f, 0xdd, 0x3e, 0xf3, 0x7d, 0x3e, 0xdf, 0xe7, 0x79, 0xe6, + 0xc9, 0x80, 0x36, 0xe3, 0x19, 0xe3, 0x09, 0x47, 0x31, 0xce, 0x32, 0x34, 0x60, 0x2c, 0x6d, 0x67, + 0x2c, 0x22, 0x29, 0x47, 0x7d, 0x9c, 0x62, 0x1a, 0x92, 0x1c, 0x89, 0x43, 0x24, 0x0e, 0xe1, 0x20, + 0x67, 0x82, 0xd9, 0x2d, 0x23, 0x87, 0x52, 0x0e, 0xa5, 0x5c, 0xab, 0xe1, 0x44, 0x0d, 0x0f, 0x3a, + 0x7d, 0x22, 0x70, 0xa7, 0xb1, 0x1c, 0xb3, 0x98, 0xa9, 0x24, 0x24, 0xbf, 0x74, 0x7e, 0x63, 0xf5, + 0xcf, 0x76, 0x93, 0x8f, 0x1d, 0xc6, 0x52, 0x93, 0xe5, 0x84, 0x2a, 0x0d, 0xf5, 0x31, 0x27, 0xc8, + 0x18, 0xa0, 0x90, 0x25, 0x54, 0x9f, 0x7b, 0x1f, 0xe7, 0xc0, 0xf5, 0x2e, 0x8f, 0xd7, 0x73, 0x82, + 0x05, 0xf1, 0xcf, 0xe5, 0xdb, 0xf7, 0x40, 0x8d, 0x13, 0x1a, 0x91, 0xbc, 0x6e, 0x35, 0xad, 0xd6, + 0x7f, 0xfe, 0xd2, 0xb8, 0x70, 0xff, 0x3f, 0xc2, 0x59, 0xfa, 0xd8, 0xd3, 0x71, 0x2f, 0x30, 0x02, + 0x7b, 0x17, 0x2c, 0xca, 0x7a, 0x7a, 0x03, 0x9c, 0xe3, 0x8c, 0xd7, 0xe7, 0x9a, 0x56, 0x6b, 0x71, + 0xa5, 0x09, 0x7f, 0x6a, 0xd8, 0x78, 0x43, 0xc9, 0xde, 0x51, 0x3a, 0xff, 0xc6, 0xb8, 0x70, 0x6d, + 0x4d, 0x3c, 0x97, 0xee, 0x05, 0x60, 0x30, 0xd5, 0xd8, 0x9b, 0x06, 0x8d, 0x39, 0x27, 0x82, 0xd7, + 0xab, 0xcd, 0x6a, 0x6b, 0x71, 0xc5, 0xbd, 0x18, 0xfd, 0x54, 0xea, 0xfc, 0xf9, 0xe3, 0xc2, 0xad, + 0x68, 0x8e, 0x0a, 0x70, 0xfb, 0x39, 0x58, 0x7e, 0x35, 0x14, 0xc3, 0x9c, 0xf4, 0x14, 0x2e, 0x66, + 0x07, 0x24, 0xa7, 0x2c, 0xaf, 0xcf, 0xab, 0xde, 0xdc, 0x71, 0xe1, 0xde, 0xd2, 0x95, 0xcc, 0x52, + 0x79, 0x81, 0xad, 0xc3, 0xd2, 0x61, 0x6b, 0x12, 0xdc, 0x00, 0xb7, 0x67, 0x4e, 0x2e, 0x20, 0x7c, + 0xc0, 0x28, 0x27, 0xf6, 0x1d, 0xb0, 0xa0, 0x30, 0x49, 0xa4, 0x46, 0x38, 0xef, 0x83, 0x51, 0xe1, + 0xd6, 0xa4, 0x64, 0x7b, 0x23, 0xa8, 0xc9, 0xa3, 0xed, 0xc8, 0xfb, 0x64, 0x01, 0xd4, 0xe5, 0x71, + 0x37, 0x89, 0x73, 0x2c, 0xc8, 0x8b, 0x7d, 0x9c, 0x13, 0xfe, 0x92, 0x6d, 0x0e, 0xd3, 0x34, 0xc0, + 0x34, 0x26, 0xeb, 0x8c, 0x86, 0x84, 0x0a, 0x79, 0x16, 0xed, 0x30, 0x9e, 0x88, 0x84, 0xd1, 0x32, + 0x57, 0x13, 0x83, 0x25, 0xae, 0x98, 0x3d, 0xc1, 0x7a, 0x99, 0x36, 0x31, 0x17, 0x74, 0x13, 0xea, + 0xdd, 0x80, 0x72, 0x37, 0xa6, 0x43, 0x5c, 0x67, 0x09, 0xf5, 0x9b, 0x72, 0x7e, 0xe3, 0xc2, 0xad, + 0x1b, 0xe8, 0xaf, 0x04, 0x2f, 0xb8, 0xca, 0x4d, 0xa5, 0xa6, 0x70, 0xef, 0xdb, 0x1c, 0x78, 0x54, + 0xb2, 0x8f, 0xe9, 0xa0, 0xf6, 0xc0, 0x02, 0xce, 0xd8, 0x90, 0x8a, 0x07, 0xa6, 0xa1, 0x27, 0xd2, + 0xff, 0x4b, 0xe1, 0xde, 0x8d, 0x13, 0xb1, 0x3f, 0xec, 0xc3, 0x90, 0x65, 0xc8, 0x2c, 0xb2, 0xfe, + 0xd3, 0xe6, 0xd1, 0x6b, 0x24, 0x8e, 0x06, 0x84, 0xc3, 0x6d, 0x2a, 0xc6, 0x85, 0x7b, 0x45, 0x57, + 0x6a, 0x30, 0x5e, 0x30, 0x01, 0x9e, 0xb1, 0x3b, 0xaa, 0xed, 0x4b, 0xb3, 0x3b, 0x53, 0x76, 0xc7, + 0x7e, 0x0b, 0x96, 0xd2, 0xe4, 0xcd, 0x30, 0x89, 0x12, 0x71, 0xd4, 0x0b, 0xd5, 0x22, 0x44, 0xf5, + 0xaa, 0x72, 0x79, 0x56, 0xc2, 0x65, 0x83, 0x84, 0x67, 0xb3, 0xfe, 0x0d, 0xe8, 0x05, 0xd7, 0xa6, + 0x31, 0xbd, 0x6c, 0xd1, 0xca, 0xfb, 0x2a, 0xa8, 0x76, 0x79, 0x6c, 0x7f, 0xb0, 0x80, 0x3d, 0xe3, + 0xa7, 0xbb, 0x06, 0xff, 0xf6, 0xad, 0x81, 0x33, 0x37, 0xb8, 0xb1, 0x75, 0x49, 0xc0, 0xf4, 0x66, + 0xbf, 0x5b, 0xe0, 0x7e, 0xa9, 0xd5, 0xde, 0x2d, 0xe5, 0x5c, 0x06, 0xdd, 0xc0, 0xff, 0x0c, 0x3d, + 0x69, 0xd7, 0xdf, 0x3d, 0x1e, 0x39, 0xd6, 0xc9, 0xc8, 0xb1, 0xbe, 0x8e, 0x1c, 0xeb, 0xdd, 0xa9, + 0x53, 0x39, 0x39, 0x75, 0x2a, 0x9f, 0x4f, 0x9d, 0xca, 0xde, 0xda, 0xb9, 0x3d, 0x30, 0x65, 0xb4, + 0x53, 0xdc, 0xe7, 0x93, 0x7f, 0xd0, 0x41, 0x67, 0x15, 0x1d, 0x5e, 0xfc, 0xb6, 0xf7, 0x6b, 0xea, + 0xbd, 0x7e, 0xf8, 0x23, 0x00, 0x00, 0xff, 0xff, 0x8c, 0x6c, 0xd3, 0x3f, 0x76, 0x06, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -538,11 +527,6 @@ func (m *MsgMigrateSharesToFullRangeConcentratedPosition) MarshalToSizedBuffer(d _ = i var l int _ = l - if m.PoolIdEntering != 0 { - i = encodeVarintTx(dAtA, i, uint64(m.PoolIdEntering)) - i-- - dAtA[i] = 0x18 - } { size, err := m.SharesToMigrate.MarshalToSizedBuffer(dAtA[:i]) if err != nil { @@ -678,9 +662,6 @@ func (m *MsgMigrateSharesToFullRangeConcentratedPosition) Size() (n int) { } l = m.SharesToMigrate.Size() n += 1 + l + sovTx(uint64(l)) - if m.PoolIdEntering != 0 { - n += 1 + sovTx(uint64(m.PoolIdEntering)) - } return n } @@ -1052,25 +1033,6 @@ func (m *MsgMigrateSharesToFullRangeConcentratedPosition) Unmarshal(dAtA []byte) return err } iNdEx = postIndex - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field PoolIdEntering", wireType) - } - m.PoolIdEntering = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.PoolIdEntering |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) diff --git a/x/gamm/simulation/sim_msgs.go b/x/gamm/simulation/sim_msgs.go index 981df6f9bbb..ee217f547d6 100644 --- a/x/gamm/simulation/sim_msgs.go +++ b/x/gamm/simulation/sim_msgs.go @@ -267,6 +267,11 @@ func RandomJoinSwapShareAmountOut(k keeper.Keeper, sim *simtypes.SimCtx, ctx sdk return nil, err } + accountBalance := sim.BankKeeper().GetBalance(ctx, sender.Address, tokenIn.Denom) + if accountBalance.Amount.LT(tokenInAmount) { + return nil, fmt.Errorf("insufficient funds") + } + return &types.MsgJoinSwapShareAmountOut{ Sender: sender.Address.String(), PoolId: pool_id, diff --git a/x/gamm/types/errors.go b/x/gamm/types/errors.go index e24941f64d3..dbc6b17f20b 100644 --- a/x/gamm/types/errors.go +++ b/x/gamm/types/errors.go @@ -41,15 +41,6 @@ func (e PoolMigrationLinkNotFoundError) Error() string { return fmt.Sprintf("given poolIdLeaving (%d) does not have a canonical link for any concentrated pool", e.PoolIdLeaving) } -type InvalidPoolMigrationLinkError struct { - PoolIdEntering uint64 - CanonicalId uint64 -} - -func (e InvalidPoolMigrationLinkError) Error() string { - return fmt.Sprintf("given poolIdEntering (%d) does not match the canonical link for the concentrated pool (%d)", e.PoolIdEntering, e.CanonicalId) -} - // x/gamm module sentinel errors. var ( ErrPoolNotFound = sdkerrors.Register(ModuleName, 1, "pool not found") diff --git a/x/gamm/types/expected_keepers.go b/x/gamm/types/expected_keepers.go index f1fee2ef46b..edf40c113b1 100644 --- a/x/gamm/types/expected_keepers.go +++ b/x/gamm/types/expected_keepers.go @@ -7,6 +7,7 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + cltypes "github.com/osmosis-labs/osmosis/v14/x/concentrated-liquidity/types" poolmanagertypes "github.com/osmosis-labs/osmosis/v14/x/poolmanager/types" ) @@ -48,8 +49,8 @@ type CommunityPoolKeeper interface { // CLKeeper defines the contract needed to be fulfilled for the concentrated liquidity keeper. type CLKeeper interface { - CreatePosition(ctx sdk.Context, poolId uint64, owner sdk.AccAddress, amount0Desired, amount1Desired, amount0Min, amount1Min sdk.Int, lowerTick, upperTick int64, frozenUntil time.Time) (sdk.Int, sdk.Int, sdk.Dec, error) - GetPool(ctx sdk.Context, poolId uint64) (poolmanagertypes.PoolI, error) + GetPoolFromPoolIdAndConvertToConcentrated(ctx sdk.Context, poolId uint64) (cltypes.ConcentratedPoolExtension, error) + CreateFullRangePosition(ctx sdk.Context, concentratedPool cltypes.ConcentratedPoolExtension, owner sdk.AccAddress, coins sdk.Coins, frozenUntil time.Time) (amount0, amount1 sdk.Int, liquidity sdk.Dec, err error) } // PoolManager defines the interface needed to be fulfilled for diff --git a/x/gamm/types/key.go b/x/gamm/types/key.go index 3a2e3b3b246..16be15f4ed1 100644 --- a/x/gamm/types/key.go +++ b/x/gamm/types/key.go @@ -37,13 +37,13 @@ func MustGetPoolIdFromShareDenom(denom string) uint64 { return uint64(number) } -func ValidatePoolShareDenom(denom string) error { +func GetPoolIdFromShareDenom(denom string) (uint64, error) { numberStr := strings.TrimLeft(denom, "gamm/pool/") - _, err := strconv.Atoi(numberStr) + number, err := strconv.Atoi(numberStr) if err != nil { - return err + return 0, err } - return nil + return uint64(number), nil } func GetDenomPrefix(denom string) []byte { diff --git a/x/ibc-rate-limit/ibc_middleware_test.go b/x/ibc-rate-limit/ibc_middleware_test.go index ccf0ce54346..2909d10aafd 100644 --- a/x/ibc-rate-limit/ibc_middleware_test.go +++ b/x/ibc-rate-limit/ibc_middleware_test.go @@ -15,6 +15,8 @@ import ( ibctesting "github.com/cosmos/ibc-go/v4/testing" "github.com/stretchr/testify/suite" + txfeetypes "github.com/osmosis-labs/osmosis/v14/x/txfees/types" + "github.com/osmosis-labs/osmosis/v14/app/apptesting" "github.com/osmosis-labs/osmosis/v14/tests/osmosisibctesting" "github.com/osmosis-labs/osmosis/v14/x/ibc-rate-limit/types" @@ -31,6 +33,8 @@ type MiddlewareTestSuite struct { path *ibctesting.Path } +var oldConsensusMinFee = txfeetypes.ConsensusMinFee + // Setup func TestMiddlewareTestSuite(t *testing.T) { suite.Run(t, new(MiddlewareTestSuite)) @@ -46,6 +50,8 @@ func NewTransferPath(chainA, chainB *osmosisibctesting.TestChain) *ibctesting.Pa } func (suite *MiddlewareTestSuite) SetupTest() { + // TODO: This needs to get removed. Waiting on https://github.com/cosmos/ibc-go/issues/3123 + txfeetypes.ConsensusMinFee = sdk.ZeroDec() suite.Setup() ibctesting.DefaultTestingAppInit = osmosisibctesting.SetupTestingApp suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) @@ -64,6 +70,11 @@ func (suite *MiddlewareTestSuite) SetupTest() { suite.coordinator.Setup(suite.path) } +// TODO: This needs to get removed. Waiting on https://github.com/cosmos/ibc-go/issues/3123 +func (suite *MiddlewareTestSuite) TearDownSuite() { + txfeetypes.ConsensusMinFee = oldConsensusMinFee +} + // Helpers func (suite *MiddlewareTestSuite) MessageFromAToB(denom string, amount sdk.Int) sdk.Msg { coin := sdk.NewCoin(denom, amount) diff --git a/x/incentives/keeper/gauge.go b/x/incentives/keeper/gauge.go index 482f3c81ce7..c6b5ff1f8ef 100644 --- a/x/incentives/keeper/gauge.go +++ b/x/incentives/keeper/gauge.go @@ -2,6 +2,7 @@ package keeper import ( "encoding/json" + "errors" "fmt" "strconv" "strings" @@ -148,6 +149,9 @@ func (k Keeper) AddToGaugeRewards(ctx sdk.Context, owner sdk.AccAddress, coins s if err != nil { return err } + if gauge.IsFinishedGauge(ctx.BlockTime()) { + return errors.New("gauge is already completed") + } if err := k.bk.SendCoinsFromAccountToModule(ctx, owner, types.ModuleName, coins); err != nil { return err } diff --git a/x/incentives/keeper/msg_server_test.go b/x/incentives/keeper/msg_server_test.go index 1c6c9166a63..2ec07e18bc9 100644 --- a/x/incentives/keeper/msg_server_test.go +++ b/x/incentives/keeper/msg_server_test.go @@ -17,6 +17,9 @@ import ( var _ = suite.TestingSuite(nil) +var seventyTokens = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(70000000))) +var tenTokens = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10000000))) + func (suite *KeeperTestSuite) TestCreateGauge_Fee() { tests := []struct { name string @@ -30,35 +33,35 @@ func (suite *KeeperTestSuite) TestCreateGauge_Fee() { { name: "user creates a non-perpetual gauge and fills gauge with all remaining tokens", accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(60000000))), - gaugeAddition: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10000000))), + gaugeAddition: tenTokens, }, { name: "user creates a non-perpetual gauge and fills gauge with some remaining tokens", accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(70000000))), - gaugeAddition: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10000000))), + gaugeAddition: tenTokens, }, { name: "user with multiple denoms creates a non-perpetual gauge and fills gauge with some remaining tokens", accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(70000000)), sdk.NewCoin("foo", sdk.NewInt(70000000))), - gaugeAddition: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10000000))), + gaugeAddition: tenTokens, }, { name: "module account creates a perpetual gauge and fills gauge with some remaining tokens", accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(70000000)), sdk.NewCoin("foo", sdk.NewInt(70000000))), - gaugeAddition: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10000000))), + gaugeAddition: tenTokens, isPerpetual: true, isModuleAccount: true, }, { name: "user with multiple denoms creates a perpetual gauge and fills gauge with some remaining tokens", accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(70000000)), sdk.NewCoin("foo", sdk.NewInt(70000000))), - gaugeAddition: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10000000))), + gaugeAddition: tenTokens, isPerpetual: true, }, { name: "user tries to create a non-perpetual gauge but does not have enough funds to pay for the create gauge fee", accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(40000000))), - gaugeAddition: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10000000))), + gaugeAddition: tenTokens, expectErr: true, }, { @@ -141,40 +144,41 @@ func (suite *KeeperTestSuite) TestAddToGauge_Fee() { nonexistentGauge bool isPerpetual bool isModuleAccount bool + isGaugeComplete bool expectErr bool }{ { name: "user creates a non-perpetual gauge and fills gauge with all remaining tokens", accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(35000000))), - gaugeAddition: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10000000))), + gaugeAddition: tenTokens, }, { name: "user creates a non-perpetual gauge and fills gauge with some remaining tokens", - accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(70000000))), - gaugeAddition: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10000000))), + accountBalanceToFund: seventyTokens, + gaugeAddition: tenTokens, }, { name: "user with multiple denoms creates a non-perpetual gauge and fills gauge with some remaining tokens", accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(70000000)), sdk.NewCoin("foo", sdk.NewInt(70000000))), - gaugeAddition: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10000000))), + gaugeAddition: tenTokens, }, { name: "module account creates a perpetual gauge and fills gauge with some remaining tokens", accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(70000000)), sdk.NewCoin("foo", sdk.NewInt(70000000))), - gaugeAddition: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10000000))), + gaugeAddition: tenTokens, isPerpetual: true, isModuleAccount: true, }, { name: "user with multiple denoms creates a perpetual gauge and fills gauge with some remaining tokens", accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(70000000)), sdk.NewCoin("foo", sdk.NewInt(70000000))), - gaugeAddition: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10000000))), + gaugeAddition: tenTokens, isPerpetual: true, }, { name: "user tries to create a non-perpetual gauge but does not have enough funds to pay for the create gauge fee", accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(20000000))), - gaugeAddition: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10000000))), + gaugeAddition: tenTokens, expectErr: true, }, { @@ -183,6 +187,13 @@ func (suite *KeeperTestSuite) TestAddToGauge_Fee() { gaugeAddition: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(10000000))), expectErr: true, }, + { + name: "user tries to add to a finished gauge", + accountBalanceToFund: seventyTokens, + gaugeAddition: tenTokens, + isGaugeComplete: true, + expectErr: true, + }, } for _, tc := range tests { @@ -191,7 +202,6 @@ func (suite *KeeperTestSuite) TestAddToGauge_Fee() { testAccountPubkey := secp256k1.GenPrivKeyFromSecret([]byte("acc")).PubKey() testAccountAddress := sdk.AccAddress(testAccountPubkey.Address()) - ctx := suite.Ctx bankKeeper := suite.App.BankKeeper incentivesKeeper := suite.App.IncentivesKeeper accountKeeper := suite.App.AccountKeeper @@ -204,14 +214,18 @@ func (suite *KeeperTestSuite) TestAddToGauge_Fee() { "module", "permission", ) - accountKeeper.SetModuleAccount(ctx, modAcc) + accountKeeper.SetModuleAccount(suite.Ctx, modAcc) } // System under test. coins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(500000000))) - gaugeID, _, _, _ := suite.SetupNewGauge(true, coins) + gaugeID, gauge, _, _ := suite.SetupNewGauge(tc.isPerpetual, coins) if tc.nonexistentGauge { - gaugeID = incentivesKeeper.GetLastGaugeID(ctx) + 1 + gaugeID = incentivesKeeper.GetLastGaugeID(suite.Ctx) + 1 + } + // simulate times to complete the gauge. + if tc.isGaugeComplete { + suite.completeGauge(gauge, sdk.AccAddress([]byte("a___________________"))) } msg := &types.MsgAddToGauge{ Owner: testAccountAddress.String(), @@ -219,23 +233,42 @@ func (suite *KeeperTestSuite) TestAddToGauge_Fee() { Rewards: tc.gaugeAddition, } - _, err := msgServer.AddToGauge(sdk.WrapSDKContext(ctx), msg) + _, err := msgServer.AddToGauge(sdk.WrapSDKContext(suite.Ctx), msg) if tc.expectErr { - suite.Require().Error(err) + suite.Require().Error(err, tc.name) } else { - suite.Require().NoError(err) + suite.Require().NoError(err, tc.name) } - bal := bankKeeper.GetAllBalances(ctx, testAccountAddress) + bal := bankKeeper.GetAllBalances(suite.Ctx, testAccountAddress) - if tc.expectErr { - suite.Require().Equal(tc.accountBalanceToFund.String(), bal.String(), "test: %v", tc.name) - } else { + if !tc.expectErr { fee := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, types.AddToGaugeFee)) accountBalance := tc.accountBalanceToFund.Sub(tc.gaugeAddition) finalAccountBalance := accountBalance.Sub(fee) suite.Require().Equal(finalAccountBalance.String(), bal.String(), "test: %v", tc.name) + } else if tc.expectErr && !tc.isGaugeComplete { + suite.Require().Equal(tc.accountBalanceToFund.String(), bal.String(), "test: %v", tc.name) } } } + +func (suite *KeeperTestSuite) completeGauge(gauge *types.Gauge, sendingAddress sdk.AccAddress) { + lockCoins := sdk.NewCoin(gauge.DistributeTo.Denom, sdk.NewInt(1000)) + suite.FundAcc(sendingAddress, sdk.NewCoins(lockCoins)) + suite.LockTokens(sendingAddress, sdk.NewCoins(lockCoins), gauge.DistributeTo.Duration) + epochId := suite.App.IncentivesKeeper.GetEpochInfo(suite.Ctx).Identifier + if suite.Ctx.BlockTime().Before(gauge.StartTime) { + suite.Ctx = suite.Ctx.WithBlockTime(gauge.StartTime.Add(time.Hour)) + } + suite.BeginNewBlock(false) + for i := 0; i < int(gauge.NumEpochsPaidOver); i++ { + suite.App.IncentivesKeeper.BeforeEpochStart(suite.Ctx, epochId, int64(i)) + suite.App.IncentivesKeeper.AfterEpochEnd(suite.Ctx, epochId, int64(i)) + } + suite.BeginNewBlock(false) + gauge2, err := suite.App.IncentivesKeeper.GetGaugeByID(suite.Ctx, gauge.Id) + suite.Require().NoError(err) + suite.Require().True(gauge2.IsFinishedGauge(suite.Ctx.BlockTime())) +} diff --git a/x/incentives/simulation/operations.go b/x/incentives/simulation/operations.go index 493dac276d2..e76afecf42d 100644 --- a/x/incentives/simulation/operations.go +++ b/x/incentives/simulation/operations.go @@ -174,14 +174,16 @@ func SimulateMsgAddToGauge(ak stakingTypes.AccountKeeper, bk stakingTypes.BankKe if gauge == nil { return simtypes.NoOpMsg( types.ModuleName, types.TypeMsgAddToGauge, "No gauge exists"), nil, nil + } else if gauge.IsFinishedGauge(ctx.BlockTime()) { + // TODO: Ideally we'd still run this but expect failure. + return simtypes.NoOpMsg( + types.ModuleName, types.TypeMsgAddToGauge, "Selected a gauge that is finished"), nil, nil } - gaugeId := RandomGauge(ctx, r, k).Id rewards := genRewardCoins(r, simCoins, types.AddToGaugeFee) - msg := types.MsgAddToGauge{ Owner: simAccount.Address.String(), - GaugeId: gaugeId, + GaugeId: gauge.Id, Rewards: rewards, } diff --git a/x/incentives/types/gauge.go b/x/incentives/types/gauge.go index 8460614b14c..64814e1c2e9 100644 --- a/x/incentives/types/gauge.go +++ b/x/incentives/types/gauge.go @@ -36,7 +36,7 @@ func (gauge Gauge) IsUpcomingGauge(curTime time.Time) bool { // IsActiveGauge returns true if the gauge is in an active state during the provided time. func (gauge Gauge) IsActiveGauge(curTime time.Time) bool { - if curTime.After(gauge.StartTime) || curTime.Equal(gauge.StartTime) && (gauge.IsPerpetual || gauge.FilledEpochs < gauge.NumEpochsPaidOver) { + if (curTime.After(gauge.StartTime) || curTime.Equal(gauge.StartTime)) && (gauge.IsPerpetual || gauge.FilledEpochs < gauge.NumEpochsPaidOver) { return true } return false diff --git a/x/superfluid/keeper/genesis_test.go b/x/superfluid/keeper/genesis_test.go index 7922a479b37..05c951cccd7 100644 --- a/x/superfluid/keeper/genesis_test.go +++ b/x/superfluid/keeper/genesis_test.go @@ -56,7 +56,7 @@ func TestMarshalUnmarshalGenesis(t *testing.T) { encodingConfig := simapp.MakeEncodingConfig() appCodec := encodingConfig.Marshaler - am := superfluid.NewAppModule(*app.SuperfluidKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.LockupKeeper, app.GAMMKeeper, app.EpochsKeeper) + am := superfluid.NewAppModule(*app.SuperfluidKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.LockupKeeper, app.GAMMKeeper, app.EpochsKeeper, app.ConcentratedLiquidityKeeper) genesis := testGenesis app.SuperfluidKeeper.InitGenesis(ctx, genesis) @@ -65,7 +65,7 @@ func TestMarshalUnmarshalGenesis(t *testing.T) { app := simapp.Setup(false) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) ctx = ctx.WithBlockTime(now.Add(time.Second)) - am := superfluid.NewAppModule(*app.SuperfluidKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.LockupKeeper, app.GAMMKeeper, app.EpochsKeeper) + am := superfluid.NewAppModule(*app.SuperfluidKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.LockupKeeper, app.GAMMKeeper, app.EpochsKeeper, app.ConcentratedLiquidityKeeper) am.InitGenesis(ctx, appCodec, genesisExported) }) } diff --git a/x/superfluid/keeper/keeper.go b/x/superfluid/keeper/keeper.go index dd04d89e3d9..5fba70839c5 100644 --- a/x/superfluid/keeper/keeper.go +++ b/x/superfluid/keeper/keeper.go @@ -18,14 +18,15 @@ type Keeper struct { storeKey sdk.StoreKey paramSpace paramtypes.Subspace - ak authkeeper.AccountKeeper - bk types.BankKeeper - sk types.StakingKeeper - ck types.CommunityPoolKeeper - ek types.EpochKeeper - lk types.LockupKeeper - gk types.GammKeeper - ik types.IncentivesKeeper + ak authkeeper.AccountKeeper + bk types.BankKeeper + sk types.StakingKeeper + ck types.CommunityPoolKeeper + ek types.EpochKeeper + lk types.LockupKeeper + gk types.GammKeeper + ik types.IncentivesKeeper + clk types.ConcentratedKeeper lms types.LockupMsgServer } @@ -33,7 +34,7 @@ type Keeper struct { var _ govtypes.StakingKeeper = (*Keeper)(nil) // NewKeeper returns an instance of Keeper. -func NewKeeper(storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, ak authkeeper.AccountKeeper, bk types.BankKeeper, sk types.StakingKeeper, dk types.CommunityPoolKeeper, ek types.EpochKeeper, lk types.LockupKeeper, gk types.GammKeeper, ik types.IncentivesKeeper, lms types.LockupMsgServer) *Keeper { +func NewKeeper(storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, ak authkeeper.AccountKeeper, bk types.BankKeeper, sk types.StakingKeeper, dk types.CommunityPoolKeeper, ek types.EpochKeeper, lk types.LockupKeeper, gk types.GammKeeper, ik types.IncentivesKeeper, lms types.LockupMsgServer, clk types.ConcentratedKeeper) *Keeper { // set KeyTable if it has not already been set if !paramSpace.HasKeyTable() { paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) @@ -50,6 +51,7 @@ func NewKeeper(storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, ak authkee lk: lk, gk: gk, ik: ik, + clk: clk, lms: lms, } diff --git a/x/superfluid/keeper/migrate.go b/x/superfluid/keeper/migrate.go new file mode 100644 index 00000000000..a10d62ab1ee --- /dev/null +++ b/x/superfluid/keeper/migrate.go @@ -0,0 +1,103 @@ +package keeper + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + gammtypes "github.com/osmosis-labs/osmosis/v14/x/gamm/types" +) + +// UnlockAndMigrate unlocks a balancer pool lock, exits the pool and migrates the LP position to a full range concentrated liquidity position. +// If the lock is superfluid delegated, it will undelegate the superfluid position. +// Errors if the lock is not found, if the lock is not a balancer pool lock, or if the lock is not owned by the sender. +func (k Keeper) UnlockAndMigrate(ctx sdk.Context, sender sdk.AccAddress, lockId uint64, sharesToMigrate sdk.Coin) (amount0, amount1 sdk.Int, liquidity sdk.Dec, poolIdLeaving, poolIdEntering, newLockId uint64, frozenUntil time.Time, err error) { + // Get the balancer poolId by parsing the gamm share denom. + poolIdLeaving = gammtypes.MustGetPoolIdFromShareDenom(sharesToMigrate.Denom) + + // Ensure a governance sanctioned link exists between the balancer pool and the concentrated pool. + poolIdEntering, err = k.gk.GetLinkedConcentratedPoolID(ctx, poolIdLeaving) + if err != nil { + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, 0, time.Time{}, err + } + + // Get the concentrated pool from the provided ID and type cast it to ConcentratedPoolExtension. + concentratedPool, err := k.clk.GetPoolFromPoolIdAndConvertToConcentrated(ctx, poolIdEntering) + if err != nil { + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, 0, time.Time{}, err + } + + // Check that lockID corresponds to sender, and contains correct denomination of LP shares. + lock, err := k.validateLockForUnpool(ctx, sender, poolIdLeaving, lockId) + if err != nil { + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, 0, time.Time{}, err + } + gammSharesInLock := lock.Coins[0] + preUnlockLock := *lock + + // Before we break the lock, we must note the time remaining on the lock. + // We will be freezing the concentrated liquidity position for this duration. + freezeDuration := k.getExistingLockRemainingDuration(ctx, lock) + + // If superfluid delegated, superfluid undelegate + // This also burns the underlying synthetic osmo + err = k.unbondSuperfluidIfExists(ctx, sender, lockId) + if err != nil { + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, 0, time.Time{}, err + } + + // Finish unlocking directly for locked locks + // this also unlocks locks that were in the unlocking queue + err = k.lk.ForceUnlock(ctx, *lock) + if err != nil { + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, 0, time.Time{}, err + } + + // If shares to migrate is not specified, we migrate all shares. + if sharesToMigrate.IsZero() { + sharesToMigrate = gammSharesInLock + } + + // Otherwise, we must ensure that the shares to migrate is less than or equal to the shares in the lock. + if sharesToMigrate.Amount.GT(gammSharesInLock.Amount) { + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, 0, time.Time{}, fmt.Errorf("shares to migrate must be less than or equal to shares in lock") + } + + // Exit the balancer pool position. + exitCoins, err := k.gk.ExitPool(ctx, sender, poolIdLeaving, sharesToMigrate.Amount, sdk.NewCoins()) + if err != nil { + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, 0, time.Time{}, err + } + // Defense in depth, ensuring we are returning exactly two coins. + if len(exitCoins) != 2 { + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, 0, time.Time{}, fmt.Errorf("Balancer pool must have exactly two tokens") + } + + frozenUntil = ctx.BlockTime().Add(freezeDuration) + + // Create a full range (min to max tick) concentrated liquidity position. + amount0, amount1, liquidity, err = k.clk.CreateFullRangePosition(ctx, concentratedPool, sender, exitCoins, frozenUntil) + if err != nil { + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, 0, time.Time{}, err + } + + // If there are remaining gamm shares, we must re-lock them. + remainingGammShares := gammSharesInLock.Sub(sharesToMigrate) + if !remainingGammShares.IsZero() { + newLock, err := k.lk.CreateLock(ctx, sender, sdk.NewCoins(remainingGammShares), freezeDuration) + newLockId = newLock.ID + if err != nil { + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, 0, time.Time{}, err + } + // If the lock was unlocking, we begin the unlock from where it left off. + if preUnlockLock.IsUnlocking() { + _, err := k.lk.BeginForceUnlock(ctx, newLock.ID, newLock.Coins) + if err != nil { + return sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, 0, time.Time{}, err + } + } + } + + return amount0, amount1, liquidity, poolIdLeaving, poolIdEntering, newLockId, frozenUntil, nil +} diff --git a/x/superfluid/keeper/migrate_test.go b/x/superfluid/keeper/migrate_test.go new file mode 100644 index 00000000000..b7986e137bc --- /dev/null +++ b/x/superfluid/keeper/migrate_test.go @@ -0,0 +1,259 @@ +package keeper_test + +import ( + "time" + + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + cl "github.com/osmosis-labs/osmosis/v14/x/concentrated-liquidity" + + "github.com/osmosis-labs/osmosis/osmomath" + "github.com/osmosis-labs/osmosis/v14/x/gamm/pool-models/balancer" + gammtypes "github.com/osmosis-labs/osmosis/v14/x/gamm/types" + "github.com/osmosis-labs/osmosis/v14/x/superfluid/keeper" + "github.com/osmosis-labs/osmosis/v14/x/superfluid/types" +) + +// We test migrating in the following circumstances: +// 1. Migrating lock that is not superfluid delegated, not unlocking. +// 2. Migrating lock that is not superfluid delegated, unlocking. +// 3. Migrating lock that is superfluid delegated, not unlocking. +// 4. Migrating lock that is superfluid undelegating, not unlocking. +// 5. Migrating lock that is superfluid undelegating, unlocking. +func (suite *KeeperTestSuite) TestUnlockAndMigrate() { + testCases := []struct { + name string + superfluidDelegated bool + superfluidUndelegating bool + unlocking bool + percentOfSharesToMigrate sdk.Dec + }{ + { + "lock that is not superfluid delegated, not unlocking", + false, + false, + false, + sdk.MustNewDecFromStr("0.9"), + }, + { + "lock that is not superfluid delegated, unlocking", + false, + false, + true, + sdk.MustNewDecFromStr("0.6"), + }, + { + "lock that is superfluid delegated, not unlocking", + true, + false, + false, + sdk.MustNewDecFromStr("1"), + }, + { + "lock that is superfluid undelegating, not unlocking", + true, + true, + false, + sdk.MustNewDecFromStr("0.5"), + }, + { + "lock that is superfluid undelegating, unlocking", + true, + true, + true, + sdk.MustNewDecFromStr("0.3"), + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupTest() + ctx := suite.Ctx + bankKeeper := suite.App.BankKeeper + gammKeeper := suite.App.GAMMKeeper + superfluidKeeper := suite.App.SuperfluidKeeper + lockupKeeper := suite.App.LockupKeeper + stakingKeeper := suite.App.StakingKeeper + poolmanagerKeeper := suite.App.PoolManagerKeeper + + // Generate and fund two accounts. + // Account 1 will be the account that creates the pool. + // Account 2 will be the account that joins the pool. + delAddrs := CreateRandomAccounts(2) + poolCreateAcc := delAddrs[0] + poolJoinAcc := delAddrs[1] + for _, acc := range delAddrs { + err := simapp.FundAccount(bankKeeper, ctx, acc, defaultAcctFunds) + suite.Require().NoError(err) + } + + // Set up a single validator. + valAddr := suite.SetupValidator(stakingtypes.BondStatus(stakingtypes.Bonded)) + + // Create a balancer pool of "stake" and "foo". + msg := balancer.NewMsgCreateBalancerPool(poolCreateAcc, balancer.PoolParams{ + SwapFee: sdk.NewDecWithPrec(1, 2), + ExitFee: sdk.NewDec(0), + }, defaultPoolAssets, defaultFutureGovernor) + balancerPooId, err := poolmanagerKeeper.CreatePool(ctx, msg) + suite.Require().NoError(err) + + // Join the balancer pool. + // Note the account balance before and after joining the pool. + balanceBeforeJoin := bankKeeper.GetAllBalances(ctx, poolJoinAcc) + _, _, err = gammKeeper.JoinPoolNoSwap(ctx, poolJoinAcc, balancerPooId, gammtypes.OneShare.MulRaw(50), sdk.Coins{}) + suite.Require().NoError(err) + balanceAfterJoin := bankKeeper.GetAllBalances(ctx, poolJoinAcc) + + // The balancer join pool amount is the difference between the account balance before and after joining the pool. + joinPoolAmt, _ := balanceBeforeJoin.SafeSub(balanceAfterJoin) + + // Determine the pool's LP token denomination. + balancerPool, err := gammKeeper.GetPoolAndPoke(ctx, balancerPooId) + suite.Require().NoError(err) + poolDenom := gammtypes.GetPoolShareDenom(balancerPool.GetId()) + + // Register the LP token as a superfluid asset + err = superfluidKeeper.AddNewSuperfluidAsset(ctx, types.SuperfluidAsset{ + Denom: poolDenom, + AssetType: types.SuperfluidAssetTypeLPShare, + }) + suite.Require().NoError(err) + + // Note how much of the LP token the account that joined the pool has. + poolShareOut := bankKeeper.GetBalance(ctx, poolJoinAcc, poolDenom) + + // Create a cl pool with the same underlying assets as the balancer pool. + clPool := suite.PrepareCustomConcentratedPool(poolCreateAcc, defaultPoolAssets[0].Token.Denom, defaultPoolAssets[1].Token.Denom, 1, sdk.NewInt(-6), sdk.ZeroDec()) + + // Add a sanctioned link between the balancer and concentrated liquidity pool. + migrationRecord := gammtypes.MigrationRecords{BalancerToConcentratedPoolLinks: []gammtypes.BalancerToConcentratedPoolLink{ + {BalancerPoolId: balancerPool.GetId(), ClPoolId: clPool.GetId()}, + }} + gammKeeper.SetMigrationInfo(ctx, migrationRecord) + + // The unbonding duration is the same as the staking module's unbonding duration. + unbondingDuration := stakingKeeper.GetParams(ctx).UnbondingTime + + // Lock the LP tokens for the duration of the unbonding period. + lockID := suite.LockTokens(poolJoinAcc, sdk.NewCoins(poolShareOut), unbondingDuration) + + // Superfluid delegate the lock if the test case requires it. + // Note the intermediary account that was created. + intermediaryAcc := types.SuperfluidIntermediaryAccount{} + if tc.superfluidDelegated { + err = superfluidKeeper.SuperfluidDelegate(ctx, poolJoinAcc.String(), lockID, valAddr.String()) + suite.Require().NoError(err) + intermediaryAccConnection := superfluidKeeper.GetLockIdIntermediaryAccountConnection(ctx, lockID) + intermediaryAcc = superfluidKeeper.GetIntermediaryAccount(ctx, intermediaryAccConnection) + } + + // Superfluid undelegate the lock if the test case requires it. + if tc.superfluidUndelegating { + err = superfluidKeeper.SuperfluidUndelegate(ctx, poolJoinAcc.String(), lockID) + suite.Require().NoError(err) + } + + // Unlock the lock if the test case requires it. + if tc.unlocking { + // If lock was superfluid staked, we can't unlock via `BeginUnlock`, + // we need to unlock lock via `SuperfluidUnbondLock` + if tc.superfluidUndelegating { + err = superfluidKeeper.SuperfluidUnbondLock(ctx, lockID, poolJoinAcc.String()) + suite.Require().NoError(err) + } else { + lock, err := lockupKeeper.GetLockByID(ctx, lockID) + suite.Require().NoError(err) + err = lockupKeeper.BeginUnlock(ctx, lockID, lock.Coins) + suite.Require().NoError(err) + } + } + + // add time to current time to test lock end time + ctx = ctx.WithBlockTime(ctx.BlockTime().Add(time.Hour * 24)) + + lock, err := lockupKeeper.GetLockByID(ctx, lockID) + suite.Require().NoError(err) + + // Depending on the test case, we attempt to migrate a subset of the LP tokens we originally. + coinsToMigrate := poolShareOut + coinsToMigrate.Amount = coinsToMigrate.Amount.ToDec().Mul(tc.percentOfSharesToMigrate).RoundInt() + + // Run the unlock and migrate logic. + amount0, amount1, _, poolIdLeaving, poolIdEntering, newLockId, frozenUntil, err := superfluidKeeper.UnlockAndMigrate(ctx, poolJoinAcc, lockID, coinsToMigrate) + suite.Require().NoError(err) + suite.AssertEventEmitted(ctx, gammtypes.TypeEvtPoolExited, 1) + + newLock, err := lockupKeeper.GetLockByID(ctx, newLockId) + if tc.percentOfSharesToMigrate.LT(sdk.OneDec()) { + // If we migrated a subset of the LP tokens, we expect the new lock to have a the same end time. + suite.Require().NoError(err) + suite.Require().Equal(lock.EndTime, newLock.EndTime) + } else { + // If we migrated all of the LP tokens, we expect no new lock to be created. + suite.Require().Error(err) + suite.Require().Nil(newLock) + } + + // Check that concentrated liquidity position now exists + minTick, maxTick := cl.GetMinAndMaxTicksFromExponentAtPriceOne(clPool.GetPrecisionFactorAtPriceOne()) + position, err := suite.App.ConcentratedLiquidityKeeper.GetPosition(ctx, poolIdEntering, poolJoinAcc, minTick, maxTick, frozenUntil) + suite.Require().NoError(err) + suite.Require().NotNil(position) + + // Expect the poolIdLeaving to be the balancer pool id + // Expect the poolIdEntering to be the concentrated liquidity pool id + suite.Require().Equal(balancerPooId, poolIdLeaving) + suite.Require().Equal(clPool.GetId(), poolIdEntering) + + // exitPool has rounding difference. + // We test if correct amt has been exited and frozen by comparing with rounding tolerance. + defaultErrorTolerance := osmomath.ErrTolerance{ + AdditiveTolerance: sdk.NewDec(2), + RoundingDir: osmomath.RoundDown, + } + suite.Require().Equal(0, defaultErrorTolerance.Compare(joinPoolAmt.AmountOf("foo").ToDec().Mul(tc.percentOfSharesToMigrate).RoundInt(), amount0)) + suite.Require().Equal(0, defaultErrorTolerance.Compare(joinPoolAmt.AmountOf("stake").ToDec().Mul(tc.percentOfSharesToMigrate).RoundInt(), amount1)) + + // Check if the original lock was deleted. + _, err = lockupKeeper.GetLockByID(ctx, lockID) + suite.Require().Error(err) + + // If we didn't migrate the entire lock, we expect a new lock to be created we the remaining lock time and coins associated with it. + if tc.percentOfSharesToMigrate.LT(sdk.OneDec()) { + // Check if the new lock was created. + newLock, err := lockupKeeper.GetLockByID(ctx, newLockId) + suite.Require().NoError(err) + // The new lock should have the same owner and end time. + // The new lock should have the difference in coins between the original lock and the coins migrated. + suite.Require().Equal(sdk.NewCoins(poolShareOut.Sub(coinsToMigrate)).String(), newLock.Coins.String()) + suite.Require().Equal(lock.Owner, newLock.Owner) + suite.Require().Equal(lock.EndTime.String(), newLock.EndTime.String()) + // If original lock was unlocking, the new lock should also be unlocking. + if lock.IsUnlocking() { + suite.Require().True(newLock.IsUnlocking()) + } + } else { + suite.Require().Equal(uint64(0), newLockId) + } + + // Additional checks if the lock was superfluid staked. + if tc.superfluidDelegated { + // Check if migration deleted intermediary account connection. + addr := superfluidKeeper.GetLockIdIntermediaryAccountConnection(ctx, lockID) + suite.Require().Equal(addr.String(), "") + + // Check if migration deleted synthetic lockup. + _, err = lockupKeeper.GetSyntheticLockup(ctx, lockID, keeper.StakingSyntheticDenom(lock.Coins[0].Denom, valAddr.String())) + suite.Require().Error(err) + + // Check if delegation has reduced from intermediary account. + delegation, found := stakingKeeper.GetDelegation(ctx, intermediaryAcc.GetAccAddress(), valAddr) + suite.Require().False(found, "expected no delegation, found delegation w/ %d shares", delegation.Shares) + } + }) + } +} diff --git a/x/superfluid/keeper/msg_server.go b/x/superfluid/keeper/msg_server.go index f0b5336aa57..f93cbfc7d78 100644 --- a/x/superfluid/keeper/msg_server.go +++ b/x/superfluid/keeper/msg_server.go @@ -3,6 +3,7 @@ package keeper import ( "context" "encoding/json" + "strconv" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -163,3 +164,29 @@ func (server msgServer) UnPoolWhitelistedPool(goCtx context.Context, msg *types. return &types.MsgUnPoolWhitelistedPoolResponse{ExitedLockIds: allExitedLockIDs}, nil } + +func (server msgServer) UnlockAndMigrateSharesToFullRangeConcentratedPosition(goCtx context.Context, msg *types.MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) (*types.MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + sender, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + return nil, err + } + + amount0, amount1, liquidity, poolIdLeaving, poolIdEntering, newLockId, frozenUntil, err := server.keeper.UnlockAndMigrate(ctx, sender, msg.LockId, msg.SharesToMigrate) + if err != nil { + return nil, err + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.TypeEvtUnlockAndMigrateShares, + sdk.NewAttribute(types.AttributeKeyPoolIdEntering, strconv.FormatUint(poolIdEntering, 10)), + sdk.NewAttribute(types.AttributeKeyPoolIdLeaving, strconv.FormatUint(poolIdLeaving, 10)), + sdk.NewAttribute(types.AttributeNewLockId, strconv.FormatUint(newLockId, 10)), + sdk.NewAttribute(types.AttributeKeyPoolIdLeaving, frozenUntil.String()), + ), + }) + + return &types.MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse{Amount0: amount0, Amount1: amount1, LiquidityCreated: liquidity}, err +} diff --git a/x/superfluid/keeper/msg_server_test.go b/x/superfluid/keeper/msg_server_test.go index 5184b5ea341..991ca633f9d 100644 --- a/x/superfluid/keeper/msg_server_test.go +++ b/x/superfluid/keeper/msg_server_test.go @@ -2,12 +2,14 @@ package keeper_test import ( // "fmt" + "time" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" v8constants "github.com/osmosis-labs/osmosis/v14/app/upgrades/v8/constants" + gammtypes "github.com/osmosis-labs/osmosis/v14/x/gamm/types" lockupkeeper "github.com/osmosis-labs/osmosis/v14/x/lockup/keeper" lockuptypes "github.com/osmosis-labs/osmosis/v14/x/lockup/types" "github.com/osmosis-labs/osmosis/v14/x/superfluid/keeper" @@ -383,3 +385,37 @@ func (suite *KeeperTestSuite) TestMsgUnPoolWhitelistedPool_Event() { suite.AssertEventEmitted(suite.Ctx, types.TypeEvtUnpoolId, 1) } } + +func (suite *KeeperTestSuite) TestUnlockAndMigrateSharesToFullRangeConcentratedPosition_Event() { + suite.SetupTest() + msgServer := keeper.NewMsgServerImpl(suite.App.SuperfluidKeeper) + + // Set validators + valAddrs := suite.SetupValidators([]stakingtypes.BondStatus{stakingtypes.Bonded}) + + // Set balancer pool and make its respective gamm share an authorized superfluid asset + denoms, poolIds := suite.SetupGammPoolsAndSuperfluidAssets([]sdk.Dec{sdk.NewDec(20)}) + balancerPool, err := suite.App.GAMMKeeper.GetPoolAndPoke(suite.Ctx, poolIds[0]) + suite.Require().NoError(err) + + // Set concentrated pool with the same denoms as the balancer pool + clPool := suite.PrepareCustomConcentratedPool(suite.TestAccs[0], "stake", "token0", 1, sdk.NewInt(-6), sdk.ZeroDec()) + + // Set migration link between the balancer and concentrated pool + migrationRecord := gammtypes.MigrationRecords{BalancerToConcentratedPoolLinks: []gammtypes.BalancerToConcentratedPoolLink{ + {BalancerPoolId: balancerPool.GetId(), ClPoolId: clPool.GetId()}, + }} + suite.App.GAMMKeeper.SetMigrationInfo(suite.Ctx, migrationRecord) + + // Superfluid delegate the balancer pool shares + _, _, locks := suite.setupSuperfluidDelegations(valAddrs, []superfluidDelegation{{0, 0, 0, 9000000000000000000}}, denoms) + + // Execute UnlockAndMigrateSharesToFullRangeConcentratedPosition message + sender, _ := sdk.AccAddressFromBech32(locks[0].Owner) + _, err = msgServer.UnlockAndMigrateSharesToFullRangeConcentratedPosition(sdk.WrapSDKContext(suite.Ctx), + types.NewMsgUnlockAndMigrateSharesToFullRangeConcentratedPosition(sender, locks[0].ID, locks[0].Coins[0])) + suite.Require().NoError(err) + + // Asset event emitted + suite.AssertEventEmitted(suite.Ctx, types.TypeEvtUnlockAndMigrateShares, 1) +} diff --git a/x/superfluid/module.go b/x/superfluid/module.go index 4bbbe506fad..4859bd27d10 100644 --- a/x/superfluid/module.go +++ b/x/superfluid/module.go @@ -106,13 +106,14 @@ func (AppModuleBasic) GetQueryCmd() *cobra.Command { type AppModule struct { AppModuleBasic - keeper keeper.Keeper - accountKeeper stakingtypes.AccountKeeper - bankKeeper stakingtypes.BankKeeper - stakingKeeper types.StakingKeeper - lockupKeeper types.LockupKeeper - gammKeeper types.GammKeeper - epochKeeper types.EpochKeeper + keeper keeper.Keeper + accountKeeper stakingtypes.AccountKeeper + bankKeeper stakingtypes.BankKeeper + stakingKeeper types.StakingKeeper + lockupKeeper types.LockupKeeper + gammKeeper types.GammKeeper + epochKeeper types.EpochKeeper + concentratedKeeper types.ConcentratedKeeper } func NewAppModule(keeper keeper.Keeper, @@ -121,17 +122,19 @@ func NewAppModule(keeper keeper.Keeper, lockupKeeper types.LockupKeeper, gammKeeper types.GammKeeper, epochKeeper types.EpochKeeper, + concentratedKeeper types.ConcentratedKeeper, ) AppModule { return AppModule{ AppModuleBasic: NewAppModuleBasic(), keeper: keeper, - accountKeeper: accountKeeper, - bankKeeper: bankKeeper, - stakingKeeper: stakingKeeper, - lockupKeeper: lockupKeeper, - gammKeeper: gammKeeper, - epochKeeper: epochKeeper, + accountKeeper: accountKeeper, + bankKeeper: bankKeeper, + stakingKeeper: stakingKeeper, + lockupKeeper: lockupKeeper, + gammKeeper: gammKeeper, + epochKeeper: epochKeeper, + concentratedKeeper: concentratedKeeper, } } diff --git a/x/superfluid/types/codec.go b/x/superfluid/types/codec.go index dfa510fcef9..4c6f85a025b 100644 --- a/x/superfluid/types/codec.go +++ b/x/superfluid/types/codec.go @@ -19,6 +19,7 @@ func RegisterCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&UpdateUnpoolWhiteListProposal{}, "osmosis/update-unpool-whitelist", nil) cdc.RegisterConcrete(&RemoveSuperfluidAssetsProposal{}, "osmosis/del-superfluid-assets-proposal", nil) cdc.RegisterConcrete(&MsgUnPoolWhitelistedPool{}, "osmosis/unpool-whitelisted-pool", nil) + cdc.RegisterConcrete(&MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition{}, "osmosis/unlock-and-migrate", nil) } func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { @@ -30,6 +31,7 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { &MsgSuperfluidUnbondLock{}, &MsgSuperfluidUndelegateAndUnbondLock{}, &MsgUnPoolWhitelistedPool{}, + &MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition{}, ) registry.RegisterImplementations( diff --git a/x/superfluid/types/events.go b/x/superfluid/types/events.go index b2f204709c0..9405e560233 100644 --- a/x/superfluid/types/events.go +++ b/x/superfluid/types/events.go @@ -13,6 +13,12 @@ const ( TypeEvtUnpoolId = "unpool_pool_id" AttributeNewLockIds = "new_lock_ids" + TypeEvtUnlockAndMigrateShares = "unlock_and_migrate_shares" + AttributeKeyPoolIdEntering = "pool_id_entering" + AttributeKeyPoolIdLeaving = "pool_id_leaving" + AttributeNewLockId = "new_lock_id" + AttributeFrozenUntil = "frozen_until" + AttributeDenom = "denom" AttributeSuperfluidAssetType = "superfluid_asset_type" AttributeLockId = "lock_id" diff --git a/x/superfluid/types/expected_keepers.go b/x/superfluid/types/expected_keepers.go index e2dbe627908..d4935561e7e 100644 --- a/x/superfluid/types/expected_keepers.go +++ b/x/superfluid/types/expected_keepers.go @@ -7,6 +7,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + cltypes "github.com/osmosis-labs/osmosis/v14/x/concentrated-liquidity/types" epochstypes "github.com/osmosis-labs/osmosis/v14/x/epochs/types" gammtypes "github.com/osmosis-labs/osmosis/v14/x/gamm/types" incentivestypes "github.com/osmosis-labs/osmosis/v14/x/incentives/types" @@ -47,6 +48,8 @@ type GammKeeper interface { GetPoolAndPoke(ctx sdk.Context, poolId uint64) (gammtypes.CFMMPoolI, error) GetPoolsAndPoke(ctx sdk.Context) (res []gammtypes.CFMMPoolI, err error) ExitPool(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, shareInAmount sdk.Int, tokenOutMins sdk.Coins) (exitCoins sdk.Coins, err error) + GetMigrationInfo(ctx sdk.Context) gammtypes.MigrationRecords + GetLinkedConcentratedPoolID(ctx sdk.Context, poolIdLeaving uint64) (poolIdEntering uint64, err error) } type BankKeeper interface { @@ -95,3 +98,8 @@ type EpochKeeper interface { GetEpochInfo(ctx sdk.Context, identifier string) epochstypes.EpochInfo NumBlocksSinceEpochStart(ctx sdk.Context, identifier string) (int64, error) } + +type ConcentratedKeeper interface { + GetPoolFromPoolIdAndConvertToConcentrated(ctx sdk.Context, poolId uint64) (cltypes.ConcentratedPoolExtension, error) + CreateFullRangePosition(ctx sdk.Context, concentratedPool cltypes.ConcentratedPoolExtension, owner sdk.AccAddress, coins sdk.Coins, frozenUntil time.Time) (amount0, amount1 sdk.Int, liquidity sdk.Dec, err error) +} diff --git a/x/superfluid/types/gov.go b/x/superfluid/types/gov.go index 559901136cb..bc84fec1f80 100644 --- a/x/superfluid/types/gov.go +++ b/x/superfluid/types/gov.go @@ -57,7 +57,7 @@ func (p *SetSuperfluidAssetsProposal) ValidateBasic() error { for _, asset := range p.Assets { switch asset.AssetType { case SuperfluidAssetTypeLPShare: - if err = gammtypes.ValidatePoolShareDenom(asset.Denom); err != nil { + if _, err := gammtypes.GetPoolIdFromShareDenom(asset.Denom); err != nil { return err } default: diff --git a/x/superfluid/types/msgs.go b/x/superfluid/types/msgs.go index dc2a2910346..e025a25aeb5 100644 --- a/x/superfluid/types/msgs.go +++ b/x/superfluid/types/msgs.go @@ -16,6 +16,7 @@ const ( TypeMsgSuperfluidUndeledgateAndUnbondLock = "superfluid_undelegate_and_unbond_lock" TypeMsgLockAndSuperfluidDelegate = "lock_and_superfluid_delegate" TypeMsgUnPoolWhitelistedPool = "unpool_whitelisted_pool" + TypeMsgUnlockAndMigrateShares = "unlock_and_migrate_shares" ) var _ sdk.Msg = &MsgSuperfluidDelegate{} @@ -260,3 +261,43 @@ func (msg MsgUnPoolWhitelistedPool) GetSigners() []sdk.AccAddress { } return []sdk.AccAddress{sender} } + +var _ sdk.Msg = &MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition{} + +func NewMsgUnlockAndMigrateSharesToFullRangeConcentratedPosition(sender sdk.AccAddress, lockId uint64, sharesToMigrate sdk.Coin) *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition { + return &MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition{ + Sender: sender.String(), + LockId: lockId, + SharesToMigrate: sharesToMigrate, + } +} + +func (msg MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) Route() string { return RouterKey } +func (msg MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) Type() string { + return TypeMsgUnlockAndMigrateShares +} +func (msg MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) + } + if msg.LockId <= 0 { + return fmt.Errorf("Invalid lock ID (%d)", msg.LockId) + } + if msg.SharesToMigrate.IsNegative() { + return fmt.Errorf("Invalid shares to migrate (%s)", msg.SharesToMigrate) + } + return nil +} + +func (msg MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) +} + +func (msg MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) GetSigners() []sdk.AccAddress { + sender, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + panic(err) + } + return []sdk.AccAddress{sender} +} diff --git a/x/superfluid/types/tx.pb.go b/x/superfluid/types/tx.pb.go index 5e57c8cca25..c695d5e35eb 100644 --- a/x/superfluid/types/tx.pb.go +++ b/x/superfluid/types/tx.pb.go @@ -615,6 +615,115 @@ func (m *MsgUnPoolWhitelistedPoolResponse) GetExitedLockIds() []uint64 { return nil } +// ===================== +// MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition +type MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition struct { + Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` + LockId uint64 `protobuf:"varint,2,opt,name=lock_id,json=lockId,proto3" json:"lock_id,omitempty" yaml:"lock_id"` + SharesToMigrate types.Coin `protobuf:"bytes,3,opt,name=shares_to_migrate,json=sharesToMigrate,proto3" json:"shares_to_migrate" yaml:"shares_to_migrate"` +} + +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) Reset() { + *m = MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition{} +} +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) String() string { + return proto.CompactTextString(m) +} +func (*MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) ProtoMessage() {} +func (*MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) Descriptor() ([]byte, []int) { + return fileDescriptor_55b645f187d22814, []int{12} +} +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition.Merge(m, src) +} +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) XXX_Size() int { + return m.Size() +} +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition proto.InternalMessageInfo + +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) GetSender() string { + if m != nil { + return m.Sender + } + return "" +} + +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) GetLockId() uint64 { + if m != nil { + return m.LockId + } + return 0 +} + +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) GetSharesToMigrate() types.Coin { + if m != nil { + return m.SharesToMigrate + } + return types.Coin{} +} + +type MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse struct { + Amount0 github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=amount0,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"amount0" yaml:"amount0"` + Amount1 github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,2,opt,name=amount1,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"amount1" yaml:"amount1"` + LiquidityCreated github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=liquidity_created,json=liquidityCreated,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"liquidity_created" yaml:"liquidity_created"` +} + +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse) Reset() { + *m = MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse{} +} +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse) String() string { + return proto.CompactTextString(m) +} +func (*MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse) ProtoMessage() {} +func (*MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_55b645f187d22814, []int{13} +} +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse.Merge(m, src) +} +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgSuperfluidDelegate)(nil), "osmosis.superfluid.MsgSuperfluidDelegate") proto.RegisterType((*MsgSuperfluidDelegateResponse)(nil), "osmosis.superfluid.MsgSuperfluidDelegateResponse") @@ -628,55 +737,69 @@ func init() { proto.RegisterType((*MsgLockAndSuperfluidDelegateResponse)(nil), "osmosis.superfluid.MsgLockAndSuperfluidDelegateResponse") proto.RegisterType((*MsgUnPoolWhitelistedPool)(nil), "osmosis.superfluid.MsgUnPoolWhitelistedPool") proto.RegisterType((*MsgUnPoolWhitelistedPoolResponse)(nil), "osmosis.superfluid.MsgUnPoolWhitelistedPoolResponse") + proto.RegisterType((*MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition)(nil), "osmosis.superfluid.MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition") + proto.RegisterType((*MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse)(nil), "osmosis.superfluid.MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse") } func init() { proto.RegisterFile("osmosis/superfluid/tx.proto", fileDescriptor_55b645f187d22814) } var fileDescriptor_55b645f187d22814 = []byte{ - // 674 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0x5f, 0x4f, 0xd3, 0x5e, - 0x18, 0x5e, 0xb7, 0xfd, 0xc6, 0xcf, 0x97, 0x80, 0xb1, 0x42, 0x28, 0x55, 0xdb, 0x51, 0x8d, 0x99, - 0x01, 0x7a, 0x18, 0x10, 0x42, 0xbc, 0x92, 0xc9, 0xcd, 0x0c, 0x4b, 0x48, 0x0d, 0x31, 0x31, 0x31, - 0xa4, 0xdd, 0x39, 0x94, 0x86, 0xd2, 0xb3, 0xf4, 0x74, 0xcb, 0x88, 0x1f, 0xc0, 0x5b, 0xaf, 0xbc, - 0xf5, 0xde, 0x0b, 0xbf, 0x86, 0x5c, 0x72, 0xe9, 0xd5, 0x34, 0xf0, 0x0d, 0xf8, 0x04, 0xe6, 0xf4, - 0x1f, 0xa2, 0x2d, 0x6c, 0x8a, 0x57, 0x3b, 0xe7, 0xbc, 0xcf, 0xfb, 0xbc, 0xcf, 0x9b, 0xf7, 0xcf, - 0x0a, 0xf7, 0x28, 0x3b, 0xa4, 0xcc, 0x61, 0x88, 0x75, 0x3b, 0xc4, 0xdf, 0x73, 0xbb, 0x0e, 0x46, - 0x41, 0x5f, 0xef, 0xf8, 0x34, 0xa0, 0xa2, 0x18, 0x1b, 0xf5, 0x0b, 0xa3, 0x3c, 0x65, 0x53, 0x9b, - 0x86, 0x66, 0xc4, 0x4f, 0x11, 0x52, 0x56, 0x6c, 0x4a, 0x6d, 0x97, 0xa0, 0xf0, 0x66, 0x75, 0xf7, - 0x10, 0xee, 0xfa, 0x66, 0xe0, 0x50, 0x2f, 0xb1, 0xb7, 0x43, 0x2a, 0x64, 0x99, 0x8c, 0xa0, 0x5e, - 0xdd, 0x22, 0x81, 0x59, 0x47, 0x6d, 0xea, 0x24, 0xf6, 0x87, 0x19, 0x32, 0x2e, 0x8e, 0x11, 0x48, - 0xeb, 0xc1, 0x74, 0x8b, 0xd9, 0x2f, 0xd3, 0xe7, 0x4d, 0xe2, 0x12, 0xdb, 0x0c, 0x88, 0xf8, 0x04, - 0x2a, 0x8c, 0x78, 0x98, 0xf8, 0x92, 0x50, 0x15, 0x6a, 0xb7, 0x1a, 0x77, 0xce, 0x07, 0xea, 0xc4, - 0x91, 0x79, 0xe8, 0x3e, 0xd5, 0xa2, 0x77, 0xcd, 0x88, 0x01, 0xe2, 0x0c, 0x8c, 0xb9, 0xb4, 0x7d, - 0xb0, 0xeb, 0x60, 0xa9, 0x58, 0x15, 0x6a, 0x65, 0xa3, 0xc2, 0xaf, 0x4d, 0x2c, 0xce, 0xc2, 0xff, - 0x3d, 0xd3, 0xdd, 0x35, 0x31, 0xf6, 0xa5, 0x12, 0x67, 0x31, 0xc6, 0x7a, 0xa6, 0xbb, 0x81, 0xb1, - 0xaf, 0xa9, 0xf0, 0x20, 0x33, 0xae, 0x41, 0x58, 0x87, 0x7a, 0x8c, 0x68, 0x6f, 0x60, 0xe6, 0x12, - 0x60, 0xc7, 0xc3, 0x37, 0x28, 0x4d, 0x9b, 0x03, 0x35, 0x87, 0xfe, 0x0a, 0x05, 0x16, 0xf5, 0xf0, - 0x16, 0x6d, 0x1f, 0xfc, 0x23, 0x05, 0x09, 0x7d, 0xaa, 0xe0, 0xb3, 0x00, 0x8f, 0x72, 0x54, 0x6e, - 0x78, 0x37, 0xac, 0x47, 0x6c, 0x40, 0x99, 0x37, 0x4f, 0x58, 0xa8, 0xf1, 0xe5, 0x59, 0x3d, 0xea, - 0x2e, 0x9d, 0x77, 0x97, 0x1e, 0x77, 0x97, 0xfe, 0x9c, 0x3a, 0x5e, 0xe3, 0xee, 0xf1, 0x40, 0x2d, - 0x9c, 0x0f, 0xd4, 0xf1, 0x28, 0x00, 0x77, 0xd2, 0x8c, 0xd0, 0x57, 0xd3, 0x61, 0x61, 0x18, 0xbd, - 0x69, 0x82, 0x5f, 0x04, 0xb8, 0xdf, 0x62, 0x36, 0x7f, 0xdb, 0xf0, 0xf0, 0xdf, 0x75, 0xa1, 0x09, - 0xff, 0x71, 0x0d, 0x4c, 0x2a, 0x56, 0x4b, 0x57, 0x27, 0xb0, 0xc4, 0x13, 0xf8, 0xf4, 0x4d, 0xad, - 0xd9, 0x4e, 0xb0, 0xdf, 0xb5, 0xf4, 0x36, 0x3d, 0x44, 0xf1, 0x2c, 0x45, 0x3f, 0x8b, 0x0c, 0x1f, - 0xa0, 0xe0, 0xa8, 0x43, 0x58, 0xe8, 0xc0, 0x8c, 0x88, 0xf9, 0xaa, 0x7e, 0x5e, 0x0b, 0x2b, 0x95, - 0x9b, 0x48, 0x92, 0xb1, 0x38, 0x09, 0xc5, 0xe6, 0x66, 0x98, 0x4c, 0xd9, 0x28, 0x36, 0x37, 0x35, - 0x1f, 0xa4, 0x16, 0xb3, 0x77, 0xbc, 0x6d, 0x4a, 0xdd, 0x57, 0xfb, 0x4e, 0x40, 0x5c, 0x87, 0x05, - 0x04, 0xf3, 0xeb, 0x28, 0xc9, 0xcf, 0xc3, 0x58, 0x87, 0x52, 0x37, 0xad, 0x6a, 0x43, 0x3c, 0x1f, - 0xa8, 0x93, 0x11, 0x36, 0x36, 0x68, 0x46, 0x85, 0x9f, 0x9a, 0x58, 0x7b, 0x01, 0xd5, 0xbc, 0x98, - 0xa9, 0xce, 0xc7, 0x70, 0x9b, 0xf4, 0x9d, 0x80, 0xe0, 0xdd, 0xb8, 0x5b, 0x98, 0x24, 0x54, 0x4b, - 0xb5, 0xb2, 0x31, 0x11, 0x3d, 0x6f, 0x85, 0x4d, 0xc3, 0x96, 0x3f, 0x54, 0xa0, 0xd4, 0x62, 0xb6, - 0xe8, 0x83, 0x98, 0x55, 0x3e, 0xfd, 0xf7, 0x6d, 0xa7, 0x67, 0xce, 0xbd, 0x5c, 0x1f, 0x1a, 0x9a, - 0x6a, 0xec, 0xc3, 0x54, 0xe6, 0x7e, 0x98, 0xbf, 0x96, 0xea, 0x02, 0x2c, 0xaf, 0x8c, 0x00, 0xce, - 0x8b, 0x9c, 0xce, 0xe1, 0x30, 0x91, 0x13, 0xf0, 0x50, 0x91, 0x7f, 0x9d, 0x18, 0xf1, 0xa3, 0x00, - 0x73, 0xd7, 0xef, 0x83, 0xf5, 0x11, 0x92, 0xba, 0xe4, 0x29, 0x3f, 0xfb, 0x53, 0xcf, 0x54, 0xe1, - 0x3b, 0x01, 0x66, 0xf3, 0x07, 0x7a, 0x29, 0x87, 0x3f, 0xd7, 0x43, 0x5e, 0x1f, 0xd5, 0x23, 0x55, - 0xf2, 0x16, 0xa6, 0xb3, 0x07, 0x6b, 0x21, 0x87, 0x32, 0x13, 0x2d, 0xaf, 0x8e, 0x82, 0x4e, 0x82, - 0x37, 0xb6, 0x8f, 0x4f, 0x15, 0xe1, 0xe4, 0x54, 0x11, 0xbe, 0x9f, 0x2a, 0xc2, 0xfb, 0x33, 0xa5, - 0x70, 0x72, 0xa6, 0x14, 0xbe, 0x9e, 0x29, 0x85, 0xd7, 0x6b, 0x3f, 0xad, 0x9d, 0x98, 0x79, 0xd1, - 0x35, 0x2d, 0x96, 0x5c, 0x50, 0xaf, 0xbe, 0x8a, 0xfa, 0x97, 0x3e, 0x1e, 0xf8, 0x2a, 0xb2, 0x2a, - 0xe1, 0x3f, 0xf6, 0xca, 0x8f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x04, 0x3d, 0x2c, 0x3c, 0x5f, 0x08, - 0x00, 0x00, + // 868 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xcd, 0x4e, 0xeb, 0x46, + 0x14, 0x8e, 0x93, 0x34, 0x69, 0xe7, 0xea, 0xde, 0x5b, 0xdc, 0x7b, 0x45, 0x70, 0xdb, 0x38, 0x4c, + 0x2b, 0x94, 0x0a, 0xb0, 0x09, 0x50, 0x84, 0xba, 0x82, 0x10, 0x55, 0x0a, 0x22, 0x12, 0x32, 0xa0, + 0x4a, 0x48, 0x55, 0xe4, 0x78, 0x06, 0x63, 0xe1, 0x78, 0x52, 0xcf, 0x38, 0x0d, 0xea, 0x03, 0x74, + 0xdb, 0x37, 0xe8, 0xbe, 0x8b, 0xbe, 0x46, 0x59, 0xb2, 0xac, 0x5a, 0x29, 0xad, 0xe0, 0x0d, 0x68, + 0x77, 0xdd, 0x54, 0xe3, 0xbf, 0x10, 0x88, 0x21, 0x09, 0xdc, 0x55, 0xec, 0x39, 0xe7, 0x7c, 0xe7, + 0xfb, 0x66, 0xbe, 0x9c, 0x31, 0xf8, 0x98, 0xd0, 0x36, 0xa1, 0x16, 0x55, 0xa9, 0xd7, 0xc1, 0xee, + 0x89, 0xed, 0x59, 0x48, 0x65, 0x3d, 0xa5, 0xe3, 0x12, 0x46, 0x44, 0x31, 0x0c, 0x2a, 0x83, 0xa0, + 0xf4, 0xc6, 0x24, 0x26, 0xf1, 0xc3, 0x2a, 0x7f, 0x0a, 0x32, 0xa5, 0xa2, 0x49, 0x88, 0x69, 0x63, + 0xd5, 0x7f, 0x6b, 0x79, 0x27, 0x2a, 0xf2, 0x5c, 0x9d, 0x59, 0xc4, 0x89, 0xe2, 0x86, 0x0f, 0xa5, + 0xb6, 0x74, 0x8a, 0xd5, 0x6e, 0xa5, 0x85, 0x99, 0x5e, 0x51, 0x0d, 0x62, 0x45, 0xf1, 0xcf, 0x46, + 0xd0, 0x18, 0x3c, 0x06, 0x49, 0xb0, 0x0b, 0xde, 0x36, 0xa8, 0x79, 0x10, 0x2f, 0xd7, 0xb0, 0x8d, + 0x4d, 0x9d, 0x61, 0xf1, 0x0b, 0x90, 0xa3, 0xd8, 0x41, 0xd8, 0x2d, 0x08, 0x25, 0xa1, 0xfc, 0x41, + 0x75, 0xe6, 0xa6, 0x2f, 0xbf, 0x3c, 0xd7, 0xdb, 0xf6, 0x57, 0x30, 0x58, 0x87, 0x5a, 0x98, 0x20, + 0xce, 0x82, 0xbc, 0x4d, 0x8c, 0xb3, 0xa6, 0x85, 0x0a, 0xe9, 0x92, 0x50, 0xce, 0x6a, 0x39, 0xfe, + 0x5a, 0x47, 0xe2, 0x1c, 0x78, 0xbf, 0xab, 0xdb, 0x4d, 0x1d, 0x21, 0xb7, 0x90, 0xe1, 0x28, 0x5a, + 0xbe, 0xab, 0xdb, 0xdb, 0x08, 0xb9, 0x50, 0x06, 0x9f, 0x8e, 0xec, 0xab, 0x61, 0xda, 0x21, 0x0e, + 0xc5, 0xf0, 0x5b, 0x30, 0x3b, 0x94, 0x70, 0xe4, 0xa0, 0x67, 0xa4, 0x06, 0xe7, 0x81, 0x9c, 0x00, + 0xff, 0x00, 0x83, 0x16, 0x71, 0xd0, 0x1e, 0x31, 0xce, 0xde, 0x11, 0x83, 0x08, 0x3e, 0x66, 0xf0, + 0xab, 0x00, 0x3e, 0x4f, 0x60, 0xb9, 0xed, 0x3c, 0x33, 0x1f, 0xb1, 0x0a, 0xb2, 0xdc, 0x3c, 0xfe, + 0x41, 0xbd, 0x58, 0x9d, 0x53, 0x02, 0x77, 0x29, 0xdc, 0x5d, 0x4a, 0xe8, 0x2e, 0x65, 0x87, 0x58, + 0x4e, 0xf5, 0xa3, 0x8b, 0xbe, 0x9c, 0xba, 0xe9, 0xcb, 0x2f, 0x82, 0x06, 0xbc, 0x08, 0x6a, 0x7e, + 0x2d, 0x54, 0xc0, 0xd2, 0x38, 0x7c, 0x63, 0x81, 0xbf, 0x09, 0xe0, 0x93, 0x06, 0x35, 0xf9, 0xda, + 0xb6, 0x83, 0x9e, 0xe6, 0x42, 0x1d, 0xbc, 0xc7, 0x39, 0xd0, 0x42, 0xba, 0x94, 0x79, 0x58, 0xc0, + 0x0a, 0x17, 0xf0, 0xcb, 0x5f, 0x72, 0xd9, 0xb4, 0xd8, 0xa9, 0xd7, 0x52, 0x0c, 0xd2, 0x56, 0xc3, + 0xff, 0x52, 0xf0, 0xb3, 0x4c, 0xd1, 0x99, 0xca, 0xce, 0x3b, 0x98, 0xfa, 0x05, 0x54, 0x0b, 0x90, + 0x1f, 0xf2, 0xf3, 0x86, 0x7f, 0x52, 0x89, 0x42, 0x22, 0xc5, 0xe2, 0x2b, 0x90, 0xae, 0xd7, 0x7c, + 0x31, 0x59, 0x2d, 0x5d, 0xaf, 0x41, 0x17, 0x14, 0x1a, 0xd4, 0x3c, 0x72, 0xf6, 0x09, 0xb1, 0xbf, + 0x39, 0xb5, 0x18, 0xb6, 0x2d, 0xca, 0x30, 0xe2, 0xaf, 0x93, 0x88, 0x5f, 0x04, 0xf9, 0x0e, 0x21, + 0x76, 0x7c, 0xaa, 0x55, 0xf1, 0xa6, 0x2f, 0xbf, 0x0a, 0x72, 0xc3, 0x00, 0xd4, 0x72, 0xfc, 0xa9, + 0x8e, 0xe0, 0x2e, 0x28, 0x25, 0xf5, 0x8c, 0x79, 0x2e, 0x80, 0xd7, 0xb8, 0x67, 0x31, 0x8c, 0x9a, + 0xa1, 0x5b, 0x68, 0x41, 0x28, 0x65, 0xca, 0x59, 0xed, 0x65, 0xb0, 0xbc, 0xe7, 0x9b, 0x86, 0xc2, + 0xff, 0x04, 0xb0, 0xe9, 0x83, 0xd9, 0x81, 0xf4, 0x86, 0x65, 0xba, 0x3a, 0xc3, 0x07, 0xa7, 0xba, + 0x8b, 0xe9, 0x21, 0xf9, 0xda, 0xb3, 0x6d, 0x4d, 0x77, 0x4c, 0xbc, 0x43, 0x1c, 0x03, 0x3b, 0x8c, + 0xc7, 0xd0, 0x3e, 0xa1, 0x16, 0x9f, 0x63, 0x13, 0x0a, 0x1c, 0xb2, 0xed, 0x6d, 0x81, 0x61, 0x00, + 0xc6, 0x56, 0x36, 0xc1, 0x0c, 0xf5, 0x09, 0x34, 0x19, 0x69, 0xb6, 0x03, 0x46, 0x8f, 0xfb, 0xba, + 0x14, 0xfa, 0xba, 0x10, 0x32, 0xb8, 0x8b, 0x00, 0xb5, 0xd7, 0x34, 0x94, 0x15, 0xaa, 0x84, 0xff, + 0xa4, 0xc1, 0xd6, 0xb4, 0xea, 0xe3, 0xad, 0x3e, 0x06, 0x79, 0xbd, 0x4d, 0x3c, 0x87, 0xad, 0x84, + 0xdb, 0xb0, 0xc5, 0x89, 0xfc, 0xd1, 0x97, 0x17, 0xc6, 0xf0, 0x67, 0xdd, 0x61, 0x83, 0x8d, 0x08, + 0x61, 0xa0, 0x16, 0x01, 0x0e, 0xb0, 0x2b, 0xfe, 0xb6, 0x3d, 0x19, 0xbb, 0x12, 0x63, 0x57, 0xc4, + 0xef, 0xc1, 0x8c, 0x6d, 0x7d, 0xe7, 0x59, 0xc8, 0x62, 0xe7, 0x4d, 0xc3, 0xc5, 0x5c, 0x5c, 0xf0, + 0xb7, 0xa8, 0xee, 0x4e, 0xd0, 0xa5, 0x86, 0x8d, 0xc1, 0xa6, 0xdf, 0x03, 0x84, 0xda, 0x87, 0xf1, + 0xda, 0x4e, 0xb0, 0xb4, 0xfa, 0x6f, 0x1e, 0x64, 0x1a, 0xd4, 0x14, 0x5d, 0x20, 0x8e, 0x1a, 0x19, + 0xca, 0xfd, 0x1b, 0x56, 0x19, 0x79, 0xd7, 0x48, 0x95, 0xb1, 0x53, 0xe3, 0xc3, 0xea, 0x81, 0x37, + 0x23, 0xef, 0xa4, 0xc5, 0x47, 0xa1, 0x06, 0xc9, 0xd2, 0xda, 0x04, 0xc9, 0x49, 0x9d, 0xe3, 0xd9, + 0x3f, 0x4e, 0xe7, 0x28, 0x79, 0xac, 0xce, 0x77, 0xa7, 0xb4, 0xf8, 0xb3, 0x00, 0xe6, 0x1f, 0xbf, + 0x83, 0x36, 0x27, 0x10, 0x35, 0x54, 0x29, 0x6d, 0x4d, 0x5b, 0x19, 0x33, 0xfc, 0x51, 0x00, 0x73, + 0xc9, 0x97, 0xc8, 0x4a, 0x02, 0x7e, 0x62, 0x85, 0xb4, 0x39, 0x69, 0x45, 0xcc, 0xe4, 0x07, 0xf0, + 0x76, 0xf4, 0x30, 0x5f, 0x4a, 0x80, 0x1c, 0x99, 0x2d, 0xad, 0x4f, 0x92, 0x1d, 0x37, 0xff, 0x53, + 0x00, 0x5f, 0x4e, 0x37, 0x89, 0xf7, 0x12, 0xfb, 0x4d, 0x81, 0x26, 0x1d, 0x3e, 0x27, 0x5a, 0xa4, + 0xae, 0xba, 0x7f, 0x71, 0x55, 0x14, 0x2e, 0xaf, 0x8a, 0xc2, 0xdf, 0x57, 0x45, 0xe1, 0xa7, 0xeb, + 0x62, 0xea, 0xf2, 0xba, 0x98, 0xfa, 0xfd, 0xba, 0x98, 0x3a, 0xde, 0xb8, 0x35, 0x66, 0xc2, 0xce, + 0xcb, 0xb6, 0xde, 0xa2, 0xd1, 0x8b, 0xda, 0xad, 0xac, 0xab, 0xbd, 0xa1, 0xcf, 0x71, 0x3e, 0x7a, + 0x5a, 0x39, 0xff, 0x1b, 0x78, 0xed, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x52, 0xfa, 0x23, 0x90, + 0xb1, 0x0b, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -703,6 +826,7 @@ type MsgClient interface { // Execute lockup lock and superfluid delegation in a single msg LockAndSuperfluidDelegate(ctx context.Context, in *MsgLockAndSuperfluidDelegate, opts ...grpc.CallOption) (*MsgLockAndSuperfluidDelegateResponse, error) UnPoolWhitelistedPool(ctx context.Context, in *MsgUnPoolWhitelistedPool, opts ...grpc.CallOption) (*MsgUnPoolWhitelistedPoolResponse, error) + UnlockAndMigrateSharesToFullRangeConcentratedPosition(ctx context.Context, in *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition, opts ...grpc.CallOption) (*MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse, error) } type msgClient struct { @@ -767,6 +891,15 @@ func (c *msgClient) UnPoolWhitelistedPool(ctx context.Context, in *MsgUnPoolWhit return out, nil } +func (c *msgClient) UnlockAndMigrateSharesToFullRangeConcentratedPosition(ctx context.Context, in *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition, opts ...grpc.CallOption) (*MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse, error) { + out := new(MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse) + err := c.cc.Invoke(ctx, "/osmosis.superfluid.Msg/UnlockAndMigrateSharesToFullRangeConcentratedPosition", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { // Execute superfluid delegation for a lockup @@ -781,6 +914,7 @@ type MsgServer interface { // Execute lockup lock and superfluid delegation in a single msg LockAndSuperfluidDelegate(context.Context, *MsgLockAndSuperfluidDelegate) (*MsgLockAndSuperfluidDelegateResponse, error) UnPoolWhitelistedPool(context.Context, *MsgUnPoolWhitelistedPool) (*MsgUnPoolWhitelistedPoolResponse, error) + UnlockAndMigrateSharesToFullRangeConcentratedPosition(context.Context, *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) (*MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -805,6 +939,9 @@ func (*UnimplementedMsgServer) LockAndSuperfluidDelegate(ctx context.Context, re func (*UnimplementedMsgServer) UnPoolWhitelistedPool(ctx context.Context, req *MsgUnPoolWhitelistedPool) (*MsgUnPoolWhitelistedPoolResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UnPoolWhitelistedPool not implemented") } +func (*UnimplementedMsgServer) UnlockAndMigrateSharesToFullRangeConcentratedPosition(ctx context.Context, req *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) (*MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UnlockAndMigrateSharesToFullRangeConcentratedPosition not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -918,6 +1055,24 @@ func _Msg_UnPoolWhitelistedPool_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } +func _Msg_UnlockAndMigrateSharesToFullRangeConcentratedPosition_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UnlockAndMigrateSharesToFullRangeConcentratedPosition(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/osmosis.superfluid.Msg/UnlockAndMigrateSharesToFullRangeConcentratedPosition", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UnlockAndMigrateSharesToFullRangeConcentratedPosition(ctx, req.(*MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "osmosis.superfluid.Msg", HandlerType: (*MsgServer)(nil), @@ -946,6 +1101,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "UnPoolWhitelistedPool", Handler: _Msg_UnPoolWhitelistedPool_Handler, }, + { + MethodName: "UnlockAndMigrateSharesToFullRangeConcentratedPosition", + Handler: _Msg_UnlockAndMigrateSharesToFullRangeConcentratedPosition_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "osmosis/superfluid/tx.proto", @@ -1355,6 +1514,104 @@ func (m *MsgUnPoolWhitelistedPoolResponse) MarshalToSizedBuffer(dAtA []byte) (in return len(dAtA) - i, nil } +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.SharesToMigrate.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if m.LockId != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.LockId)) + i-- + dAtA[i] = 0x10 + } + if len(m.Sender) > 0 { + i -= len(m.Sender) + copy(dAtA[i:], m.Sender) + i = encodeVarintTx(dAtA, i, uint64(len(m.Sender))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.LiquidityCreated.Size() + i -= size + if _, err := m.LiquidityCreated.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + { + size := m.Amount1.Size() + i -= size + if _, err := m.Amount1.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size := m.Amount0.Size() + i -= size + if _, err := m.Amount0.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -1539,6 +1796,39 @@ func (m *MsgUnPoolWhitelistedPoolResponse) Size() (n int) { return n } +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Sender) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.LockId != 0 { + n += 1 + sovTx(uint64(m.LockId)) + } + l = m.SharesToMigrate.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Amount0.Size() + n += 1 + l + sovTx(uint64(l)) + l = m.Amount1.Size() + n += 1 + l + sovTx(uint64(l)) + l = m.LiquidityCreated.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -2658,6 +2948,292 @@ func (m *MsgUnPoolWhitelistedPoolResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUnlockAndMigrateSharesToFullRangeConcentratedPosition: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Sender = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LockId", wireType) + } + m.LockId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.LockId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SharesToMigrate", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.SharesToMigrate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUnlockAndMigrateSharesToFullRangeConcentratedPositionResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount0", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Amount0.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount1", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Amount1.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LiquidityCreated", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.LiquidityCreated.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/twap/api.go b/x/twap/api.go index 81570ba074d..77b7d3a85dd 100644 --- a/x/twap/api.go +++ b/x/twap/api.go @@ -1,6 +1,7 @@ package twap import ( + "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -42,7 +43,9 @@ func (k Keeper) GetArithmeticTwap( startTime time.Time, endTime time.Time, ) (sdk.Dec, error) { - return k.getTwap(ctx, poolId, baseAssetDenom, quoteAssetDenom, startTime, endTime, k.GetArithmeticStrategy()) + result, err := k.getTwap(ctx, poolId, baseAssetDenom, quoteAssetDenom, startTime, endTime, k.GetArithmeticStrategy()) + fmt.Print("\n\n\n") + return result, err } func (k Keeper) GetGeometricTwap( @@ -92,15 +95,19 @@ func (k Keeper) getTwap( if startTime.After(endTime) { return sdk.Dec{}, types.StartTimeAfterEndTimeError{StartTime: startTime, EndTime: endTime} } + fmt.Printf("start time (%d), end time (%d), block time (%d)", startTime.UnixMilli(), endTime.UnixMilli(), ctx.BlockTime().UnixMilli()) if endTime.Equal(ctx.BlockTime()) { return k.getTwapToNow(ctx, poolId, baseAssetDenom, quoteAssetDenom, startTime, strategy) } else if endTime.After(ctx.BlockTime()) { return sdk.Dec{}, types.EndTimeInFutureError{EndTime: endTime, BlockTime: ctx.BlockTime()} } + fmt.Println("Interpolating start time") startRecord, err := k.getInterpolatedRecord(ctx, poolId, startTime, baseAssetDenom, quoteAssetDenom) if err != nil { return sdk.Dec{}, err } + fmt.Println() + fmt.Println("Interpolating end time") endRecord, err := k.getInterpolatedRecord(ctx, poolId, endTime, baseAssetDenom, quoteAssetDenom) if err != nil { return sdk.Dec{}, err @@ -123,10 +130,12 @@ func (k Keeper) getTwapToNow( return sdk.Dec{}, types.StartTimeAfterEndTimeError{StartTime: startTime, EndTime: ctx.BlockTime()} } + fmt.Println("Interpolating start time") startRecord, err := k.getInterpolatedRecord(ctx, poolId, startTime, baseAssetDenom, quoteAssetDenom) if err != nil { return sdk.Dec{}, err } + fmt.Println("getting beging block time start time") endRecord, err := k.GetBeginBlockAccumulatorRecord(ctx, poolId, baseAssetDenom, quoteAssetDenom) if err != nil { return sdk.Dec{}, err diff --git a/x/twap/client/cli/query.go b/x/twap/client/cli/query.go index 36b74f807d1..ce012e953cb 100644 --- a/x/twap/client/cli/query.go +++ b/x/twap/client/cli/query.go @@ -28,19 +28,19 @@ func GetQueryCmd() *cobra.Command { // GetQueryArithmeticCommand returns an arithmetic twap query command. func GetQueryArithmeticCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "arithmetic [poolid] [base denom] [start time] [end time]", + Use: "arithmetic [poolid] [base denom] [quote denom] [start time] [end time]", Short: "Query arithmetic twap", Aliases: []string{"twap"}, Long: osmocli.FormatLongDescDirect(`Query arithmetic twap for pool. Start time must be unix time. End time can be unix time or duration. Example: -{{.CommandPrefix}} arithmetic 1 uosmo 1667088000 24h -{{.CommandPrefix}} arithmetic 1 uosmo 1667088000 1667174400 +{{.CommandPrefix}} arithmetic 1 uosmo stake 1667088000 24h +{{.CommandPrefix}} arithmetic 1 uosmo stake 1667088000 1667174400 `, types.ModuleName), - Args: cobra.ExactArgs(4), + Args: cobra.ExactArgs(5), RunE: func(cmd *cobra.Command, args []string) error { // boilerplate parse fields - poolId, baseDenom, startTime, endTime, err := twapQueryParseArgs(args) + poolId, baseDenom, quoteDenom, startTime, endTime, err := twapQueryParseArgs(args) if err != nil { return err } @@ -48,10 +48,10 @@ Example: if err != nil { return err } - quoteDenom, err := getQuoteDenomFromLiquidity(cmd.Context(), clientCtx, poolId, baseDenom) - if err != nil { - return err - } + // quoteDenom, err := getQuoteDenomFromLiquidity(cmd.Context(), clientCtx, poolId, baseDenom) + // if err != nil { + // return err + // } queryClient := queryproto.NewQueryClient(clientCtx) res, err := queryClient.ArithmeticTwap(cmd.Context(), &queryproto.ArithmeticTwapRequest{ @@ -86,10 +86,10 @@ Example: {{.CommandPrefix}} geometric 1 uosmo 1667088000 24h {{.CommandPrefix}} geometric 1 uosmo 1667088000 1667174400 `, types.ModuleName), - Args: cobra.ExactArgs(4), + Args: cobra.ExactArgs(5), RunE: func(cmd *cobra.Command, args []string) error { // boilerplate parse fields - poolId, baseDenom, startTime, endTime, err := twapQueryParseArgs(args) + poolId, baseDenom, quoteDenom, startTime, endTime, err := twapQueryParseArgs(args) if err != nil { return err } @@ -97,16 +97,66 @@ Example: if err != nil { return err } - quoteDenom, err := getQuoteDenomFromLiquidity(cmd.Context(), clientCtx, poolId, baseDenom) + // quoteDenom, err := getQuoteDenomFromLiquidity(cmd.Context(), clientCtx, poolId, baseDenom) + // if err != nil { + // return err + // } + queryClient := queryproto.NewQueryClient(clientCtx) if err != nil { return err } - queryClient := queryproto.NewQueryClient(clientCtx) + + res, err := queryClient.GeometricTwap(cmd.Context(), &queryproto.GeometricTwapRequest{ + PoolId: poolId, + BaseAsset: baseDenom, + QuoteAsset: quoteDenom, + StartTime: startTime, + EndTime: &endTime, + }) + if err != nil { return err } - res, err := queryClient.GeometricTwap(cmd.Context(), &queryproto.GeometricTwapRequest{ + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetQueryArithmeticCommand returns an arithmetic twap query command. +func GetTwapRecordAtOrBeforeTime() *cobra.Command { + cmd := &cobra.Command{ + Use: "[start time] [end time]", + Short: "Query arithmetic twap", + Aliases: []string{"twap"}, + Long: osmocli.FormatLongDescDirect(`Query arithmetic twap for pool. Start time must be unix time. End time can be unix time or duration. + +Example: +{{.CommandPrefix}} arithmetic 1 uosmo stake 1667088000 24h +{{.CommandPrefix}} arithmetic 1 uosmo stake 1667088000 1667174400 +`, types.ModuleName), + Args: cobra.ExactArgs(5), + RunE: func(cmd *cobra.Command, args []string) error { + // boilerplate parse fields + poolId, baseDenom, quoteDenom, startTime, endTime, err := twapQueryParseArgs(args) + if err != nil { + return err + } + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + // quoteDenom, err := getQuoteDenomFromLiquidity(cmd.Context(), clientCtx, poolId, baseDenom) + // if err != nil { + // return err + // } + + queryClient := queryproto.NewQueryClient(clientCtx) + res, err := queryClient.ArithmeticTwap(cmd.Context(), &queryproto.ArithmeticTwapRequest{ PoolId: poolId, BaseAsset: baseDenom, QuoteAsset: quoteDenom, @@ -151,7 +201,7 @@ func getQuoteDenomFromLiquidity(ctx context.Context, clientCtx client.Context, p return quoteDenom, nil } -func twapQueryParseArgs(args []string) (poolId uint64, baseDenom string, startTime time.Time, endTime time.Time, err error) { +func twapQueryParseArgs(args []string) (poolId uint64, baseDenom, quoteDenom string, startTime time.Time, endTime time.Time, err error) { // boilerplate parse fields // poolId, err = osmocli.ParseUint(args[0], "poolId") @@ -159,27 +209,30 @@ func twapQueryParseArgs(args []string) (poolId uint64, baseDenom string, startTi return } - // + // baseDenom = strings.TrimSpace(args[1]) + // + quoteDenom = strings.TrimSpace(args[2]) + // - startTime, err = osmocli.ParseUnixTime(args[2], "start time") + startTime, err = osmocli.ParseUnixTime(args[3], "start time") if err != nil { return } // END TIME PARSE: ONEOF {, } // try parsing in unix time, if failed try parsing in duration - endTime, err = osmocli.ParseUnixTime(args[3], "end time") + endTime, err = osmocli.ParseUnixTime(args[4], "end time") if err != nil { // TODO if we don't use protoreflect: // make better error combiner, rather than just returning last error - duration, err2 := time.ParseDuration(args[3]) + duration, err2 := time.ParseDuration(args[5]) if err2 != nil { err = err2 return } endTime = startTime.Add(duration) } - return poolId, baseDenom, startTime, endTime, nil + return poolId, baseDenom, quoteDenom, startTime, endTime, nil } diff --git a/x/twap/client/query_proto_wrap.go b/x/twap/client/query_proto_wrap.go index 12e19710cb7..345a3745fe5 100644 --- a/x/twap/client/query_proto_wrap.go +++ b/x/twap/client/query_proto_wrap.go @@ -1,6 +1,7 @@ package client import ( + "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -19,9 +20,11 @@ func (q Querier) ArithmeticTwap(ctx sdk.Context, req queryproto.ArithmeticTwapRequest, ) (*queryproto.ArithmeticTwapResponse, error) { if req.EndTime == nil { + fmt.Println("req.EndTime == nil") req.EndTime = &time.Time{} } if (*req.EndTime == time.Time{}) { + fmt.Println("*req.EndTime == time.Time{}") *req.EndTime = ctx.BlockTime() } diff --git a/x/twap/logic.go b/x/twap/logic.go index d517b925e5a..cc7c05b4552 100644 --- a/x/twap/logic.go +++ b/x/twap/logic.go @@ -182,16 +182,19 @@ func recordWithUpdatedAccumulators(record types.TwapRecord, newTime time.Time) t return record } newRecord := record - timeDelta := newTime.Sub(record.Time) + timeDelta := newTime.Round(0).UTC().UnixMilli() - record.Time.Round(0).UTC().UnixMilli() + fmt.Println("timeDelta ", timeDelta) newRecord.Time = newTime // record.LastSpotPrice is the last spot price from the block the record was created in, // thus it is treated as the effective spot price until the new time. // (As there was no change until at or after this time) p0NewAccum := types.SpotPriceMulDuration(record.P0LastSpotPrice, timeDelta) + fmt.Println("p0NewAccum ", p0NewAccum) newRecord.P0ArithmeticTwapAccumulator = newRecord.P0ArithmeticTwapAccumulator.Add(p0NewAccum) p1NewAccum := types.SpotPriceMulDuration(record.P1LastSpotPrice, timeDelta) + fmt.Println("p1NewAccum ", p1NewAccum) newRecord.P1ArithmeticTwapAccumulator = newRecord.P1ArithmeticTwapAccumulator.Add(p1NewAccum) // If the last spot price is zero, then the logarithm is undefined. @@ -217,6 +220,9 @@ func recordWithUpdatedAccumulators(record types.TwapRecord, newTime time.Time) t // If for the record obtained, r.Time == r.LastErrorTime, this will also hold for the interpolated record. func (k Keeper) getInterpolatedRecord(ctx sdk.Context, poolId uint64, t time.Time, assetA, assetB string) (types.TwapRecord, error) { record, err := k.getRecordAtOrBeforeTime(ctx, poolId, t, assetA, assetB) + fmt.Println("time ", t.UnixMilli()) + fmt.Println("recordAtOrBeforeTime ", record) + fmt.Println("AtOrBeforeTime ", record.Time.UnixMilli()) if err != nil { return types.TwapRecord{}, err } @@ -225,6 +231,8 @@ func (k Keeper) getInterpolatedRecord(ctx sdk.Context, poolId uint64, t time.Tim record.LastErrorTime = t } record = recordWithUpdatedAccumulators(record, t) + fmt.Println("recordWithUpdatedAccumulators ", record) + fmt.Println("recordWithUpdatedAccumulators time ", record.Time.UnixMilli()) return record, nil } @@ -233,7 +241,12 @@ func (k Keeper) getMostRecentRecord(ctx sdk.Context, poolId uint64, assetA, asse if err != nil { return types.TwapRecord{}, err } + fmt.Println("mostRecentRecordStoreRepresentatione ", record) + fmt.Println("mostRecentRecordStoreRepresentatione time ", record.Time.UnixMilli()) record = recordWithUpdatedAccumulators(record, ctx.BlockTime()) + + fmt.Println("mostRecent recordWithUpdatedAccumulators ", record) + fmt.Println("mostRecent recordWithUpdatedAccumulators time ", record.Time.UnixMilli()) return record, nil } diff --git a/x/twap/strategy.go b/x/twap/strategy.go index a449b7f56ae..37e0306bb2b 100644 --- a/x/twap/strategy.go +++ b/x/twap/strategy.go @@ -1,6 +1,8 @@ package twap import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/osmosis-labs/osmosis/osmomath" @@ -34,8 +36,12 @@ func (s *arithmetic) computeTwap(startRecord types.TwapRecord, endRecord types.T } else { accumDiff = endRecord.P1ArithmeticTwapAccumulator.Sub(startRecord.P1ArithmeticTwapAccumulator) } - timeDelta := endRecord.Time.Sub(startRecord.Time) - return types.AccumDiffDivDuration(accumDiff, timeDelta) + timeDelta := endRecord.Time.Round(0).UTC().UnixMilli() - startRecord.Time.Round(0).UTC().UnixMilli() + fmt.Println("accumDiff", accumDiff) + fmt.Println("timeDelta", timeDelta) + result := types.AccumDiffDivDuration(accumDiff, timeDelta) + fmt.Println("twap", result) + return result } // computeTwap computes and returns a geometric TWAP between @@ -47,7 +53,7 @@ func (s *geometric) computeTwap(startRecord types.TwapRecord, endRecord types.Tw return sdk.ZeroDec() } - timeDelta := endRecord.Time.Sub(startRecord.Time) + timeDelta := endRecord.Time.Round(0).UTC().UnixMilli() - startRecord.Time.Round(0).UTC().UnixMilli() arithmeticMeanOfLogPrices := types.AccumDiffDivDuration(accumDiff, timeDelta) exponent := arithmeticMeanOfLogPrices diff --git a/x/twap/types/utils.go b/x/twap/types/utils.go index 1bd70a1d28f..f70e06b2bd7 100644 --- a/x/twap/types/utils.go +++ b/x/twap/types/utils.go @@ -3,7 +3,6 @@ package types import ( "fmt" "sort" - "time" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -37,16 +36,14 @@ func GetAllUniqueDenomPairs(denoms []string) []DenomPair { // SpotPriceMulDuration returns the spot price multiplied by the time delta, // that is the spot price between the current and last TWAP record. // A single second accounts for 1_000_000_000 when converted to int64. -func SpotPriceMulDuration(sp sdk.Dec, timeDelta time.Duration) sdk.Dec { - deltaMS := timeDelta.Milliseconds() - return sp.MulInt64(deltaMS) +func SpotPriceMulDuration(sp sdk.Dec, timeDeltaMs int64) sdk.Dec { + return sp.MulInt64(timeDeltaMs) } // AccumDiffDivDuration returns the accumulated difference divided by the the // time delta, that is the spot price between the current and last TWAP record. -func AccumDiffDivDuration(accumDiff sdk.Dec, timeDelta time.Duration) sdk.Dec { - deltaMS := timeDelta.Milliseconds() - return accumDiff.QuoInt64(deltaMS) +func AccumDiffDivDuration(accumDiff sdk.Dec, timeDeltaMs int64) sdk.Dec { + return accumDiff.QuoInt64(timeDeltaMs) } // LexicographicalOrderDenoms takes two denoms and returns them to be in lexicographically ascending order. diff --git a/x/txfees/keeper/feedecorator.go b/x/txfees/keeper/feedecorator.go index 4e2f254fe2c..b956bb0235b 100644 --- a/x/txfees/keeper/feedecorator.go +++ b/x/txfees/keeper/feedecorator.go @@ -71,27 +71,44 @@ func (mfd MempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate b } } - // If we are in CheckTx, this function is ran locally to determine if these fees are sufficient - // to enter our mempool. - // So we ensure that the provided fees meet a minimum threshold for the validator, - // converting every non-osmo specified asset into an osmo-equivalent amount, to determine sufficiency. - if (ctx.IsCheckTx() || ctx.IsReCheckTx()) && !simulate { - minBaseGasPrice := mfd.GetMinBaseGasPriceForTx(ctx, baseDenom, feeTx) - if !(minBaseGasPrice.IsZero()) { - // You should only be able to pay with one fee token in a single tx - if len(feeCoins) != 1 { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "no fee attached") - } - err = mfd.TxFeesKeeper.IsSufficientFee(ctx, minBaseGasPrice, feeTx.GetGas(), feeCoins[0]) - if err != nil { - return ctx, err - } - } + // Determine if these fees are sufficient for the tx to pass. + // Once ABCI++ Process Proposal lands, we can have block validity conditions enforce this. + minBaseGasPrice := mfd.getMinBaseGasPrice(ctx, baseDenom, simulate, feeTx) + + // If minBaseGasPrice is zero, then we don't need to check the fee. Continue + if minBaseGasPrice.IsZero() { + return next(ctx, tx, simulate) + } + // You should only be able to pay with one fee token in a single tx + if len(feeCoins) != 1 { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, + "Expected 1 fee denom attached, got %d", len(feeCoins)) + } + // The minimum base gas price is in uosmo, convert the fee denom's worth to uosmo terms. + // Then compare if its sufficient for paying the tx fee. + err = mfd.TxFeesKeeper.IsSufficientFee(ctx, minBaseGasPrice, feeTx.GetGas(), feeCoins[0]) + if err != nil { + return ctx, err } return next(ctx, tx, simulate) } +func (mfd MempoolFeeDecorator) getMinBaseGasPrice(ctx sdk.Context, baseDenom string, simulate bool, feeTx sdk.FeeTx) sdk.Dec { + // In block execution (DeliverTx), its set to the governance decided upon consensus min fee. + minBaseGasPrice := types.ConsensusMinFee + // If we are in CheckTx, a separate function is ran locally to ensure sufficient fees for entering our mempool. + // So we ensure that the provided fees meet a minimum threshold for the validator + if (ctx.IsCheckTx() || ctx.IsReCheckTx()) && !simulate { + minBaseGasPrice = sdk.MaxDec(minBaseGasPrice, mfd.GetMinBaseGasPriceForTx(ctx, baseDenom, feeTx)) + } + // If we are in genesis, then we actually override all of the above, to set it to 0. + if ctx.IsGenesis() { + minBaseGasPrice = sdk.ZeroDec() + } + return minBaseGasPrice +} + // IsSufficientFee checks if the feeCoin provided (in any asset), is worth enough osmo at current spot prices // to pay the gas cost of this tx. func (k Keeper) IsSufficientFee(ctx sdk.Context, minBaseGasPrice sdk.Dec, gasRequested uint64, feeCoin sdk.Coin) error { diff --git a/x/txfees/keeper/feedecorator_test.go b/x/txfees/keeper/feedecorator_test.go index 321040de360..f0537a7fc52 100644 --- a/x/txfees/keeper/feedecorator_test.go +++ b/x/txfees/keeper/feedecorator_test.go @@ -1,6 +1,8 @@ package keeper_test import ( + "fmt" + clienttx "github.com/cosmos/cosmos-sdk/client/tx" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/simapp" @@ -19,137 +21,96 @@ func (suite *KeeperTestSuite) TestFeeDecorator() { mempoolFeeOpts := types.NewDefaultMempoolFeeOptions() mempoolFeeOpts.MinGasPriceForHighGasTx = sdk.MustNewDecFromStr("0.0025") baseDenom, _ := suite.App.TxFeesKeeper.GetBaseDenom(suite.Ctx) + baseGas := uint64(10000) + consensusMinFeeAmt := int64(25) + point1BaseDenomMinGasPrices := sdk.NewDecCoins(sdk.NewDecCoinFromDec(baseDenom, + sdk.MustNewDecFromStr("0.1"))) + // uion is setup with a relative price of 1:1 uion := "uion" - uionPoolId := suite.PrepareBalancerPoolWithCoins( - sdk.NewInt64Coin(sdk.DefaultBondDenom, 500), - sdk.NewInt64Coin(uion, 500), - ) - suite.ExecuteUpgradeFeeTokenProposal(uion, uionPoolId) - - tests := []struct { + type testcase struct { name string txFee sdk.Coins - minGasPrices sdk.DecCoins - gasRequested uint64 + minGasPrices sdk.DecCoins // if blank, set to 0 + gasRequested uint64 // if blank, set to base gas isCheckTx bool expectPass bool - baseDenomGas bool - }{ - { - name: "no min gas price - checktx", - txFee: sdk.NewCoins(), - minGasPrices: sdk.NewDecCoins(), - gasRequested: 10000, - isCheckTx: true, - expectPass: true, - baseDenomGas: true, - }, - { - name: "no min gas price - delivertx", - txFee: sdk.NewCoins(), - minGasPrices: sdk.NewDecCoins(), - gasRequested: 10000, - isCheckTx: false, - expectPass: true, - baseDenomGas: true, - }, - { - name: "works with valid basedenom fee", - txFee: sdk.NewCoins(sdk.NewInt64Coin(baseDenom, 1000)), - minGasPrices: sdk.NewDecCoins(sdk.NewDecCoinFromDec(baseDenom, - sdk.MustNewDecFromStr("0.1"))), - gasRequested: 10000, - isCheckTx: true, - expectPass: true, - baseDenomGas: true, - }, - { - name: "doesn't work with not enough fee in checktx", - txFee: sdk.NewCoins(sdk.NewInt64Coin(baseDenom, 1)), - minGasPrices: sdk.NewDecCoins(sdk.NewDecCoinFromDec(baseDenom, - sdk.MustNewDecFromStr("0.1"))), - gasRequested: 10000, - isCheckTx: true, - expectPass: false, - baseDenomGas: true, - }, - { - name: "works with not enough fee in delivertx", - txFee: sdk.NewCoins(sdk.NewInt64Coin(baseDenom, 1)), - minGasPrices: sdk.NewDecCoins(sdk.NewDecCoinFromDec(baseDenom, - sdk.MustNewDecFromStr("0.1"))), - gasRequested: 10000, - isCheckTx: false, - expectPass: true, - baseDenomGas: true, - }, - { - name: "works with valid converted fee", - txFee: sdk.NewCoins(sdk.NewInt64Coin(uion, 1000)), - minGasPrices: sdk.NewDecCoins(sdk.NewDecCoinFromDec(baseDenom, - sdk.MustNewDecFromStr("0.1"))), - gasRequested: 10000, - isCheckTx: true, - expectPass: true, - baseDenomGas: false, - }, - { - name: "doesn't work with not enough converted fee in checktx", - txFee: sdk.NewCoins(sdk.NewInt64Coin(uion, 1)), - minGasPrices: sdk.NewDecCoins(sdk.NewDecCoinFromDec(baseDenom, - sdk.MustNewDecFromStr("0.1"))), - gasRequested: 10000, - isCheckTx: true, - expectPass: false, - baseDenomGas: false, - }, - { - name: "works with not enough converted fee in delivertx", - txFee: sdk.NewCoins(sdk.NewInt64Coin(uion, 1)), - minGasPrices: sdk.NewDecCoins(sdk.NewDecCoinFromDec(baseDenom, - sdk.MustNewDecFromStr("0.1"))), - gasRequested: 10000, - isCheckTx: false, - expectPass: true, - baseDenomGas: false, - }, - { - name: "multiple fee coins - checktx", - txFee: sdk.NewCoins(sdk.NewInt64Coin(baseDenom, 1), sdk.NewInt64Coin(uion, 1)), - minGasPrices: sdk.NewDecCoins(), - gasRequested: 10000, - isCheckTx: true, - expectPass: false, - baseDenomGas: false, - }, - { - name: "multiple fee coins - delivertx", - txFee: sdk.NewCoins(sdk.NewInt64Coin(baseDenom, 1), sdk.NewInt64Coin(uion, 1)), - minGasPrices: sdk.NewDecCoins(), - gasRequested: 10000, - isCheckTx: false, - expectPass: false, - baseDenomGas: false, - }, - { - name: "invalid fee denom", - txFee: sdk.NewCoins(sdk.NewInt64Coin("moo", 1)), - minGasPrices: sdk.NewDecCoins(), - gasRequested: 10000, - isCheckTx: false, - expectPass: false, - baseDenomGas: false, - }, + } + + tests := []testcase{} + txType := []string{"delivertx", "checktx"} + succesType := []string{"does", "doesn't"} + for isCheckTx := 0; isCheckTx <= 1; isCheckTx++ { + tests = append(tests, []testcase{ + { + name: fmt.Sprintf("no min gas price - %s. Fails w/ consensus minimum", txType[isCheckTx]), + txFee: sdk.NewCoins(), + isCheckTx: isCheckTx == 1, + expectPass: false, + }, + { + name: fmt.Sprintf("LT Consensus min gas price - %s", txType[isCheckTx]), + txFee: sdk.NewCoins(sdk.NewInt64Coin(baseDenom, consensusMinFeeAmt-1)), + isCheckTx: isCheckTx == 1, + expectPass: false, + }, + { + name: fmt.Sprintf("Consensus min gas price - %s", txType[isCheckTx]), + txFee: sdk.NewCoins(sdk.NewInt64Coin(baseDenom, consensusMinFeeAmt)), + isCheckTx: isCheckTx == 1, + expectPass: true, + }, + { + name: fmt.Sprintf("multiple fee coins - %s", txType[isCheckTx]), + txFee: sdk.NewCoins(sdk.NewInt64Coin(baseDenom, 1), sdk.NewInt64Coin(uion, 1)), + isCheckTx: isCheckTx == 1, + expectPass: false, + }, + { + name: fmt.Sprintf("works with valid basedenom fee - %s", txType[isCheckTx]), + txFee: sdk.NewCoins(sdk.NewInt64Coin(baseDenom, 1000)), + minGasPrices: point1BaseDenomMinGasPrices, + isCheckTx: isCheckTx == 1, + expectPass: true, + }, + { + name: fmt.Sprintf("works with valid converted fee - %s", txType[isCheckTx]), + txFee: sdk.NewCoins(sdk.NewInt64Coin(uion, 1000)), + minGasPrices: point1BaseDenomMinGasPrices, + isCheckTx: isCheckTx == 1, + expectPass: true, + }, + { + name: fmt.Sprintf("%s work with insufficient mempool fee in %s", succesType[isCheckTx], txType[isCheckTx]), + txFee: sdk.NewCoins(sdk.NewInt64Coin(baseDenom, consensusMinFeeAmt)), // consensus minimum + minGasPrices: point1BaseDenomMinGasPrices, + isCheckTx: isCheckTx == 1, + expectPass: isCheckTx != 1, + }, + { + name: fmt.Sprintf("%s work with insufficient converted mempool fee in %s", succesType[isCheckTx], txType[isCheckTx]), + txFee: sdk.NewCoins(sdk.NewInt64Coin(uion, 25)), // consensus minimum + minGasPrices: point1BaseDenomMinGasPrices, + isCheckTx: isCheckTx == 1, + expectPass: isCheckTx != 1, + }, + { + name: "invalid fee denom", + txFee: sdk.NewCoins(sdk.NewInt64Coin("moooooo", 1000)), + isCheckTx: isCheckTx == 1, + expectPass: false, + }, + }...) + } + + custTests := []testcase{ { - name: "mingasprice not containing basedenom gets treated as min gas price 0", - txFee: sdk.NewCoins(sdk.NewInt64Coin(uion, 100000000)), - minGasPrices: sdk.NewDecCoins(sdk.NewInt64DecCoin(uion, 1)), - gasRequested: 10000, + name: "min gas price not containing basedenom gets treated as min gas price 0", + txFee: sdk.NewCoins(sdk.NewInt64Coin(uion, 1000)), + minGasPrices: sdk.NewDecCoins(sdk.NewInt64DecCoin(uion, 1000000)), isCheckTx: true, expectPass: true, - baseDenomGas: false, }, { name: "tx with gas wanted more than allowed should not pass", @@ -158,7 +119,6 @@ func (suite *KeeperTestSuite) TestFeeDecorator() { gasRequested: mempoolFeeOpts.MaxGasWantedPerTx + 1, isCheckTx: true, expectPass: false, - baseDenomGas: false, }, { name: "tx with high gas and not enough fee should no pass", @@ -167,7 +127,6 @@ func (suite *KeeperTestSuite) TestFeeDecorator() { gasRequested: mempoolFeeOpts.HighGasTxThreshold, isCheckTx: true, expectPass: false, - baseDenomGas: false, }, { name: "tx with high gas and enough fee should pass", @@ -176,23 +135,30 @@ func (suite *KeeperTestSuite) TestFeeDecorator() { gasRequested: mempoolFeeOpts.HighGasTxThreshold, isCheckTx: true, expectPass: true, - baseDenomGas: false, }, } + tests = append(tests, custTests...) for _, tc := range tests { // reset pool and accounts for each test suite.SetupTest(false) suite.Run(tc.name, func() { + // setup uion with 1:1 fee uionPoolId := suite.PrepareBalancerPoolWithCoins( sdk.NewInt64Coin(sdk.DefaultBondDenom, 500), sdk.NewInt64Coin(uion, 500), ) suite.ExecuteUpgradeFeeTokenProposal(uion, uionPoolId) + if tc.minGasPrices == nil { + tc.minGasPrices = sdk.NewDecCoins() + } + if tc.gasRequested == 0 { + tc.gasRequested = baseGas + } suite.Ctx = suite.Ctx.WithIsCheckTx(tc.isCheckTx).WithMinGasPrices(tc.minGasPrices) - suite.Ctx = suite.Ctx.WithMinGasPrices(tc.minGasPrices) + // TODO: Cleanup this code. // TxBuilder components reset for every test case txBuilder := suite.clientCtx.TxConfig.NewTxBuilder() priv0, _, addr0 := testdata.KeyTestPubAddr() @@ -226,11 +192,13 @@ func (suite *KeeperTestSuite) TestFeeDecorator() { _, err := antehandlerMFD(suite.Ctx, tx, false) if tc.expectPass { - if tc.baseDenomGas && !tc.txFee.IsZero() { - moduleAddr := suite.App.AccountKeeper.GetModuleAddress(types.FeeCollectorName) - suite.Require().Equal(tc.txFee[0], suite.App.BankKeeper.GetBalance(suite.Ctx, moduleAddr, baseDenom), tc.name) - } else if !tc.txFee.IsZero() { - moduleAddr := suite.App.AccountKeeper.GetModuleAddress(types.NonNativeFeeCollectorName) + // ensure fee was collected + if !tc.txFee.IsZero() { + moduleName := types.FeeCollectorName + if tc.txFee[0].Denom != baseDenom { + moduleName = types.NonNativeFeeCollectorName + } + moduleAddr := suite.App.AccountKeeper.GetModuleAddress(moduleName) suite.Require().Equal(tc.txFee[0], suite.App.BankKeeper.GetBalance(suite.Ctx, moduleAddr, tc.txFee[0].Denom), tc.name) } suite.Require().NoError(err, "test: %s", tc.name) diff --git a/x/txfees/types/constants.go b/x/txfees/types/constants.go new file mode 100644 index 00000000000..57d4352ad20 --- /dev/null +++ b/x/txfees/types/constants.go @@ -0,0 +1,7 @@ +package types + +import sdk "github.com/cosmos/cosmos-sdk/types" + +// ConsensusMinFee is a governance set parameter from prop 354 (https://www.mintscan.io/osmosis/proposals/354) +// Its intended to be .0025 uosmo / gas +var ConsensusMinFee sdk.Dec = sdk.NewDecWithPrec(25, 4) diff --git a/x/valset-pref/simulation/sim_msgs.go b/x/valset-pref/simulation/sim_msgs.go index 92705ae982d..cfec8dba0ab 100644 --- a/x/valset-pref/simulation/sim_msgs.go +++ b/x/valset-pref/simulation/sim_msgs.go @@ -172,10 +172,7 @@ func GetRandomValAndWeights(ctx sdk.Context, k valsetkeeper.Keeper, sim *osmosim return nil, fmt.Errorf("No validator") } - randValue, err := RandomWeight(remainingWeight) - if err != nil { - return nil, fmt.Errorf("Error with random weights") - } + randValue := sim.RandomDecAmount(remainingWeight) remainingWeight = remainingWeight.Sub(randValue) if !randValue.Equal(sdk.ZeroDec()) { @@ -208,17 +205,3 @@ func GetRandomDelegations(ctx sdk.Context, k valsetkeeper.Keeper, sim *osmosimty return delegations.Preferences, err } - -// Random float point from 0-1 -func RandomWeight(maxVal sdk.Dec) (sdk.Dec, error) { - rand.Seed(time.Now().UnixNano()) - val, err := maxVal.Float64() - if err != nil { - return sdk.Dec{}, err - } - - randVal := rand.Float64() * val - valWeightStr := fmt.Sprintf("%.2f", randVal) - - return sdk.MustNewDecFromStr(valWeightStr), nil -}