From 1497d5b7d0284c3236ae143846e7ab19d4d4f93f Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 7 Aug 2023 18:09:31 +0200 Subject: [PATCH 1/6] docs: Osmosis protocol book (#5971) --- .github/workflows/protocol-docs.yml | 52 +++++++++++++++++++++++++++ .gitignore | 5 ++- docs/protocol/book.toml | 16 +++++++++ docs/protocol/src/SUMMARY.md | 12 +++++++ docs/protocol/src/core_dex.md | 12 +++++++ docs/protocol/src/dex/concentrated.md | 1 + docs/protocol/src/dex/cosmwams.md | 1 + docs/protocol/src/dex/cosmwasm.md | 1 + docs/protocol/src/dex/stableswap.md | 1 + docs/protocol/src/dex/weighted.md | 7 ++++ docs/protocol/src/ecosystem.md | 1 + docs/protocol/src/governance.md | 17 +++++++++ docs/protocol/src/osmosis.md | 31 ++++++++++++++++ docs/protocol/src/smartcontracts.md | 1 + docs/protocol/src/staking.md | 1 + 15 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/protocol-docs.yml create mode 100644 docs/protocol/book.toml create mode 100644 docs/protocol/src/SUMMARY.md create mode 100644 docs/protocol/src/core_dex.md create mode 100644 docs/protocol/src/dex/concentrated.md create mode 100644 docs/protocol/src/dex/cosmwams.md create mode 100644 docs/protocol/src/dex/cosmwasm.md create mode 100644 docs/protocol/src/dex/stableswap.md create mode 100644 docs/protocol/src/dex/weighted.md create mode 100644 docs/protocol/src/ecosystem.md create mode 100644 docs/protocol/src/governance.md create mode 100644 docs/protocol/src/osmosis.md create mode 100644 docs/protocol/src/smartcontracts.md create mode 100644 docs/protocol/src/staking.md diff --git a/.github/workflows/protocol-docs.yml b/.github/workflows/protocol-docs.yml new file mode 100644 index 00000000000..499829768c3 --- /dev/null +++ b/.github/workflows/protocol-docs.yml @@ -0,0 +1,52 @@ +name: Render and Deploy Docs + +on: + workflow_dispatch: + push: + branches: + - main + +jobs: + + build: + name: Render and deploy protocol and API docs + timeout-minutes: 30 + runs-on: ubuntu-latest + steps: + - name: Checkout the source code + uses: actions/checkout@v3 + with: + lfs: true + - name: Install rust toolchain + uses: dtolnay/rust-toolchain@nightly + - name: Load Rust caching + uses: astriaorg/buildjet-rust-cache@v2.5.1 + - name: Load get-version action to grab version component of deployment path + uses: battila7/get-version-action@v2 + id: get_version + - name: Print version component of deployment path + run: echo ${{ steps.get_version.outputs.version }} + - name: Install mdbook + run: cargo install mdbook mdbook-katex mdbook-mermaid + - name: Build protocol spec + run: cd docs/protocol && mdbook build + - name: Move protocol spec to subdirectory & Deploy + env: + DO_DOCS_PK: ${{ secrets.DO_DOCS_PK }} + DO_DOCS_IP: ${{ secrets.DO_DOCS_IP }} + run: | + cd docs/protocol + if [ -d "do-tmp" ]; then rm -rf do-tmp; fi + mkdir do-tmp + mv book do-tmp/${{ steps.get_version.outputs.version }} + tree do-tmp + which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y ) + which rsync || ( apt-get update -y && apt-get install rsync -y ) + eval $(ssh-agent -s) + ssh-add <(echo "$DO_DOCS_PK" ) + mkdir -p ~/.ssh + chmod 700 ~/.ssh + ssh-keyscan $DO_DOCS_IP >> ~/.ssh/known_hosts + chmod 644 ~/.ssh/known_hosts + cd do-tmp/main + scp -r * root@$DO_DOCS_IP:/var/www/html diff --git a/.gitignore b/.gitignore index 4313d719db8..84f981a2f3a 100644 --- a/.gitignore +++ b/.gitignore @@ -250,5 +250,8 @@ blocks.db tests/cl-genesis-positions/script tests/cl-genesis-positions/*.json +# Docs +docs/protocol/book + # Release folder -dist/ \ No newline at end of file +dist/ diff --git a/docs/protocol/book.toml b/docs/protocol/book.toml new file mode 100644 index 00000000000..c94f58c7ffe --- /dev/null +++ b/docs/protocol/book.toml @@ -0,0 +1,16 @@ +[book] +authors = ["Osmosis Labs"] +language = "en" +multilingual = false +src = "src" +title = "The Osmosis Protocol" + +[preprocessor.katex] + +[preprocessor.mermaid] +command = "mdbook-mermaid" + +[output.html] +curly-quotes = true +fold = { enable = true, level = 1 } +git-repository-url = "https://github.com/osmosis-labs/osmosis" diff --git a/docs/protocol/src/SUMMARY.md b/docs/protocol/src/SUMMARY.md new file mode 100644 index 00000000000..88f1c283c13 --- /dev/null +++ b/docs/protocol/src/SUMMARY.md @@ -0,0 +1,12 @@ +# Summary + +[Osmosis](./osmosis.md) +- [Decentralized Exchange](./core_dex.md) + - [Weighted Pool](./dex/weighted.md) + - [Stableswap Pool](./dex/stableswap.md) + - [Concentrated Pool](./dex/concentrated.md) + - [Cosmwasm Pool](./dex/cosmwasm.md) +- [Governance](./governance.md) +- [Staking](./staking.md) +- [Smart Contracts](./smartcontracts.md) +- [Ecosystem](./ecosystem.md) diff --git a/docs/protocol/src/core_dex.md b/docs/protocol/src/core_dex.md new file mode 100644 index 00000000000..8a32582c463 --- /dev/null +++ b/docs/protocol/src/core_dex.md @@ -0,0 +1,12 @@ +# Decentralized Exchange + +Osmosis is a decentralized automated market maker (AMM) protocol built using Cosmos SDK that represents a flexible building block for programmable liquidity. + +By separating the AMM curve logic and math from the core swapping functionality, Osmosis becomes an extensible AMM that can incorporate any number of swap curves and pool types. This includes: + +- Traditional 50/50 weighted pools +- Custom weights like 80/20 for controlled exposure +- Solidly-style Stableswap curve +- Concentrated Liquidity pools +- CosmWasm pools + diff --git a/docs/protocol/src/dex/concentrated.md b/docs/protocol/src/dex/concentrated.md new file mode 100644 index 00000000000..d319e9e23c8 --- /dev/null +++ b/docs/protocol/src/dex/concentrated.md @@ -0,0 +1 @@ +# Concentrated Pool diff --git a/docs/protocol/src/dex/cosmwams.md b/docs/protocol/src/dex/cosmwams.md new file mode 100644 index 00000000000..a0361e071aa --- /dev/null +++ b/docs/protocol/src/dex/cosmwams.md @@ -0,0 +1 @@ +# CosmWasm Pool diff --git a/docs/protocol/src/dex/cosmwasm.md b/docs/protocol/src/dex/cosmwasm.md new file mode 100644 index 00000000000..8f9da51e6be --- /dev/null +++ b/docs/protocol/src/dex/cosmwasm.md @@ -0,0 +1 @@ +# Cosmwasm Pool diff --git a/docs/protocol/src/dex/stableswap.md b/docs/protocol/src/dex/stableswap.md new file mode 100644 index 00000000000..76fe3b47606 --- /dev/null +++ b/docs/protocol/src/dex/stableswap.md @@ -0,0 +1 @@ +# Stableswap Pool diff --git a/docs/protocol/src/dex/weighted.md b/docs/protocol/src/dex/weighted.md new file mode 100644 index 00000000000..cfdde988e32 --- /dev/null +++ b/docs/protocol/src/dex/weighted.md @@ -0,0 +1,7 @@ +# Weighted Pool + +Liquidity pools are clusters of tokens with pre-determined weights. A token's weight is how much its value accounts for the total value within the pool. For example, Uniswap pools involve two tokens with 50-50 weights. The total value of Asset A must remain equal to the total value of Asset B. Other token weights are possible, such as 90-10. It is also possible to have a liquidity pool with more than two assets. + +In Osmosis, pool creators are allowed to choose the tokens within the pool and their respective weights. The parameters chosen by the pool creator cannot be changed. Other users can create separate pools with different parameters. + +Weighted Pools are an extension of the classical AMM pools popularized by Uniswap v1. Weighted Pools are great for general cases, including tokens that don't necessarily have any price correlation (ex. DAI/WETH). Unlike pools in other AMMs that only provide 50/50 weightings, Osmosis Weighted Pools enable users to build pools with more than two tokens and custom weightings, such as pools with 80/20 or 60/20/20 weightings. diff --git a/docs/protocol/src/ecosystem.md b/docs/protocol/src/ecosystem.md new file mode 100644 index 00000000000..e46e912ed82 --- /dev/null +++ b/docs/protocol/src/ecosystem.md @@ -0,0 +1 @@ +# Ecosystem diff --git a/docs/protocol/src/governance.md b/docs/protocol/src/governance.md new file mode 100644 index 00000000000..66109a0f3d8 --- /dev/null +++ b/docs/protocol/src/governance.md @@ -0,0 +1,17 @@ +# Governance + +Osmosis is a sovereign delegated Proof-of-Stake chain with its own validator set. +Users delegate their stake to validators to secure the network while keeping the +opportunity to overwrite validator's decision with their own vote. + +The decisions are made through the governance process consisting of proposals. + +A proposal can be created by anyone. Once created with the required deposit, the +voting period begins. + +The proposal is either accepted or rejected. + +There are two types of proposals that determine the length of the voting period, +deposit amount and quorum: +1. Standard proposal +2. Expedited proposal diff --git a/docs/protocol/src/osmosis.md b/docs/protocol/src/osmosis.md new file mode 100644 index 00000000000..d558c222eb7 --- /dev/null +++ b/docs/protocol/src/osmosis.md @@ -0,0 +1,31 @@ +# Introduction to Osmosis + +Osmosis is a fair-launched, customizable automated market maker for +interchain assets that allows the creation and management of +non-custodial, self-balancing, interchain token index similar to one of +Balancer. + +Inspired by [Balancer](http://balancer.finance/whitepaper) and Sunny +Aggarwal's '[DAOfying Uniswap Automated Market Maker +Pools](https://www.sunnya97.com/blog/daoifying-uniswap-automated-market-maker-pools)', +the goal for Osmosis is to provide the best-in-class tools that extend +the use of AMMs within the Cosmos ecosystem beyond traditional token +swap-type use cases. Bonding curves, while have found its primary use +case in decentralized exchange mechanisms, its potential use case can be +further extended through the customizability that Osmosis offers. +Through the customizability offered by Osmosis such as custom-curve AMMs, +dynamic adjustments of spread factors, multi-token liquidity pools--the AMM +can offer decentralized formation of token fundraisers, interchain +staking, options market, and more for the Cosmos ecosystem. + +Whereas most Cosmos zones have focused their incentive scheme on the +delegators, Osmosis attempts to align the interests of multiple +stakeholders of the ecosystem such as LPs, DAO members, as well as +delegators. One mechanism that is introduced is how staked liquidity +providers have sovereign ownership over their pools, and through the +pool governance process allow them to adjust the parameters depending on +the pool's competition and market conditions. Osmosis is a sovereign +Cosmos zone that derives its sovereignty not only from its +application-specific blockchain architecture but also the collective +sovereignty of the LPs that has aligned interest to different tokens +that they are providing liquidity for. diff --git a/docs/protocol/src/smartcontracts.md b/docs/protocol/src/smartcontracts.md new file mode 100644 index 00000000000..95badcb05e1 --- /dev/null +++ b/docs/protocol/src/smartcontracts.md @@ -0,0 +1 @@ +# Smart Contracts (CosmWasm) diff --git a/docs/protocol/src/staking.md b/docs/protocol/src/staking.md new file mode 100644 index 00000000000..abe5cd84eb4 --- /dev/null +++ b/docs/protocol/src/staking.md @@ -0,0 +1 @@ +# Staking From e1552a3bb3f6799bb4b65033ec9f4f2096005c0d Mon Sep 17 00:00:00 2001 From: "Matt, Park" <45252226+mattverse@users.noreply.github.com> Date: Tue, 8 Aug 2023 01:19:47 +0900 Subject: [PATCH 2/6] Fix token factory (#5497) --- app/keepers/keepers.go | 2 +- x/tokenfactory/keeper/admins_test.go | 18 ++++++++++++++++++ x/tokenfactory/types/denoms.go | 16 ---------------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index b2195f036b3..ba63d28cbac 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -428,7 +428,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers( appKeepers.keys[tokenfactorytypes.StoreKey], appKeepers.GetSubspace(tokenfactorytypes.ModuleName), appKeepers.AccountKeeper, - appKeepers.BankKeeper.WithMintCoinsRestriction(tokenfactorytypes.NewTokenFactoryDenomMintCoinsRestriction()), + appKeepers.BankKeeper, appKeepers.DistrKeeper, ) appKeepers.TokenFactoryKeeper = &tokenFactoryKeeper diff --git a/x/tokenfactory/keeper/admins_test.go b/x/tokenfactory/keeper/admins_test.go index 30582a094e2..043e385f5b6 100644 --- a/x/tokenfactory/keeper/admins_test.go +++ b/x/tokenfactory/keeper/admins_test.go @@ -129,6 +129,15 @@ func (s *KeeperTestSuite) TestMintDenom() { ), expectPass: true, }, + { + desc: "error: try minting non-tokenfactory denom", + mintMsg: *types.NewMsgMintTo( + s.TestAccs[0].String(), + sdk.NewInt64Coin("uosmo", 10), + s.TestAccs[1].String(), + ), + expectPass: false, + }, } { s.Run(fmt.Sprintf("Case %s", tc.desc), func() { _, err := s.msgServer.Mint(sdk.WrapSDKContext(s.Ctx), &tc.mintMsg) @@ -217,6 +226,15 @@ func (s *KeeperTestSuite) TestBurnDenom() { ), expectPass: false, }, + { + desc: "fail case - burn non-tokenfactory denom", + burnMsg: *types.NewMsgBurnFrom( + s.TestAccs[0].String(), + sdk.NewInt64Coin("uosmo", 10), + moduleAdress.String(), + ), + expectPass: false, + }, } { s.Run(fmt.Sprintf("Case %s", tc.desc), func() { _, err := s.msgServer.Burn(sdk.WrapSDKContext(s.Ctx), &tc.burnMsg) diff --git a/x/tokenfactory/types/denoms.go b/x/tokenfactory/types/denoms.go index c705a9b95c5..658399856ef 100644 --- a/x/tokenfactory/types/denoms.go +++ b/x/tokenfactory/types/denoms.go @@ -1,12 +1,10 @@ package types import ( - fmt "fmt" "strings" errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" ) const ( @@ -68,17 +66,3 @@ func DeconstructDenom(denom string) (creator string, subdenom string, err error) return creatorAddr.String(), subdenom, nil } - -// NewTokenFactoryDenomMintCoinsRestriction creates and returns a BankMintingRestrictionFn that only allows minting of -// valid tokenfactory denoms -func NewTokenFactoryDenomMintCoinsRestriction() bankkeeper.BankMintingRestrictionFn { - return func(ctx sdk.Context, coinsToMint sdk.Coins) error { - for _, coin := range coinsToMint { - _, _, err := DeconstructDenom(coin.Denom) - if err != nil { - return fmt.Errorf("does not have permission to mint %s", coin.Denom) - } - } - return nil - } -} From 59c5a8c077aef6bb0cbe739e90f7705ad33d66d5 Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Mon, 7 Aug 2023 11:37:48 -0500 Subject: [PATCH 3/6] transform normal denom to base denom and mod value (#5947) --- cmd/osmosisd/cmd/config.go | 18 ++++++-- cmd/osmosisd/cmd/root.go | 89 ++++++++++++++++++++++++++++++-------- 2 files changed, 86 insertions(+), 21 deletions(-) diff --git a/cmd/osmosisd/cmd/config.go b/cmd/osmosisd/cmd/config.go index 19542a2f465..5bed196185a 100644 --- a/cmd/osmosisd/cmd/config.go +++ b/cmd/osmosisd/cmd/config.go @@ -171,19 +171,31 @@ const defaultConfigTemplate = `# This is a TOML config file. # The network chain ID chain-id = "{{ .ChainID }}" + # The keyring's backend, where the keys are stored (os|file|kwallet|pass|test|memory) keyring-backend = "{{ .KeyringBackend }}" + # CLI output format (text|json) output = "{{ .Output }}" + # : to Tendermint RPC interface for this chain node = "{{ .Node }}" + # Transaction broadcasting mode (sync|async) broadcast-mode = "{{ .BroadcastMode }}" -# Human-readable denoms -# If enabled, when using CLI, user can input base denoms (baseatom, basescrt, baseweth, basewbtc, basewbtc.grv etc.) instead of their ibc equivalents. + +# Human-readable denoms: Input +# If enabled, when using CLI, user can input 0 exponent denoms (atom, scrt, avax, wbtc, etc.) instead of their ibc equivalents. +# Note, this will also change the coin's value to it's base value if the input or flag is a coin. +# Example: +# * 10.45atom input will automatically change to 10450000ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 +# * uakt will change to ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4 +# * 12000000uscrt will change to 12000000ibc/0954E1C28EB7AF5B72D24F3BC2B47BBB2FDF91BDDFD57B74B99E133AED40972A # This feature isn't stable yet, and outputs will change in subsequent releases human-readable-denoms-input = {{ .HumanReadableDenomsInput }} -# If enabled, CLI response return base denoms (baseatom, basescrt, baseweth, basewbtc, basewbtc.grv etc.) instead of their ibc equivalents. + +# Human-readable denoms: Output +# If enabled, CLI response return base denoms (uatom, uscrt, wavax-wei, wbtc-satoshi, etc.) instead of their ibc equivalents. # This feature isn't stable yet, and outputs will change in subsequent releases human-readable-denoms-output = {{ .HumanReadableDenomsOutput }} diff --git a/cmd/osmosisd/cmd/root.go b/cmd/osmosisd/cmd/root.go index 5e9a8106822..01ba5a597ed 100644 --- a/cmd/osmosisd/cmd/root.go +++ b/cmd/osmosisd/cmd/root.go @@ -67,8 +67,9 @@ type Asset struct { } type DenomUnit struct { - Denom string `json:"denom"` - Exponent uint64 `json:"exponent"` + Denom string `json:"denom"` + Exponent uint64 `json:"exponent"` + Aliases []string `json:"aliases"` } type Trace struct { @@ -79,8 +80,8 @@ type Trace struct { } type DenomUnitMap struct { - Base string - DenomUnits []DenomUnit `json:"denom_units"` + Base string + Exponent uint64 `json:"exponent"` } var ( @@ -143,16 +144,38 @@ func loadAssetList(initClientCtx client.Context, cmd *cobra.Command, basedenomTo if basedenomToIBC { for _, asset := range assetList.Assets { - DenomUnitMap := DenomUnitMap{ - Base: asset.Base, - DenomUnits: asset.DenomUnits, + // Each asset has a list of denom units. A majority of them have 2 entries, one being the base 0 exponent denom and the other being a larger exponent denom. + // An example for tether: + // * Exponent 0: uusdt + // * Exponent 6: usdt + // This implies that if a usdt value is given, in order to convert it to it's base denom (uusdt), we need to multiply the provided value by 10^6. + for i, denomUnit := range asset.DenomUnits { + DenomUnitMap := DenomUnitMap{ + Base: asset.Base, + Exponent: asset.DenomUnits[i].Exponent, + } + // The 0 exponent denom is the base denom. + if asset.DenomUnits[i].Exponent == 0 { + // To make everyone's life harder, some assets have multiple base denom aliases. For example, the asset list has the following base aliases for the asset "luna": + // * uluna + // * microluna + for _, alias := range denomUnit.Aliases { + baseMap[strings.ToLower(alias)] = DenomUnitMap + } + } else { + // Otherwise we just store the denom alias for that exponent. + baseMap[strings.ToLower(denomUnit.Denom)] = DenomUnitMap + } } - baseMap["base"+strings.ToLower(asset.Symbol)] = DenomUnitMap } } if IBCtoBasedenom { + // We just store a link from the first base denom alias to the IBC denom. This is just used for display purposes on the terminal's output. for _, asset := range assetList.Assets { - baseMapRev[asset.Base] = "base" + strings.ToLower(asset.Symbol) + if len(asset.DenomUnits) > 0 && asset.DenomUnits[0].Exponent == 0 && len(asset.DenomUnits[0].Aliases) > 0 { + baseDenom := asset.DenomUnits[0].Aliases[0] + baseMapRev[asset.Base] = strings.ToLower(baseDenom) + } } } return baseMap, baseMapRev @@ -295,17 +318,20 @@ func NewRootCmd() (*cobra.Command, params.EncodingConfig) { for i, arg := range args { lowerCaseArg := strings.ToLower(arg) lowerCaseArgArray := strings.Split(lowerCaseArg, ",") - re := regexp.MustCompile(`^(\d+)(.+)`) + + re := regexp.MustCompile(`^([\d.]+)(\D+)$`) for i, lowerCaseArg := range lowerCaseArgArray { match := re.FindStringSubmatch(lowerCaseArg) if len(match) == 3 { + value, denom := match[1], match[2] // If the index has a length of 3 then it has a number and a denom (this is a coin object) // Note, index 0 is the entire string, index 1 is the number, and index 2 is the denom - if _, ok := assetMap[match[2]]; ok { - // In this case, we just need to replace the denom with the base denom and retain the number - lowerCaseArgArray[i] = match[1] + assetMap[match[2]].Base + transformedCoin, err := transformCoinValueToBaseInt(value, denom, assetMap) + if err != nil { + continue } + lowerCaseArgArray[i] = transformedCoin } else { if _, ok := assetMap[lowerCaseArg]; ok { // In this case, we just need to replace the denom with the base denom @@ -321,17 +347,19 @@ func NewRootCmd() (*cobra.Command, params.EncodingConfig) { lowerCaseFlagValue := strings.ToLower(flag.Value.String()) lowerCaseFlagValueArray := strings.Split(lowerCaseFlagValue, ",") - re := regexp.MustCompile(`^(\d+)(.+)`) + re := regexp.MustCompile(`^([\d.]+)(\D+)$`) for i, lowerCaseFlagValue := range lowerCaseFlagValueArray { match := re.FindStringSubmatch(lowerCaseFlagValue) if len(match) == 3 { + value, denom := match[1], match[2] // If the index has a length of 3 then it has a number and a denom (this is a coin object) // Note, index 0 is the entire string, index 1 is the number, and index 2 is the denom - if _, ok := assetMap[match[2]]; ok { - // In this case, we just need to replace the denom with the base denom and retain the number - lowerCaseFlagValueArray[i] = strings.Replace(lowerCaseFlagValue, match[2], assetMap[match[2]].Base, -1) + transformedCoin, err := transformCoinValueToBaseInt(value, denom, assetMap) + if err != nil { + continue } + lowerCaseFlagValueArray[i] = transformedCoin } else { if _, ok := assetMap[lowerCaseFlagValue]; ok { // Otherwise, we just need to replace the denom with the base denom @@ -407,7 +435,7 @@ func initAppConfig() (string, interface{}) { ############################################################################### [osmosis-mempool] -# This is the max allowed gas any tx. +# This is the max allowed gas any tx. # This is only for local mempool purposes, and thus is only ran on check tx. max-gas-wanted-per-tx = "25000000" @@ -676,3 +704,28 @@ source ~/.zshrc }, }) } + +// transformCoinValueToBaseInt transforms a cli input that has been split into a number and a denom into it's base int value and base denom. +// i.e. 10.7osmo -> 10700000uosmo +// 12atom -> 12000000uatom +// 15000000uakt -> 15000000uakt (does nothing since it's already in base denom format) +func transformCoinValueToBaseInt(coinValue, coinDenom string, assetMap map[string]DenomUnitMap) (string, error) { + // If the index has a length of 3 then it has a number and a denom (this is a coin object) + // Note, index 0 is the entire string, index 1 is the number, and index 2 is the denom + if denomUnitMap, ok := assetMap[coinDenom]; ok { + // In this case, we just need to replace the denom with the base denom and retain the number + if denomUnitMap.Exponent != 0 { + coinDec, err := sdk.NewDecFromStr(coinValue) + if err != nil { + return "", err + } + transformedCoinValue := coinDec.Mul(sdk.MustNewDecFromStr("10").Power(denomUnitMap.Exponent)) + transformedCoinValueInt := transformedCoinValue.TruncateInt() + transformedCoinValueStr := transformedCoinValueInt.String() + return transformedCoinValueStr + assetMap[coinDenom].Base, nil + } else { + return coinValue + assetMap[coinDenom].Base, nil + } + } + return "", fmt.Errorf("denom %s not found in asset map", coinDenom) +} From 6240cabb2d6a4628c075fdce6db388306fa9ee79 Mon Sep 17 00:00:00 2001 From: David Terpay <35130517+davidterpay@users.noreply.github.com> Date: Mon, 7 Aug 2023 14:16:57 -0400 Subject: [PATCH 4/6] fix(ProtoRev): Parameterizing Pool Type Information (#5948) * init * testing update * changelog * comments * nit * refactor * clean up * nit * comment * cr * cl are not symetric * init * smh more symetric changes * simpler safe swap * map update * more stuff * testing fixes * more testing * nits * benchmark + CLI clean up * test fix * no map no problems * update with merge * better docs --- CHANGELOG.md | 1 + app/upgrades/v17/upgrades.go | 7 +- proto/osmosis/protorev/v1beta1/genesis.proto | 9 + proto/osmosis/protorev/v1beta1/protorev.proto | 71 + proto/osmosis/protorev/v1beta1/query.proto | 31 +- proto/osmosis/protorev/v1beta1/tx.proto | 25 +- tests/e2e/configurer/chain/queries.go | 12 +- tests/e2e/e2e_test.go | 6 +- x/protorev/client/cli/query.go | 12 +- x/protorev/client/cli/tx.go | 35 +- x/protorev/client/cli/utils.go | 68 +- x/protorev/keeper/genesis.go | 7 +- x/protorev/keeper/genesis_test.go | 4 +- x/protorev/keeper/grpc_query.go | 8 +- x/protorev/keeper/grpc_query_test.go | 23 +- x/protorev/keeper/keeper_test.go | 19 +- x/protorev/keeper/msg_server.go | 11 +- x/protorev/keeper/msg_server_test.go | 63 +- x/protorev/keeper/posthandler_test.go | 2 - x/protorev/keeper/protorev.go | 20 +- x/protorev/keeper/protorev_test.go | 25 +- x/protorev/keeper/rebalance.go | 4 +- x/protorev/keeper/routes.go | 22 +- x/protorev/keeper/routes_test.go | 26 +- x/protorev/types/codec.go | 6 +- x/protorev/types/constants.go | 5 +- x/protorev/types/genesis.go | 26 +- x/protorev/types/genesis.pb.go | 149 +- x/protorev/types/keys.go | 6 +- x/protorev/types/msg.go | 32 +- x/protorev/types/msg_test.go | 80 +- x/protorev/types/protorev.pb.go | 1420 ++++++++++++++++- x/protorev/types/query.pb.go | 347 ++-- x/protorev/types/query.pb.gw.go | 28 +- x/protorev/types/tx.pb.go | 261 +-- x/protorev/types/tx.pb.gw.go | 34 +- x/protorev/types/validate.go | 89 +- 37 files changed, 2281 insertions(+), 713 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ce9634072e..9315564afc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [#5927] (https://github.com/osmosis-labs/osmosis/pull/5927) Add gas metering to x/tokenfactory trackBeforeSend hook * [#5890](https://github.com/osmosis-labs/osmosis/pull/5890) feat: CreateCLPool & LinkCFMMtoCL pool into one gov-prop * [#5964](https://github.com/osmosis-labs/osmosis/pull/5964) fix e2e test concurrency bugs +* [#5948] (https://github.com/osmosis-labs/osmosis/pull/5948) Parameterizing Pool Type Information in Protorev ### Minor improvements & Bug Fixes diff --git a/app/upgrades/v17/upgrades.go b/app/upgrades/v17/upgrades.go index f01cb002a83..419e25fdce7 100644 --- a/app/upgrades/v17/upgrades.go +++ b/app/upgrades/v17/upgrades.go @@ -123,12 +123,7 @@ func CreateUpgradeHandler( keepers.DistrKeeper.SetFeePool(ctx, feePool) // Reset the pool weights upon upgrade. This will add support for CW pools on ProtoRev. - keepers.ProtoRevKeeper.SetPoolWeights(ctx, types.PoolWeights{ - BalancerWeight: 1, - StableWeight: 4, - ConcentratedWeight: 300, - CosmwasmWeight: 300, - }) + keepers.ProtoRevKeeper.SetInfoByPoolType(ctx, types.DefaultPoolTypeInfo) return migrations, nil } diff --git a/proto/osmosis/protorev/v1beta1/genesis.proto b/proto/osmosis/protorev/v1beta1/genesis.proto index 38dfc6442b1..689f984526c 100644 --- a/proto/osmosis/protorev/v1beta1/genesis.proto +++ b/proto/osmosis/protorev/v1beta1/genesis.proto @@ -27,6 +27,9 @@ message GenesisState { ]; // The pool weights that are being used to calculate the weight (compute cost) // of each route. + // + // DEPRECATED: This field is deprecated and will be removed in the next + // release. It is replaced by the `info_by_pool_type` field. PoolWeights pool_weights = 4 [ (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"pool_weights\"" @@ -63,4 +66,10 @@ message GenesisState { (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"profits\"" ]; + // Information that is used to estimate execution time / gas + // consumption of a swap on a given pool type. + InfoByPoolType info_by_pool_type = 13 [ + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"info_by_pool_type\"" + ]; } \ No newline at end of file diff --git a/proto/osmosis/protorev/v1beta1/protorev.proto b/proto/osmosis/protorev/v1beta1/protorev.proto index c3dbda4db31..34e624e34df 100644 --- a/proto/osmosis/protorev/v1beta1/protorev.proto +++ b/proto/osmosis/protorev/v1beta1/protorev.proto @@ -78,6 +78,9 @@ message RouteStatistics { // significantly between the different pool types. Each weight roughly // corresponds to the amount of time (in ms) it takes to execute a swap on that // pool type. +// +// DEPRECATED: This field is deprecated and will be removed in the next +// release. It is replaced by the `info_by_pool_type` field. message PoolWeights { // The weight of a stableswap pool uint64 stable_weight = 1 [ (gogoproto.moretags) = "yaml:\"stable_weight\"" ]; @@ -92,6 +95,74 @@ message PoolWeights { [ (gogoproto.moretags) = "yaml:\"cosmwasm_weight\"" ]; } +// InfoByPoolType contains information pertaining to how expensive (in terms of +// gas and time) it is to execute a swap on a given pool type. This distinction +// is made and necessary because the execution time ranges significantly between +// the different pool types. +message InfoByPoolType { + // The stable pool info + StablePoolInfo stable = 1 [ + (gogoproto.moretags) = "yaml:\"stable\"", + (gogoproto.nullable) = false + ]; + // The balancer pool info + BalancerPoolInfo balancer = 2 [ + (gogoproto.moretags) = "yaml:\"balancer\"", + (gogoproto.nullable) = false + ]; + // The concentrated pool info + ConcentratedPoolInfo concentrated = 3 [ + (gogoproto.moretags) = "yaml:\"concentrated\"", + (gogoproto.nullable) = false + ]; + // The cosmwasm pool info + CosmwasmPoolInfo cosmwasm = 4 [ + (gogoproto.moretags) = "yaml:\"cosmwasm\"", + (gogoproto.nullable) = false + ]; +} + +// StablePoolInfo contains meta data pertaining to a stableswap pool type. +message StablePoolInfo { + // The weight of a stableswap pool + uint64 weight = 1 [ (gogoproto.moretags) = "yaml:\"weight\"" ]; +} + +// BalancerPoolInfo contains meta data pertaining to a balancer pool type. +message BalancerPoolInfo { + // The weight of a balancer pool + uint64 weight = 1 [ (gogoproto.moretags) = "yaml:\"weight\"" ]; +} + +// ConcentratedPoolInfo contains meta data pertaining to a concentrated pool +// type. +message ConcentratedPoolInfo { + // The weight of a concentrated pool + uint64 weight = 1 [ (gogoproto.moretags) = "yaml:\"weight\"" ]; + // The maximum number of ticks we can move when rebalancing + uint64 max_ticks_crossed = 2 + [ (gogoproto.moretags) = "yaml:\"max_ticks_crossed\"" ]; +} + +// CosmwasmPoolInfo contains meta data pertaining to a cosmwasm pool type. +message CosmwasmPoolInfo { + // The weight of a cosmwasm pool (by contract address) + repeated WeightMap weight_maps = 1 [ + (gogoproto.moretags) = "yaml:\"weight_maps\"", + (gogoproto.nullable) = false + ]; +} + +// WeightMap maps a contract address to a weight. The weight of an address +// corresponds to the amount of ms required to execute a swap on that contract. +message WeightMap { + // The weight of a cosmwasm pool (by contract address) + uint64 weight = 1 [ (gogoproto.moretags) = "yaml:\"weight\"" ]; + // The contract address + string contract_address = 2 + [ (gogoproto.moretags) = "yaml:\"contract_address\"" ]; +} + // BaseDenom represents a single base denom that the module uses for its // arbitrage trades. It contains the denom name alongside the step size of the // binary search that is used to find the optimal swap amount diff --git a/proto/osmosis/protorev/v1beta1/query.proto b/proto/osmosis/protorev/v1beta1/query.proto index 0c0b3f13bcc..a57757a9aff 100644 --- a/proto/osmosis/protorev/v1beta1/query.proto +++ b/proto/osmosis/protorev/v1beta1/query.proto @@ -71,11 +71,11 @@ service Query { option (google.api.http).get = "/osmosis/protorev/developer_account"; } - // GetProtoRevPoolWeights queries the weights of each pool type currently - // being used by the module - rpc GetProtoRevPoolWeights(QueryGetProtoRevPoolWeightsRequest) - returns (QueryGetProtoRevPoolWeightsResponse) { - option (google.api.http).get = "/osmosis/protorev/pool_weights"; + // GetProtoRevInfoByPoolType queries pool type information that is currently + // being utilized by the module + rpc GetProtoRevInfoByPoolType(QueryGetProtoRevInfoByPoolTypeRequest) + returns (QueryGetProtoRevInfoByPoolTypeResponse) { + option (google.api.http).get = "/osmosis/protorev/info_by_pool_type"; } // GetProtoRevMaxPoolPointsPerTx queries the maximum number of pool points @@ -242,16 +242,17 @@ message QueryGetProtoRevDeveloperAccountResponse { [ (gogoproto.moretags) = "yaml:\"developer_account\"" ]; } -// QueryGetProtoRevPoolWeightsRequest is request type for the -// Query/GetProtoRevPoolWeights RPC method. -message QueryGetProtoRevPoolWeightsRequest {} - -// QueryGetProtoRevPoolWeightsResponse is response type for the -// Query/GetProtoRevPoolWeights RPC method. -message QueryGetProtoRevPoolWeightsResponse { - // pool_weights is a list of all of the pool weights - PoolWeights pool_weights = 1 [ - (gogoproto.moretags) = "yaml:\"pool_weights\"", +// QueryGetProtoRevInfoByPoolTypeRequest is request type for the +// Query/GetProtoRevInfoByPoolType RPC method. +message QueryGetProtoRevInfoByPoolTypeRequest {} + +// QueryGetProtoRevInfoByPoolTypeResponse is response type for the +// Query/GetProtoRevInfoByPoolType RPC method. +message QueryGetProtoRevInfoByPoolTypeResponse { + // InfoByPoolType contains all information pertaining to how different + // pool types are handled by the module. + InfoByPoolType info_by_pool_type = 1 [ + (gogoproto.moretags) = "yaml:\"info_by_pool_type\"", (gogoproto.nullable) = false ]; } diff --git a/proto/osmosis/protorev/v1beta1/tx.proto b/proto/osmosis/protorev/v1beta1/tx.proto index 4fb23be05b3..9c59aaa3d38 100644 --- a/proto/osmosis/protorev/v1beta1/tx.proto +++ b/proto/osmosis/protorev/v1beta1/tx.proto @@ -39,10 +39,11 @@ service Msg { "/osmosis/protorev/set_max_pool_points_per_block"; }; - // SetPoolWeights sets the weights of each pool type in the store. Can only be - // called by the admin account. - rpc SetPoolWeights(MsgSetPoolWeights) returns (MsgSetPoolWeightsResponse) { - option (google.api.http).post = "/osmosis/protorev/set_pool_weights"; + // SetInfoByPoolType sets the pool type information needed to make smart + // assumptions about swapping on different pool types + rpc SetInfoByPoolType(MsgSetInfoByPoolType) + returns (MsgSetInfoByPoolTypeResponse) { + option (google.api.http).post = "/osmosis/protorev/set_info_by_pool_type"; }; // SetBaseDenoms sets the base denoms that will be used to create cyclic @@ -90,24 +91,24 @@ message MsgSetDeveloperAccount { // type. message MsgSetDeveloperAccountResponse {} -// MsgSetPoolWeights defines the Msg/SetPoolWeights request type. -message MsgSetPoolWeights { - option (amino.name) = "osmosis/MsgSetPoolWeights"; +// MsgSetInfoByPoolType defines the Msg/SetInfoByPoolType request type. +message MsgSetInfoByPoolType { + option (amino.name) = "osmosis/MsgSetInfoByPoolType"; // admin is the account that is authorized to set the pool weights. string admin = 1 [ (gogoproto.moretags) = "yaml:\"admin\"", (cosmos_proto.scalar) = "cosmos.AddressString" ]; - // pool_weights is the list of pool weights to set. - PoolWeights pool_weights = 2 [ - (gogoproto.moretags) = "yaml:\"pool_weights\"", + // info_by_pool_type contains information about the pool types. + InfoByPoolType info_by_pool_type = 2 [ + (gogoproto.moretags) = "yaml:\"info_by_pool_type\"", (gogoproto.nullable) = false ]; } -// MsgSetPoolWeightsResponse defines the Msg/SetPoolWeights response type. -message MsgSetPoolWeightsResponse {} +// MsgSetInfoByPoolTypeResponse defines the Msg/SetInfoByPoolType response type. +message MsgSetInfoByPoolTypeResponse {} // MsgSetMaxPoolPointsPerTx defines the Msg/SetMaxPoolPointsPerTx request type. message MsgSetMaxPoolPointsPerTx { diff --git a/tests/e2e/configurer/chain/queries.go b/tests/e2e/configurer/chain/queries.go index 759dc7a12e1..67483c74c08 100644 --- a/tests/e2e/configurer/chain/queries.go +++ b/tests/e2e/configurer/chain/queries.go @@ -118,20 +118,20 @@ func (n *NodeConfig) QueryProtoRevDeveloperAccount() (sdk.AccAddress, error) { return account, nil } -// QueryProtoRevPoolWeights gets the pool point weights of the module. -func (n *NodeConfig) QueryProtoRevPoolWeights() (protorevtypes.PoolWeights, error) { - path := "/osmosis/protorev/pool_weights" +// QueryProtoRevInfoByPoolType gets information on how the module handles different pool types. +func (n *NodeConfig) QueryProtoRevInfoByPoolType() (*protorevtypes.InfoByPoolType, error) { + path := "/osmosis/protorev/info_by_pool_type" bz, err := n.QueryGRPCGateway(path) if err != nil { - return protorevtypes.PoolWeights{}, err + return nil, err } // nolint: staticcheck - var response protorevtypes.QueryGetProtoRevPoolWeightsResponse + var response protorevtypes.QueryGetProtoRevInfoByPoolTypeResponse err = util.Cdc.UnmarshalJSON(bz, &response) require.NoError(n.t, err) // this error should not happen - return response.PoolWeights, nil + return &response.InfoByPoolType, nil } // QueryProtoRevMaxPoolPointsPerTx gets the max pool points per tx of the module. diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index b67d061d20e..18fc4cd6a6b 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -222,10 +222,10 @@ func (s *IntegrationTestSuite) ProtoRev() { s.Require().Error(err) // The module should have pool weights by default. - poolWeights, err := chainANode.QueryProtoRevPoolWeights() - s.T().Logf("checking that the protorev module has pool weights on init: %v", poolWeights) + info, err := chainANode.QueryProtoRevInfoByPoolType() + s.T().Logf("checking that the protorev module has pool info on init: %v", info) s.Require().NoError(err) - s.Require().NotNil(poolWeights) + s.Require().NotNil(info) // The module should have max pool points per tx by default. maxPoolPointsPerTx, err := chainANode.QueryProtoRevMaxPoolPointsPerTx() diff --git a/x/protorev/client/cli/query.go b/x/protorev/client/cli/query.go index afded878afd..7e8af2fdded 100644 --- a/x/protorev/client/cli/query.go +++ b/x/protorev/client/cli/query.go @@ -28,7 +28,7 @@ func NewCmdQuery() *cobra.Command { osmocli.AddQueryCmd(cmd, types.NewQueryClient, NewQueryMaxPoolPointsPerBlockCmd) osmocli.AddQueryCmd(cmd, types.NewQueryClient, NewQueryBaseDenomsCmd) osmocli.AddQueryCmd(cmd, types.NewQueryClient, NewQueryEnabledCmd) - osmocli.AddQueryCmd(cmd, types.NewQueryClient, NewQueryPoolWeightsCmd) + osmocli.AddQueryCmd(cmd, types.NewQueryClient, NewQueryInfoByPoolTypeCmd) osmocli.AddQueryCmd(cmd, types.NewQueryClient, NewQueryPoolCmd) return cmd @@ -141,12 +141,12 @@ func NewQueryEnabledCmd() (*osmocli.QueryDescriptor, *types.QueryGetProtoRevEnab }, &types.QueryGetProtoRevEnabledRequest{} } -// NewQueryPoolWeightsCmd returns the command to query the pool weights of protorev -func NewQueryPoolWeightsCmd() (*osmocli.QueryDescriptor, *types.QueryGetProtoRevPoolWeightsRequest) { +// NewQueryInfoByPoolTypeCmd returns the command to query the pool type info of protorev +func NewQueryInfoByPoolTypeCmd() (*osmocli.QueryDescriptor, *types.QueryGetProtoRevInfoByPoolTypeRequest) { return &osmocli.QueryDescriptor{ - Use: "pool-weights", - Short: "Query the pool weights used to determine how computationally expensive a route is", - }, &types.QueryGetProtoRevPoolWeightsRequest{} + Use: "info-by-pool-type", + Short: "Query the pool info used to determine how computationally expensive a route is", + }, &types.QueryGetProtoRevInfoByPoolTypeRequest{} } // NewQueryPoolCmd returns the command to query the pool id for a given denom pair stored via the highest liquidity method in ProtoRev diff --git a/x/protorev/client/cli/tx.go b/x/protorev/client/cli/tx.go index ffe479b749a..4d02d54e36c 100644 --- a/x/protorev/client/cli/tx.go +++ b/x/protorev/client/cli/tx.go @@ -29,7 +29,7 @@ func NewCmdTx() *cobra.Command { osmocli.AddTxCmd(txCmd, CmdSetMaxPoolPointsPerBlock) txCmd.AddCommand( CmdSetDeveloperHotRoutes().BuildCommandCustomFn(), - CmdSetPoolWeights().BuildCommandCustomFn(), + CmdSetInfoByPoolType().BuildCommandCustomFn(), CmdSetBaseDenoms().BuildCommandCustomFn(), CmdSetProtoRevAdminAccountProposal(), CmdSetProtoRevEnabledProposal(), @@ -141,22 +141,35 @@ func CmdSetMaxPoolPointsPerBlock() (*osmocli.TxCliDesc, *types.MsgSetMaxPoolPoin }, &types.MsgSetMaxPoolPointsPerBlock{} } -// CmdSetPoolWeights implements the command to set the pool weights used to estimate execution costs -func CmdSetPoolWeights() *osmocli.TxCliDesc { +// CmdSetInfoByPoolType implements the command to set the pool information used throughout the module +func CmdSetInfoByPoolType() *osmocli.TxCliDesc { desc := osmocli.TxCliDesc{ - Use: "set-pool-weights [path/to/routes.json]", - Short: "set the protorev pool weights", - Long: `Must provide a json file with all the pool weights that will be set. + Use: "set-info-by-pool-type [path/to/pool_info.json]", + Short: "set the protorev pool type info", + Long: `Must provide a json file with all the pool info that will be set. This does NOT set info for a single pool type. + All information must be provided across all pool types in the json file. Sample json file: { - "stable_weight" : 1, - "balancer_weight" : 1, - "concentrated_weight" : 1 + "stable" : { + "weight" : 1, + }, + "concentrated" : { + "weight" : 1, + "max_ticks_crossed": 10, + }, + "balancer" : { + "weight" : 1, + }, + "cosmwasm" : { + "weight_maps" : [ + {"contract_address" : "cosmos123...", "weight" : 1} + ], + }, } `, - Example: fmt.Sprintf(`$ %s tx protorev set-pool-weights weights.json --from mykey`, version.AppName), + Example: fmt.Sprintf(`$ %s tx protorev set-info-by-pool-type pool_info.json --from mykey`, version.AppName), NumArgs: 1, - ParseAndBuildMsg: BuildSetPoolWeightsMsg, + ParseAndBuildMsg: BuildSetInfoByPoolTypeMsg, } return &desc diff --git a/x/protorev/client/cli/utils.go b/x/protorev/client/cli/utils.go index 689c5682b04..bdbc01fef7d 100644 --- a/x/protorev/client/cli/utils.go +++ b/x/protorev/client/cli/utils.go @@ -122,38 +122,76 @@ func BuildSetHotRoutesMsg(clientCtx client.Context, args []string, fs *flag.Flag }, nil } -// ------------ types/functions to handle a SetPoolWeights CLI TX ------------ // -type createPoolWeightsInput types.PoolWeights +// ------------ types/functions to handle a SetInfoByPoolType CLI TX ------------ // +type InfoByPoolTypeInput struct { + Stable StablePoolInfoInput `json:"stable"` + Balancer BalancerPoolInfoInput `json:"balancer"` + Concentrated ConcentratedPoolInfoInput `json:"concentrated"` + Cosmwasm CosmwasmPoolInfoInput `json:"cosmwasm"` +} + +type StablePoolInfoInput struct { + Weight uint64 `json:"weight"` +} + +type BalancerPoolInfoInput struct { + Weight uint64 `json:"weight"` +} + +type ConcentratedPoolInfoInput struct { + Weight uint64 `json:"weight"` + MaxTicksCrossed uint64 `json:"max_ticks_crossed"` +} -type XCreatePoolWeightsInputs createPoolWeightsInput +type CosmwasmPoolInfoInput struct { + WeightMap map[string]uint64 `json:"weight_map"` +} +type createInfoByPoolTypeInput types.InfoByPoolType -type XCreatePoolWeightsExceptions struct { - XCreatePoolWeightsInputs +type XCreateInfoByPoolTypeInputs createInfoByPoolTypeInput + +type XCreateInfoByPoolTypeExceptions struct { + XCreateInfoByPoolTypeInputs Other *string // Other won't raise an error } // UnmarshalJSON should error if there are fields unexpected. -func (release *createPoolWeightsInput) UnmarshalJSON(data []byte) error { - var createPoolWeightsE XCreatePoolWeightsExceptions +func (release *createInfoByPoolTypeInput) UnmarshalJSON(data []byte) error { + var createInfoByPoolTypeE XCreateInfoByPoolTypeExceptions dec := json.NewDecoder(bytes.NewReader(data)) dec.DisallowUnknownFields() // Force - if err := dec.Decode(&createPoolWeightsE); err != nil { + if err := dec.Decode(&createInfoByPoolTypeE); err != nil { return err } - *release = createPoolWeightsInput(createPoolWeightsE.XCreatePoolWeightsInputs) + *release = createInfoByPoolTypeInput(createInfoByPoolTypeE.XCreateInfoByPoolTypeInputs) return nil } -// BuildSetPoolWeightsMsg builds a MsgSetPoolWeights from the provided json file -func BuildSetPoolWeightsMsg(clientCtx client.Context, args []string, fs *flag.FlagSet) (sdk.Msg, error) { +// createInfoByPoolTypeInput converts the input to the types.InfoByPoolType type +func (release *createInfoByPoolTypeInput) convertToInfoByPoolType() types.InfoByPoolType { + if release == nil { + return types.InfoByPoolType{} + } + + infoByPoolType := types.InfoByPoolType{} + infoByPoolType.Stable = release.Stable + infoByPoolType.Balancer = release.Balancer + infoByPoolType.Concentrated = release.Concentrated + infoByPoolType.Cosmwasm = release.Cosmwasm + + return infoByPoolType +} + +// BuildSetInfoByPoolTypeMsg builds a MsgSetInfoByPoolType from the provided json file +func BuildSetInfoByPoolTypeMsg(clientCtx client.Context, args []string, fs *flag.FlagSet) (sdk.Msg, error) { if len(args) == 0 { return nil, fmt.Errorf("must provide a json file") } // Read the json file - input := &createPoolWeightsInput{} + input := &createInfoByPoolTypeInput{} path := args[0] contents, err := os.ReadFile(path) if err != nil { @@ -167,9 +205,9 @@ func BuildSetPoolWeightsMsg(clientCtx client.Context, args []string, fs *flag.Fl // Build the msg admin := clientCtx.GetFromAddress().String() - return &types.MsgSetPoolWeights{ - Admin: admin, - PoolWeights: types.PoolWeights(*input), + return &types.MsgSetInfoByPoolType{ + Admin: admin, + InfoByPoolType: input.convertToInfoByPoolType(), }, nil } diff --git a/x/protorev/keeper/genesis.go b/x/protorev/keeper/genesis.go index 6f88098aa66..cd4621741f3 100644 --- a/x/protorev/keeper/genesis.go +++ b/x/protorev/keeper/genesis.go @@ -77,9 +77,8 @@ func (k Keeper) InitGenesis(ctx sdk.Context, genState types.GenesisState) { // Set the number of pool points that have been consumed in the current block. k.SetPointCountForBlock(ctx, genState.PointCountForBlock) - // Configure the pool weights for genesis. This roughly correlates to the ms of execution time - // by pool type. - k.SetPoolWeights(ctx, genState.PoolWeights) + // Configure the pool info for genesis. + k.SetInfoByPoolType(ctx, genState.InfoByPoolType) // Set the profits that have been collected by Protorev. for _, coin := range genState.Profits { @@ -99,7 +98,7 @@ func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { genesis.Params = k.GetParams(ctx) // Export the pool weights. - genesis.PoolWeights = k.GetPoolWeights(ctx) + genesis.InfoByPoolType = k.GetInfoByPoolType(ctx) // Export the token pair arb routes (hot routes). routes, err := k.GetAllTokenPairArbRoutes(ctx) diff --git a/x/protorev/keeper/genesis_test.go b/x/protorev/keeper/genesis_test.go index d68f647f2c1..3ddba2cfd88 100644 --- a/x/protorev/keeper/genesis_test.go +++ b/x/protorev/keeper/genesis_test.go @@ -23,8 +23,8 @@ func (s *KeeperTestSuite) TestInitGenesis() { params := s.App.ProtoRevKeeper.GetParams(s.Ctx) s.Require().Equal(params, exportedGenesis.Params) - poolWeights := s.App.ProtoRevKeeper.GetPoolWeights(s.Ctx) - s.Require().Equal(poolWeights, exportedGenesis.PoolWeights) + poolInfo := s.App.ProtoRevKeeper.GetInfoByPoolType(s.Ctx) + s.Require().Equal(poolInfo, exportedGenesis.InfoByPoolType) daysSinceGenesis, err := s.App.ProtoRevKeeper.GetDaysSinceModuleGenesis(s.Ctx) s.Require().NoError(err) diff --git a/x/protorev/keeper/grpc_query.go b/x/protorev/keeper/grpc_query.go index 49f23b4f461..6bf05c7f54a 100644 --- a/x/protorev/keeper/grpc_query.go +++ b/x/protorev/keeper/grpc_query.go @@ -169,15 +169,15 @@ func (q Querier) GetProtoRevDeveloperAccount(c context.Context, req *types.Query return &types.QueryGetProtoRevDeveloperAccountResponse{DeveloperAccount: developerAccount.String()}, nil } -// GetProtoRevPoolWeights queries the weights of each pool type that is being used for arbitrage -func (q Querier) GetProtoRevPoolWeights(c context.Context, req *types.QueryGetProtoRevPoolWeightsRequest) (*types.QueryGetProtoRevPoolWeightsResponse, error) { +// GetProtoRevInfoByPoolType queries information pertaining to each pool type the module is using for arbitrage +func (q Querier) GetProtoRevInfoByPoolType(c context.Context, req *types.QueryGetProtoRevInfoByPoolTypeRequest) (*types.QueryGetProtoRevInfoByPoolTypeResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "invalid request") } ctx := sdk.UnwrapSDKContext(c) - poolWeights := q.Keeper.GetPoolWeights(ctx) + infoByPoolType := q.Keeper.GetInfoByPoolType(ctx) - return &types.QueryGetProtoRevPoolWeightsResponse{PoolWeights: poolWeights}, nil + return &types.QueryGetProtoRevInfoByPoolTypeResponse{InfoByPoolType: infoByPoolType}, nil } // GetProtoRevPoolPointsPerTx queries the maximum number of pool points that can be consumed per transaction diff --git a/x/protorev/keeper/grpc_query_test.go b/x/protorev/keeper/grpc_query_test.go index de1f85d350c..11b8620dc54 100644 --- a/x/protorev/keeper/grpc_query_test.go +++ b/x/protorev/keeper/grpc_query_test.go @@ -281,20 +281,23 @@ func (s *KeeperTestSuite) TestGetProtoRevDeveloperAccount() { s.Require().Equal(developerAccount.String(), res.DeveloperAccount) } -// TestGetProtoRevPoolWeights tests the query to retrieve the pool weights -func (s *KeeperTestSuite) TestGetProtoRevPoolWeights() { +// TestGetProtoRevInfoByPoolType tests the query to retrieve the pool info +func (s *KeeperTestSuite) TestGetProtoRevInfoByPoolType() { // Set the pool weights - poolWeights := types.PoolWeights{ - StableWeight: 5, - BalancerWeight: 1, - ConcentratedWeight: 3, + poolInfo := types.InfoByPoolType{ + Stable: types.StablePoolInfo{Weight: 1}, + Balancer: types.BalancerPoolInfo{Weight: 1}, + Concentrated: types.ConcentratedPoolInfo{Weight: 1, MaxTicksCrossed: 1}, + Cosmwasm: types.CosmwasmPoolInfo{WeightMaps: []types.WeightMap{ + {ContractAddress: "test", Weight: 1}, + }}, } - s.App.AppKeepers.ProtoRevKeeper.SetPoolWeights(s.Ctx, poolWeights) + s.App.AppKeepers.ProtoRevKeeper.SetInfoByPoolType(s.Ctx, poolInfo) - req := &types.QueryGetProtoRevPoolWeightsRequest{} - res, err := s.queryClient.GetProtoRevPoolWeights(sdk.WrapSDKContext(s.Ctx), req) + req := &types.QueryGetProtoRevInfoByPoolTypeRequest{} + res, err := s.queryClient.GetProtoRevInfoByPoolType(sdk.WrapSDKContext(s.Ctx), req) s.Require().NoError(err) - s.Require().Equal(poolWeights, res.PoolWeights) + s.Require().Equal(poolInfo, res.InfoByPoolType) } // TestGetProtoRevMaxPoolPointsPerTx tests the query to retrieve the max pool points per tx diff --git a/x/protorev/keeper/keeper_test.go b/x/protorev/keeper/keeper_test.go index 0cb50715022..b0e73167292 100644 --- a/x/protorev/keeper/keeper_test.go +++ b/x/protorev/keeper/keeper_test.go @@ -78,13 +78,6 @@ func (s *KeeperTestSuite) SetupTest() { panic(err) } - poolWeights := types.PoolWeights{ - StableWeight: 5, // it takes around 5 ms to simulate and execute a stable swap - BalancerWeight: 2, // it takes around 2 ms to simulate and execute a balancer swap - ConcentratedWeight: 2, // it takes around 2 ms to simulate and execute a concentrated swap - } - s.App.ProtoRevKeeper.SetPoolWeights(s.Ctx, poolWeights) - // Configure the initial base denoms used for cyclic route building baseDenomPriorities := []types.BaseDenom{ { @@ -911,7 +904,17 @@ func (s *KeeperTestSuite) setUpPools() { // Create a cosmwasm pool for testing // Pool 51 - s.PrepareCosmWasmPool() + cwPool := s.PrepareCosmWasmPool() + + // Add the new cosmwasm pool to the pool info + poolInfo := types.DefaultPoolTypeInfo + poolInfo.Cosmwasm.WeightMaps = []types.WeightMap{ + { + ContractAddress: cwPool.GetContractAddress(), + Weight: 4, + }, + } + s.App.ProtoRevKeeper.SetInfoByPoolType(s.Ctx, poolInfo) // Create a concentrated liquidity pool for range testing // Pool 52 diff --git a/x/protorev/keeper/msg_server.go b/x/protorev/keeper/msg_server.go index 04ece97a5b5..d8a2fa53e26 100644 --- a/x/protorev/keeper/msg_server.go +++ b/x/protorev/keeper/msg_server.go @@ -114,10 +114,9 @@ func (m MsgServer) SetMaxPoolPointsPerBlock(c context.Context, msg *types.MsgSet return &types.MsgSetMaxPoolPointsPerBlockResponse{}, nil } -// SetPoolWeights sets the weights corresponding to each pool type. This distinction is necessary because the -// pool types have different execution times. Each weight roughly corresponds to the amount of time (in ms) it takes -// to simulate and execute a trade. -func (m MsgServer) SetPoolWeights(c context.Context, msg *types.MsgSetPoolWeights) (*types.MsgSetPoolWeightsResponse, error) { +// SetInfoByPoolType sets the execution time/gas consumption parameters corresponding to each pool type. +// This distinction is necessary because the pool types have different execution times / gas consumption. +func (m MsgServer) SetInfoByPoolType(c context.Context, msg *types.MsgSetInfoByPoolType) (*types.MsgSetInfoByPoolTypeResponse, error) { ctx := sdk.UnwrapSDKContext(c) // Ensure the account has the admin role and can make the tx @@ -125,9 +124,9 @@ func (m MsgServer) SetPoolWeights(c context.Context, msg *types.MsgSetPoolWeight return nil, err } - m.k.SetPoolWeights(ctx, msg.PoolWeights) + m.k.SetInfoByPoolType(ctx, msg.InfoByPoolType) - return &types.MsgSetPoolWeightsResponse{}, nil + return &types.MsgSetInfoByPoolTypeResponse{}, nil } // SetBaseDenoms sets the base denoms that will be used to generate cyclic arbitrage routes diff --git a/x/protorev/keeper/msg_server_test.go b/x/protorev/keeper/msg_server_test.go index 9ca1a591079..103a751e87d 100644 --- a/x/protorev/keeper/msg_server_test.go +++ b/x/protorev/keeper/msg_server_test.go @@ -465,23 +465,23 @@ func (s *KeeperTestSuite) TestMsgSetMaxPoolPointsPerBlock() { } } -// TestMsgSetPoolWeights tests the MsgSetPoolWeights message. -func (s *KeeperTestSuite) TestMsgSetPoolWeights() { +// TestMsgSetPoolTypeInfo tests the MsgSetInfoByPoolType message. +func (s *KeeperTestSuite) TestMsgSetPoolTypeInfo() { cases := []struct { description string admin string - poolWeights types.PoolWeights + poolInfo types.InfoByPoolType passValidateBasic bool pass bool }{ { "Invalid message (invalid admin)", "admin", - types.PoolWeights{ - StableWeight: 1, - BalancerWeight: 2, - ConcentratedWeight: 3, - CosmwasmWeight: 4, + types.InfoByPoolType{ + Stable: types.StablePoolInfo{Weight: 1}, + Balancer: types.BalancerPoolInfo{Weight: 1}, + Concentrated: types.ConcentratedPoolInfo{Weight: 1, MaxTicksCrossed: 1}, + Cosmwasm: types.CosmwasmPoolInfo{WeightMaps: nil}, }, false, false, @@ -489,11 +489,11 @@ func (s *KeeperTestSuite) TestMsgSetPoolWeights() { { "Invalid message (invalid pool weight)", s.adminAccount.String(), - types.PoolWeights{ - StableWeight: 0, - BalancerWeight: 2, - ConcentratedWeight: 1, - CosmwasmWeight: 4, + types.InfoByPoolType{ + Stable: types.StablePoolInfo{Weight: 0}, + Balancer: types.BalancerPoolInfo{Weight: 1}, + Concentrated: types.ConcentratedPoolInfo{Weight: 1, MaxTicksCrossed: 1}, + Cosmwasm: types.CosmwasmPoolInfo{WeightMaps: nil}, }, false, false, @@ -501,9 +501,10 @@ func (s *KeeperTestSuite) TestMsgSetPoolWeights() { { "Invalid message (unset pool weight)", s.adminAccount.String(), - types.PoolWeights{ - StableWeight: 1, - CosmwasmWeight: 4, + types.InfoByPoolType{ + Stable: types.StablePoolInfo{Weight: 1}, + Concentrated: types.ConcentratedPoolInfo{Weight: 1, MaxTicksCrossed: 1}, + Cosmwasm: types.CosmwasmPoolInfo{WeightMaps: nil}, }, false, false, @@ -511,11 +512,11 @@ func (s *KeeperTestSuite) TestMsgSetPoolWeights() { { "Invalid message (wrong admin)", apptesting.CreateRandomAccounts(1)[0].String(), - types.PoolWeights{ - StableWeight: 1, - BalancerWeight: 2, - ConcentratedWeight: 3, - CosmwasmWeight: 4, + types.InfoByPoolType{ + Stable: types.StablePoolInfo{Weight: 1}, + Balancer: types.BalancerPoolInfo{Weight: 1}, + Concentrated: types.ConcentratedPoolInfo{Weight: 1, MaxTicksCrossed: 1}, + Cosmwasm: types.CosmwasmPoolInfo{WeightMaps: nil}, }, true, false, @@ -523,11 +524,11 @@ func (s *KeeperTestSuite) TestMsgSetPoolWeights() { { "Valid message (correct admin)", s.adminAccount.String(), - types.PoolWeights{ - StableWeight: 1, - BalancerWeight: 2, - ConcentratedWeight: 3, - CosmwasmWeight: 4, + types.InfoByPoolType{ + Stable: types.StablePoolInfo{Weight: 1}, + Balancer: types.BalancerPoolInfo{Weight: 1}, + Concentrated: types.ConcentratedPoolInfo{Weight: 1, MaxTicksCrossed: 1}, + Cosmwasm: types.CosmwasmPoolInfo{WeightMaps: nil}, }, true, true, @@ -536,7 +537,7 @@ func (s *KeeperTestSuite) TestMsgSetPoolWeights() { for _, testCase := range cases { s.Run(testCase.description, func() { - msg := types.NewMsgSetPoolWeights(testCase.admin, testCase.poolWeights) + msg := types.NewMsgSetPoolTypeInfo(testCase.admin, testCase.poolInfo) err := msg.ValidateBasic() if testCase.passValidateBasic { @@ -548,14 +549,14 @@ func (s *KeeperTestSuite) TestMsgSetPoolWeights() { server := keeper.NewMsgServer(*s.App.AppKeepers.ProtoRevKeeper) wrappedCtx := sdk.WrapSDKContext(s.Ctx) - response, err := server.SetPoolWeights(wrappedCtx, msg) + response, err := server.SetInfoByPoolType(wrappedCtx, msg) if testCase.pass { s.Require().NoError(err) - s.Require().Equal(response, &types.MsgSetPoolWeightsResponse{}) + s.Require().Equal(response, &types.MsgSetInfoByPoolTypeResponse{}) - poolWeights := s.App.AppKeepers.ProtoRevKeeper.GetPoolWeights(s.Ctx) + poolWeights := s.App.AppKeepers.ProtoRevKeeper.GetInfoByPoolType(s.Ctx) s.Require().NoError(err) - s.Require().Equal(testCase.poolWeights, poolWeights) + s.Require().Equal(testCase.poolInfo, poolWeights) } else { s.Require().Error(err) } diff --git a/x/protorev/keeper/posthandler_test.go b/x/protorev/keeper/posthandler_test.go index 1897f807669..784d7493a01 100644 --- a/x/protorev/keeper/posthandler_test.go +++ b/x/protorev/keeper/posthandler_test.go @@ -342,7 +342,6 @@ func (s *KeeperTestSuite) TestAnteHandle() { s.Require().NoError(err) err = s.App.ProtoRevKeeper.SetMaxPointsPerBlock(s.Ctx, 100) s.Require().NoError(err) - s.App.ProtoRevKeeper.SetPoolWeights(s.Ctx, types.PoolWeights{StableWeight: 5, BalancerWeight: 2, ConcentratedWeight: 2}) for _, tc := range tests { s.Run(tc.name, func() { @@ -703,7 +702,6 @@ func setUpBenchmarkSuite(msgs []sdk.Msg) (*KeeperTestSuite, authsigning.Tx, sdk. s.Ctx = s.Ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) err := s.App.ProtoRevKeeper.SetMaxPointsPerTx(s.Ctx, 40) s.Require().NoError(err) - s.App.ProtoRevKeeper.SetPoolWeights(s.Ctx, types.PoolWeights{StableWeight: 5, BalancerWeight: 2, ConcentratedWeight: 2}) // Init a new account and fund it with tokens for gas fees priv0, _, addr0 := testdata.KeyTestPubAddr() diff --git a/x/protorev/keeper/protorev.go b/x/protorev/keeper/protorev.go index fac66ce78fe..0a97320eeff 100644 --- a/x/protorev/keeper/protorev.go +++ b/x/protorev/keeper/protorev.go @@ -448,17 +448,17 @@ func (k Keeper) SetMaxPointsPerBlock(ctx sdk.Context, maxPoints uint64) error { return nil } -// GetPoolWeights retrieves the weights of different pool types. The weight of a pool type roughly -// corresponds to the amount of time it will take to simulate and execute a swap on that pool type (in ms). -func (k Keeper) GetPoolWeights(ctx sdk.Context) types.PoolWeights { - store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixPoolWeights) - poolWeights := &types.PoolWeights{} - osmoutils.MustGet(store, types.KeyPrefixPoolWeights, poolWeights) +// GetInfoByPoolType retrieves the metadata about the different pool types. This is used to determine the execution costs of +// different pool types when calculating the optimal route (in terms of time and gas consumption). +func (k Keeper) GetInfoByPoolType(ctx sdk.Context) types.InfoByPoolType { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixInfoByPoolType) + poolWeights := &types.InfoByPoolType{} + osmoutils.MustGet(store, types.KeyPrefixInfoByPoolType, poolWeights) return *poolWeights } -// SetPoolWeights sets the weights of different pool types. -func (k Keeper) SetPoolWeights(ctx sdk.Context, poolWeights types.PoolWeights) { - store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixPoolWeights) - osmoutils.MustSet(store, types.KeyPrefixPoolWeights, &poolWeights) +// SetInfoByPoolType sets the pool type information. +func (k Keeper) SetInfoByPoolType(ctx sdk.Context, poolWeights types.InfoByPoolType) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixInfoByPoolType) + osmoutils.MustSet(store, types.KeyPrefixInfoByPoolType, &poolWeights) } diff --git a/x/protorev/keeper/protorev_test.go b/x/protorev/keeper/protorev_test.go index d2cae88e96f..5168e2ac7cf 100644 --- a/x/protorev/keeper/protorev_test.go +++ b/x/protorev/keeper/protorev_test.go @@ -305,21 +305,20 @@ func (s *KeeperTestSuite) TestGetMaxPointsPerBlock() { s.Require().Error(err) } -// TestGetPoolWeights tests the GetPoolWeights and SetPoolWeights functions. -func (s *KeeperTestSuite) TestGetPoolWeights() { - // Should be initialized on genesis - poolWeights := s.App.ProtoRevKeeper.GetPoolWeights(s.Ctx) - s.Require().Equal(types.PoolWeights{StableWeight: 5, BalancerWeight: 2, ConcentratedWeight: 2}, poolWeights) - - // Should be able to set the PoolWeights - newRouteWeights := types.PoolWeights{ - StableWeight: 10, - BalancerWeight: 2, - ConcentratedWeight: 22, +// TestGetInfoByPoolType tests the GetInfoByPoolType and SetInfoByPoolType functions. +func (s *KeeperTestSuite) TestGetInfoByPoolType() { + // Should be able to set the InfoByPoolType + newRouteWeights := types.DefaultPoolTypeInfo + newRouteWeights.Balancer.Weight = 100 + newRouteWeights.Cosmwasm.WeightMaps = []types.WeightMap{ + { + ContractAddress: "contractAddress", + Weight: 1, + }, } - s.App.ProtoRevKeeper.SetPoolWeights(s.Ctx, newRouteWeights) + s.App.ProtoRevKeeper.SetInfoByPoolType(s.Ctx, newRouteWeights) - poolWeights = s.App.ProtoRevKeeper.GetPoolWeights(s.Ctx) + poolWeights := s.App.ProtoRevKeeper.GetInfoByPoolType(s.Ctx) s.Require().Equal(newRouteWeights, poolWeights) } diff --git a/x/protorev/keeper/rebalance.go b/x/protorev/keeper/rebalance.go index f1df9655f2f..1be215948d8 100644 --- a/x/protorev/keeper/rebalance.go +++ b/x/protorev/keeper/rebalance.go @@ -207,6 +207,8 @@ func (k Keeper) CalculateUpperBoundForSearch( ) (sdk.Int, error) { var intermidiateCoin sdk.Coin + poolInfo := k.GetInfoByPoolType(ctx) + // Iterate through all CL pools and determine the maximal amount of input that can be used // respecting the max ticks moved. for index := route.Route.Length() - 1; index >= 0; index-- { @@ -230,7 +232,7 @@ func (k Keeper) CalculateUpperBoundForSearch( ctx, pool.GetId(), tokenInDenom, - types.MaxTicksCrossed, + poolInfo.Concentrated.MaxTicksCrossed, ) if err != nil { return sdk.ZeroInt(), err diff --git a/x/protorev/keeper/routes.go b/x/protorev/keeper/routes.go index 404b6576ee8..d33ee42c900 100644 --- a/x/protorev/keeper/routes.go +++ b/x/protorev/keeper/routes.go @@ -153,7 +153,7 @@ func (k Keeper) BuildHighestLiquidityRoute(ctx sdk.Context, swapDenom types.Base // is only added to the global pool point counter if the route simulated is minimally profitable i.e. it will make a profit. func (k Keeper) CalculateRoutePoolPoints(ctx sdk.Context, route poolmanagertypes.SwapAmountInRoutes) (uint64, error) { // Calculate the number of pool points this route will consume - poolWeights := k.GetPoolWeights(ctx) + infoByPoolType := k.GetInfoByPoolType(ctx) totalWeight := uint64(0) for _, poolId := range route.PoolIds() { @@ -169,13 +169,25 @@ func (k Keeper) CalculateRoutePoolPoints(ctx sdk.Context, route poolmanagertypes switch pool.GetType() { case poolmanagertypes.Balancer: - totalWeight += poolWeights.BalancerWeight + totalWeight += infoByPoolType.Balancer.Weight case poolmanagertypes.Stableswap: - totalWeight += poolWeights.StableWeight + totalWeight += infoByPoolType.Stable.Weight case poolmanagertypes.Concentrated: - totalWeight += poolWeights.ConcentratedWeight + totalWeight += infoByPoolType.Concentrated.Weight case poolmanagertypes.CosmWasm: - totalWeight += poolWeights.CosmwasmWeight + weight, ok := uint64(0), false + for _, weightMap := range infoByPoolType.Cosmwasm.WeightMaps { + if weightMap.ContractAddress == pool.GetAddress().String() { + weight = weightMap.Weight + ok = true + break + } + } + if !ok { + return 0, fmt.Errorf("cosmwasm pool %d does not have a weight", poolId) + } + + totalWeight += weight default: return 0, fmt.Errorf("invalid pool type") } diff --git a/x/protorev/keeper/routes_test.go b/x/protorev/keeper/routes_test.go index 70ac96276a0..86c8e3a2833 100644 --- a/x/protorev/keeper/routes_test.go +++ b/x/protorev/keeper/routes_test.go @@ -199,11 +199,7 @@ func (s *KeeperTestSuite) TestBuildHighestLiquidityRoute() { for _, tc := range cases { s.Run(tc.description, func() { - s.App.ProtoRevKeeper.SetPoolWeights(s.Ctx, types.PoolWeights{ - StableWeight: 5, - BalancerWeight: 2, - ConcentratedWeight: 2, - }) + s.App.ProtoRevKeeper.SetInfoByPoolType(s.Ctx, types.DefaultPoolTypeInfo) baseDenom := types.BaseDenom{ Denom: tc.swapDenom, @@ -274,11 +270,7 @@ func (s *KeeperTestSuite) TestBuildHotRoutes() { for _, tc := range cases { s.Run(tc.description, func() { - s.App.ProtoRevKeeper.SetPoolWeights(s.Ctx, types.PoolWeights{ - StableWeight: 5, - BalancerWeight: 2, - ConcentratedWeight: 2, - }) + s.App.ProtoRevKeeper.SetInfoByPoolType(s.Ctx, types.DefaultPoolTypeInfo) routes, err := s.App.ProtoRevKeeper.BuildHotRoutes(s.Ctx, tc.swapIn, tc.swapOut, tc.poolId) @@ -332,13 +324,13 @@ func (s *KeeperTestSuite) TestCalculateRoutePoolPoints() { { description: "Valid route containing only stable swap pools", route: []poolmanagertypes.SwapAmountInRoute{{PoolId: 40, TokenOutDenom: ""}, {PoolId: 40, TokenOutDenom: ""}, {PoolId: 40, TokenOutDenom: ""}}, - expectedRoutePoolPoints: 9, + expectedRoutePoolPoints: 15, expectedPass: true, }, { description: "Valid route with more than 3 hops", route: []poolmanagertypes.SwapAmountInRoute{{PoolId: 40, TokenOutDenom: ""}, {PoolId: 40, TokenOutDenom: ""}, {PoolId: 40, TokenOutDenom: ""}, {PoolId: 1, TokenOutDenom: ""}}, - expectedRoutePoolPoints: 11, + expectedRoutePoolPoints: 17, expectedPass: true, }, { @@ -356,7 +348,7 @@ func (s *KeeperTestSuite) TestCalculateRoutePoolPoints() { { description: "Valid route with cw pool, balancer, stable swap and cl pool", route: []poolmanagertypes.SwapAmountInRoute{{PoolId: 1, TokenOutDenom: ""}, {PoolId: 51, TokenOutDenom: ""}, {PoolId: 40, TokenOutDenom: ""}, {PoolId: 50, TokenOutDenom: ""}}, - expectedRoutePoolPoints: 10, + expectedRoutePoolPoints: 18, expectedPass: true, }, } @@ -364,12 +356,8 @@ func (s *KeeperTestSuite) TestCalculateRoutePoolPoints() { for _, tc := range cases { s.Run(tc.description, func() { s.SetupTest() - s.App.ProtoRevKeeper.SetPoolWeights(s.Ctx, types.PoolWeights{ - StableWeight: 3, - BalancerWeight: 2, - ConcentratedWeight: 1, - CosmwasmWeight: 4, - }) + s.Require().NoError(s.App.ProtoRevKeeper.SetMaxPointsPerTx(s.Ctx, 25)) + s.Require().NoError(s.App.ProtoRevKeeper.SetMaxPointsPerBlock(s.Ctx, 100)) routePoolPoints, err := s.App.ProtoRevKeeper.CalculateRoutePoolPoints(s.Ctx, tc.route) if tc.expectedPass { diff --git a/x/protorev/types/codec.go b/x/protorev/types/codec.go index ea8308a2e43..c5bf7fe4c5b 100644 --- a/x/protorev/types/codec.go +++ b/x/protorev/types/codec.go @@ -20,7 +20,7 @@ const ( setDeveloperAccount = "osmosis/MsgSetDeveloperAccount" setMaxPoolPointsPerTx = "osmosis/MsgSetMaxPoolPointsPerTx" setMaxPoolPointsPerBlock = "osmosis/MsgSetMaxPoolPointsPerBlock" - setPoolWeights = "osmosis/MsgSetPoolWeights" + setInfoByPoolType = "osmosis/MsgSetInfoByPoolType" setBaseDenoms = "osmosis/MsgSetBaseDenoms" // proposals @@ -40,7 +40,7 @@ func RegisterCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&MsgSetDeveloperAccount{}, setDeveloperAccount, nil) cdc.RegisterConcrete(&MsgSetMaxPoolPointsPerTx{}, setMaxPoolPointsPerTx, nil) cdc.RegisterConcrete(&MsgSetMaxPoolPointsPerBlock{}, setMaxPoolPointsPerBlock, nil) - cdc.RegisterConcrete(&MsgSetPoolWeights{}, setPoolWeights, nil) + cdc.RegisterConcrete(&MsgSetInfoByPoolType{}, setInfoByPoolType, nil) cdc.RegisterConcrete(&MsgSetBaseDenoms{}, setBaseDenoms, nil) // proposals @@ -55,7 +55,7 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { &MsgSetDeveloperAccount{}, &MsgSetMaxPoolPointsPerTx{}, &MsgSetMaxPoolPointsPerBlock{}, - &MsgSetPoolWeights{}, + &MsgSetInfoByPoolType{}, &MsgSetBaseDenoms{}, ) diff --git a/x/protorev/types/constants.go b/x/protorev/types/constants.go index ffdc56c2a7f..23c0cbc7772 100644 --- a/x/protorev/types/constants.go +++ b/x/protorev/types/constants.go @@ -27,9 +27,8 @@ const MaxPoolPointsPerTx uint64 = 50 // to the maximum execution time (in ms) of protorev per block const MaxPoolPointsPerBlock uint64 = 200 -// Max number of ticks we can move in a concentrated pool swap. This will be parameterized in a -// follow up PR. -const MaxTicksCrossed uint64 = 5 +// Max number of ticks we can move in a concentrated pool swap. +const MaxTicksCrossed uint64 = 10 // ---------------- Module Profit Splitting Constants ---------------- // diff --git a/x/protorev/types/genesis.go b/x/protorev/types/genesis.go index cb7466f5ce0..aa6672a2fe5 100644 --- a/x/protorev/types/genesis.go +++ b/x/protorev/types/genesis.go @@ -16,14 +16,18 @@ var ( StepSize: sdk.NewInt(1_000_000), }, } - DefaultPoolWeights = PoolWeights{ - StableWeight: 5, // it takes around 5 ms to simulate and execute a stable swap - BalancerWeight: 2, // it takes around 2 ms to simulate and execute a balancer swap - ConcentratedWeight: 2, // it takes around 2 ms to simulate and execute a concentrated swap - - // TODO: This is a temporary weight until we can get a more accurate weight for cosmwasm swaps - // ref: https://github.com/osmosis-labs/osmosis/issues/5858 - CosmwasmWeight: 5, // it takes around 5 ms to simulate and execute a cosmwasm swap + DefaultPoolTypeInfo = InfoByPoolType{ + Balancer: BalancerPoolInfo{ + Weight: 2, // it takes around 2 ms to simulate and execute a balancer swap + }, + Stable: StablePoolInfo{ + Weight: 5, // it takes around 5 ms to simulate and execute a stable swap + }, + Concentrated: ConcentratedPoolInfo{ + Weight: 7, // it takes around 7 ms to simulate and execute a concentrated swap + MaxTicksCrossed: 5, + }, + Cosmwasm: CosmwasmPoolInfo{}, } DefaultDaysSinceModuleGenesis = uint64(0) DefaultDeveloperFees = []sdk.Coin{} @@ -41,7 +45,7 @@ func DefaultGenesis() *GenesisState { Params: DefaultParams(), TokenPairArbRoutes: DefaultTokenPairArbRoutes, BaseDenoms: DefaultBaseDenoms, - PoolWeights: DefaultPoolWeights, + InfoByPoolType: DefaultPoolTypeInfo, DaysSinceModuleGenesis: DefaultDaysSinceModuleGenesis, DeveloperFees: DefaultDeveloperFees, DeveloperAddress: DefaultDeveloperAddress, @@ -65,8 +69,8 @@ func (gs GenesisState) Validate() error { return err } - // Validate the pool weights - if err := gs.PoolWeights.Validate(); err != nil { + // Validate the pool type information + if err := gs.InfoByPoolType.Validate(); err != nil { return err } diff --git a/x/protorev/types/genesis.pb.go b/x/protorev/types/genesis.pb.go index b6f6fded674..a3e31189ba1 100644 --- a/x/protorev/types/genesis.pb.go +++ b/x/protorev/types/genesis.pb.go @@ -36,6 +36,9 @@ type GenesisState struct { BaseDenoms []BaseDenom `protobuf:"bytes,3,rep,name=base_denoms,json=baseDenoms,proto3" json:"base_denoms" yaml:"base_denoms"` // The pool weights that are being used to calculate the weight (compute cost) // of each route. + // + // DEPRECATED: This field is deprecated and will be removed in the next + // release. It is replaced by the `info_by_pool_type` field. PoolWeights PoolWeights `protobuf:"bytes,4,opt,name=pool_weights,json=poolWeights,proto3" json:"pool_weights" yaml:"pool_weights"` // The number of days since module genesis. DaysSinceModuleGenesis uint64 `protobuf:"varint,5,opt,name=days_since_module_genesis,json=daysSinceModuleGenesis,proto3" json:"days_since_module_genesis,omitempty" yaml:"days_since_module_genesis"` @@ -55,6 +58,9 @@ type GenesisState struct { PointCountForBlock uint64 `protobuf:"varint,11,opt,name=point_count_for_block,json=pointCountForBlock,proto3" json:"point_count_for_block,omitempty" yaml:"point_count_for_block"` // All of the profits that have been accumulated by the module. Profits []types.Coin `protobuf:"bytes,12,rep,name=profits,proto3" json:"profits" yaml:"profits"` + // Information that is used to estimate execution time / gas + // consumption of a swap on a given pool type. + InfoByPoolType InfoByPoolType `protobuf:"bytes,13,opt,name=info_by_pool_type,json=infoByPoolType,proto3" json:"info_by_pool_type" yaml:"info_by_pool_type"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -174,6 +180,13 @@ func (m *GenesisState) GetProfits() []types.Coin { return nil } +func (m *GenesisState) GetInfoByPoolType() InfoByPoolType { + if m != nil { + return m.InfoByPoolType + } + return InfoByPoolType{} +} + func init() { proto.RegisterType((*GenesisState)(nil), "osmosis.protorev.v1beta1.GenesisState") } @@ -183,50 +196,53 @@ func init() { } var fileDescriptor_3c77fc2da5752af2 = []byte{ - // 688 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x94, 0xcf, 0x6e, 0xd3, 0x4a, - 0x14, 0xc6, 0xe3, 0xdb, 0xde, 0xfe, 0x99, 0xf4, 0x56, 0xb7, 0xd3, 0x9b, 0xca, 0xc9, 0xa5, 0x8e, - 0x31, 0x2d, 0x64, 0x41, 0x6d, 0xb5, 0x20, 0x21, 0xb1, 0x40, 0xaa, 0x8b, 0x0a, 0x12, 0xa2, 0x8a, - 0xa6, 0x45, 0x48, 0x20, 0x31, 0x8c, 0x93, 0x69, 0x6a, 0xd5, 0xf6, 0x58, 0x9e, 0x49, 0x48, 0x1f, - 0x80, 0x3d, 0x0f, 0xc3, 0x43, 0x74, 0x59, 0xb1, 0x62, 0x15, 0xa1, 0x76, 0xc1, 0x3e, 0x4f, 0x80, - 0x3c, 0x33, 0x49, 0x4a, 0x89, 0x61, 0x97, 0x39, 0xe7, 0x77, 0xbe, 0xef, 0x9c, 0x99, 0xe3, 0x80, - 0xbb, 0x8c, 0xc7, 0x8c, 0x87, 0xdc, 0x4b, 0x33, 0x26, 0x58, 0x46, 0x7b, 0x5e, 0x6f, 0x3b, 0xa0, - 0x82, 0x6c, 0x7b, 0x1d, 0x9a, 0x50, 0x1e, 0x72, 0x57, 0x26, 0xa0, 0xa9, 0x39, 0x77, 0xc4, 0xb9, - 0x9a, 0xab, 0xfd, 0xd7, 0x61, 0x1d, 0x26, 0xa3, 0x5e, 0xfe, 0x4b, 0x01, 0xb5, 0x7b, 0x85, 0xba, - 0x63, 0x01, 0x05, 0x6e, 0x16, 0x83, 0x24, 0x23, 0xb1, 0x36, 0xac, 0x55, 0x5b, 0x92, 0xc3, 0xca, - 0x48, 0x1d, 0x74, 0xca, 0x52, 0x27, 0x2f, 0x20, 0x9c, 0x8e, 0x8b, 0x5b, 0x2c, 0x4c, 0x54, 0xde, - 0xf9, 0xbe, 0x00, 0x96, 0x9e, 0xa9, 0x61, 0x0e, 0x05, 0x11, 0x14, 0x3e, 0x01, 0x73, 0x4a, 0xdb, - 0x34, 0x6c, 0xa3, 0x51, 0xde, 0xb1, 0xdd, 0xa2, 0xe1, 0xdc, 0xa6, 0xe4, 0xfc, 0xd9, 0xf3, 0x41, - 0xbd, 0x84, 0x74, 0x15, 0xfc, 0x68, 0x80, 0x8a, 0x60, 0xa7, 0x34, 0xc1, 0x29, 0x09, 0x33, 0x4c, - 0xb2, 0x00, 0x67, 0xac, 0x2b, 0x28, 0x37, 0xff, 0xb2, 0x67, 0x1a, 0xe5, 0x9d, 0xfb, 0xc5, 0x7a, - 0x47, 0x79, 0x59, 0x93, 0x84, 0xd9, 0x6e, 0x16, 0x20, 0x59, 0xe3, 0x6f, 0xe4, 0xda, 0xc3, 0x41, - 0xfd, 0xd6, 0x19, 0x89, 0xa3, 0xc7, 0xce, 0x54, 0x61, 0x07, 0x41, 0xf1, 0x4b, 0x25, 0x7c, 0x0f, - 0xca, 0xf9, 0xcc, 0xb8, 0x4d, 0x13, 0x16, 0x73, 0x73, 0x46, 0x9a, 0xdf, 0x29, 0x36, 0xf7, 0x09, - 0xa7, 0x4f, 0x73, 0xd6, 0xaf, 0x69, 0x4f, 0xa8, 0x3c, 0xaf, 0xa9, 0x38, 0x08, 0x04, 0x23, 0x8c, - 0x43, 0x0a, 0x96, 0x52, 0xc6, 0x22, 0xfc, 0x81, 0x86, 0x9d, 0x13, 0xc1, 0xcd, 0x59, 0x79, 0x5f, - 0x9b, 0xbf, 0xb9, 0x2f, 0xc6, 0xa2, 0xd7, 0x0a, 0xf6, 0xff, 0xd7, 0x26, 0xab, 0xca, 0xe4, 0xba, - 0x90, 0x83, 0xca, 0xe9, 0x84, 0x84, 0x18, 0x54, 0xdb, 0xe4, 0x8c, 0x63, 0x1e, 0x26, 0x2d, 0x8a, - 0x63, 0xd6, 0xee, 0x46, 0x14, 0xeb, 0xfd, 0x33, 0xff, 0xb6, 0x8d, 0xc6, 0xac, 0xbf, 0x31, 0x1c, - 0xd4, 0x6d, 0x25, 0x54, 0x88, 0x3a, 0x68, 0x2d, 0xcf, 0x1d, 0xe6, 0xa9, 0x97, 0x32, 0xa3, 0x9f, - 0x1d, 0x62, 0xb0, 0xdc, 0xa6, 0x3d, 0x1a, 0xb1, 0x94, 0x66, 0xf8, 0x98, 0x52, 0x6e, 0xce, 0xc9, - 0xcb, 0xaa, 0xba, 0x7a, 0x93, 0xf2, 0x99, 0xc7, 0x43, 0xec, 0xb1, 0x30, 0xf1, 0xd7, 0x75, 0xf7, - 0x15, 0x6d, 0xfa, 0x53, 0xb9, 0x83, 0xfe, 0x19, 0x07, 0xf6, 0x29, 0xe5, 0xf0, 0x00, 0xac, 0x46, - 0x44, 0x50, 0x2e, 0x70, 0x10, 0xb1, 0xd6, 0x29, 0x3e, 0x91, 0x93, 0x99, 0xf3, 0xb2, 0x77, 0x6b, - 0x38, 0xa8, 0xd7, 0x94, 0xcc, 0x14, 0xc8, 0x41, 0x2b, 0x2a, 0xea, 0xe7, 0xc1, 0xe7, 0x32, 0x06, - 0xdf, 0x82, 0x95, 0x89, 0x23, 0x69, 0xb7, 0x33, 0xca, 0xb9, 0xb9, 0x60, 0x1b, 0x8d, 0x45, 0xdf, - 0x1d, 0x0e, 0xea, 0xe6, 0xcd, 0xa6, 0x34, 0xe2, 0x7c, 0xf9, 0xbc, 0xb5, 0xac, 0x47, 0xda, 0x55, - 0x21, 0xf4, 0xef, 0x98, 0xd2, 0x11, 0xf8, 0x0e, 0x54, 0x63, 0xd2, 0xc7, 0xf2, 0x41, 0x52, 0x16, - 0x26, 0x82, 0xe3, 0x5c, 0x43, 0x36, 0x65, 0x2e, 0xde, 0xbc, 0xee, 0x42, 0xd4, 0x41, 0x95, 0x98, - 0xf4, 0xf3, 0x17, 0x6f, 0xca, 0x4c, 0x93, 0x66, 0x72, 0x04, 0xf8, 0x0a, 0xac, 0x4d, 0x2b, 0x12, - 0x7d, 0x13, 0x48, 0xf1, 0xdb, 0xc3, 0x41, 0x7d, 0xbd, 0x58, 0x5c, 0xf4, 0x1d, 0x04, 0x6f, 0x2a, - 0x1f, 0xf5, 0xe1, 0x21, 0xa8, 0x48, 0x0a, 0xb7, 0x58, 0x37, 0x11, 0xf8, 0x98, 0x8d, 0x5a, 0x2e, - 0x4b, 0x55, 0x7b, 0xf2, 0x0d, 0x4d, 0xc5, 0x1c, 0x04, 0x65, 0x7c, 0x2f, 0x0f, 0xef, 0x33, 0xdd, - 0xeb, 0x0b, 0x30, 0x9f, 0x66, 0xec, 0x38, 0x14, 0xdc, 0x5c, 0xfa, 0xd3, 0x4a, 0xac, 0xe9, 0x95, - 0x58, 0xd6, 0x2e, 0xaa, 0xce, 0x41, 0x23, 0x05, 0xff, 0xe0, 0xfc, 0xd2, 0x32, 0x2e, 0x2e, 0x2d, - 0xe3, 0xdb, 0xa5, 0x65, 0x7c, 0xba, 0xb2, 0x4a, 0x17, 0x57, 0x56, 0xe9, 0xeb, 0x95, 0x55, 0x7a, - 0xf3, 0xb0, 0x13, 0x8a, 0x93, 0x6e, 0xe0, 0xb6, 0x58, 0xec, 0xe9, 0x8f, 0x67, 0x2b, 0x22, 0x01, - 0x1f, 0x1d, 0xbc, 0xde, 0xf6, 0x23, 0xaf, 0x3f, 0xf9, 0x0f, 0x14, 0x67, 0x29, 0xe5, 0xc1, 0x9c, - 0x3c, 0x3f, 0xf8, 0x11, 0x00, 0x00, 0xff, 0xff, 0xf2, 0x2a, 0x10, 0x89, 0xa5, 0x05, 0x00, 0x00, + // 728 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x94, 0xcd, 0x6e, 0xd3, 0x4a, + 0x14, 0xc7, 0xe3, 0xdb, 0xde, 0xf6, 0x76, 0xd2, 0x46, 0xb7, 0xd3, 0x9b, 0xca, 0xc9, 0xa5, 0x8e, + 0x31, 0x2d, 0x64, 0x41, 0x6d, 0xb5, 0x20, 0x21, 0xb1, 0x40, 0xaa, 0x8b, 0x0a, 0x08, 0x51, 0x45, + 0x6e, 0x11, 0x12, 0x48, 0x0c, 0xe3, 0x64, 0x92, 0x5a, 0xb5, 0x3d, 0x96, 0x67, 0x12, 0x92, 0x07, + 0x60, 0xcf, 0xc3, 0xf0, 0x10, 0x5d, 0x56, 0xac, 0x58, 0x45, 0xa8, 0x7d, 0x83, 0x3c, 0x01, 0xf2, + 0xcc, 0x24, 0xe9, 0x47, 0x0c, 0x3b, 0xcf, 0x39, 0xbf, 0xf3, 0xff, 0x9f, 0x33, 0x1f, 0x06, 0xf7, + 0x29, 0x8b, 0x28, 0x0b, 0x98, 0x93, 0xa4, 0x94, 0xd3, 0x94, 0xf4, 0x9c, 0xde, 0x8e, 0x4f, 0x38, + 0xde, 0x71, 0x3a, 0x24, 0x26, 0x2c, 0x60, 0xb6, 0x48, 0x40, 0x5d, 0x71, 0xf6, 0x98, 0xb3, 0x15, + 0x57, 0xfd, 0xaf, 0x43, 0x3b, 0x54, 0x44, 0x9d, 0xec, 0x4b, 0x02, 0xd5, 0x07, 0xb9, 0xba, 0x13, + 0x01, 0x09, 0x6e, 0xe5, 0x83, 0x38, 0xc5, 0x91, 0x32, 0xac, 0x56, 0x9a, 0x82, 0x43, 0xd2, 0x48, + 0x2e, 0x54, 0xca, 0x90, 0x2b, 0xc7, 0xc7, 0x8c, 0x4c, 0x8a, 0x9b, 0x34, 0x88, 0x65, 0xde, 0x1a, + 0x2e, 0x81, 0xe5, 0x17, 0x72, 0x98, 0x23, 0x8e, 0x39, 0x81, 0xcf, 0xc0, 0x82, 0xd4, 0xd6, 0x35, + 0x53, 0xab, 0x17, 0x77, 0x4d, 0x3b, 0x6f, 0x38, 0xbb, 0x21, 0x38, 0x77, 0xfe, 0x6c, 0x58, 0x2b, + 0x78, 0xaa, 0x0a, 0x7e, 0xd1, 0x40, 0x99, 0xd3, 0x53, 0x12, 0xa3, 0x04, 0x07, 0x29, 0xc2, 0xa9, + 0x8f, 0x52, 0xda, 0xe5, 0x84, 0xe9, 0x7f, 0x99, 0x73, 0xf5, 0xe2, 0xee, 0xc3, 0x7c, 0xbd, 0xe3, + 0xac, 0xac, 0x81, 0x83, 0x74, 0x2f, 0xf5, 0x3d, 0x51, 0xe3, 0x6e, 0x66, 0xda, 0xa3, 0x61, 0xed, + 0xce, 0x00, 0x47, 0xe1, 0x53, 0x6b, 0xa6, 0xb0, 0xe5, 0x41, 0x7e, 0xab, 0x12, 0x7e, 0x02, 0xc5, + 0x6c, 0x66, 0xd4, 0x22, 0x31, 0x8d, 0x98, 0x3e, 0x27, 0xcc, 0xef, 0xe5, 0x9b, 0xbb, 0x98, 0x91, + 0xe7, 0x19, 0xeb, 0x56, 0x95, 0x27, 0x94, 0x9e, 0x57, 0x54, 0x2c, 0x0f, 0xf8, 0x63, 0x8c, 0x41, + 0x02, 0x96, 0x13, 0x4a, 0x43, 0xf4, 0x99, 0x04, 0x9d, 0x13, 0xce, 0xf4, 0x79, 0xb1, 0x5f, 0x5b, + 0xbf, 0xd9, 0x2f, 0x4a, 0xc3, 0x77, 0x12, 0x76, 0xff, 0x57, 0x26, 0x6b, 0xd2, 0xe4, 0xaa, 0x90, + 0xe5, 0x15, 0x93, 0x29, 0x09, 0x11, 0xa8, 0xb4, 0xf0, 0x80, 0x21, 0x16, 0xc4, 0x4d, 0x82, 0x22, + 0xda, 0xea, 0x86, 0x04, 0xa9, 0xfb, 0xa7, 0xff, 0x6d, 0x6a, 0xf5, 0x79, 0x77, 0x73, 0x34, 0xac, + 0x99, 0x52, 0x28, 0x17, 0xb5, 0xbc, 0xf5, 0x2c, 0x77, 0x94, 0xa5, 0xde, 0x88, 0x8c, 0x3a, 0x76, + 0x88, 0x40, 0xa9, 0x45, 0x7a, 0x24, 0xa4, 0x09, 0x49, 0x51, 0x9b, 0x10, 0xa6, 0x2f, 0x88, 0xcd, + 0xaa, 0xd8, 0xea, 0x26, 0x65, 0x33, 0x4f, 0x86, 0xd8, 0xa7, 0x41, 0xec, 0x6e, 0xa8, 0xee, 0xcb, + 0xca, 0xf4, 0x5a, 0xb9, 0xe5, 0xad, 0x4c, 0x02, 0x07, 0x84, 0x30, 0x78, 0x08, 0xd6, 0x42, 0xcc, + 0x09, 0xe3, 0xc8, 0x0f, 0x69, 0xf3, 0x14, 0x9d, 0x88, 0xc9, 0xf4, 0x45, 0xd1, 0xbb, 0x31, 0x1a, + 0xd6, 0xaa, 0x52, 0x66, 0x06, 0x64, 0x79, 0xab, 0x32, 0xea, 0x66, 0xc1, 0x97, 0x22, 0x06, 0x3f, + 0x80, 0xd5, 0xa9, 0x23, 0x6e, 0xb5, 0x52, 0xc2, 0x98, 0xfe, 0x8f, 0xa9, 0xd5, 0x97, 0x5c, 0x7b, + 0x34, 0xac, 0xe9, 0x37, 0x9b, 0x52, 0x88, 0xf5, 0xfd, 0xdb, 0x76, 0x49, 0x8d, 0xb4, 0x27, 0x43, + 0xde, 0xbf, 0x13, 0x4a, 0x45, 0xe0, 0x47, 0x50, 0x89, 0x70, 0x1f, 0x89, 0x03, 0x49, 0x68, 0x10, + 0x73, 0x86, 0x32, 0x0d, 0xd1, 0x94, 0xbe, 0x74, 0x73, 0xbb, 0x73, 0x51, 0xcb, 0x2b, 0x47, 0xb8, + 0x9f, 0x9d, 0x78, 0x43, 0x64, 0x1a, 0x24, 0x15, 0x23, 0xc0, 0xb7, 0x60, 0x7d, 0x56, 0x11, 0xef, + 0xeb, 0x40, 0x88, 0xdf, 0x1d, 0x0d, 0x6b, 0x1b, 0xf9, 0xe2, 0xbc, 0x6f, 0x79, 0xf0, 0xa6, 0xf2, + 0x71, 0x1f, 0x1e, 0x81, 0xb2, 0xa0, 0x50, 0x93, 0x76, 0x63, 0x8e, 0xda, 0x74, 0xdc, 0x72, 0x51, + 0xa8, 0x9a, 0xd3, 0x37, 0x34, 0x13, 0xb3, 0x3c, 0x28, 0xe2, 0xfb, 0x59, 0xf8, 0x80, 0xaa, 0x5e, + 0x5f, 0x83, 0xc5, 0x24, 0xa5, 0xed, 0x80, 0x33, 0x7d, 0xf9, 0x4f, 0x57, 0x62, 0x5d, 0x5d, 0x89, + 0x92, 0x72, 0x91, 0x75, 0x96, 0x37, 0x56, 0x80, 0x5d, 0xb0, 0x1a, 0xc4, 0x6d, 0x8a, 0xfc, 0x81, + 0x1c, 0x8a, 0x0f, 0x12, 0xa2, 0xaf, 0x88, 0x37, 0x53, 0xcf, 0x7f, 0x33, 0xaf, 0xe2, 0x36, 0x75, + 0x07, 0xd9, 0xb4, 0xc7, 0x83, 0x84, 0xb8, 0xa6, 0x72, 0x51, 0x67, 0x7c, 0x4b, 0xd0, 0xf2, 0x4a, + 0xc1, 0xf5, 0x8a, 0xc3, 0xb3, 0x0b, 0x43, 0x3b, 0xbf, 0x30, 0xb4, 0x9f, 0x17, 0x86, 0xf6, 0xf5, + 0xd2, 0x28, 0x9c, 0x5f, 0x1a, 0x85, 0x1f, 0x97, 0x46, 0xe1, 0xfd, 0xe3, 0x4e, 0xc0, 0x4f, 0xba, + 0xbe, 0xdd, 0xa4, 0x91, 0xa3, 0xfc, 0xb7, 0x43, 0xec, 0xb3, 0xf1, 0xc2, 0xe9, 0xed, 0x3c, 0x71, + 0xfa, 0xd3, 0x5f, 0x6f, 0xa6, 0xcf, 0xfc, 0x05, 0xb1, 0x7e, 0xf4, 0x2b, 0x00, 0x00, 0xff, 0xff, + 0x04, 0xad, 0x0a, 0x11, 0x1c, 0x06, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -249,6 +265,16 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size, err := m.InfoByPoolType.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x6a if len(m.Profits) > 0 { for iNdEx := len(m.Profits) - 1; iNdEx >= 0; iNdEx-- { { @@ -424,6 +450,8 @@ func (m *GenesisState) Size() (n int) { n += 1 + l + sovGenesis(uint64(l)) } } + l = m.InfoByPoolType.Size() + n += 1 + l + sovGenesis(uint64(l)) return n } @@ -791,6 +819,39 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InfoByPoolType", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.InfoByPoolType.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/x/protorev/types/keys.go b/x/protorev/types/keys.go index 86aeb24733a..cb5c906f289 100644 --- a/x/protorev/types/keys.go +++ b/x/protorev/types/keys.go @@ -34,7 +34,7 @@ const ( prefixMaxPoolPointsPerBlock prefixPoolPointCountForBlock prefixLatestBlockHeight - prefixPoolWeights + prefixInfoByPoolType prefixSwapsToBackrun ) @@ -84,8 +84,8 @@ var ( // KeyPrefixLatestBlockHeight is the prefix for store that keeps track of the latest recorded block height KeyPrefixLatestBlockHeight = []byte{prefixLatestBlockHeight} - // KeyPrefixPoolWeights is the prefix for store that keeps track of the weights for different pool types - KeyPrefixPoolWeights = []byte{prefixPoolWeights} + // KeyPrefixInfoByPoolType is the prefix for store that keeps track of the pool type info + KeyPrefixInfoByPoolType = []byte{prefixInfoByPoolType} // KeyPrefixSwapsToBackrun is the prefix for store that keeps track of the swaps that need to be backrun for a given tx KeyPrefixSwapsToBackrun = []byte{prefixSwapsToBackrun} diff --git a/x/protorev/types/msg.go b/x/protorev/types/msg.go index 1210405a36c..26cd32a2a6a 100644 --- a/x/protorev/types/msg.go +++ b/x/protorev/types/msg.go @@ -10,7 +10,7 @@ var ( _ sdk.Msg = &MsgSetDeveloperAccount{} _ sdk.Msg = &MsgSetMaxPoolPointsPerTx{} _ sdk.Msg = &MsgSetMaxPoolPointsPerBlock{} - _ sdk.Msg = &MsgSetPoolWeights{} + _ sdk.Msg = &MsgSetInfoByPoolType{} _ sdk.Msg = &MsgSetBaseDenoms{} ) @@ -19,7 +19,7 @@ const ( TypeMsgSetDeveloperAccount = "set_developer_account" TypeMsgSetMaxPoolPointsPerTx = "set_max_pool_points_per_tx" TypeMsgSetMaxPoolPointsPerBlock = "set_max_pool_points_per_block" - TypeMsgSetPoolWeights = "set_pool_weights" + TypeMsgSetPoolTypeInfo = "set_info_by_pool_type" TypeMsgSetBaseDenoms = "set_base_denoms" ) @@ -205,33 +205,33 @@ func (msg MsgSetMaxPoolPointsPerBlock) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{addr} } -// ---------------------- Interface for MsgSetPoolWeights ---------------------- // -// NewMsgSetPoolWeights creates a new MsgSetPoolWeights instance -func NewMsgSetPoolWeights(admin string, poolWeights PoolWeights) *MsgSetPoolWeights { - return &MsgSetPoolWeights{ - Admin: admin, - PoolWeights: poolWeights, +// ---------------------- Interface for MsgSetInfoByPoolType ---------------------- // +// NewMsgSetPoolTypeInfo creates a new MsgSetInfoByPoolType instance +func NewMsgSetPoolTypeInfo(admin string, infoByPoolType InfoByPoolType) *MsgSetInfoByPoolType { + return &MsgSetInfoByPoolType{ + Admin: admin, + InfoByPoolType: infoByPoolType, } } // Route returns the name of the module -func (msg MsgSetPoolWeights) Route() string { +func (msg MsgSetInfoByPoolType) Route() string { return RouterKey } // Type returns the type of the message -func (msg MsgSetPoolWeights) Type() string { - return TypeMsgSetPoolWeights +func (msg MsgSetInfoByPoolType) Type() string { + return TypeMsgSetPoolTypeInfo } -// ValidateBasic validates the MsgSetPoolWeights -func (msg MsgSetPoolWeights) ValidateBasic() error { +// ValidateBasic validates the MsgSetInfoByPoolType +func (msg MsgSetInfoByPoolType) ValidateBasic() error { // Account must be a valid bech32 address if _, err := sdk.AccAddressFromBech32(msg.Admin); err != nil { return errorsmod.Wrap(err, "invalid admin address (must be bech32)") } - if err := msg.PoolWeights.Validate(); err != nil { + if err := msg.InfoByPoolType.Validate(); err != nil { return err } @@ -239,12 +239,12 @@ func (msg MsgSetPoolWeights) ValidateBasic() error { } // GetSignBytes encodes the message for signing -func (msg MsgSetPoolWeights) GetSignBytes() []byte { +func (msg MsgSetInfoByPoolType) GetSignBytes() []byte { return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) } // GetSigners defines whose signature is required -func (msg MsgSetPoolWeights) GetSigners() []sdk.AccAddress { +func (msg MsgSetInfoByPoolType) GetSigners() []sdk.AccAddress { addr := sdk.MustAccAddressFromBech32(msg.Admin) return []sdk.AccAddress{addr} } diff --git a/x/protorev/types/msg_test.go b/x/protorev/types/msg_test.go index 32dcdc40371..93115703be0 100644 --- a/x/protorev/types/msg_test.go +++ b/x/protorev/types/msg_test.go @@ -506,43 +506,79 @@ func TestMsgSetMaxPoolPointsPerBlock(t *testing.T) { } } -func TestMsgSetPoolWeights(t *testing.T) { +func TestMsgSetPoolTypeInfo(t *testing.T) { cases := []struct { - description string - admin string - poolWeights types.PoolWeights - pass bool + description string + admin string + infoByPoolType types.InfoByPoolType + pass bool }{ { "Invalid message (invalid admin)", "admin", - types.PoolWeights{ - BalancerWeight: 1, - StableWeight: 1, - ConcentratedWeight: 1, - CosmwasmWeight: 1, + types.InfoByPoolType{ + Balancer: types.BalancerPoolInfo{Weight: 1}, + Stable: types.StablePoolInfo{Weight: 1}, + Concentrated: types.ConcentratedPoolInfo{Weight: 1, MaxTicksCrossed: 1}, + Cosmwasm: types.CosmwasmPoolInfo{}, }, false, }, { - "Invalid message (invalid pool weights)", + "Invalid message (invalid pool weights for balancer)", createAccount().String(), - types.PoolWeights{ - BalancerWeight: 0, - StableWeight: 1, - ConcentratedWeight: 1, - CosmwasmWeight: 1, + types.InfoByPoolType{ + Balancer: types.BalancerPoolInfo{Weight: 0}, + Stable: types.StablePoolInfo{Weight: 1}, + Concentrated: types.ConcentratedPoolInfo{Weight: 1, MaxTicksCrossed: 1}, + Cosmwasm: types.CosmwasmPoolInfo{}, + }, + false, + }, + { + "Invalid message (invalid pool info for cosmwasm)", + createAccount().String(), + types.InfoByPoolType{ + Balancer: types.BalancerPoolInfo{Weight: 1}, + Stable: types.StablePoolInfo{Weight: 1}, + Concentrated: types.ConcentratedPoolInfo{Weight: 1, MaxTicksCrossed: 1}, + Cosmwasm: types.CosmwasmPoolInfo{ + WeightMaps: []types.WeightMap{ + { + ContractAddress: "contractAddress", + Weight: 1, + }, + }, + }, + }, + false, + }, + { + "Invalid message (invalid pool info for concentrated)", + createAccount().String(), + types.InfoByPoolType{ + Balancer: types.BalancerPoolInfo{Weight: 1}, + Stable: types.StablePoolInfo{Weight: 1}, + Concentrated: types.ConcentratedPoolInfo{Weight: 1}, + Cosmwasm: types.CosmwasmPoolInfo{}, }, false, }, { "Valid message", createAccount().String(), - types.PoolWeights{ - BalancerWeight: 1, - StableWeight: 1, - ConcentratedWeight: 1, - CosmwasmWeight: 1, + types.InfoByPoolType{ + Balancer: types.BalancerPoolInfo{Weight: 1}, + Stable: types.StablePoolInfo{Weight: 1}, + Concentrated: types.ConcentratedPoolInfo{Weight: 1, MaxTicksCrossed: 1}, + Cosmwasm: types.CosmwasmPoolInfo{ + WeightMaps: []types.WeightMap{ + { + ContractAddress: createAccount().String(), + Weight: 1, + }, + }, + }, }, true, }, @@ -550,7 +586,7 @@ func TestMsgSetPoolWeights(t *testing.T) { for _, tc := range cases { t.Run(tc.description, func(t *testing.T) { - msg := types.NewMsgSetPoolWeights(tc.admin, tc.poolWeights) + msg := types.NewMsgSetPoolTypeInfo(tc.admin, tc.infoByPoolType) err := msg.ValidateBasic() if tc.pass { require.NoError(t, err) diff --git a/x/protorev/types/protorev.pb.go b/x/protorev/types/protorev.pb.go index d6409e24beb..2a343ed2ce7 100644 --- a/x/protorev/types/protorev.pb.go +++ b/x/protorev/types/protorev.pb.go @@ -268,6 +268,9 @@ func (m *RouteStatistics) GetRoute() []uint64 { // significantly between the different pool types. Each weight roughly // corresponds to the amount of time (in ms) it takes to execute a swap on that // pool type. +// +// DEPRECATED: This field is deprecated and will be removed in the next +// release. It is replaced by the `info_by_pool_type` field. type PoolWeights struct { // The weight of a stableswap pool StableWeight uint64 `protobuf:"varint,1,opt,name=stable_weight,json=stableWeight,proto3" json:"stable_weight,omitempty" yaml:"stable_weight"` @@ -340,6 +343,332 @@ func (m *PoolWeights) GetCosmwasmWeight() uint64 { return 0 } +// InfoByPoolType contains information pertaining to how expensive (in terms of +// gas and time) it is to execute a swap on a given pool type. This distinction +// is made and necessary because the execution time ranges significantly between +// the different pool types. +type InfoByPoolType struct { + // The stable pool info + Stable StablePoolInfo `protobuf:"bytes,1,opt,name=stable,proto3" json:"stable" yaml:"stable"` + // The balancer pool info + Balancer BalancerPoolInfo `protobuf:"bytes,2,opt,name=balancer,proto3" json:"balancer" yaml:"balancer"` + // The concentrated pool info + Concentrated ConcentratedPoolInfo `protobuf:"bytes,3,opt,name=concentrated,proto3" json:"concentrated" yaml:"concentrated"` + // The cosmwasm pool info + Cosmwasm CosmwasmPoolInfo `protobuf:"bytes,4,opt,name=cosmwasm,proto3" json:"cosmwasm" yaml:"cosmwasm"` +} + +func (m *InfoByPoolType) Reset() { *m = InfoByPoolType{} } +func (m *InfoByPoolType) String() string { return proto.CompactTextString(m) } +func (*InfoByPoolType) ProtoMessage() {} +func (*InfoByPoolType) Descriptor() ([]byte, []int) { + return fileDescriptor_1e9f2391fd9fec01, []int{5} +} +func (m *InfoByPoolType) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *InfoByPoolType) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_InfoByPoolType.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 *InfoByPoolType) XXX_Merge(src proto.Message) { + xxx_messageInfo_InfoByPoolType.Merge(m, src) +} +func (m *InfoByPoolType) XXX_Size() int { + return m.Size() +} +func (m *InfoByPoolType) XXX_DiscardUnknown() { + xxx_messageInfo_InfoByPoolType.DiscardUnknown(m) +} + +var xxx_messageInfo_InfoByPoolType proto.InternalMessageInfo + +func (m *InfoByPoolType) GetStable() StablePoolInfo { + if m != nil { + return m.Stable + } + return StablePoolInfo{} +} + +func (m *InfoByPoolType) GetBalancer() BalancerPoolInfo { + if m != nil { + return m.Balancer + } + return BalancerPoolInfo{} +} + +func (m *InfoByPoolType) GetConcentrated() ConcentratedPoolInfo { + if m != nil { + return m.Concentrated + } + return ConcentratedPoolInfo{} +} + +func (m *InfoByPoolType) GetCosmwasm() CosmwasmPoolInfo { + if m != nil { + return m.Cosmwasm + } + return CosmwasmPoolInfo{} +} + +// StablePoolInfo contains meta data pertaining to a stableswap pool type. +type StablePoolInfo struct { + // The weight of a stableswap pool + Weight uint64 `protobuf:"varint,1,opt,name=weight,proto3" json:"weight,omitempty" yaml:"weight"` +} + +func (m *StablePoolInfo) Reset() { *m = StablePoolInfo{} } +func (m *StablePoolInfo) String() string { return proto.CompactTextString(m) } +func (*StablePoolInfo) ProtoMessage() {} +func (*StablePoolInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_1e9f2391fd9fec01, []int{6} +} +func (m *StablePoolInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *StablePoolInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_StablePoolInfo.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 *StablePoolInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_StablePoolInfo.Merge(m, src) +} +func (m *StablePoolInfo) XXX_Size() int { + return m.Size() +} +func (m *StablePoolInfo) XXX_DiscardUnknown() { + xxx_messageInfo_StablePoolInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_StablePoolInfo proto.InternalMessageInfo + +func (m *StablePoolInfo) GetWeight() uint64 { + if m != nil { + return m.Weight + } + return 0 +} + +// BalancerPoolInfo contains meta data pertaining to a balancer pool type. +type BalancerPoolInfo struct { + // The weight of a balancer pool + Weight uint64 `protobuf:"varint,1,opt,name=weight,proto3" json:"weight,omitempty" yaml:"weight"` +} + +func (m *BalancerPoolInfo) Reset() { *m = BalancerPoolInfo{} } +func (m *BalancerPoolInfo) String() string { return proto.CompactTextString(m) } +func (*BalancerPoolInfo) ProtoMessage() {} +func (*BalancerPoolInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_1e9f2391fd9fec01, []int{7} +} +func (m *BalancerPoolInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *BalancerPoolInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_BalancerPoolInfo.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 *BalancerPoolInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_BalancerPoolInfo.Merge(m, src) +} +func (m *BalancerPoolInfo) XXX_Size() int { + return m.Size() +} +func (m *BalancerPoolInfo) XXX_DiscardUnknown() { + xxx_messageInfo_BalancerPoolInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_BalancerPoolInfo proto.InternalMessageInfo + +func (m *BalancerPoolInfo) GetWeight() uint64 { + if m != nil { + return m.Weight + } + return 0 +} + +// ConcentratedPoolInfo contains meta data pertaining to a concentrated pool +// type. +type ConcentratedPoolInfo struct { + // The weight of a concentrated pool + Weight uint64 `protobuf:"varint,1,opt,name=weight,proto3" json:"weight,omitempty" yaml:"weight"` + // The maximum number of ticks we can move when rebalancing + MaxTicksCrossed uint64 `protobuf:"varint,2,opt,name=max_ticks_crossed,json=maxTicksCrossed,proto3" json:"max_ticks_crossed,omitempty" yaml:"max_ticks_crossed"` +} + +func (m *ConcentratedPoolInfo) Reset() { *m = ConcentratedPoolInfo{} } +func (m *ConcentratedPoolInfo) String() string { return proto.CompactTextString(m) } +func (*ConcentratedPoolInfo) ProtoMessage() {} +func (*ConcentratedPoolInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_1e9f2391fd9fec01, []int{8} +} +func (m *ConcentratedPoolInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ConcentratedPoolInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ConcentratedPoolInfo.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 *ConcentratedPoolInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConcentratedPoolInfo.Merge(m, src) +} +func (m *ConcentratedPoolInfo) XXX_Size() int { + return m.Size() +} +func (m *ConcentratedPoolInfo) XXX_DiscardUnknown() { + xxx_messageInfo_ConcentratedPoolInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_ConcentratedPoolInfo proto.InternalMessageInfo + +func (m *ConcentratedPoolInfo) GetWeight() uint64 { + if m != nil { + return m.Weight + } + return 0 +} + +func (m *ConcentratedPoolInfo) GetMaxTicksCrossed() uint64 { + if m != nil { + return m.MaxTicksCrossed + } + return 0 +} + +// CosmwasmPoolInfo contains meta data pertaining to a cosmwasm pool type. +type CosmwasmPoolInfo struct { + // The weight of a cosmwasm pool (by contract address) + WeightMaps []WeightMap `protobuf:"bytes,1,rep,name=weight_maps,json=weightMaps,proto3" json:"weight_maps" yaml:"weight_maps"` +} + +func (m *CosmwasmPoolInfo) Reset() { *m = CosmwasmPoolInfo{} } +func (m *CosmwasmPoolInfo) String() string { return proto.CompactTextString(m) } +func (*CosmwasmPoolInfo) ProtoMessage() {} +func (*CosmwasmPoolInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_1e9f2391fd9fec01, []int{9} +} +func (m *CosmwasmPoolInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *CosmwasmPoolInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_CosmwasmPoolInfo.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 *CosmwasmPoolInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_CosmwasmPoolInfo.Merge(m, src) +} +func (m *CosmwasmPoolInfo) XXX_Size() int { + return m.Size() +} +func (m *CosmwasmPoolInfo) XXX_DiscardUnknown() { + xxx_messageInfo_CosmwasmPoolInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_CosmwasmPoolInfo proto.InternalMessageInfo + +func (m *CosmwasmPoolInfo) GetWeightMaps() []WeightMap { + if m != nil { + return m.WeightMaps + } + return nil +} + +// WeightMap maps a contract address to a weight. The weight of an address +// corresponds to the amount of ms required to execute a swap on that contract. +type WeightMap struct { + // The weight of a cosmwasm pool (by contract address) + Weight uint64 `protobuf:"varint,1,opt,name=weight,proto3" json:"weight,omitempty" yaml:"weight"` + // The contract address + ContractAddress string `protobuf:"bytes,2,opt,name=contract_address,json=contractAddress,proto3" json:"contract_address,omitempty" yaml:"contract_address"` +} + +func (m *WeightMap) Reset() { *m = WeightMap{} } +func (m *WeightMap) String() string { return proto.CompactTextString(m) } +func (*WeightMap) ProtoMessage() {} +func (*WeightMap) Descriptor() ([]byte, []int) { + return fileDescriptor_1e9f2391fd9fec01, []int{10} +} +func (m *WeightMap) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *WeightMap) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_WeightMap.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 *WeightMap) XXX_Merge(src proto.Message) { + xxx_messageInfo_WeightMap.Merge(m, src) +} +func (m *WeightMap) XXX_Size() int { + return m.Size() +} +func (m *WeightMap) XXX_DiscardUnknown() { + xxx_messageInfo_WeightMap.DiscardUnknown(m) +} + +var xxx_messageInfo_WeightMap proto.InternalMessageInfo + +func (m *WeightMap) GetWeight() uint64 { + if m != nil { + return m.Weight + } + return 0 +} + +func (m *WeightMap) GetContractAddress() string { + if m != nil { + return m.ContractAddress + } + return "" +} + // BaseDenom represents a single base denom that the module uses for its // arbitrage trades. It contains the denom name alongside the step size of the // binary search that is used to find the optimal swap amount @@ -355,7 +684,7 @@ func (m *BaseDenom) Reset() { *m = BaseDenom{} } func (m *BaseDenom) String() string { return proto.CompactTextString(m) } func (*BaseDenom) ProtoMessage() {} func (*BaseDenom) Descriptor() ([]byte, []int) { - return fileDescriptor_1e9f2391fd9fec01, []int{5} + return fileDescriptor_1e9f2391fd9fec01, []int{11} } func (m *BaseDenom) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -397,6 +726,12 @@ func init() { proto.RegisterType((*Trade)(nil), "osmosis.protorev.v1beta1.Trade") proto.RegisterType((*RouteStatistics)(nil), "osmosis.protorev.v1beta1.RouteStatistics") proto.RegisterType((*PoolWeights)(nil), "osmosis.protorev.v1beta1.PoolWeights") + proto.RegisterType((*InfoByPoolType)(nil), "osmosis.protorev.v1beta1.InfoByPoolType") + proto.RegisterType((*StablePoolInfo)(nil), "osmosis.protorev.v1beta1.StablePoolInfo") + proto.RegisterType((*BalancerPoolInfo)(nil), "osmosis.protorev.v1beta1.BalancerPoolInfo") + proto.RegisterType((*ConcentratedPoolInfo)(nil), "osmosis.protorev.v1beta1.ConcentratedPoolInfo") + proto.RegisterType((*CosmwasmPoolInfo)(nil), "osmosis.protorev.v1beta1.CosmwasmPoolInfo") + proto.RegisterType((*WeightMap)(nil), "osmosis.protorev.v1beta1.WeightMap") proto.RegisterType((*BaseDenom)(nil), "osmosis.protorev.v1beta1.BaseDenom") } @@ -405,50 +740,67 @@ func init() { } var fileDescriptor_1e9f2391fd9fec01 = []byte{ - // 682 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x54, 0xbf, 0x6f, 0xd3, 0x40, - 0x14, 0xce, 0x35, 0xe9, 0x8f, 0x5c, 0xdb, 0xa4, 0xb8, 0xa5, 0xb8, 0x19, 0xec, 0xe8, 0x90, 0x4a, - 0x96, 0xda, 0x0a, 0x20, 0x21, 0x55, 0x62, 0xc0, 0x65, 0xa9, 0x90, 0xda, 0xea, 0x5a, 0x09, 0xc1, - 0x62, 0x9d, 0x9d, 0x6b, 0x6a, 0x35, 0xf1, 0x45, 0xbe, 0x4b, 0x4b, 0xfb, 0x57, 0x30, 0xc0, 0xce, - 0xc6, 0x7f, 0xc1, 0xdc, 0xb1, 0x63, 0xc5, 0x60, 0xa1, 0x76, 0x61, 0xf6, 0xca, 0x82, 0x7c, 0x77, - 0x4e, 0xa2, 0x08, 0x24, 0x18, 0x60, 0xca, 0xbd, 0xef, 0xbd, 0xef, 0x7b, 0xf7, 0xbe, 0x77, 0x31, - 0x7c, 0xc4, 0x78, 0x9f, 0xf1, 0x88, 0xbb, 0x83, 0x84, 0x09, 0x96, 0xd0, 0x33, 0xf7, 0xac, 0x1d, - 0x50, 0x41, 0xda, 0x23, 0xc0, 0x91, 0x07, 0xc3, 0xd4, 0x85, 0xce, 0x08, 0xd7, 0x85, 0x8d, 0x8d, - 0x50, 0xa6, 0x7c, 0x99, 0x70, 0x55, 0xa0, 0xaa, 0x1a, 0x6b, 0x5d, 0xd6, 0x65, 0x0a, 0xcf, 0x4f, - 0x1a, 0xb5, 0x54, 0x8d, 0x1b, 0x10, 0x4e, 0x47, 0xed, 0x42, 0x16, 0xc5, 0x2a, 0x8f, 0x6e, 0x00, - 0x34, 0x8e, 0xd8, 0x29, 0x8d, 0x0f, 0x48, 0x94, 0xbc, 0x48, 0x02, 0xcc, 0x86, 0x82, 0x72, 0xe3, - 0x0d, 0x84, 0x24, 0x09, 0xfc, 0x44, 0x46, 0x26, 0x68, 0x96, 0x5b, 0x8b, 0x8f, 0x6d, 0xe7, 0x77, - 0xd7, 0x72, 0x24, 0xcb, 0xdb, 0xb8, 0x4a, 0xed, 0x52, 0x96, 0xda, 0xf7, 0x2e, 0x48, 0xbf, 0xb7, - 0x8d, 0xc6, 0x02, 0x08, 0x57, 0xc9, 0x48, 0xda, 0x81, 0x0b, 0x22, 0x6f, 0xe8, 0x47, 0xb1, 0x39, - 0xd3, 0x04, 0xad, 0xaa, 0xb7, 0x9a, 0xa5, 0x76, 0x5d, 0x71, 0x8a, 0x0c, 0xc2, 0xf3, 0xf2, 0xb8, - 0x1b, 0x1b, 0x6d, 0x58, 0x55, 0x28, 0x1b, 0x0a, 0xb3, 0x2c, 0x09, 0x6b, 0x59, 0x6a, 0xaf, 0x4c, - 0x12, 0xd8, 0x50, 0x20, 0xac, 0x64, 0xf7, 0x87, 0x62, 0xbb, 0xf2, 0xfd, 0x93, 0x0d, 0xd0, 0x17, - 0x00, 0x67, 0x65, 0x4f, 0x63, 0x0f, 0xce, 0x89, 0x84, 0x74, 0xfe, 0x64, 0x92, 0xa3, 0xbc, 0xce, - 0xbb, 0xaf, 0x27, 0x59, 0xd6, 0x4d, 0x24, 0x19, 0x61, 0xad, 0x62, 0xf8, 0xb0, 0xca, 0x05, 0x1d, - 0xf8, 0x3c, 0xba, 0xa4, 0x7a, 0x06, 0x2f, 0x67, 0x7c, 0x4d, 0xed, 0xcd, 0x6e, 0x24, 0x4e, 0x86, - 0x81, 0x13, 0xb2, 0xbe, 0x5e, 0x8f, 0xfe, 0xd9, 0xe2, 0x9d, 0x53, 0x57, 0x5c, 0x0c, 0x28, 0x77, - 0x76, 0x63, 0x31, 0x1e, 0x60, 0x24, 0x84, 0xf0, 0x42, 0x7e, 0x3e, 0x8c, 0x2e, 0xa9, 0x1e, 0xe0, - 0x23, 0x80, 0xb3, 0xf2, 0x3e, 0xc6, 0x43, 0x58, 0x19, 0x30, 0xd6, 0x33, 0x41, 0x13, 0xb4, 0x2a, - 0x5e, 0x3d, 0x4b, 0xed, 0x45, 0xc5, 0xce, 0x51, 0x84, 0x65, 0xf2, 0xff, 0x19, 0xfb, 0x03, 0xc0, - 0xba, 0x34, 0xf6, 0x50, 0x10, 0x11, 0x71, 0x11, 0x85, 0xdc, 0x78, 0x05, 0xe7, 0x07, 0x09, 0x3b, - 0x8e, 0x44, 0xe1, 0xf1, 0x86, 0xa3, 0x5f, 0x67, 0xfe, 0xf2, 0x46, 0xf6, 0xee, 0xb0, 0x28, 0xf6, - 0xd6, 0xb5, 0xbb, 0x35, 0x3d, 0x83, 0xe2, 0x21, 0x5c, 0x28, 0x18, 0x1c, 0xae, 0xc4, 0xc3, 0x7e, - 0x40, 0x13, 0x9f, 0x1d, 0xfb, 0x7a, 0x73, 0x6a, 0xa2, 0xdd, 0xbf, 0xb6, 0xf9, 0x81, 0x6a, 0x32, - 0xad, 0x87, 0x70, 0x4d, 0x41, 0xfb, 0xc7, 0x47, 0x6a, 0xa9, 0x9b, 0x70, 0x56, 0xbe, 0x56, 0xb3, - 0xdc, 0x2c, 0xb7, 0x2a, 0xde, 0x4a, 0x96, 0xda, 0x4b, 0x8a, 0x2b, 0x61, 0x84, 0x55, 0x1a, 0x7d, - 0x9e, 0x81, 0x8b, 0x07, 0x8c, 0xf5, 0x5e, 0xd3, 0xa8, 0x7b, 0x22, 0xb8, 0xf1, 0x1c, 0x2e, 0x73, - 0x41, 0x82, 0x1e, 0xf5, 0xcf, 0x25, 0xa2, 0x97, 0x64, 0x66, 0xa9, 0xbd, 0x56, 0xac, 0x78, 0x22, - 0x8d, 0xf0, 0x92, 0x8a, 0x15, 0xdf, 0xd8, 0x81, 0xf5, 0x80, 0xf4, 0x48, 0x1c, 0xd2, 0xa4, 0x10, - 0x98, 0x91, 0x02, 0x8d, 0x2c, 0xb5, 0xd7, 0x95, 0xc0, 0x54, 0x01, 0xc2, 0xb5, 0x02, 0xd1, 0x22, - 0xfb, 0x70, 0x35, 0x64, 0x71, 0x48, 0x63, 0x91, 0x10, 0x41, 0x3b, 0x85, 0x50, 0x59, 0x0a, 0x59, - 0x59, 0x6a, 0x37, 0x94, 0xd0, 0x2f, 0x8a, 0x10, 0x36, 0x26, 0xd1, 0xf1, 0xad, 0x72, 0x3f, 0xcf, - 0x09, 0xef, 0x17, 0x62, 0x95, 0xe9, 0x5b, 0x4d, 0x15, 0x20, 0x5c, 0x2b, 0x10, 0x25, 0x82, 0x3e, - 0x00, 0x58, 0xf5, 0x08, 0xa7, 0x2f, 0x69, 0xcc, 0xfa, 0xb9, 0xbf, 0x9d, 0xfc, 0x20, 0xfd, 0xa9, - 0x4e, 0xfa, 0x2b, 0x61, 0x84, 0x55, 0xfa, 0x9f, 0xff, 0xb9, 0xbc, 0xbd, 0xab, 0x5b, 0x0b, 0x5c, - 0xdf, 0x5a, 0xe0, 0xdb, 0xad, 0x05, 0xde, 0xdf, 0x59, 0xa5, 0xeb, 0x3b, 0xab, 0x74, 0x73, 0x67, - 0x95, 0xde, 0x3e, 0x9d, 0xd0, 0xd7, 0x5f, 0x88, 0xad, 0x1e, 0x09, 0x78, 0x11, 0xb8, 0x67, 0xed, - 0x67, 0xee, 0xbb, 0xf1, 0xe7, 0x5b, 0x76, 0x0c, 0xe6, 0x64, 0xfc, 0xe4, 0x67, 0x00, 0x00, 0x00, - 0xff, 0xff, 0x25, 0xcc, 0x13, 0x2b, 0xdf, 0x05, 0x00, 0x00, + // 950 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0xcf, 0x6f, 0x1b, 0x45, + 0x14, 0xce, 0xc6, 0x4e, 0x1a, 0x8f, 0x53, 0xdb, 0x99, 0x84, 0xd6, 0x71, 0x91, 0x37, 0x9a, 0x4a, + 0xc5, 0x20, 0x75, 0xad, 0x04, 0x24, 0xa4, 0xa2, 0x1e, 0xba, 0x46, 0x88, 0x08, 0xd1, 0x54, 0x13, + 0x4b, 0x15, 0x5c, 0x96, 0xd9, 0xf5, 0x24, 0x5d, 0xc5, 0xbb, 0x63, 0xed, 0x8c, 0xf3, 0xa3, 0x07, + 0xfe, 0x01, 0x2e, 0x1c, 0xe0, 0xc6, 0x81, 0x1b, 0xff, 0x05, 0xe7, 0x1e, 0x7b, 0xac, 0x38, 0xac, + 0x50, 0x72, 0xe1, 0xbc, 0x57, 0x2e, 0x68, 0xe7, 0xc7, 0xda, 0x71, 0x63, 0x84, 0x0f, 0xf4, 0x94, + 0x99, 0xef, 0x7d, 0xef, 0x7b, 0xf3, 0xbe, 0x37, 0x3b, 0x31, 0xf8, 0x80, 0xf1, 0x88, 0xf1, 0x90, + 0x77, 0x47, 0x09, 0x13, 0x2c, 0xa1, 0xa7, 0xdd, 0xd3, 0x5d, 0x9f, 0x0a, 0xb2, 0x5b, 0x00, 0x8e, + 0x5c, 0xc0, 0xa6, 0x26, 0x3a, 0x05, 0xae, 0x89, 0xad, 0xed, 0x40, 0x86, 0x3c, 0x19, 0xe8, 0xaa, + 0x8d, 0x62, 0xb5, 0xb6, 0x8e, 0xd9, 0x31, 0x53, 0x78, 0xbe, 0xd2, 0x68, 0x5b, 0x71, 0xba, 0x3e, + 0xe1, 0xb4, 0x28, 0x17, 0xb0, 0x30, 0x56, 0x71, 0xf4, 0xc6, 0x02, 0xb0, 0xcf, 0x4e, 0x68, 0xfc, + 0x8c, 0x84, 0xc9, 0x93, 0xc4, 0xc7, 0x6c, 0x2c, 0x28, 0x87, 0xdf, 0x00, 0x40, 0x12, 0xdf, 0x4b, + 0xe4, 0xae, 0x69, 0xed, 0x94, 0x3a, 0xd5, 0x3d, 0xdb, 0x99, 0x77, 0x2c, 0x47, 0x66, 0xb9, 0xdb, + 0xaf, 0x52, 0x7b, 0x29, 0x4b, 0xed, 0x8d, 0x0b, 0x12, 0x0d, 0x1f, 0xa1, 0x89, 0x00, 0xc2, 0x15, + 0x52, 0x48, 0x3b, 0x60, 0x4d, 0xe4, 0x05, 0xbd, 0x30, 0x6e, 0x2e, 0xef, 0x58, 0x9d, 0x8a, 0xbb, + 0x99, 0xa5, 0x76, 0x5d, 0xe5, 0x98, 0x08, 0xc2, 0xb7, 0xe4, 0x72, 0x3f, 0x86, 0xbb, 0xa0, 0xa2, + 0x50, 0x36, 0x16, 0xcd, 0x92, 0x4c, 0xd8, 0xca, 0x52, 0xbb, 0x31, 0x9d, 0xc0, 0xc6, 0x02, 0x61, + 0x25, 0x7b, 0x30, 0x16, 0x8f, 0xca, 0x7f, 0xfd, 0x6a, 0x5b, 0xe8, 0x77, 0x0b, 0xac, 0xc8, 0x9a, + 0xf0, 0x29, 0x58, 0x15, 0x09, 0x19, 0xfc, 0x97, 0x4e, 0xfa, 0x39, 0xcf, 0x7d, 0x4f, 0x77, 0x72, + 0x5b, 0x17, 0x91, 0xc9, 0x08, 0x6b, 0x15, 0xe8, 0x81, 0x0a, 0x17, 0x74, 0xe4, 0xf1, 0xf0, 0x25, + 0xd5, 0x3d, 0xb8, 0x79, 0xc6, 0x1f, 0xa9, 0xfd, 0xe0, 0x38, 0x14, 0x2f, 0xc6, 0xbe, 0x13, 0xb0, + 0x48, 0x8f, 0x47, 0xff, 0x79, 0xc8, 0x07, 0x27, 0x5d, 0x71, 0x31, 0xa2, 0xdc, 0xd9, 0x8f, 0xc5, + 0xa4, 0x81, 0x42, 0x08, 0xe1, 0xb5, 0x7c, 0x7d, 0x18, 0xbe, 0xa4, 0xba, 0x81, 0x9f, 0x2d, 0xb0, + 0x22, 0xcf, 0x03, 0xef, 0x83, 0xf2, 0x88, 0xb1, 0x61, 0xd3, 0xda, 0xb1, 0x3a, 0x65, 0xb7, 0x9e, + 0xa5, 0x76, 0x55, 0x65, 0xe7, 0x28, 0xc2, 0x32, 0xf8, 0xee, 0x8c, 0xfd, 0xdb, 0x02, 0x75, 0x69, + 0xec, 0xa1, 0x20, 0x22, 0xe4, 0x22, 0x0c, 0x38, 0xfc, 0x0a, 0xdc, 0x1a, 0x25, 0xec, 0x28, 0x14, + 0xc6, 0xe3, 0x6d, 0x47, 0xdf, 0xce, 0xfc, 0xe6, 0x15, 0xf6, 0xf6, 0x58, 0x18, 0xbb, 0x77, 0xb4, + 0xbb, 0x35, 0xdd, 0x83, 0xca, 0x43, 0xd8, 0x28, 0x40, 0x0e, 0x1a, 0xf1, 0x38, 0xf2, 0x69, 0xe2, + 0xb1, 0x23, 0x4f, 0x4f, 0x4e, 0x75, 0xb4, 0xbf, 0xb0, 0xcd, 0x77, 0x55, 0x91, 0x59, 0x3d, 0x84, + 0x6b, 0x0a, 0x3a, 0x38, 0xea, 0xab, 0xa1, 0x3e, 0x00, 0x2b, 0xf2, 0xb6, 0x36, 0x4b, 0x3b, 0xa5, + 0x4e, 0xd9, 0x6d, 0x64, 0xa9, 0xbd, 0xae, 0x72, 0x25, 0x8c, 0xb0, 0x0a, 0xa3, 0xdf, 0x96, 0x41, + 0xf5, 0x19, 0x63, 0xc3, 0xe7, 0x34, 0x3c, 0x7e, 0x21, 0x38, 0x7c, 0x0c, 0x6e, 0x73, 0x41, 0xfc, + 0x21, 0xf5, 0xce, 0x24, 0xa2, 0x87, 0xd4, 0xcc, 0x52, 0x7b, 0xcb, 0x8c, 0x78, 0x2a, 0x8c, 0xf0, + 0xba, 0xda, 0xab, 0x7c, 0xd8, 0x03, 0x75, 0x9f, 0x0c, 0x49, 0x1c, 0xd0, 0xc4, 0x08, 0x2c, 0x4b, + 0x81, 0x56, 0x96, 0xda, 0x77, 0x94, 0xc0, 0x0c, 0x01, 0xe1, 0x9a, 0x41, 0xb4, 0xc8, 0x01, 0xd8, + 0x0c, 0x58, 0x1c, 0xd0, 0x58, 0x24, 0x44, 0xd0, 0x81, 0x11, 0x2a, 0x49, 0xa1, 0x76, 0x96, 0xda, + 0x2d, 0x25, 0x74, 0x03, 0x09, 0x61, 0x38, 0x8d, 0x4e, 0x4e, 0x95, 0xfb, 0x79, 0x46, 0x78, 0x64, + 0xc4, 0xca, 0xb3, 0xa7, 0x9a, 0x21, 0x20, 0x5c, 0x33, 0x88, 0x12, 0x41, 0xbf, 0x94, 0x40, 0x6d, + 0x3f, 0x3e, 0x62, 0xee, 0x45, 0xee, 0x57, 0xff, 0x62, 0x44, 0xe1, 0x73, 0xb0, 0xaa, 0xba, 0x97, + 0x2e, 0x55, 0xf7, 0x3a, 0xf3, 0xbf, 0xc4, 0x43, 0xc9, 0xcb, 0x33, 0xa5, 0xc6, 0xcc, 0x27, 0xa9, + 0x54, 0x10, 0xd6, 0x72, 0xd0, 0x03, 0x6b, 0xc6, 0x13, 0xe9, 0x5f, 0x75, 0xef, 0xa3, 0xf9, 0xd2, + 0xae, 0x66, 0x16, 0xe2, 0x77, 0xb5, 0x78, 0xfd, 0xba, 0xdf, 0x08, 0x17, 0xa2, 0x90, 0x81, 0xf5, + 0x69, 0x9f, 0xa4, 0xb7, 0xd5, 0x3d, 0x67, 0x7e, 0x91, 0xde, 0x14, 0xbb, 0x28, 0x74, 0x4f, 0x17, + 0xda, 0x7c, 0x7b, 0x1e, 0x08, 0x5f, 0x2b, 0x90, 0x77, 0x64, 0xfc, 0x94, 0xde, 0xff, 0x6b, 0x47, + 0x3d, 0xcd, 0x9c, 0xd7, 0x91, 0x51, 0x42, 0xb8, 0x10, 0x45, 0x9f, 0x81, 0xda, 0x75, 0x8f, 0xe1, + 0x87, 0x60, 0xf5, 0xda, 0x1d, 0xde, 0x98, 0xf8, 0x6d, 0x66, 0xac, 0x09, 0xe8, 0x31, 0x68, 0xcc, + 0xba, 0xb8, 0x48, 0xfa, 0x0f, 0x16, 0xd8, 0xba, 0xc9, 0xa0, 0x05, 0x34, 0xe0, 0x97, 0x60, 0x23, + 0x22, 0xe7, 0x9e, 0x08, 0x83, 0x13, 0xee, 0x05, 0x09, 0xe3, 0x9c, 0x0e, 0xf4, 0xb7, 0xf3, 0x7e, + 0x96, 0xda, 0x4d, 0x95, 0xf5, 0x16, 0x05, 0xe1, 0x7a, 0x44, 0xce, 0xfb, 0x39, 0xd4, 0xd3, 0x88, + 0x00, 0x8d, 0x59, 0x03, 0xe1, 0x77, 0xa0, 0xaa, 0xea, 0x78, 0x11, 0x19, 0x99, 0x47, 0xed, 0xfe, + 0xfc, 0x09, 0xa8, 0x3b, 0xff, 0x35, 0x19, 0xb9, 0x2d, 0x6d, 0x3d, 0x9c, 0x3e, 0xb6, 0x54, 0x41, + 0x18, 0x9c, 0x19, 0x1a, 0x47, 0xdf, 0x83, 0x4a, 0x91, 0xb4, 0x48, 0xdf, 0x5f, 0x80, 0x46, 0xc0, + 0x72, 0xdf, 0x02, 0xe1, 0x91, 0xc1, 0x20, 0xa1, 0xdc, 0xbc, 0x8e, 0xf7, 0x26, 0xef, 0xdd, 0x2c, + 0x03, 0xe1, 0xba, 0x81, 0x9e, 0x68, 0xe4, 0x27, 0x0b, 0x54, 0x5c, 0xc2, 0xe9, 0xe7, 0x34, 0x66, + 0x51, 0xfe, 0xfc, 0x0d, 0xf2, 0x85, 0xac, 0x5f, 0x99, 0x7e, 0xfe, 0x24, 0x8c, 0xb0, 0x0a, 0xff, + 0xef, 0xff, 0xfb, 0xdc, 0xa7, 0xaf, 0x2e, 0xdb, 0xd6, 0xeb, 0xcb, 0xb6, 0xf5, 0xe7, 0x65, 0xdb, + 0xfa, 0xf1, 0xaa, 0xbd, 0xf4, 0xfa, 0xaa, 0xbd, 0xf4, 0xe6, 0xaa, 0xbd, 0xf4, 0xed, 0x27, 0x53, + 0xfa, 0x7a, 0x0e, 0x0f, 0x87, 0xc4, 0xe7, 0x66, 0xd3, 0x3d, 0xdd, 0xfd, 0xb4, 0x7b, 0x3e, 0xf9, + 0x75, 0x25, 0x2b, 0xfa, 0xab, 0x72, 0xff, 0xf1, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x86, 0x45, + 0x14, 0x25, 0x7e, 0x09, 0x00, 0x00, } func (this *TokenPairArbRoutes) Equal(that interface{}) bool { @@ -796,7 +1148,7 @@ func (m *PoolWeights) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *BaseDenom) Marshal() (dAtA []byte, err error) { +func (m *InfoByPoolType) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -806,66 +1158,290 @@ func (m *BaseDenom) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *BaseDenom) MarshalTo(dAtA []byte) (int, error) { +func (m *InfoByPoolType) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *BaseDenom) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *InfoByPoolType) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l { - size := m.StepSize.Size() + size, err := m.Cosmwasm.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } i -= size - if _, err := m.StepSize.MarshalTo(dAtA[i:]); err != nil { + i = encodeVarintProtorev(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + { + size, err := m.Concentrated.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintProtorev(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + { + size, err := m.Balancer.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { return 0, err } + i -= size i = encodeVarintProtorev(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 - if len(m.Denom) > 0 { - i -= len(m.Denom) - copy(dAtA[i:], m.Denom) - i = encodeVarintProtorev(dAtA, i, uint64(len(m.Denom))) - i-- - dAtA[i] = 0xa + { + size, err := m.Stable.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintProtorev(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0xa return len(dAtA) - i, nil } -func encodeVarintProtorev(dAtA []byte, offset int, v uint64) int { - offset -= sovProtorev(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ +func (m *StablePoolInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - dAtA[offset] = uint8(v) - return base + return dAtA[:n], nil } -func (m *TokenPairArbRoutes) Size() (n int) { - if m == nil { - return 0 - } + +func (m *StablePoolInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *StablePoolInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - if len(m.ArbRoutes) > 0 { - for _, e := range m.ArbRoutes { - l = e.Size() - n += 1 + l + sovProtorev(uint64(l)) - } - } - l = len(m.TokenIn) - if l > 0 { - n += 1 + l + sovProtorev(uint64(l)) - } - l = len(m.TokenOut) - if l > 0 { - n += 1 + l + sovProtorev(uint64(l)) + if m.Weight != 0 { + i = encodeVarintProtorev(dAtA, i, uint64(m.Weight)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *BalancerPoolInfo) 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 *BalancerPoolInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BalancerPoolInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Weight != 0 { + i = encodeVarintProtorev(dAtA, i, uint64(m.Weight)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *ConcentratedPoolInfo) 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 *ConcentratedPoolInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ConcentratedPoolInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.MaxTicksCrossed != 0 { + i = encodeVarintProtorev(dAtA, i, uint64(m.MaxTicksCrossed)) + i-- + dAtA[i] = 0x10 + } + if m.Weight != 0 { + i = encodeVarintProtorev(dAtA, i, uint64(m.Weight)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *CosmwasmPoolInfo) 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 *CosmwasmPoolInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CosmwasmPoolInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.WeightMaps) > 0 { + for iNdEx := len(m.WeightMaps) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.WeightMaps[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintProtorev(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *WeightMap) 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 *WeightMap) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *WeightMap) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ContractAddress) > 0 { + i -= len(m.ContractAddress) + copy(dAtA[i:], m.ContractAddress) + i = encodeVarintProtorev(dAtA, i, uint64(len(m.ContractAddress))) + i-- + dAtA[i] = 0x12 + } + if m.Weight != 0 { + i = encodeVarintProtorev(dAtA, i, uint64(m.Weight)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *BaseDenom) 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 *BaseDenom) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BaseDenom) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.StepSize.Size() + i -= size + if _, err := m.StepSize.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintProtorev(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Denom) > 0 { + i -= len(m.Denom) + copy(dAtA[i:], m.Denom) + i = encodeVarintProtorev(dAtA, i, uint64(len(m.Denom))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintProtorev(dAtA []byte, offset int, v uint64) int { + offset -= sovProtorev(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *TokenPairArbRoutes) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ArbRoutes) > 0 { + for _, e := range m.ArbRoutes { + l = e.Size() + n += 1 + l + sovProtorev(uint64(l)) + } + } + l = len(m.TokenIn) + if l > 0 { + n += 1 + l + sovProtorev(uint64(l)) + } + l = len(m.TokenOut) + if l > 0 { + n += 1 + l + sovProtorev(uint64(l)) } return n } @@ -952,6 +1528,93 @@ func (m *PoolWeights) Size() (n int) { return n } +func (m *InfoByPoolType) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Stable.Size() + n += 1 + l + sovProtorev(uint64(l)) + l = m.Balancer.Size() + n += 1 + l + sovProtorev(uint64(l)) + l = m.Concentrated.Size() + n += 1 + l + sovProtorev(uint64(l)) + l = m.Cosmwasm.Size() + n += 1 + l + sovProtorev(uint64(l)) + return n +} + +func (m *StablePoolInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Weight != 0 { + n += 1 + sovProtorev(uint64(m.Weight)) + } + return n +} + +func (m *BalancerPoolInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Weight != 0 { + n += 1 + sovProtorev(uint64(m.Weight)) + } + return n +} + +func (m *ConcentratedPoolInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Weight != 0 { + n += 1 + sovProtorev(uint64(m.Weight)) + } + if m.MaxTicksCrossed != 0 { + n += 1 + sovProtorev(uint64(m.MaxTicksCrossed)) + } + return n +} + +func (m *CosmwasmPoolInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.WeightMaps) > 0 { + for _, e := range m.WeightMaps { + l = e.Size() + n += 1 + l + sovProtorev(uint64(l)) + } + } + return n +} + +func (m *WeightMap) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Weight != 0 { + n += 1 + sovProtorev(uint64(m.Weight)) + } + l = len(m.ContractAddress) + if l > 0 { + n += 1 + l + sovProtorev(uint64(l)) + } + return n +} + func (m *BaseDenom) Size() (n int) { if m == nil { return 0 @@ -1692,6 +2355,599 @@ func (m *PoolWeights) Unmarshal(dAtA []byte) error { } return nil } +func (m *InfoByPoolType) 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 ErrIntOverflowProtorev + } + 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: InfoByPoolType: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: InfoByPoolType: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Stable", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtorev + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthProtorev + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthProtorev + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Stable.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Balancer", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtorev + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthProtorev + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthProtorev + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Balancer.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Concentrated", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtorev + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthProtorev + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthProtorev + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Concentrated.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Cosmwasm", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtorev + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthProtorev + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthProtorev + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Cosmwasm.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProtorev(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthProtorev + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StablePoolInfo) 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 ErrIntOverflowProtorev + } + 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: StablePoolInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StablePoolInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Weight", wireType) + } + m.Weight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtorev + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Weight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipProtorev(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthProtorev + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BalancerPoolInfo) 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 ErrIntOverflowProtorev + } + 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: BalancerPoolInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BalancerPoolInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Weight", wireType) + } + m.Weight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtorev + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Weight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipProtorev(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthProtorev + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ConcentratedPoolInfo) 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 ErrIntOverflowProtorev + } + 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: ConcentratedPoolInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConcentratedPoolInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Weight", wireType) + } + m.Weight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtorev + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Weight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxTicksCrossed", wireType) + } + m.MaxTicksCrossed = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtorev + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxTicksCrossed |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipProtorev(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthProtorev + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CosmwasmPoolInfo) 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 ErrIntOverflowProtorev + } + 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: CosmwasmPoolInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CosmwasmPoolInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field WeightMaps", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtorev + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthProtorev + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthProtorev + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.WeightMaps = append(m.WeightMaps, WeightMap{}) + if err := m.WeightMaps[len(m.WeightMaps)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProtorev(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthProtorev + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *WeightMap) 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 ErrIntOverflowProtorev + } + 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: WeightMap: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: WeightMap: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Weight", wireType) + } + m.Weight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtorev + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Weight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ContractAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtorev + } + 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 ErrInvalidLengthProtorev + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthProtorev + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ContractAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProtorev(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthProtorev + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *BaseDenom) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/protorev/types/query.pb.go b/x/protorev/types/query.pb.go index 5b639a6faa2..eba853f61a9 100644 --- a/x/protorev/types/query.pb.go +++ b/x/protorev/types/query.pb.go @@ -839,23 +839,23 @@ func (m *QueryGetProtoRevDeveloperAccountResponse) GetDeveloperAccount() string return "" } -// QueryGetProtoRevPoolWeightsRequest is request type for the -// Query/GetProtoRevPoolWeights RPC method. -type QueryGetProtoRevPoolWeightsRequest struct { +// QueryGetProtoRevInfoByPoolTypeRequest is request type for the +// Query/GetProtoRevInfoByPoolType RPC method. +type QueryGetProtoRevInfoByPoolTypeRequest struct { } -func (m *QueryGetProtoRevPoolWeightsRequest) Reset() { *m = QueryGetProtoRevPoolWeightsRequest{} } -func (m *QueryGetProtoRevPoolWeightsRequest) String() string { return proto.CompactTextString(m) } -func (*QueryGetProtoRevPoolWeightsRequest) ProtoMessage() {} -func (*QueryGetProtoRevPoolWeightsRequest) Descriptor() ([]byte, []int) { +func (m *QueryGetProtoRevInfoByPoolTypeRequest) Reset() { *m = QueryGetProtoRevInfoByPoolTypeRequest{} } +func (m *QueryGetProtoRevInfoByPoolTypeRequest) String() string { return proto.CompactTextString(m) } +func (*QueryGetProtoRevInfoByPoolTypeRequest) ProtoMessage() {} +func (*QueryGetProtoRevInfoByPoolTypeRequest) Descriptor() ([]byte, []int) { return fileDescriptor_f5e7ac9973cce389, []int{18} } -func (m *QueryGetProtoRevPoolWeightsRequest) XXX_Unmarshal(b []byte) error { +func (m *QueryGetProtoRevInfoByPoolTypeRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryGetProtoRevPoolWeightsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryGetProtoRevInfoByPoolTypeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryGetProtoRevPoolWeightsRequest.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryGetProtoRevInfoByPoolTypeRequest.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -865,37 +865,40 @@ func (m *QueryGetProtoRevPoolWeightsRequest) XXX_Marshal(b []byte, deterministic return b[:n], nil } } -func (m *QueryGetProtoRevPoolWeightsRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryGetProtoRevPoolWeightsRequest.Merge(m, src) +func (m *QueryGetProtoRevInfoByPoolTypeRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryGetProtoRevInfoByPoolTypeRequest.Merge(m, src) } -func (m *QueryGetProtoRevPoolWeightsRequest) XXX_Size() int { +func (m *QueryGetProtoRevInfoByPoolTypeRequest) XXX_Size() int { return m.Size() } -func (m *QueryGetProtoRevPoolWeightsRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryGetProtoRevPoolWeightsRequest.DiscardUnknown(m) +func (m *QueryGetProtoRevInfoByPoolTypeRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryGetProtoRevInfoByPoolTypeRequest.DiscardUnknown(m) } -var xxx_messageInfo_QueryGetProtoRevPoolWeightsRequest proto.InternalMessageInfo +var xxx_messageInfo_QueryGetProtoRevInfoByPoolTypeRequest proto.InternalMessageInfo -// QueryGetProtoRevPoolWeightsResponse is response type for the -// Query/GetProtoRevPoolWeights RPC method. -type QueryGetProtoRevPoolWeightsResponse struct { - // pool_weights is a list of all of the pool weights - PoolWeights PoolWeights `protobuf:"bytes,1,opt,name=pool_weights,json=poolWeights,proto3" json:"pool_weights" yaml:"pool_weights"` +// QueryGetProtoRevInfoByPoolTypeResponse is response type for the +// Query/GetProtoRevInfoByPoolType RPC method. +type QueryGetProtoRevInfoByPoolTypeResponse struct { + // InfoByPoolType contains all information pertaining to how different + // pool types are handled by the module. + InfoByPoolType InfoByPoolType `protobuf:"bytes,1,opt,name=info_by_pool_type,json=infoByPoolType,proto3" json:"info_by_pool_type" yaml:"info_by_pool_type"` } -func (m *QueryGetProtoRevPoolWeightsResponse) Reset() { *m = QueryGetProtoRevPoolWeightsResponse{} } -func (m *QueryGetProtoRevPoolWeightsResponse) String() string { return proto.CompactTextString(m) } -func (*QueryGetProtoRevPoolWeightsResponse) ProtoMessage() {} -func (*QueryGetProtoRevPoolWeightsResponse) Descriptor() ([]byte, []int) { +func (m *QueryGetProtoRevInfoByPoolTypeResponse) Reset() { + *m = QueryGetProtoRevInfoByPoolTypeResponse{} +} +func (m *QueryGetProtoRevInfoByPoolTypeResponse) String() string { return proto.CompactTextString(m) } +func (*QueryGetProtoRevInfoByPoolTypeResponse) ProtoMessage() {} +func (*QueryGetProtoRevInfoByPoolTypeResponse) Descriptor() ([]byte, []int) { return fileDescriptor_f5e7ac9973cce389, []int{19} } -func (m *QueryGetProtoRevPoolWeightsResponse) XXX_Unmarshal(b []byte) error { +func (m *QueryGetProtoRevInfoByPoolTypeResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryGetProtoRevPoolWeightsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryGetProtoRevInfoByPoolTypeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryGetProtoRevPoolWeightsResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryGetProtoRevInfoByPoolTypeResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -905,23 +908,23 @@ func (m *QueryGetProtoRevPoolWeightsResponse) XXX_Marshal(b []byte, deterministi return b[:n], nil } } -func (m *QueryGetProtoRevPoolWeightsResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryGetProtoRevPoolWeightsResponse.Merge(m, src) +func (m *QueryGetProtoRevInfoByPoolTypeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryGetProtoRevInfoByPoolTypeResponse.Merge(m, src) } -func (m *QueryGetProtoRevPoolWeightsResponse) XXX_Size() int { +func (m *QueryGetProtoRevInfoByPoolTypeResponse) XXX_Size() int { return m.Size() } -func (m *QueryGetProtoRevPoolWeightsResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryGetProtoRevPoolWeightsResponse.DiscardUnknown(m) +func (m *QueryGetProtoRevInfoByPoolTypeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryGetProtoRevInfoByPoolTypeResponse.DiscardUnknown(m) } -var xxx_messageInfo_QueryGetProtoRevPoolWeightsResponse proto.InternalMessageInfo +var xxx_messageInfo_QueryGetProtoRevInfoByPoolTypeResponse proto.InternalMessageInfo -func (m *QueryGetProtoRevPoolWeightsResponse) GetPoolWeights() PoolWeights { +func (m *QueryGetProtoRevInfoByPoolTypeResponse) GetInfoByPoolType() InfoByPoolType { if m != nil { - return m.PoolWeights + return m.InfoByPoolType } - return PoolWeights{} + return InfoByPoolType{} } // QueryGetProtoRevMaxPoolPointsPerBlockRequest is request type for the @@ -1405,8 +1408,8 @@ func init() { proto.RegisterType((*QueryGetProtoRevAdminAccountResponse)(nil), "osmosis.protorev.v1beta1.QueryGetProtoRevAdminAccountResponse") proto.RegisterType((*QueryGetProtoRevDeveloperAccountRequest)(nil), "osmosis.protorev.v1beta1.QueryGetProtoRevDeveloperAccountRequest") proto.RegisterType((*QueryGetProtoRevDeveloperAccountResponse)(nil), "osmosis.protorev.v1beta1.QueryGetProtoRevDeveloperAccountResponse") - proto.RegisterType((*QueryGetProtoRevPoolWeightsRequest)(nil), "osmosis.protorev.v1beta1.QueryGetProtoRevPoolWeightsRequest") - proto.RegisterType((*QueryGetProtoRevPoolWeightsResponse)(nil), "osmosis.protorev.v1beta1.QueryGetProtoRevPoolWeightsResponse") + proto.RegisterType((*QueryGetProtoRevInfoByPoolTypeRequest)(nil), "osmosis.protorev.v1beta1.QueryGetProtoRevInfoByPoolTypeRequest") + proto.RegisterType((*QueryGetProtoRevInfoByPoolTypeResponse)(nil), "osmosis.protorev.v1beta1.QueryGetProtoRevInfoByPoolTypeResponse") proto.RegisterType((*QueryGetProtoRevMaxPoolPointsPerBlockRequest)(nil), "osmosis.protorev.v1beta1.QueryGetProtoRevMaxPoolPointsPerBlockRequest") proto.RegisterType((*QueryGetProtoRevMaxPoolPointsPerBlockResponse)(nil), "osmosis.protorev.v1beta1.QueryGetProtoRevMaxPoolPointsPerBlockResponse") proto.RegisterType((*QueryGetProtoRevMaxPoolPointsPerTxRequest)(nil), "osmosis.protorev.v1beta1.QueryGetProtoRevMaxPoolPointsPerTxRequest") @@ -1424,103 +1427,103 @@ func init() { } var fileDescriptor_f5e7ac9973cce389 = []byte{ - // 1531 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0x5d, 0x6f, 0x1b, 0xc5, - 0x1a, 0xce, 0xf6, 0x23, 0x39, 0x9d, 0xb4, 0x3d, 0xed, 0xb4, 0x49, 0x93, 0x4d, 0x6a, 0x3b, 0x93, - 0x38, 0x9f, 0x8d, 0x7d, 0xfa, 0x71, 0x4e, 0x0f, 0x1f, 0x05, 0xb2, 0x0d, 0x54, 0x51, 0x45, 0x63, - 0x96, 0x22, 0x24, 0x90, 0x30, 0x6b, 0x7b, 0xe2, 0xae, 0xba, 0xde, 0x71, 0x77, 0xd7, 0x21, 0xbe, - 0x05, 0x09, 0x84, 0x84, 0xc4, 0xd7, 0x35, 0x12, 0xf7, 0xf0, 0x07, 0xb8, 0xe0, 0x82, 0xbb, 0x8a, - 0xab, 0x22, 0x24, 0x84, 0x0a, 0xb2, 0x50, 0xcb, 0x05, 0xd7, 0xfe, 0x05, 0x68, 0x67, 0xde, 0xb5, - 0xd7, 0x3b, 0xbb, 0x8e, 0x9d, 0x48, 0x5c, 0xd5, 0x3b, 0xf3, 0xbe, 0xcf, 0xfb, 0x3c, 0x33, 0xf3, - 0xce, 0x3c, 0x29, 0x5a, 0x60, 0x6e, 0x8d, 0xb9, 0xa6, 0x9b, 0xaf, 0x3b, 0xcc, 0x63, 0x0e, 0xdd, - 0xcd, 0xef, 0x5e, 0x2e, 0x51, 0xcf, 0xb8, 0x9c, 0x7f, 0xd0, 0xa0, 0x4e, 0x33, 0xc7, 0x87, 0xf1, - 0x14, 0x44, 0xe5, 0x82, 0xa8, 0x1c, 0x44, 0xa9, 0xe7, 0xab, 0xac, 0xca, 0xf8, 0x68, 0xde, 0xff, - 0x25, 0x02, 0xd4, 0xd9, 0x2a, 0x63, 0x55, 0x8b, 0xe6, 0x8d, 0xba, 0x99, 0x37, 0x6c, 0x9b, 0x79, - 0x86, 0x67, 0x32, 0x1b, 0xd2, 0xd5, 0xd5, 0x32, 0x87, 0xcb, 0x97, 0x0c, 0x97, 0x8a, 0x32, 0x9d, - 0xa2, 0x75, 0xa3, 0x6a, 0xda, 0x3c, 0x18, 0x62, 0xb3, 0x89, 0xfc, 0xea, 0x86, 0x63, 0xd4, 0x02, - 0xc8, 0xa5, 0xe4, 0xb0, 0x80, 0xb1, 0x08, 0x4c, 0x85, 0x6b, 0x07, 0x31, 0x65, 0x66, 0x42, 0x3d, - 0x72, 0x1e, 0xe1, 0xd7, 0x7c, 0x46, 0x05, 0x8e, 0xae, 0xd3, 0x07, 0x0d, 0xea, 0x7a, 0x64, 0x07, - 0x9d, 0xeb, 0x19, 0x75, 0xeb, 0xcc, 0x76, 0x29, 0xde, 0x46, 0xa3, 0x82, 0xc5, 0x94, 0x92, 0x51, - 0x96, 0xc7, 0xaf, 0x64, 0x72, 0x49, 0xeb, 0x94, 0x13, 0x99, 0xda, 0xc4, 0xc3, 0x56, 0x7a, 0xa4, - 0xdd, 0x4a, 0x9f, 0x6a, 0x1a, 0x35, 0xeb, 0x59, 0x22, 0xb2, 0x89, 0x0e, 0x30, 0x64, 0x09, 0x65, - 0x79, 0x9d, 0x5b, 0xd4, 0x2b, 0xf8, 0x08, 0x3a, 0xdd, 0xbd, 0xd3, 0xa8, 0x95, 0xa8, 0xb3, 0xbd, - 0x73, 0xd7, 0x31, 0x2a, 0xb4, 0x43, 0xe8, 0x2b, 0x05, 0x2d, 0xee, 0x17, 0x09, 0x24, 0x5d, 0x74, - 0xc6, 0xe6, 0x33, 0x45, 0xb6, 0x53, 0xf4, 0xf8, 0x1c, 0xa7, 0x7b, 0x42, 0xdb, 0xf2, 0xc9, 0x3c, - 0x6e, 0xa5, 0x17, 0xab, 0xa6, 0x77, 0xaf, 0x51, 0xca, 0x95, 0x59, 0x2d, 0x0f, 0xcb, 0x23, 0xfe, - 0x59, 0x77, 0x2b, 0xf7, 0xf3, 0x5e, 0xb3, 0x4e, 0xdd, 0xdc, 0x96, 0xed, 0xb5, 0x5b, 0xe9, 0x0b, - 0x82, 0x76, 0x14, 0x8f, 0xe8, 0xa7, 0xed, 0x9e, 0xe2, 0x64, 0x5b, 0x16, 0x52, 0x70, 0xd8, 0x8e, - 0xe9, 0xb9, 0x5a, 0x73, 0x93, 0xda, 0xac, 0x06, 0x42, 0xf0, 0x22, 0x3a, 0x5e, 0xf1, 0xbf, 0x81, - 0xd2, 0x99, 0x76, 0x2b, 0x7d, 0x52, 0x14, 0xe1, 0xc3, 0x44, 0x17, 0xd3, 0xc4, 0x96, 0xf5, 0x46, - 0x01, 0x41, 0xef, 0x26, 0x1a, 0xad, 0xf3, 0x19, 0xd8, 0x94, 0xe9, 0x9c, 0x10, 0x93, 0xf3, 0xb7, - 0xbc, 0xb3, 0x1f, 0x37, 0x99, 0x69, 0x6b, 0x67, 0x43, 0x3b, 0xc1, 0x53, 0xfc, 0x9d, 0x10, 0x3f, - 0xe6, 0xd1, 0x5c, 0xb4, 0xde, 0x86, 0x65, 0x41, 0xc9, 0x60, 0x17, 0x1e, 0x20, 0xd2, 0x2f, 0x08, - 0x08, 0xdd, 0x46, 0x63, 0x02, 0xd4, 0x5f, 0xf7, 0xa3, 0xfd, 0x19, 0x4d, 0xc2, 0xf9, 0x38, 0x1d, - 0x66, 0xe5, 0x12, 0x7d, 0xac, 0xf3, 0x0b, 0x2d, 0x47, 0x4b, 0xbe, 0xee, 0x77, 0x97, 0xeb, 0x99, - 0x65, 0x57, 0x6b, 0xea, 0xac, 0xe1, 0xd1, 0xd0, 0xda, 0x3a, 0xfe, 0x37, 0x2f, 0x7b, 0x2c, 0xbc, - 0xb6, 0x7c, 0x98, 0xe8, 0x62, 0x9a, 0x7c, 0xae, 0xa0, 0x95, 0x01, 0x40, 0x41, 0x4e, 0x05, 0x21, - 0xb7, 0x33, 0x09, 0x6b, 0xbc, 0x92, 0x7c, 0xf0, 0x79, 0x72, 0x08, 0x6d, 0x1a, 0x14, 0x9e, 0x15, - 0x4c, 0xba, 0x50, 0x44, 0x0f, 0xe1, 0x92, 0x35, 0x99, 0xd2, 0x86, 0x65, 0x45, 0xc0, 0x82, 0x7d, - 0xf8, 0x42, 0x41, 0xab, 0x83, 0x44, 0x27, 0x28, 0x38, 0xfa, 0x4f, 0x29, 0xb8, 0xcb, 0xee, 0x53, - 0xbb, 0x60, 0x98, 0xce, 0x86, 0x53, 0xe2, 0xa8, 0x1d, 0x05, 0x1f, 0xc7, 0x28, 0x88, 0x8b, 0x06, - 0x05, 0x6f, 0xa3, 0x51, 0xbe, 0x75, 0x01, 0xfb, 0x4b, 0xc9, 0xec, 0x65, 0x94, 0xe8, 0x25, 0x24, - 0x90, 0x88, 0x0e, 0x90, 0x24, 0x8b, 0xe6, 0xa5, 0xc5, 0xac, 0xd4, 0x4c, 0x7b, 0xa3, 0x5c, 0x66, - 0x0d, 0xdb, 0x0b, 0x28, 0x53, 0xb4, 0xd0, 0x3f, 0x0c, 0xb8, 0xde, 0x40, 0xa7, 0x0c, 0x7f, 0xbc, - 0x68, 0x88, 0x09, 0xe8, 0xf4, 0xa9, 0x76, 0x2b, 0x7d, 0x5e, 0x10, 0xe8, 0x99, 0x26, 0xfa, 0x49, - 0x23, 0x04, 0x43, 0x56, 0xd0, 0x52, 0xb4, 0xcc, 0x26, 0xdd, 0xa5, 0x16, 0xab, 0x53, 0x27, 0xc2, - 0xa8, 0x21, 0xf7, 0x86, 0x1c, 0x0a, 0xac, 0xb6, 0xd0, 0xd9, 0x4a, 0x30, 0x17, 0x61, 0x36, 0xdb, - 0x6e, 0xa5, 0xa7, 0x82, 0x3b, 0x28, 0x12, 0x42, 0xf4, 0x33, 0x95, 0x08, 0x24, 0x59, 0x90, 0x6f, - 0x81, 0x02, 0x63, 0xd6, 0x9b, 0xd4, 0xac, 0xde, 0xeb, 0xde, 0x15, 0x9f, 0x28, 0xf2, 0xb2, 0xf6, - 0x84, 0x01, 0x31, 0x8a, 0x4e, 0xd6, 0x19, 0xb3, 0x8a, 0xef, 0x89, 0x71, 0x68, 0xb0, 0x6c, 0x9f, - 0x97, 0xa5, 0x0b, 0xa2, 0xcd, 0xc0, 0xce, 0x9e, 0x83, 0xeb, 0x23, 0x04, 0x44, 0xf4, 0xf1, 0x7a, - 0x37, 0x92, 0xe4, 0xd0, 0xa5, 0x28, 0x9b, 0x57, 0x8d, 0x3d, 0x1f, 0xab, 0xc0, 0x4c, 0xdb, 0x73, - 0x0b, 0xd4, 0xd1, 0x2c, 0x56, 0xbe, 0x1f, 0xd0, 0xff, 0x54, 0x41, 0xeb, 0x03, 0x26, 0x80, 0x90, - 0x77, 0xd0, 0x74, 0xcd, 0xd8, 0x2b, 0x72, 0x0e, 0x75, 0x1e, 0x52, 0xf4, 0x17, 0xb2, 0xe4, 0x07, - 0x71, 0x55, 0xc7, 0xb4, 0x85, 0x76, 0x2b, 0x9d, 0x11, 0x54, 0x13, 0x43, 0x89, 0x3e, 0x51, 0x8b, - 0xab, 0x13, 0xd7, 0x5f, 0x51, 0x42, 0x77, 0xf7, 0x02, 0xfa, 0x1f, 0xc4, 0xf4, 0x57, 0x5c, 0x34, - 0x70, 0x7f, 0x03, 0x4d, 0xc6, 0x11, 0xf2, 0xf6, 0x80, 0xf8, 0x5c, 0xbb, 0x95, 0xbe, 0x98, 0x4c, - 0xdc, 0xdb, 0x23, 0x3a, 0xae, 0x49, 0xf0, 0x71, 0x8f, 0x8a, 0x66, 0xb8, 0x94, 0xbf, 0x5f, 0x9d, - 0x83, 0xf2, 0xa1, 0x22, 0x9f, 0xa7, 0x70, 0x14, 0x50, 0x7c, 0x17, 0x8d, 0xfb, 0xcf, 0x47, 0x91, - 0x3f, 0x8f, 0xc1, 0x3d, 0x30, 0x9f, 0x7c, 0x4c, 0x3a, 0x10, 0x9a, 0x0a, 0x87, 0x04, 0x0b, 0x01, - 0x21, 0x14, 0xa2, 0xa3, 0x52, 0xa7, 0x12, 0xc9, 0xa0, 0x54, 0x94, 0xc7, 0xcb, 0xb6, 0x51, 0xb2, - 0x68, 0x25, 0xa0, 0xba, 0x8d, 0xd2, 0x89, 0x11, 0x40, 0xf3, 0x12, 0x1a, 0xa3, 0x62, 0x88, 0x2f, - 0xdd, 0xbf, 0x34, 0xdc, 0x7d, 0xdd, 0x60, 0x82, 0xe8, 0x41, 0x88, 0xdf, 0x24, 0x33, 0x71, 0x4d, - 0x12, 0xbc, 0x68, 0xd7, 0x10, 0xea, 0xd2, 0x85, 0x76, 0x9d, 0xe8, 0x5e, 0xc5, 0xdd, 0x39, 0xa2, - 0x9f, 0xe8, 0x28, 0xc1, 0xd7, 0xd1, 0x38, 0xf3, 0xee, 0x51, 0x07, 0xd2, 0x8e, 0xf0, 0xb4, 0xc9, - 0xee, 0x0a, 0x84, 0x26, 0x89, 0x8e, 0xf8, 0x17, 0x4f, 0x24, 0xb7, 0xd1, 0x6c, 0x3c, 0x1b, 0x10, - 0xb7, 0x86, 0xc6, 0xf8, 0xd6, 0x9b, 0x15, 0x38, 0x17, 0x21, 0x71, 0x30, 0xe1, 0x3b, 0x0a, 0xc6, - 0xac, 0xad, 0xca, 0x95, 0x6f, 0x2e, 0xa0, 0xe3, 0x1c, 0x0d, 0x7f, 0xa4, 0xa0, 0x51, 0xe1, 0x07, - 0x71, 0x9f, 0x8b, 0x5b, 0xb6, 0xa1, 0xea, 0xfa, 0x80, 0xd1, 0x82, 0x1e, 0xc9, 0xbc, 0xff, 0xf3, - 0x9f, 0x5f, 0x1e, 0x51, 0xf1, 0x54, 0x5e, 0x72, 0xc7, 0xc2, 0x6f, 0xe2, 0x1f, 0x15, 0x34, 0x9d, - 0xe8, 0x20, 0xf1, 0x8b, 0xfb, 0x94, 0xdb, 0xcf, 0xa5, 0xaa, 0x2f, 0x1d, 0x1c, 0x00, 0x24, 0xac, - 0x72, 0x09, 0x0b, 0x98, 0xc8, 0x12, 0xa2, 0x26, 0x34, 0x2a, 0xa6, 0xd7, 0x1e, 0x0e, 0x23, 0x26, - 0xd6, 0xa9, 0x0e, 0x23, 0x26, 0xde, 0x99, 0xf6, 0x13, 0x03, 0xf6, 0xae, 0x58, 0x6a, 0x8a, 0x73, - 0x88, 0xbf, 0x53, 0xd0, 0x44, 0xac, 0xad, 0xc4, 0xcf, 0x0d, 0xce, 0x43, 0x72, 0xac, 0xea, 0xf3, - 0x07, 0x4b, 0x06, 0x01, 0x59, 0x2e, 0x20, 0x8d, 0x2f, 0xca, 0x02, 0x0c, 0xcb, 0x2a, 0x82, 0x08, - 0xfc, 0x8b, 0x82, 0x66, 0xfb, 0x59, 0x49, 0xac, 0x0d, 0xce, 0x22, 0xc9, 0xdc, 0xaa, 0x37, 0x0f, - 0x85, 0x01, 0x82, 0xd6, 0xb9, 0xa0, 0x25, 0x9c, 0x95, 0x05, 0x75, 0x9d, 0x9c, 0xbf, 0x29, 0xdc, - 0x1a, 0xe1, 0xc7, 0x0a, 0xba, 0xd8, 0xd7, 0x62, 0xe2, 0x9b, 0x43, 0xad, 0x6f, 0xbc, 0x9d, 0x55, - 0x37, 0x0f, 0x07, 0x02, 0xda, 0x72, 0x5c, 0xdb, 0x32, 0x5e, 0x8c, 0xdf, 0x2c, 0xae, 0xa8, 0xd8, - 0x55, 0x89, 0x7f, 0xeb, 0x15, 0x27, 0xfb, 0xc6, 0x61, 0xc4, 0x25, 0x3a, 0xdd, 0x61, 0xc4, 0x25, - 0x1b, 0x60, 0x92, 0xe7, 0xe2, 0x56, 0xf0, 0x92, 0x2c, 0xce, 0xf3, 0xb3, 0x8a, 0x75, 0xc3, 0x74, - 0x8a, 0x86, 0x53, 0x12, 0x3a, 0x5d, 0xfc, 0x83, 0x82, 0x2e, 0x24, 0x38, 0x55, 0x7c, 0x63, 0x88, - 0xf5, 0x96, 0x8d, 0xb0, 0xfa, 0xc2, 0x41, 0xd3, 0x41, 0xcb, 0x12, 0xd7, 0x32, 0x87, 0xd3, 0x31, - 0x1b, 0x15, 0x76, 0xc6, 0xf8, 0x27, 0x05, 0xcd, 0xf4, 0xf1, 0xb6, 0x78, 0x63, 0x70, 0x22, 0x09, - 0x16, 0x5a, 0xd5, 0x0e, 0x03, 0x01, 0x7a, 0xd6, 0xb8, 0x9e, 0x2c, 0x9e, 0x97, 0xf5, 0x48, 0x7e, - 0x1a, 0x7f, 0xaf, 0xa0, 0xc9, 0x78, 0x47, 0x8c, 0x87, 0xb8, 0xab, 0x64, 0xbf, 0xad, 0xde, 0x38, - 0x60, 0x36, 0x88, 0x58, 0xe4, 0x22, 0x32, 0x38, 0x15, 0x73, 0x57, 0x87, 0x5c, 0x35, 0xfe, 0xbd, - 0xb7, 0x6b, 0x64, 0x4f, 0x39, 0x4c, 0xd7, 0x24, 0xfa, 0xd7, 0x61, 0xba, 0x26, 0xd9, 0xd6, 0x92, - 0xff, 0x70, 0x51, 0xab, 0x78, 0x59, 0x16, 0x15, 0x6f, 0x63, 0xf1, 0x5f, 0x0a, 0xca, 0xec, 0xe7, - 0xf8, 0xf1, 0x2b, 0x07, 0x27, 0x17, 0xfe, 0x1b, 0x43, 0xbd, 0x75, 0x68, 0x1c, 0xd0, 0x79, 0x95, - 0xeb, 0x5c, 0xc7, 0x6b, 0x83, 0xe9, 0xe4, 0x7f, 0x67, 0x44, 0x5f, 0xdc, 0xae, 0xe5, 0x1e, 0xe6, - 0xc5, 0x95, 0xec, 0xfc, 0x30, 0x2f, 0xae, 0xec, 0xf2, 0xfb, 0xbd, 0xb8, 0x21, 0xdf, 0x8e, 0xbf, - 0x55, 0x10, 0x96, 0x4d, 0x38, 0xfe, 0xff, 0xe0, 0xb5, 0x7b, 0x9d, 0xbd, 0xfa, 0xcc, 0x01, 0x32, - 0x81, 0xf2, 0x1c, 0xa7, 0x3c, 0x83, 0xa7, 0x65, 0xca, 0x60, 0xf3, 0xf1, 0xd7, 0x0a, 0xfa, 0x77, - 0xa4, 0xff, 0xf0, 0x7f, 0x87, 0xeb, 0xd7, 0x80, 0xe8, 0xff, 0x86, 0x4d, 0x03, 0x96, 0x29, 0xce, - 0x72, 0x0a, 0x4f, 0xc6, 0xf7, 0xb7, 0x76, 0xe7, 0xe1, 0x93, 0x94, 0xf2, 0xe8, 0x49, 0x4a, 0xf9, - 0xe3, 0x49, 0x4a, 0xf9, 0xec, 0x69, 0x6a, 0xe4, 0xd1, 0xd3, 0xd4, 0xc8, 0xaf, 0x4f, 0x53, 0x23, - 0x6f, 0x5d, 0x0b, 0xfd, 0x6f, 0x29, 0xe4, 0xae, 0x5b, 0x46, 0xc9, 0xed, 0x00, 0xed, 0x5e, 0xbe, - 0x9e, 0xdf, 0x0b, 0xbd, 0x47, 0xcd, 0x3a, 0x75, 0x4b, 0xa3, 0xfc, 0xfb, 0xea, 0xdf, 0x01, 0x00, - 0x00, 0xff, 0xff, 0xea, 0xd0, 0xea, 0xdc, 0x70, 0x17, 0x00, 0x00, + // 1532 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xdb, 0x6f, 0x1b, 0xc5, + 0x17, 0xce, 0xf6, 0x92, 0xfc, 0x3a, 0xbd, 0xfc, 0x9a, 0xa1, 0x49, 0x93, 0x6d, 0x6a, 0x3b, 0x93, + 0x7b, 0xd2, 0xd8, 0xf4, 0x02, 0xe5, 0x56, 0x20, 0xdb, 0x40, 0x15, 0x55, 0x34, 0x66, 0x09, 0x2f, + 0x20, 0x61, 0xd6, 0xf6, 0x24, 0x5d, 0x75, 0xbd, 0xe3, 0xee, 0xae, 0xa3, 0xf8, 0x15, 0x24, 0x10, + 0x12, 0x12, 0xb7, 0x67, 0x04, 0x7f, 0x00, 0xff, 0x40, 0x1f, 0x79, 0xab, 0xe0, 0xa5, 0x08, 0x09, + 0xa1, 0x82, 0x2c, 0xd4, 0xf2, 0xc0, 0xb3, 0xff, 0x02, 0xb4, 0x33, 0x67, 0xed, 0xf5, 0xce, 0xae, + 0xe3, 0x4d, 0x24, 0x9e, 0x62, 0xef, 0x39, 0xe7, 0x3b, 0xdf, 0x37, 0x67, 0x67, 0xe6, 0x73, 0xd0, + 0x2c, 0x73, 0x6b, 0xcc, 0x35, 0xdd, 0x42, 0xdd, 0x61, 0x1e, 0x73, 0xe8, 0x6e, 0x61, 0xf7, 0x72, + 0x99, 0x7a, 0xc6, 0xe5, 0xc2, 0xfd, 0x06, 0x75, 0x9a, 0x79, 0xfe, 0x18, 0x4f, 0x40, 0x56, 0x3e, + 0xc8, 0xca, 0x43, 0x96, 0x7a, 0x6e, 0x87, 0xed, 0x30, 0xfe, 0xb4, 0xe0, 0x7f, 0x12, 0x09, 0xea, + 0xd4, 0x0e, 0x63, 0x3b, 0x16, 0x2d, 0x18, 0x75, 0xb3, 0x60, 0xd8, 0x36, 0xf3, 0x0c, 0xcf, 0x64, + 0x36, 0x94, 0xab, 0xcb, 0x15, 0x0e, 0x57, 0x28, 0x1b, 0x2e, 0x15, 0x6d, 0x3a, 0x4d, 0xeb, 0xc6, + 0x8e, 0x69, 0xf3, 0x64, 0xc8, 0x9d, 0x4b, 0xe4, 0x57, 0x37, 0x1c, 0xa3, 0x16, 0x40, 0x2e, 0x24, + 0xa7, 0x05, 0x8c, 0x45, 0x62, 0x26, 0xdc, 0x3b, 0xc8, 0xa9, 0x30, 0x13, 0xfa, 0x91, 0x73, 0x08, + 0xbf, 0xed, 0x33, 0x2a, 0x72, 0x74, 0x9d, 0xde, 0x6f, 0x50, 0xd7, 0x23, 0xdb, 0xe8, 0x99, 0x9e, + 0xa7, 0x6e, 0x9d, 0xd9, 0x2e, 0xc5, 0x9b, 0x68, 0x58, 0xb0, 0x98, 0x50, 0x72, 0xca, 0xe2, 0xc9, + 0x2b, 0xb9, 0x7c, 0xd2, 0x3a, 0xe5, 0x45, 0xa5, 0x36, 0xf6, 0xb0, 0x95, 0x1d, 0x6a, 0xb7, 0xb2, + 0xa7, 0x9b, 0x46, 0xcd, 0x7a, 0x89, 0x88, 0x6a, 0xa2, 0x03, 0x0c, 0x59, 0x40, 0x73, 0xbc, 0xcf, + 0x2d, 0xea, 0x15, 0x7d, 0x04, 0x9d, 0xee, 0xde, 0x69, 0xd4, 0xca, 0xd4, 0xd9, 0xdc, 0xde, 0x72, + 0x8c, 0x2a, 0xed, 0x10, 0xfa, 0x56, 0x41, 0xf3, 0xfb, 0x65, 0x02, 0x49, 0x17, 0x9d, 0xb5, 0x79, + 0xa4, 0xc4, 0xb6, 0x4b, 0x1e, 0x8f, 0x71, 0xba, 0x27, 0xb4, 0x0d, 0x9f, 0xcc, 0xe3, 0x56, 0x76, + 0x7e, 0xc7, 0xf4, 0xee, 0x36, 0xca, 0xf9, 0x0a, 0xab, 0x15, 0x60, 0x79, 0xc4, 0x9f, 0x55, 0xb7, + 0x7a, 0xaf, 0xe0, 0x35, 0xeb, 0xd4, 0xcd, 0x6f, 0xd8, 0x5e, 0xbb, 0x95, 0x3d, 0x2f, 0x68, 0x47, + 0xf1, 0x88, 0x7e, 0xc6, 0xee, 0x69, 0x4e, 0x36, 0x65, 0x21, 0x45, 0x87, 0x6d, 0x9b, 0x9e, 0xab, + 0x35, 0xd7, 0xa9, 0xcd, 0x6a, 0x20, 0x04, 0xcf, 0xa3, 0xe3, 0x55, 0xff, 0x3b, 0x50, 0x3a, 0xdb, + 0x6e, 0x65, 0x4f, 0x89, 0x26, 0xfc, 0x31, 0xd1, 0x45, 0x98, 0xd8, 0xb2, 0xde, 0x28, 0x20, 0xe8, + 0x5d, 0x47, 0xc3, 0x75, 0x1e, 0x81, 0xa1, 0x4c, 0xe6, 0x85, 0x98, 0xbc, 0x3f, 0xf2, 0xce, 0x3c, + 0x6e, 0x32, 0xd3, 0xd6, 0x46, 0x43, 0x93, 0xe0, 0x25, 0xfe, 0x24, 0xc4, 0x87, 0x19, 0x34, 0x1d, + 0xed, 0xb7, 0x66, 0x59, 0xd0, 0x32, 0x98, 0xc2, 0x7d, 0x44, 0xfa, 0x25, 0x01, 0xa1, 0xdb, 0x68, + 0x44, 0x80, 0xfa, 0xeb, 0x7e, 0xb4, 0x3f, 0xa3, 0x71, 0x78, 0x3f, 0xce, 0x84, 0x59, 0xb9, 0x44, + 0x1f, 0xe9, 0x7c, 0x42, 0x8b, 0xd1, 0x96, 0xef, 0xf8, 0xbb, 0xcb, 0xf5, 0xcc, 0x8a, 0xab, 0x35, + 0x75, 0xd6, 0xf0, 0x68, 0x68, 0x6d, 0x1d, 0xff, 0x3b, 0x6f, 0x7b, 0x2c, 0xbc, 0xb6, 0xfc, 0x31, + 0xd1, 0x45, 0x98, 0x7c, 0xa5, 0xa0, 0xa5, 0x01, 0x40, 0x41, 0x4e, 0x15, 0x21, 0xb7, 0x13, 0x84, + 0x35, 0x5e, 0x4a, 0x7e, 0xf1, 0x79, 0x71, 0x08, 0x6d, 0x12, 0x14, 0x8e, 0x0a, 0x26, 0x5d, 0x28, + 0xa2, 0x87, 0x70, 0xc9, 0x8a, 0x4c, 0x69, 0xcd, 0xb2, 0x22, 0x60, 0xc1, 0x1c, 0xbe, 0x56, 0xd0, + 0xf2, 0x20, 0xd9, 0x09, 0x0a, 0x8e, 0xfe, 0x57, 0x0a, 0xb6, 0xd8, 0x3d, 0x6a, 0x17, 0x0d, 0xd3, + 0x59, 0x73, 0xca, 0x1c, 0xb5, 0xa3, 0xe0, 0xb3, 0x18, 0x05, 0x71, 0xd9, 0xa0, 0xe0, 0x7d, 0x34, + 0xcc, 0x47, 0x17, 0xb0, 0xbf, 0x94, 0xcc, 0x5e, 0x46, 0x89, 0x1e, 0x42, 0x02, 0x89, 0xe8, 0x00, + 0x49, 0xe6, 0xd0, 0x8c, 0xb4, 0x98, 0xd5, 0x9a, 0x69, 0xaf, 0x55, 0x2a, 0xac, 0x61, 0x7b, 0x01, + 0x65, 0x8a, 0x66, 0xfb, 0xa7, 0x01, 0xd7, 0x1b, 0xe8, 0xb4, 0xe1, 0x3f, 0x2f, 0x19, 0x22, 0x00, + 0x3b, 0x7d, 0xa2, 0xdd, 0xca, 0x9e, 0x13, 0x04, 0x7a, 0xc2, 0x44, 0x3f, 0x65, 0x84, 0x60, 0xc8, + 0x12, 0x5a, 0x88, 0xb6, 0x59, 0xa7, 0xbb, 0xd4, 0x62, 0x75, 0xea, 0x44, 0x18, 0x35, 0xe4, 0xbd, + 0x21, 0xa7, 0x02, 0xab, 0x0d, 0x34, 0x5a, 0x0d, 0x62, 0x11, 0x66, 0x53, 0xed, 0x56, 0x76, 0x22, + 0x38, 0x83, 0x22, 0x29, 0x44, 0x3f, 0x5b, 0x8d, 0x40, 0xc6, 0x1d, 0xda, 0x1b, 0xf6, 0x36, 0xd3, + 0x9a, 0x45, 0xc6, 0xac, 0xad, 0x66, 0x3d, 0xd8, 0x8f, 0xe4, 0xbb, 0x98, 0x43, 0x3b, 0x9a, 0x09, + 0xf4, 0x1a, 0x68, 0xd4, 0xb4, 0xb7, 0x59, 0xa9, 0xdc, 0x2c, 0xd5, 0x19, 0xb3, 0x4a, 0xfe, 0x21, + 0x0c, 0x7b, 0x6d, 0x31, 0x79, 0xd6, 0xbd, 0x60, 0x5a, 0x0e, 0xe6, 0x0c, 0x62, 0x24, 0x40, 0xa2, + 0x9f, 0x31, 0x7b, 0x2a, 0x48, 0x1e, 0x5d, 0x8a, 0x12, 0x7c, 0xcb, 0xd8, 0xf3, 0xc3, 0x45, 0x66, + 0xda, 0x9e, 0x5b, 0xa4, 0x8e, 0x66, 0xb1, 0xca, 0xbd, 0x40, 0xd1, 0x17, 0x0a, 0x5a, 0x1d, 0xb0, + 0x00, 0x84, 0x7d, 0x80, 0x26, 0x6b, 0xc6, 0x9e, 0xe0, 0x50, 0xe7, 0x29, 0x25, 0x7f, 0x79, 0xcb, + 0x7e, 0x12, 0x17, 0x78, 0x4c, 0x9b, 0x6d, 0xb7, 0xb2, 0x39, 0x41, 0x39, 0x31, 0x95, 0xe8, 0x63, + 0xb5, 0xb8, 0x3e, 0x71, 0xbb, 0x2e, 0x4a, 0x68, 0x6b, 0x2f, 0xa0, 0xff, 0x71, 0xcc, 0xae, 0x8b, + 0xcb, 0x06, 0xee, 0xef, 0xa2, 0xf1, 0x38, 0x42, 0xde, 0x1e, 0x10, 0x9f, 0x6e, 0xb7, 0xb2, 0x17, + 0x93, 0x89, 0x7b, 0x7b, 0x44, 0xc7, 0x35, 0x09, 0x3e, 0xee, 0xaa, 0xd1, 0x0c, 0x97, 0xf2, 0x5b, + 0xad, 0x73, 0x40, 0x7c, 0xa2, 0xc8, 0x77, 0x4d, 0x38, 0x0b, 0x28, 0x7e, 0x88, 0x4e, 0xfa, 0x97, + 0x4a, 0x89, 0x5f, 0x9a, 0xc1, 0xe9, 0x30, 0x93, 0xfc, 0xc6, 0x74, 0x20, 0x34, 0x15, 0x5e, 0x16, + 0x2c, 0x04, 0x84, 0x50, 0x88, 0x8e, 0xca, 0x9d, 0x4e, 0x24, 0x87, 0x32, 0x51, 0x1e, 0x6f, 0xd8, + 0x46, 0xd9, 0xa2, 0xd5, 0x80, 0xea, 0x26, 0xca, 0x26, 0x66, 0x00, 0xcd, 0x4b, 0x68, 0x84, 0x8a, + 0x47, 0x7c, 0xe9, 0xfe, 0xa7, 0xe1, 0xee, 0x9d, 0x07, 0x01, 0xa2, 0x07, 0x29, 0xe4, 0x73, 0x05, + 0x5d, 0x90, 0x2e, 0x7f, 0xc6, 0xac, 0xe0, 0x9e, 0xbb, 0x86, 0x50, 0x97, 0x2e, 0x6c, 0xe2, 0xb1, + 0xee, 0x01, 0xdd, 0x8d, 0x11, 0xfd, 0x44, 0x47, 0x09, 0xbe, 0x8e, 0x4e, 0x32, 0xef, 0x2e, 0x75, + 0xa0, 0xec, 0x08, 0x2f, 0x1b, 0xef, 0xae, 0x40, 0x28, 0x48, 0x74, 0xc4, 0xbf, 0xf1, 0x42, 0x72, + 0x1b, 0x4d, 0xc5, 0xb3, 0x01, 0x71, 0x2b, 0x68, 0x84, 0x8f, 0xde, 0xac, 0xc2, 0x7b, 0x11, 0x12, + 0x07, 0x01, 0xdf, 0x67, 0x30, 0x66, 0x6d, 0x54, 0xaf, 0x3c, 0x38, 0x8f, 0x8e, 0x73, 0x34, 0xfc, + 0xa9, 0x82, 0x86, 0x85, 0x4b, 0xc4, 0x7d, 0x8e, 0x73, 0xd9, 0x9c, 0xaa, 0xab, 0x03, 0x66, 0x0b, + 0x7a, 0x24, 0xf7, 0xd1, 0xaf, 0x7f, 0x7f, 0x73, 0x44, 0xc5, 0x13, 0x05, 0xc9, 0x33, 0x0b, 0x17, + 0x8a, 0x7f, 0x52, 0xd0, 0x64, 0xa2, 0xaf, 0xc4, 0xaf, 0xed, 0xd3, 0x6e, 0x3f, 0xef, 0xaa, 0xbe, + 0x7e, 0x70, 0x00, 0x90, 0xb0, 0xcc, 0x25, 0xcc, 0x62, 0x22, 0x4b, 0x88, 0x5a, 0xd3, 0xa8, 0x98, + 0x5e, 0xd3, 0x98, 0x46, 0x4c, 0xac, 0x7f, 0x4d, 0x23, 0x26, 0xde, 0xaf, 0xf6, 0x13, 0x03, 0xa6, + 0xcf, 0x3f, 0xb4, 0xf9, 0x7b, 0x88, 0x1f, 0x28, 0x68, 0x2c, 0xd6, 0x6c, 0xe2, 0x97, 0x07, 0xe7, + 0x21, 0xf9, 0x58, 0xf5, 0x95, 0x83, 0x15, 0x83, 0x80, 0x39, 0x2e, 0x20, 0x8b, 0x2f, 0xca, 0x02, + 0x0c, 0xcb, 0x2a, 0x81, 0x08, 0xfc, 0x9b, 0x82, 0xa6, 0xfa, 0x19, 0x4c, 0xac, 0x0d, 0xce, 0x22, + 0xc9, 0xf2, 0xaa, 0x37, 0x0f, 0x85, 0x01, 0x82, 0x56, 0xb9, 0xa0, 0x05, 0x3c, 0x27, 0x0b, 0xea, + 0xfa, 0x3b, 0x7f, 0x28, 0xdc, 0x30, 0xe1, 0xc7, 0x0a, 0xba, 0xd8, 0xd7, 0x78, 0xe2, 0x9b, 0xa9, + 0xd6, 0x37, 0xde, 0xe4, 0xaa, 0xeb, 0x87, 0x03, 0x01, 0x6d, 0x79, 0xae, 0x6d, 0x11, 0xcf, 0xc7, + 0x0f, 0x8b, 0x2b, 0x2a, 0x75, 0x55, 0xe2, 0x3f, 0x7a, 0xc5, 0xc9, 0x6e, 0x32, 0x8d, 0xb8, 0x44, + 0xff, 0x9b, 0x46, 0x5c, 0xb2, 0x2d, 0x26, 0x05, 0x2e, 0x6e, 0x09, 0x2f, 0xc8, 0xe2, 0x3c, 0xbf, + 0xaa, 0x54, 0x37, 0x4c, 0xa7, 0x64, 0x38, 0x65, 0xa1, 0xd3, 0xc5, 0x3f, 0x2a, 0xe8, 0x7c, 0x82, + 0x7f, 0xc5, 0x37, 0x52, 0xac, 0xb7, 0x6c, 0x8f, 0xd5, 0x57, 0x0f, 0x5a, 0x0e, 0x5a, 0x16, 0xb8, + 0x96, 0x69, 0x9c, 0x8d, 0x19, 0x54, 0xd8, 0x2f, 0xe3, 0x5f, 0x14, 0x74, 0xa1, 0x8f, 0xe3, 0xc5, + 0x6b, 0x83, 0x13, 0x49, 0x30, 0xd6, 0xaa, 0x76, 0x18, 0x08, 0xd0, 0xb3, 0xc2, 0xf5, 0xcc, 0xe1, + 0x19, 0x59, 0x8f, 0xe4, 0xb2, 0xf1, 0xcf, 0xbd, 0x87, 0x76, 0xaf, 0xaf, 0x4d, 0x73, 0x68, 0xc7, + 0x1a, 0xf1, 0x34, 0x87, 0x76, 0xbc, 0x3f, 0xef, 0xa7, 0x46, 0xb2, 0xd9, 0xf8, 0xcf, 0xde, 0x3d, + 0x24, 0x3b, 0xcc, 0x34, 0x7b, 0x28, 0xd1, 0xcd, 0xa6, 0xd9, 0x43, 0xc9, 0x26, 0x97, 0x3c, 0xcb, + 0x95, 0x2d, 0xe3, 0x45, 0x59, 0x59, 0xbc, 0xa9, 0xc5, 0xff, 0x28, 0x28, 0xb7, 0x9f, 0xff, 0xc7, + 0x6f, 0x1e, 0x9c, 0x5c, 0xf8, 0x17, 0x87, 0x7a, 0xeb, 0xd0, 0x38, 0xa0, 0xf3, 0x2a, 0xd7, 0xb9, + 0x8a, 0x57, 0x06, 0xd3, 0xc9, 0x7f, 0x75, 0x44, 0xef, 0xdf, 0xae, 0x01, 0x4f, 0x73, 0xff, 0x4a, + 0xe6, 0x3e, 0xcd, 0xfd, 0x2b, 0x7b, 0xfe, 0x7e, 0xf7, 0x6f, 0xc8, 0xc5, 0xe3, 0x1f, 0x14, 0x84, + 0x65, 0x4b, 0x8e, 0x5f, 0x18, 0xbc, 0x77, 0xaf, 0xcf, 0x57, 0x5f, 0x3c, 0x40, 0x25, 0x50, 0x9e, + 0xe6, 0x94, 0x2f, 0xe0, 0x49, 0x99, 0x32, 0x98, 0x7e, 0xfc, 0xbd, 0x82, 0xfe, 0x1f, 0x71, 0xd8, + 0xf8, 0xb9, 0x14, 0x66, 0xab, 0xfb, 0xfb, 0x40, 0x7d, 0x3e, 0x6d, 0x19, 0xb0, 0xcc, 0x70, 0x96, + 0x13, 0x78, 0x3c, 0xc6, 0x99, 0x31, 0x66, 0x69, 0x77, 0x1e, 0x3e, 0xc9, 0x28, 0x8f, 0x9e, 0x64, + 0x94, 0xbf, 0x9e, 0x64, 0x94, 0x2f, 0x9f, 0x66, 0x86, 0x1e, 0x3d, 0xcd, 0x0c, 0xfd, 0xfe, 0x34, + 0x33, 0xf4, 0xde, 0xb5, 0xd0, 0x7f, 0x54, 0xa1, 0x76, 0xd5, 0x32, 0xca, 0x6e, 0x07, 0x68, 0xf7, + 0xf2, 0xf5, 0xc2, 0x5e, 0xe8, 0x76, 0x6a, 0xd6, 0xa9, 0x5b, 0x1e, 0xe6, 0xdf, 0xaf, 0xfe, 0x1b, + 0x00, 0x00, 0xff, 0xff, 0xf0, 0x78, 0x37, 0xe1, 0x94, 0x17, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1558,9 +1561,9 @@ type QueryClient interface { GetProtoRevAdminAccount(ctx context.Context, in *QueryGetProtoRevAdminAccountRequest, opts ...grpc.CallOption) (*QueryGetProtoRevAdminAccountResponse, error) // GetProtoRevDeveloperAccount queries the developer account of the module GetProtoRevDeveloperAccount(ctx context.Context, in *QueryGetProtoRevDeveloperAccountRequest, opts ...grpc.CallOption) (*QueryGetProtoRevDeveloperAccountResponse, error) - // GetProtoRevPoolWeights queries the weights of each pool type currently - // being used by the module - GetProtoRevPoolWeights(ctx context.Context, in *QueryGetProtoRevPoolWeightsRequest, opts ...grpc.CallOption) (*QueryGetProtoRevPoolWeightsResponse, error) + // GetProtoRevInfoByPoolType queries pool type information that is currently + // being utilized by the module + GetProtoRevInfoByPoolType(ctx context.Context, in *QueryGetProtoRevInfoByPoolTypeRequest, opts ...grpc.CallOption) (*QueryGetProtoRevInfoByPoolTypeResponse, error) // GetProtoRevMaxPoolPointsPerTx queries the maximum number of pool points // that can be consumed per transaction GetProtoRevMaxPoolPointsPerTx(ctx context.Context, in *QueryGetProtoRevMaxPoolPointsPerTxRequest, opts ...grpc.CallOption) (*QueryGetProtoRevMaxPoolPointsPerTxResponse, error) @@ -1666,9 +1669,9 @@ func (c *queryClient) GetProtoRevDeveloperAccount(ctx context.Context, in *Query return out, nil } -func (c *queryClient) GetProtoRevPoolWeights(ctx context.Context, in *QueryGetProtoRevPoolWeightsRequest, opts ...grpc.CallOption) (*QueryGetProtoRevPoolWeightsResponse, error) { - out := new(QueryGetProtoRevPoolWeightsResponse) - err := c.cc.Invoke(ctx, "/osmosis.protorev.v1beta1.Query/GetProtoRevPoolWeights", in, out, opts...) +func (c *queryClient) GetProtoRevInfoByPoolType(ctx context.Context, in *QueryGetProtoRevInfoByPoolTypeRequest, opts ...grpc.CallOption) (*QueryGetProtoRevInfoByPoolTypeResponse, error) { + out := new(QueryGetProtoRevInfoByPoolTypeResponse) + err := c.cc.Invoke(ctx, "/osmosis.protorev.v1beta1.Query/GetProtoRevInfoByPoolType", in, out, opts...) if err != nil { return nil, err } @@ -1745,9 +1748,9 @@ type QueryServer interface { GetProtoRevAdminAccount(context.Context, *QueryGetProtoRevAdminAccountRequest) (*QueryGetProtoRevAdminAccountResponse, error) // GetProtoRevDeveloperAccount queries the developer account of the module GetProtoRevDeveloperAccount(context.Context, *QueryGetProtoRevDeveloperAccountRequest) (*QueryGetProtoRevDeveloperAccountResponse, error) - // GetProtoRevPoolWeights queries the weights of each pool type currently - // being used by the module - GetProtoRevPoolWeights(context.Context, *QueryGetProtoRevPoolWeightsRequest) (*QueryGetProtoRevPoolWeightsResponse, error) + // GetProtoRevInfoByPoolType queries pool type information that is currently + // being utilized by the module + GetProtoRevInfoByPoolType(context.Context, *QueryGetProtoRevInfoByPoolTypeRequest) (*QueryGetProtoRevInfoByPoolTypeResponse, error) // GetProtoRevMaxPoolPointsPerTx queries the maximum number of pool points // that can be consumed per transaction GetProtoRevMaxPoolPointsPerTx(context.Context, *QueryGetProtoRevMaxPoolPointsPerTxRequest) (*QueryGetProtoRevMaxPoolPointsPerTxResponse, error) @@ -1795,8 +1798,8 @@ func (*UnimplementedQueryServer) GetProtoRevAdminAccount(ctx context.Context, re func (*UnimplementedQueryServer) GetProtoRevDeveloperAccount(ctx context.Context, req *QueryGetProtoRevDeveloperAccountRequest) (*QueryGetProtoRevDeveloperAccountResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetProtoRevDeveloperAccount not implemented") } -func (*UnimplementedQueryServer) GetProtoRevPoolWeights(ctx context.Context, req *QueryGetProtoRevPoolWeightsRequest) (*QueryGetProtoRevPoolWeightsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetProtoRevPoolWeights not implemented") +func (*UnimplementedQueryServer) GetProtoRevInfoByPoolType(ctx context.Context, req *QueryGetProtoRevInfoByPoolTypeRequest) (*QueryGetProtoRevInfoByPoolTypeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetProtoRevInfoByPoolType not implemented") } func (*UnimplementedQueryServer) GetProtoRevMaxPoolPointsPerTx(ctx context.Context, req *QueryGetProtoRevMaxPoolPointsPerTxRequest) (*QueryGetProtoRevMaxPoolPointsPerTxResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetProtoRevMaxPoolPointsPerTx not implemented") @@ -1980,20 +1983,20 @@ func _Query_GetProtoRevDeveloperAccount_Handler(srv interface{}, ctx context.Con return interceptor(ctx, in, info, handler) } -func _Query_GetProtoRevPoolWeights_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryGetProtoRevPoolWeightsRequest) +func _Query_GetProtoRevInfoByPoolType_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryGetProtoRevInfoByPoolTypeRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(QueryServer).GetProtoRevPoolWeights(ctx, in) + return srv.(QueryServer).GetProtoRevInfoByPoolType(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/osmosis.protorev.v1beta1.Query/GetProtoRevPoolWeights", + FullMethod: "/osmosis.protorev.v1beta1.Query/GetProtoRevInfoByPoolType", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).GetProtoRevPoolWeights(ctx, req.(*QueryGetProtoRevPoolWeightsRequest)) + return srv.(QueryServer).GetProtoRevInfoByPoolType(ctx, req.(*QueryGetProtoRevInfoByPoolTypeRequest)) } return interceptor(ctx, in, info, handler) } @@ -2129,8 +2132,8 @@ var _Query_serviceDesc = grpc.ServiceDesc{ Handler: _Query_GetProtoRevDeveloperAccount_Handler, }, { - MethodName: "GetProtoRevPoolWeights", - Handler: _Query_GetProtoRevPoolWeights_Handler, + MethodName: "GetProtoRevInfoByPoolType", + Handler: _Query_GetProtoRevInfoByPoolType_Handler, }, { MethodName: "GetProtoRevMaxPoolPointsPerTx", @@ -2694,7 +2697,7 @@ func (m *QueryGetProtoRevDeveloperAccountResponse) MarshalToSizedBuffer(dAtA []b return len(dAtA) - i, nil } -func (m *QueryGetProtoRevPoolWeightsRequest) Marshal() (dAtA []byte, err error) { +func (m *QueryGetProtoRevInfoByPoolTypeRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2704,12 +2707,12 @@ func (m *QueryGetProtoRevPoolWeightsRequest) Marshal() (dAtA []byte, err error) return dAtA[:n], nil } -func (m *QueryGetProtoRevPoolWeightsRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryGetProtoRevInfoByPoolTypeRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryGetProtoRevPoolWeightsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryGetProtoRevInfoByPoolTypeRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -2717,7 +2720,7 @@ func (m *QueryGetProtoRevPoolWeightsRequest) MarshalToSizedBuffer(dAtA []byte) ( return len(dAtA) - i, nil } -func (m *QueryGetProtoRevPoolWeightsResponse) Marshal() (dAtA []byte, err error) { +func (m *QueryGetProtoRevInfoByPoolTypeResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2727,18 +2730,18 @@ func (m *QueryGetProtoRevPoolWeightsResponse) Marshal() (dAtA []byte, err error) return dAtA[:n], nil } -func (m *QueryGetProtoRevPoolWeightsResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryGetProtoRevInfoByPoolTypeResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryGetProtoRevPoolWeightsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryGetProtoRevInfoByPoolTypeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l { - size, err := m.PoolWeights.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.InfoByPoolType.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -3253,7 +3256,7 @@ func (m *QueryGetProtoRevDeveloperAccountResponse) Size() (n int) { return n } -func (m *QueryGetProtoRevPoolWeightsRequest) Size() (n int) { +func (m *QueryGetProtoRevInfoByPoolTypeRequest) Size() (n int) { if m == nil { return 0 } @@ -3262,13 +3265,13 @@ func (m *QueryGetProtoRevPoolWeightsRequest) Size() (n int) { return n } -func (m *QueryGetProtoRevPoolWeightsResponse) Size() (n int) { +func (m *QueryGetProtoRevInfoByPoolTypeResponse) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = m.PoolWeights.Size() + l = m.InfoByPoolType.Size() n += 1 + l + sovQuery(uint64(l)) return n } @@ -4705,7 +4708,7 @@ func (m *QueryGetProtoRevDeveloperAccountResponse) Unmarshal(dAtA []byte) error } return nil } -func (m *QueryGetProtoRevPoolWeightsRequest) Unmarshal(dAtA []byte) error { +func (m *QueryGetProtoRevInfoByPoolTypeRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4728,10 +4731,10 @@ func (m *QueryGetProtoRevPoolWeightsRequest) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryGetProtoRevPoolWeightsRequest: wiretype end group for non-group") + return fmt.Errorf("proto: QueryGetProtoRevInfoByPoolTypeRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryGetProtoRevPoolWeightsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryGetProtoRevInfoByPoolTypeRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -4755,7 +4758,7 @@ func (m *QueryGetProtoRevPoolWeightsRequest) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryGetProtoRevPoolWeightsResponse) Unmarshal(dAtA []byte) error { +func (m *QueryGetProtoRevInfoByPoolTypeResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4778,15 +4781,15 @@ func (m *QueryGetProtoRevPoolWeightsResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryGetProtoRevPoolWeightsResponse: wiretype end group for non-group") + return fmt.Errorf("proto: QueryGetProtoRevInfoByPoolTypeResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryGetProtoRevPoolWeightsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryGetProtoRevInfoByPoolTypeResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field PoolWeights", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field InfoByPoolType", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4813,7 +4816,7 @@ func (m *QueryGetProtoRevPoolWeightsResponse) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.PoolWeights.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.InfoByPoolType.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/protorev/types/query.pb.gw.go b/x/protorev/types/query.pb.gw.go index 159161f6ef3..0aab022797c 100644 --- a/x/protorev/types/query.pb.gw.go +++ b/x/protorev/types/query.pb.gw.go @@ -231,20 +231,20 @@ func local_request_Query_GetProtoRevDeveloperAccount_0(ctx context.Context, mars } -func request_Query_GetProtoRevPoolWeights_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryGetProtoRevPoolWeightsRequest +func request_Query_GetProtoRevInfoByPoolType_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryGetProtoRevInfoByPoolTypeRequest var metadata runtime.ServerMetadata - msg, err := client.GetProtoRevPoolWeights(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.GetProtoRevInfoByPoolType(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_Query_GetProtoRevPoolWeights_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryGetProtoRevPoolWeightsRequest +func local_request_Query_GetProtoRevInfoByPoolType_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryGetProtoRevInfoByPoolTypeRequest var metadata runtime.ServerMetadata - msg, err := server.GetProtoRevPoolWeights(ctx, &protoReq) + msg, err := server.GetProtoRevInfoByPoolType(ctx, &protoReq) return msg, metadata, err } @@ -570,7 +570,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) - mux.Handle("GET", pattern_Query_GetProtoRevPoolWeights_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_GetProtoRevInfoByPoolType_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -581,7 +581,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_Query_GetProtoRevPoolWeights_0(rctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_Query_GetProtoRevInfoByPoolType_0(rctx, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { @@ -589,7 +589,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv return } - forward_Query_GetProtoRevPoolWeights_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_GetProtoRevInfoByPoolType_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -929,7 +929,7 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) - mux.Handle("GET", pattern_Query_GetProtoRevPoolWeights_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_GetProtoRevInfoByPoolType_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) @@ -938,14 +938,14 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_Query_GetProtoRevPoolWeights_0(rctx, inboundMarshaler, client, req, pathParams) + resp, md, err := request_Query_GetProtoRevInfoByPoolType_0(rctx, inboundMarshaler, client, req, pathParams) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - forward_Query_GetProtoRevPoolWeights_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_GetProtoRevInfoByPoolType_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -1071,7 +1071,7 @@ var ( pattern_Query_GetProtoRevDeveloperAccount_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"osmosis", "protorev", "developer_account"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_GetProtoRevPoolWeights_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"osmosis", "protorev", "pool_weights"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_GetProtoRevInfoByPoolType_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"osmosis", "protorev", "info_by_pool_type"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_GetProtoRevMaxPoolPointsPerTx_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"osmosis", "protorev", "max_pool_points_per_tx"}, "", runtime.AssumeColonVerbOpt(false))) @@ -1103,7 +1103,7 @@ var ( forward_Query_GetProtoRevDeveloperAccount_0 = runtime.ForwardResponseMessage - forward_Query_GetProtoRevPoolWeights_0 = runtime.ForwardResponseMessage + forward_Query_GetProtoRevInfoByPoolType_0 = runtime.ForwardResponseMessage forward_Query_GetProtoRevMaxPoolPointsPerTx_0 = runtime.ForwardResponseMessage diff --git a/x/protorev/types/tx.pb.go b/x/protorev/types/tx.pb.go index a6f8966046e..57ace2f65ec 100644 --- a/x/protorev/types/tx.pb.go +++ b/x/protorev/types/tx.pb.go @@ -217,26 +217,26 @@ func (m *MsgSetDeveloperAccountResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgSetDeveloperAccountResponse proto.InternalMessageInfo -// MsgSetPoolWeights defines the Msg/SetPoolWeights request type. -type MsgSetPoolWeights struct { +// MsgSetInfoByPoolType defines the Msg/SetInfoByPoolType request type. +type MsgSetInfoByPoolType struct { // admin is the account that is authorized to set the pool weights. Admin string `protobuf:"bytes,1,opt,name=admin,proto3" json:"admin,omitempty" yaml:"admin"` - // pool_weights is the list of pool weights to set. - PoolWeights PoolWeights `protobuf:"bytes,2,opt,name=pool_weights,json=poolWeights,proto3" json:"pool_weights" yaml:"pool_weights"` + // info_by_pool_type contains information about the pool types. + InfoByPoolType InfoByPoolType `protobuf:"bytes,2,opt,name=info_by_pool_type,json=infoByPoolType,proto3" json:"info_by_pool_type" yaml:"info_by_pool_type"` } -func (m *MsgSetPoolWeights) Reset() { *m = MsgSetPoolWeights{} } -func (m *MsgSetPoolWeights) String() string { return proto.CompactTextString(m) } -func (*MsgSetPoolWeights) ProtoMessage() {} -func (*MsgSetPoolWeights) Descriptor() ([]byte, []int) { +func (m *MsgSetInfoByPoolType) Reset() { *m = MsgSetInfoByPoolType{} } +func (m *MsgSetInfoByPoolType) String() string { return proto.CompactTextString(m) } +func (*MsgSetInfoByPoolType) ProtoMessage() {} +func (*MsgSetInfoByPoolType) Descriptor() ([]byte, []int) { return fileDescriptor_2783dce032fc6954, []int{4} } -func (m *MsgSetPoolWeights) XXX_Unmarshal(b []byte) error { +func (m *MsgSetInfoByPoolType) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgSetPoolWeights) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgSetInfoByPoolType) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgSetPoolWeights.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgSetInfoByPoolType.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -246,48 +246,48 @@ func (m *MsgSetPoolWeights) XXX_Marshal(b []byte, deterministic bool) ([]byte, e return b[:n], nil } } -func (m *MsgSetPoolWeights) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgSetPoolWeights.Merge(m, src) +func (m *MsgSetInfoByPoolType) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSetInfoByPoolType.Merge(m, src) } -func (m *MsgSetPoolWeights) XXX_Size() int { +func (m *MsgSetInfoByPoolType) XXX_Size() int { return m.Size() } -func (m *MsgSetPoolWeights) XXX_DiscardUnknown() { - xxx_messageInfo_MsgSetPoolWeights.DiscardUnknown(m) +func (m *MsgSetInfoByPoolType) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSetInfoByPoolType.DiscardUnknown(m) } -var xxx_messageInfo_MsgSetPoolWeights proto.InternalMessageInfo +var xxx_messageInfo_MsgSetInfoByPoolType proto.InternalMessageInfo -func (m *MsgSetPoolWeights) GetAdmin() string { +func (m *MsgSetInfoByPoolType) GetAdmin() string { if m != nil { return m.Admin } return "" } -func (m *MsgSetPoolWeights) GetPoolWeights() PoolWeights { +func (m *MsgSetInfoByPoolType) GetInfoByPoolType() InfoByPoolType { if m != nil { - return m.PoolWeights + return m.InfoByPoolType } - return PoolWeights{} + return InfoByPoolType{} } -// MsgSetPoolWeightsResponse defines the Msg/SetPoolWeights response type. -type MsgSetPoolWeightsResponse struct { +// MsgSetInfoByPoolTypeResponse defines the Msg/SetInfoByPoolType response type. +type MsgSetInfoByPoolTypeResponse struct { } -func (m *MsgSetPoolWeightsResponse) Reset() { *m = MsgSetPoolWeightsResponse{} } -func (m *MsgSetPoolWeightsResponse) String() string { return proto.CompactTextString(m) } -func (*MsgSetPoolWeightsResponse) ProtoMessage() {} -func (*MsgSetPoolWeightsResponse) Descriptor() ([]byte, []int) { +func (m *MsgSetInfoByPoolTypeResponse) Reset() { *m = MsgSetInfoByPoolTypeResponse{} } +func (m *MsgSetInfoByPoolTypeResponse) String() string { return proto.CompactTextString(m) } +func (*MsgSetInfoByPoolTypeResponse) ProtoMessage() {} +func (*MsgSetInfoByPoolTypeResponse) Descriptor() ([]byte, []int) { return fileDescriptor_2783dce032fc6954, []int{5} } -func (m *MsgSetPoolWeightsResponse) XXX_Unmarshal(b []byte) error { +func (m *MsgSetInfoByPoolTypeResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgSetPoolWeightsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgSetInfoByPoolTypeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgSetPoolWeightsResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgSetInfoByPoolTypeResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -297,17 +297,17 @@ func (m *MsgSetPoolWeightsResponse) XXX_Marshal(b []byte, deterministic bool) ([ return b[:n], nil } } -func (m *MsgSetPoolWeightsResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgSetPoolWeightsResponse.Merge(m, src) +func (m *MsgSetInfoByPoolTypeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSetInfoByPoolTypeResponse.Merge(m, src) } -func (m *MsgSetPoolWeightsResponse) XXX_Size() int { +func (m *MsgSetInfoByPoolTypeResponse) XXX_Size() int { return m.Size() } -func (m *MsgSetPoolWeightsResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgSetPoolWeightsResponse.DiscardUnknown(m) +func (m *MsgSetInfoByPoolTypeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSetInfoByPoolTypeResponse.DiscardUnknown(m) } -var xxx_messageInfo_MsgSetPoolWeightsResponse proto.InternalMessageInfo +var xxx_messageInfo_MsgSetInfoByPoolTypeResponse proto.InternalMessageInfo // MsgSetMaxPoolPointsPerTx defines the Msg/SetMaxPoolPointsPerTx request type. type MsgSetMaxPoolPointsPerTx struct { @@ -596,8 +596,8 @@ func init() { proto.RegisterType((*MsgSetHotRoutesResponse)(nil), "osmosis.protorev.v1beta1.MsgSetHotRoutesResponse") proto.RegisterType((*MsgSetDeveloperAccount)(nil), "osmosis.protorev.v1beta1.MsgSetDeveloperAccount") proto.RegisterType((*MsgSetDeveloperAccountResponse)(nil), "osmosis.protorev.v1beta1.MsgSetDeveloperAccountResponse") - proto.RegisterType((*MsgSetPoolWeights)(nil), "osmosis.protorev.v1beta1.MsgSetPoolWeights") - proto.RegisterType((*MsgSetPoolWeightsResponse)(nil), "osmosis.protorev.v1beta1.MsgSetPoolWeightsResponse") + proto.RegisterType((*MsgSetInfoByPoolType)(nil), "osmosis.protorev.v1beta1.MsgSetInfoByPoolType") + proto.RegisterType((*MsgSetInfoByPoolTypeResponse)(nil), "osmosis.protorev.v1beta1.MsgSetInfoByPoolTypeResponse") proto.RegisterType((*MsgSetMaxPoolPointsPerTx)(nil), "osmosis.protorev.v1beta1.MsgSetMaxPoolPointsPerTx") proto.RegisterType((*MsgSetMaxPoolPointsPerTxResponse)(nil), "osmosis.protorev.v1beta1.MsgSetMaxPoolPointsPerTxResponse") proto.RegisterType((*MsgSetMaxPoolPointsPerBlock)(nil), "osmosis.protorev.v1beta1.MsgSetMaxPoolPointsPerBlock") @@ -609,62 +609,63 @@ func init() { func init() { proto.RegisterFile("osmosis/protorev/v1beta1/tx.proto", fileDescriptor_2783dce032fc6954) } var fileDescriptor_2783dce032fc6954 = []byte{ - // 876 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x95, 0x4f, 0x6f, 0xe3, 0x44, - 0x18, 0xc6, 0x33, 0x5d, 0x40, 0xec, 0xa4, 0xc0, 0xc6, 0xfb, 0xcf, 0x71, 0xbb, 0x4e, 0x3a, 0xd9, - 0x6a, 0xd3, 0xb2, 0x1b, 0x93, 0xec, 0xa2, 0x45, 0x91, 0x40, 0xaa, 0xb5, 0x07, 0x38, 0x14, 0x45, - 0xde, 0x45, 0x48, 0x1c, 0x30, 0x76, 0x32, 0x38, 0xd6, 0xc6, 0x1e, 0xcb, 0x33, 0x0d, 0xd9, 0x2b, - 0x47, 0x4e, 0x48, 0x48, 0x1c, 0x90, 0xe0, 0x33, 0x20, 0xc4, 0x95, 0x7b, 0xb9, 0x55, 0xf4, 0x40, - 0x85, 0x44, 0x84, 0x5a, 0x24, 0xee, 0xf9, 0x04, 0xc8, 0x33, 0x8e, 0xf3, 0xcf, 0x26, 0x0d, 0xb9, - 0x54, 0xcd, 0xcc, 0x33, 0xcf, 0xfb, 0x7b, 0xde, 0x19, 0xbd, 0x86, 0x3b, 0x84, 0x7a, 0x84, 0xba, - 0x54, 0x0b, 0x42, 0xc2, 0x48, 0x88, 0xfb, 0x5a, 0xbf, 0x6e, 0x63, 0x66, 0xd5, 0x35, 0x36, 0xa8, - 0xf1, 0x35, 0x49, 0x8e, 0x25, 0xb5, 0xb1, 0xa4, 0x16, 0x4b, 0x94, 0x1b, 0x0e, 0x71, 0x08, 0x5f, - 0xd5, 0xa2, 0xff, 0x84, 0x40, 0x29, 0x58, 0x9e, 0xeb, 0x13, 0x8d, 0xff, 0x8d, 0x97, 0xb6, 0x1d, - 0x42, 0x9c, 0x1e, 0xd6, 0xac, 0xc0, 0xd5, 0x2c, 0xdf, 0x27, 0xcc, 0x62, 0x2e, 0xf1, 0x63, 0x47, - 0xe5, 0x5e, 0x26, 0x43, 0x52, 0x51, 0x08, 0x8b, 0x6d, 0xae, 0x34, 0x45, 0x49, 0xf1, 0x43, 0x6c, - 0xa1, 0xdf, 0x01, 0x7c, 0xe3, 0x90, 0x3a, 0x4f, 0x31, 0x7b, 0x9f, 0x30, 0x83, 0x1c, 0x31, 0x4c, - 0xa5, 0xf7, 0xe0, 0xcb, 0x56, 0xc7, 0x73, 0x7d, 0x19, 0x94, 0x41, 0xf5, 0xaa, 0x5e, 0x1d, 0x0d, - 0x4b, 0x9b, 0x2f, 0x2c, 0xaf, 0xd7, 0x44, 0x7c, 0x19, 0xfd, 0xf6, 0xf3, 0x83, 0x1b, 0xb1, 0xc9, - 0x41, 0xa7, 0x13, 0x62, 0x4a, 0x9f, 0xb2, 0xd0, 0xf5, 0x1d, 0x43, 0x1c, 0x93, 0x3e, 0x87, 0xb0, - 0x4b, 0x98, 0x19, 0x72, 0x37, 0x79, 0xa3, 0x7c, 0xa5, 0x9a, 0x6f, 0xdc, 0xaf, 0x65, 0x75, 0xa3, - 0xf6, 0x8c, 0x3c, 0xc7, 0x7e, 0xcb, 0x72, 0xc3, 0x83, 0xd0, 0x16, 0x04, 0x7a, 0xf1, 0x78, 0x58, - 0xca, 0x8d, 0x86, 0xa5, 0x82, 0x28, 0x3b, 0x71, 0x43, 0xc6, 0xd5, 0xee, 0x98, 0xb3, 0xb9, 0xfd, - 0xd5, 0x3f, 0x3f, 0xee, 0xdf, 0x1e, 0x37, 0x61, 0x2e, 0x05, 0x2a, 0xc2, 0xdb, 0x73, 0x4b, 0x06, - 0xa6, 0x01, 0xf1, 0x29, 0x46, 0xc7, 0x00, 0xde, 0x12, 0x7b, 0x4f, 0x70, 0x1f, 0xf7, 0x48, 0x80, - 0xc3, 0x83, 0x76, 0x9b, 0x1c, 0xf9, 0x6c, 0xed, 0xec, 0x1f, 0xc0, 0x42, 0x67, 0xec, 0x69, 0x5a, - 0xc2, 0x54, 0xde, 0xe0, 0x5e, 0xdb, 0xa3, 0x61, 0x49, 0x16, 0x5e, 0x0b, 0x12, 0x64, 0x5c, 0xeb, - 0xcc, 0xa1, 0x34, 0x2b, 0x51, 0x3c, 0x75, 0x36, 0xde, 0x3c, 0x2f, 0x2a, 0x43, 0x35, 0x7d, 0x27, - 0x09, 0xfb, 0x07, 0x80, 0x05, 0x21, 0x69, 0x11, 0xd2, 0xfb, 0x18, 0xbb, 0x4e, 0x97, 0xad, 0x7f, - 0xc7, 0x18, 0x6e, 0x06, 0x84, 0xf4, 0xcc, 0x2f, 0x84, 0x1f, 0x8f, 0x98, 0x6f, 0xec, 0x66, 0xdf, - 0xf2, 0x54, 0x71, 0x7d, 0x2b, 0xbe, 0xde, 0xeb, 0xa2, 0xe2, 0xb4, 0x11, 0x32, 0xf2, 0xc1, 0x44, - 0xd9, 0x54, 0xa3, 0x1e, 0x14, 0x67, 0x7b, 0x30, 0xe5, 0x84, 0xb6, 0x60, 0x71, 0x61, 0x31, 0x49, - 0x7e, 0x06, 0xa0, 0x2c, 0x76, 0x0f, 0xad, 0x41, 0x24, 0x68, 0x11, 0xd7, 0x67, 0xb4, 0x85, 0xc3, - 0x67, 0x83, 0xb5, 0x1b, 0xf0, 0x11, 0xbc, 0xe5, 0x59, 0x03, 0x93, 0xb3, 0x07, 0xdc, 0xd7, 0x8c, - 0xee, 0x93, 0x0d, 0x78, 0x2b, 0x5e, 0xd2, 0x77, 0x46, 0xc3, 0xd2, 0x1d, 0x61, 0x98, 0xae, 0x43, - 0x86, 0xe4, 0x2d, 0x60, 0x35, 0x77, 0xa3, 0xc0, 0xe5, 0xd9, 0xc0, 0x8b, 0xf4, 0x08, 0xc1, 0x72, - 0xd6, 0x5e, 0x12, 0xff, 0x4f, 0x00, 0xb7, 0xd2, 0x45, 0x7a, 0x8f, 0xb4, 0x9f, 0xaf, 0xdd, 0x81, - 0x4f, 0x61, 0x31, 0x2d, 0x99, 0x1d, 0x99, 0xc7, 0x4d, 0xb8, 0x3b, 0x1a, 0x96, 0xca, 0xd9, 0x4d, - 0xe0, 0x52, 0x64, 0xdc, 0xf4, 0xd2, 0xf8, 0x96, 0xde, 0xfd, 0x2e, 0xac, 0xfc, 0x47, 0xbc, 0xa4, - 0x0d, 0xa7, 0x00, 0x5e, 0x13, 0x3a, 0xdd, 0xa2, 0xf8, 0x09, 0xf6, 0x89, 0xb7, 0xfe, 0xf3, 0xff, - 0x0c, 0xe6, 0x6d, 0x8b, 0x62, 0xb3, 0xc3, 0xed, 0xe2, 0x19, 0x57, 0xc9, 0x7e, 0xfd, 0x49, 0x69, - 0x5d, 0x89, 0xdf, 0xbe, 0x24, 0xca, 0x4d, 0xb9, 0x20, 0x03, 0xda, 0x09, 0x61, 0xf3, 0x4e, 0x94, - 0x5e, 0x9e, 0x4d, 0x3f, 0x09, 0x80, 0x94, 0xf1, 0xd3, 0x9e, 0xac, 0x8d, 0x13, 0x37, 0xbe, 0x7f, - 0x15, 0x5e, 0x39, 0xa4, 0x8e, 0xf4, 0x2d, 0x80, 0x9b, 0x33, 0x83, 0x7d, 0x2f, 0x1b, 0x70, 0x6e, - 0x54, 0x2a, 0xf5, 0x4b, 0x4b, 0x93, 0x46, 0x57, 0xbf, 0x3c, 0xfd, 0xfb, 0x9b, 0x0d, 0x84, 0xca, - 0xda, 0xc2, 0x77, 0x89, 0x62, 0x66, 0x4e, 0x86, 0xb8, 0xf4, 0x13, 0x80, 0xd7, 0xd3, 0x86, 0xef, - 0x5b, 0xcb, 0x8a, 0xce, 0x9f, 0x50, 0xde, 0x59, 0xf5, 0x44, 0x42, 0xab, 0x71, 0xda, 0x3d, 0x74, - 0x2f, 0x9d, 0x76, 0x61, 0x42, 0x4b, 0xbf, 0x00, 0x78, 0x33, 0x7d, 0x94, 0x34, 0x96, 0x41, 0x2c, - 0x9e, 0x51, 0x9a, 0xab, 0x9f, 0x49, 0xd0, 0x1f, 0x71, 0xf4, 0x1a, 0xba, 0x9f, 0x8e, 0x9e, 0x3e, - 0x6e, 0xa4, 0x5f, 0x01, 0x94, 0x33, 0x67, 0xc1, 0xdb, 0xab, 0xe2, 0xf0, 0x63, 0xca, 0xbb, 0xff, - 0xeb, 0x58, 0x12, 0xe4, 0x31, 0x0f, 0x52, 0x47, 0xda, 0xe5, 0x83, 0xf0, 0x91, 0x21, 0xfd, 0x00, - 0xe0, 0xeb, 0x73, 0x1f, 0xb4, 0x37, 0x97, 0xa1, 0x4c, 0x89, 0x95, 0x87, 0x2b, 0x88, 0x13, 0xda, - 0x7d, 0x4e, 0x7b, 0x17, 0xa1, 0x74, 0xda, 0xe9, 0xaf, 0x98, 0xf4, 0x1d, 0x80, 0xaf, 0xcd, 0x4e, - 0x9c, 0xfd, 0x65, 0x25, 0x27, 0x5a, 0xa5, 0x71, 0x79, 0x6d, 0x42, 0xb7, 0xc7, 0xe9, 0x2a, 0x68, - 0x27, 0x9d, 0x6e, 0x6a, 0xce, 0xe8, 0x1f, 0x1e, 0x9f, 0xab, 0xe0, 0xe4, 0x5c, 0x05, 0x7f, 0x9d, - 0xab, 0xe0, 0xeb, 0x0b, 0x35, 0x77, 0x72, 0xa1, 0xe6, 0xce, 0x2e, 0xd4, 0xdc, 0x27, 0x8f, 0x1c, - 0x97, 0x75, 0x8f, 0xec, 0x5a, 0x9b, 0x78, 0x63, 0x9b, 0x07, 0x3d, 0xcb, 0xa6, 0x89, 0x67, 0xbf, - 0xfe, 0x58, 0x1b, 0x4c, 0x9c, 0xd9, 0x8b, 0x00, 0x53, 0xfb, 0x15, 0xfe, 0xfb, 0xe1, 0xbf, 0x01, - 0x00, 0x00, 0xff, 0xff, 0xc6, 0xa0, 0x48, 0x69, 0x14, 0x0b, 0x00, 0x00, + // 889 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0xc1, 0x6f, 0xdb, 0x54, + 0x1c, 0xee, 0xeb, 0x00, 0xb1, 0xd7, 0x01, 0x8b, 0xe9, 0xb6, 0xc4, 0x64, 0x8e, 0xfb, 0xca, 0xb4, + 0x74, 0xda, 0x6c, 0x12, 0x06, 0x43, 0x96, 0x40, 0xaa, 0xb5, 0x03, 0x3b, 0x14, 0x55, 0x5e, 0x11, + 0x12, 0x07, 0x8c, 0x9d, 0xbc, 0xba, 0xd6, 0x62, 0x3f, 0xcb, 0xef, 0xb5, 0x4a, 0xae, 0x1c, 0x39, + 0x21, 0x21, 0x71, 0xe0, 0x5f, 0x80, 0x03, 0x42, 0x5c, 0xb9, 0x97, 0xdb, 0xc4, 0x0e, 0xec, 0x42, + 0x84, 0x5a, 0x24, 0xae, 0x28, 0x7f, 0x01, 0xf2, 0x7b, 0x8e, 0x53, 0xc7, 0x36, 0x6d, 0x96, 0x4b, + 0x14, 0xff, 0xde, 0xf7, 0xfb, 0x7e, 0xdf, 0xf7, 0xd9, 0xfe, 0x19, 0x6e, 0x10, 0x1a, 0x10, 0xea, + 0x53, 0x3d, 0x8a, 0x09, 0x23, 0x31, 0x3e, 0xd2, 0x8f, 0x3a, 0x2e, 0x66, 0x4e, 0x47, 0x67, 0x43, + 0x8d, 0xd7, 0xa4, 0x7a, 0x0a, 0xd1, 0xa6, 0x10, 0x2d, 0x85, 0xc8, 0xeb, 0x1e, 0xf1, 0x08, 0xaf, + 0xea, 0xc9, 0x3f, 0x01, 0x90, 0x6b, 0x4e, 0xe0, 0x87, 0x44, 0xe7, 0xbf, 0x69, 0xa9, 0xe9, 0x11, + 0xe2, 0x0d, 0xb0, 0xee, 0x44, 0xbe, 0xee, 0x84, 0x21, 0x61, 0x0e, 0xf3, 0x49, 0x98, 0x32, 0xca, + 0xb7, 0x2b, 0x35, 0x64, 0x13, 0x05, 0xb0, 0xd1, 0xe3, 0x48, 0x5b, 0x8c, 0x14, 0x17, 0xe2, 0x08, + 0xfd, 0x01, 0xe0, 0x1b, 0x3b, 0xd4, 0x7b, 0x8c, 0xd9, 0xc7, 0x84, 0x59, 0xe4, 0x90, 0x61, 0x2a, + 0x7d, 0x04, 0x5f, 0x76, 0xfa, 0x81, 0x1f, 0xd6, 0x81, 0x0a, 0xda, 0x97, 0xcd, 0xf6, 0x64, 0xdc, + 0xba, 0x32, 0x72, 0x82, 0x81, 0x81, 0x78, 0x19, 0xfd, 0xfe, 0xcb, 0xbd, 0xf5, 0x94, 0x64, 0xbb, + 0xdf, 0x8f, 0x31, 0xa5, 0x8f, 0x59, 0xec, 0x87, 0x9e, 0x25, 0xda, 0xa4, 0x7d, 0x08, 0x0f, 0x08, + 0xb3, 0x63, 0xce, 0x56, 0x5f, 0x55, 0x2f, 0xb5, 0xd7, 0xba, 0x77, 0xb5, 0xaa, 0x34, 0xb4, 0x3d, + 0xf2, 0x04, 0x87, 0xbb, 0x8e, 0x1f, 0x6f, 0xc7, 0xae, 0x50, 0x60, 0x36, 0x8e, 0xc7, 0xad, 0x95, + 0xc9, 0xb8, 0x55, 0x13, 0x63, 0x67, 0x6c, 0xc8, 0xba, 0x7c, 0x30, 0xd5, 0x69, 0x34, 0xbf, 0xfe, + 0xe7, 0xa7, 0x3b, 0x37, 0xa6, 0x21, 0xcc, 0xb9, 0x40, 0x0d, 0x78, 0x63, 0xae, 0x64, 0x61, 0x1a, + 0x91, 0x90, 0x62, 0x74, 0x0c, 0xe0, 0x75, 0x71, 0xf6, 0x10, 0x1f, 0xe1, 0x01, 0x89, 0x70, 0xbc, + 0xdd, 0xeb, 0x91, 0xc3, 0x90, 0x2d, 0xed, 0xfd, 0x11, 0xac, 0xf5, 0xa7, 0x9c, 0xb6, 0x23, 0x48, + 0xeb, 0xab, 0x9c, 0xab, 0x39, 0x19, 0xb7, 0xea, 0x82, 0xab, 0x00, 0x41, 0xd6, 0xd5, 0xfe, 0x9c, + 0x14, 0x63, 0x33, 0xb1, 0xa7, 0xe4, 0xed, 0xcd, 0xeb, 0x45, 0x2a, 0x54, 0xca, 0x4f, 0x32, 0xb3, + 0xff, 0x02, 0xb8, 0x2e, 0x20, 0x8f, 0xc2, 0x7d, 0x62, 0x8e, 0x76, 0x09, 0x19, 0xec, 0x8d, 0x22, + 0xbc, 0xb4, 0xd5, 0x43, 0x58, 0xf3, 0xc3, 0x7d, 0x62, 0xbb, 0x23, 0x3b, 0x22, 0x64, 0x60, 0xb3, + 0x51, 0x84, 0xb9, 0xd5, 0xb5, 0x6e, 0xbb, 0xfa, 0x6e, 0xe7, 0x45, 0x98, 0x6a, 0x7a, 0xa7, 0xd3, + 0x60, 0x0a, 0x84, 0xc8, 0x7a, 0xdd, 0xcf, 0x75, 0x18, 0x1b, 0x49, 0x2c, 0xcd, 0x7c, 0x2c, 0x79, + 0x52, 0xa4, 0xc0, 0x66, 0x59, 0x3d, 0x8b, 0xe4, 0x39, 0x80, 0x75, 0x01, 0xd8, 0x71, 0x86, 0xc9, + 0xe9, 0x2e, 0xf1, 0x43, 0x46, 0x77, 0x71, 0xbc, 0x37, 0x5c, 0x3a, 0x96, 0x4f, 0xe1, 0xf5, 0xc0, + 0x19, 0x0a, 0x07, 0x11, 0xe7, 0xb5, 0x93, 0x1b, 0xcd, 0x86, 0x3c, 0x9b, 0x97, 0xcc, 0x8d, 0xc9, + 0xb8, 0x75, 0x53, 0x10, 0x96, 0xe3, 0x90, 0x25, 0x05, 0x05, 0x59, 0xc6, 0xad, 0xc4, 0xb6, 0x9a, + 0xb7, 0x5d, 0x54, 0x8f, 0x10, 0x54, 0xab, 0xce, 0x32, 0xfb, 0x7f, 0x02, 0xf8, 0x56, 0x39, 0xc8, + 0x1c, 0x90, 0xde, 0x93, 0xa5, 0x13, 0xf8, 0x02, 0x36, 0xca, 0x9c, 0xb9, 0x09, 0x79, 0x1a, 0xc2, + 0xdb, 0x93, 0x71, 0x4b, 0xad, 0x0e, 0x81, 0x43, 0x91, 0x75, 0x2d, 0x28, 0xd3, 0x67, 0x28, 0x49, + 0x14, 0x8d, 0x7c, 0x14, 0x09, 0xec, 0x33, 0xec, 0x7b, 0x07, 0x8c, 0xa2, 0x5b, 0x70, 0xf3, 0x7f, + 0xec, 0x65, 0x31, 0x3c, 0x03, 0xf0, 0xaa, 0xc0, 0x99, 0x0e, 0xc5, 0x0f, 0x71, 0x48, 0x82, 0xe5, + 0x77, 0xdf, 0x97, 0x70, 0xcd, 0x75, 0x28, 0xb6, 0xfb, 0x9c, 0x2e, 0x5d, 0x7e, 0x9b, 0xd5, 0xaf, + 0x43, 0x36, 0xda, 0x94, 0xd3, 0x37, 0x41, 0x12, 0xe3, 0xce, 0xb0, 0x20, 0x0b, 0xba, 0x99, 0x42, + 0xe3, 0x66, 0xe2, 0xbe, 0x9e, 0x77, 0x3f, 0x33, 0x80, 0xe4, 0xe9, 0xa3, 0x3d, 0xab, 0x4d, 0x1d, + 0x77, 0x7f, 0x78, 0x15, 0x5e, 0xda, 0xa1, 0x9e, 0xf4, 0x1d, 0x80, 0x57, 0x72, 0x1b, 0x7f, 0xab, + 0x5a, 0xe0, 0xdc, 0x0e, 0x95, 0x3b, 0x17, 0x86, 0x66, 0x41, 0xb7, 0xbf, 0x7a, 0xf6, 0xf7, 0xb7, + 0xab, 0x08, 0xa9, 0x7a, 0xe1, 0x83, 0x45, 0x31, 0xb3, 0x67, 0xdb, 0x5d, 0xfa, 0x19, 0xc0, 0x37, + 0xcb, 0xb6, 0xf2, 0x3b, 0xe7, 0x0d, 0x9d, 0xef, 0x90, 0x3f, 0x58, 0xb4, 0x23, 0x53, 0xab, 0x73, + 0xb5, 0x5b, 0xe8, 0x76, 0xb9, 0xda, 0xc2, 0xea, 0x96, 0x7e, 0x05, 0xf0, 0x5a, 0xf9, 0x2a, 0xe9, + 0x9e, 0x27, 0xa2, 0xd8, 0x23, 0x1b, 0x8b, 0xf7, 0x64, 0xd2, 0xef, 0x73, 0xe9, 0x1a, 0xba, 0x5b, + 0x2e, 0xbd, 0x7c, 0xdd, 0x48, 0xbf, 0x01, 0x58, 0xaf, 0xdc, 0x05, 0xef, 0x2d, 0x2a, 0x87, 0xb7, + 0xc9, 0x1f, 0xbe, 0x50, 0x5b, 0x66, 0xe4, 0x01, 0x37, 0xd2, 0x41, 0xfa, 0xc5, 0x8d, 0xf0, 0x95, + 0x21, 0xfd, 0x08, 0x60, 0xad, 0xf8, 0xa5, 0xd3, 0xce, 0x53, 0x93, 0xc7, 0xcb, 0xef, 0x2f, 0x86, + 0xbf, 0xe8, 0xa3, 0x53, 0xf8, 0xb8, 0x49, 0xdf, 0x03, 0xf8, 0x5a, 0x7e, 0xff, 0xdc, 0x39, 0x6f, + 0xf4, 0x0c, 0x2b, 0x77, 0x2f, 0x8e, 0xcd, 0x24, 0x6e, 0x71, 0x89, 0x9b, 0x68, 0xa3, 0x5c, 0xe2, + 0x99, 0xad, 0x63, 0x7e, 0x72, 0x7c, 0xa2, 0x80, 0xa7, 0x27, 0x0a, 0xf8, 0xeb, 0x44, 0x01, 0xdf, + 0x9c, 0x2a, 0x2b, 0x4f, 0x4f, 0x95, 0x95, 0xe7, 0xa7, 0xca, 0xca, 0xe7, 0xf7, 0x3d, 0x9f, 0x1d, + 0x1c, 0xba, 0x5a, 0x8f, 0x04, 0x53, 0x9a, 0x7b, 0x03, 0xc7, 0xa5, 0x19, 0xe7, 0x51, 0xe7, 0x81, + 0x3e, 0x9c, 0x31, 0x27, 0x5e, 0xa9, 0xfb, 0x0a, 0xbf, 0x7e, 0xf7, 0xbf, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x62, 0x22, 0x73, 0xe0, 0x3b, 0x0b, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -691,9 +692,9 @@ type MsgClient interface { // SetMaxPoolPointsPerBlock sets the maximum number of pool points that can be // consumed per block. Can only be called by the admin account. SetMaxPoolPointsPerBlock(ctx context.Context, in *MsgSetMaxPoolPointsPerBlock, opts ...grpc.CallOption) (*MsgSetMaxPoolPointsPerBlockResponse, error) - // SetPoolWeights sets the weights of each pool type in the store. Can only be - // called by the admin account. - SetPoolWeights(ctx context.Context, in *MsgSetPoolWeights, opts ...grpc.CallOption) (*MsgSetPoolWeightsResponse, error) + // SetInfoByPoolType sets the pool type information needed to make smart + // assumptions about swapping on different pool types + SetInfoByPoolType(ctx context.Context, in *MsgSetInfoByPoolType, opts ...grpc.CallOption) (*MsgSetInfoByPoolTypeResponse, error) // SetBaseDenoms sets the base denoms that will be used to create cyclic // arbitrage routes. Can only be called by the admin account. SetBaseDenoms(ctx context.Context, in *MsgSetBaseDenoms, opts ...grpc.CallOption) (*MsgSetBaseDenomsResponse, error) @@ -743,9 +744,9 @@ func (c *msgClient) SetMaxPoolPointsPerBlock(ctx context.Context, in *MsgSetMaxP return out, nil } -func (c *msgClient) SetPoolWeights(ctx context.Context, in *MsgSetPoolWeights, opts ...grpc.CallOption) (*MsgSetPoolWeightsResponse, error) { - out := new(MsgSetPoolWeightsResponse) - err := c.cc.Invoke(ctx, "/osmosis.protorev.v1beta1.Msg/SetPoolWeights", in, out, opts...) +func (c *msgClient) SetInfoByPoolType(ctx context.Context, in *MsgSetInfoByPoolType, opts ...grpc.CallOption) (*MsgSetInfoByPoolTypeResponse, error) { + out := new(MsgSetInfoByPoolTypeResponse) + err := c.cc.Invoke(ctx, "/osmosis.protorev.v1beta1.Msg/SetInfoByPoolType", in, out, opts...) if err != nil { return nil, err } @@ -775,9 +776,9 @@ type MsgServer interface { // SetMaxPoolPointsPerBlock sets the maximum number of pool points that can be // consumed per block. Can only be called by the admin account. SetMaxPoolPointsPerBlock(context.Context, *MsgSetMaxPoolPointsPerBlock) (*MsgSetMaxPoolPointsPerBlockResponse, error) - // SetPoolWeights sets the weights of each pool type in the store. Can only be - // called by the admin account. - SetPoolWeights(context.Context, *MsgSetPoolWeights) (*MsgSetPoolWeightsResponse, error) + // SetInfoByPoolType sets the pool type information needed to make smart + // assumptions about swapping on different pool types + SetInfoByPoolType(context.Context, *MsgSetInfoByPoolType) (*MsgSetInfoByPoolTypeResponse, error) // SetBaseDenoms sets the base denoms that will be used to create cyclic // arbitrage routes. Can only be called by the admin account. SetBaseDenoms(context.Context, *MsgSetBaseDenoms) (*MsgSetBaseDenomsResponse, error) @@ -799,8 +800,8 @@ func (*UnimplementedMsgServer) SetMaxPoolPointsPerTx(ctx context.Context, req *M func (*UnimplementedMsgServer) SetMaxPoolPointsPerBlock(ctx context.Context, req *MsgSetMaxPoolPointsPerBlock) (*MsgSetMaxPoolPointsPerBlockResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SetMaxPoolPointsPerBlock not implemented") } -func (*UnimplementedMsgServer) SetPoolWeights(ctx context.Context, req *MsgSetPoolWeights) (*MsgSetPoolWeightsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method SetPoolWeights not implemented") +func (*UnimplementedMsgServer) SetInfoByPoolType(ctx context.Context, req *MsgSetInfoByPoolType) (*MsgSetInfoByPoolTypeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetInfoByPoolType not implemented") } func (*UnimplementedMsgServer) SetBaseDenoms(ctx context.Context, req *MsgSetBaseDenoms) (*MsgSetBaseDenomsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SetBaseDenoms not implemented") @@ -882,20 +883,20 @@ func _Msg_SetMaxPoolPointsPerBlock_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } -func _Msg_SetPoolWeights_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgSetPoolWeights) +func _Msg_SetInfoByPoolType_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgSetInfoByPoolType) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(MsgServer).SetPoolWeights(ctx, in) + return srv.(MsgServer).SetInfoByPoolType(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/osmosis.protorev.v1beta1.Msg/SetPoolWeights", + FullMethod: "/osmosis.protorev.v1beta1.Msg/SetInfoByPoolType", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).SetPoolWeights(ctx, req.(*MsgSetPoolWeights)) + return srv.(MsgServer).SetInfoByPoolType(ctx, req.(*MsgSetInfoByPoolType)) } return interceptor(ctx, in, info, handler) } @@ -939,8 +940,8 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ Handler: _Msg_SetMaxPoolPointsPerBlock_Handler, }, { - MethodName: "SetPoolWeights", - Handler: _Msg_SetPoolWeights_Handler, + MethodName: "SetInfoByPoolType", + Handler: _Msg_SetInfoByPoolType_Handler, }, { MethodName: "SetBaseDenoms", @@ -1078,7 +1079,7 @@ func (m *MsgSetDeveloperAccountResponse) MarshalToSizedBuffer(dAtA []byte) (int, return len(dAtA) - i, nil } -func (m *MsgSetPoolWeights) Marshal() (dAtA []byte, err error) { +func (m *MsgSetInfoByPoolType) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1088,18 +1089,18 @@ func (m *MsgSetPoolWeights) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgSetPoolWeights) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgSetInfoByPoolType) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgSetPoolWeights) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgSetInfoByPoolType) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l { - size, err := m.PoolWeights.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.InfoByPoolType.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -1118,7 +1119,7 @@ func (m *MsgSetPoolWeights) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *MsgSetPoolWeightsResponse) Marshal() (dAtA []byte, err error) { +func (m *MsgSetInfoByPoolTypeResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1128,12 +1129,12 @@ func (m *MsgSetPoolWeightsResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgSetPoolWeightsResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgSetInfoByPoolTypeResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgSetPoolWeightsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgSetInfoByPoolTypeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1389,7 +1390,7 @@ func (m *MsgSetDeveloperAccountResponse) Size() (n int) { return n } -func (m *MsgSetPoolWeights) Size() (n int) { +func (m *MsgSetInfoByPoolType) Size() (n int) { if m == nil { return 0 } @@ -1399,12 +1400,12 @@ func (m *MsgSetPoolWeights) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } - l = m.PoolWeights.Size() + l = m.InfoByPoolType.Size() n += 1 + l + sovTx(uint64(l)) return n } -func (m *MsgSetPoolWeightsResponse) Size() (n int) { +func (m *MsgSetInfoByPoolTypeResponse) Size() (n int) { if m == nil { return 0 } @@ -1827,7 +1828,7 @@ func (m *MsgSetDeveloperAccountResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgSetPoolWeights) Unmarshal(dAtA []byte) error { +func (m *MsgSetInfoByPoolType) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1850,10 +1851,10 @@ func (m *MsgSetPoolWeights) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgSetPoolWeights: wiretype end group for non-group") + return fmt.Errorf("proto: MsgSetInfoByPoolType: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgSetPoolWeights: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgSetInfoByPoolType: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1890,7 +1891,7 @@ func (m *MsgSetPoolWeights) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field PoolWeights", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field InfoByPoolType", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -1917,7 +1918,7 @@ func (m *MsgSetPoolWeights) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.PoolWeights.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.InfoByPoolType.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1942,7 +1943,7 @@ func (m *MsgSetPoolWeights) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgSetPoolWeightsResponse) Unmarshal(dAtA []byte) error { +func (m *MsgSetInfoByPoolTypeResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1965,10 +1966,10 @@ func (m *MsgSetPoolWeightsResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgSetPoolWeightsResponse: wiretype end group for non-group") + return fmt.Errorf("proto: MsgSetInfoByPoolTypeResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgSetPoolWeightsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgSetInfoByPoolTypeResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: diff --git a/x/protorev/types/tx.pb.gw.go b/x/protorev/types/tx.pb.gw.go index dfa28a0f86a..c7bd774100e 100644 --- a/x/protorev/types/tx.pb.gw.go +++ b/x/protorev/types/tx.pb.gw.go @@ -178,37 +178,37 @@ func local_request_Msg_SetMaxPoolPointsPerBlock_0(ctx context.Context, marshaler } var ( - filter_Msg_SetPoolWeights_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + filter_Msg_SetInfoByPoolType_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) -func request_Msg_SetPoolWeights_0(ctx context.Context, marshaler runtime.Marshaler, client MsgClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq MsgSetPoolWeights +func request_Msg_SetInfoByPoolType_0(ctx context.Context, marshaler runtime.Marshaler, client MsgClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MsgSetInfoByPoolType var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_SetPoolWeights_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_SetInfoByPoolType_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := client.SetPoolWeights(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.SetInfoByPoolType(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_Msg_SetPoolWeights_0(ctx context.Context, marshaler runtime.Marshaler, server MsgServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq MsgSetPoolWeights +func local_request_Msg_SetInfoByPoolType_0(ctx context.Context, marshaler runtime.Marshaler, server MsgServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MsgSetInfoByPoolType var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_SetPoolWeights_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_SetInfoByPoolType_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := server.SetPoolWeights(ctx, &protoReq) + msg, err := server.SetInfoByPoolType(ctx, &protoReq) return msg, metadata, err } @@ -347,7 +347,7 @@ func RegisterMsgHandlerServer(ctx context.Context, mux *runtime.ServeMux, server }) - mux.Handle("POST", pattern_Msg_SetPoolWeights_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_Msg_SetInfoByPoolType_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -358,7 +358,7 @@ func RegisterMsgHandlerServer(ctx context.Context, mux *runtime.ServeMux, server runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_Msg_SetPoolWeights_0(rctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_Msg_SetInfoByPoolType_0(rctx, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { @@ -366,7 +366,7 @@ func RegisterMsgHandlerServer(ctx context.Context, mux *runtime.ServeMux, server return } - forward_Msg_SetPoolWeights_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Msg_SetInfoByPoolType_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -514,7 +514,7 @@ func RegisterMsgHandlerClient(ctx context.Context, mux *runtime.ServeMux, client }) - mux.Handle("POST", pattern_Msg_SetPoolWeights_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_Msg_SetInfoByPoolType_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) @@ -523,14 +523,14 @@ func RegisterMsgHandlerClient(ctx context.Context, mux *runtime.ServeMux, client runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_Msg_SetPoolWeights_0(rctx, inboundMarshaler, client, req, pathParams) + resp, md, err := request_Msg_SetInfoByPoolType_0(rctx, inboundMarshaler, client, req, pathParams) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - forward_Msg_SetPoolWeights_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Msg_SetInfoByPoolType_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -566,7 +566,7 @@ var ( pattern_Msg_SetMaxPoolPointsPerBlock_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"osmosis", "protorev", "set_max_pool_points_per_block"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Msg_SetPoolWeights_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"osmosis", "protorev", "set_pool_weights"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Msg_SetInfoByPoolType_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"osmosis", "protorev", "set_info_by_pool_type"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Msg_SetBaseDenoms_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"osmosis", "protorev", "set_base_denoms"}, "", runtime.AssumeColonVerbOpt(false))) ) @@ -580,7 +580,7 @@ var ( forward_Msg_SetMaxPoolPointsPerBlock_0 = runtime.ForwardResponseMessage - forward_Msg_SetPoolWeights_0 = runtime.ForwardResponseMessage + forward_Msg_SetInfoByPoolType_0 = runtime.ForwardResponseMessage forward_Msg_SetBaseDenoms_0 = runtime.ForwardResponseMessage ) diff --git a/x/protorev/types/validate.go b/x/protorev/types/validate.go index ce8df75707b..8bc2627b855 100644 --- a/x/protorev/types/validate.go +++ b/x/protorev/types/validate.go @@ -42,15 +42,90 @@ func ValidateBaseDenoms(denoms []BaseDenom) error { return nil } -// ---------------------- PoolWeights Validation ---------------------- // -// Validates that the pool weights object is ready for use in the module. -func (pw *PoolWeights) Validate() error { - if pw == nil { - return fmt.Errorf("pool weights cannot be nil") +// ---------------------- InfoByPoolType Validation ---------------------- // +// Validates the information about each pool type that is used throughout the module. +func (info *InfoByPoolType) Validate() error { + if info == nil { + return fmt.Errorf("pool type info cannot be nil") } - if pw.BalancerWeight == 0 || pw.StableWeight == 0 || pw.ConcentratedWeight == 0 || pw.CosmwasmWeight == 0 { - return fmt.Errorf("pool weights cannot be 0") + if err := info.Balancer.Validate(); err != nil { + return err + } + + if err := info.Stable.Validate(); err != nil { + return err + } + + if err := info.Concentrated.Validate(); err != nil { + return err + } + + if err := info.Cosmwasm.Validate(); err != nil { + return err + } + + return nil +} + +// Validates balancer pool information. +func (b *BalancerPoolInfo) Validate() error { + if b == nil { + return fmt.Errorf("balancer pool info cannot be nil") + } + + if b.Weight == 0 { + return fmt.Errorf("balancer pool weight cannot be 0") + } + + return nil +} + +// Validates stable pool information. +func (s *StablePoolInfo) Validate() error { + if s == nil { + return fmt.Errorf("stable pool info cannot be nil") + } + + if s.Weight == 0 { + return fmt.Errorf("stable pool weight cannot be 0") + } + + return nil +} + +// Validates concentrated pool information. +func (c *ConcentratedPoolInfo) Validate() error { + if c == nil { + return fmt.Errorf("concentrated pool info cannot be nil") + } + + if c.Weight == 0 { + return fmt.Errorf("concentrated pool weight cannot be 0") + } + + if c.MaxTicksCrossed == 0 || c.MaxTicksCrossed > MaxTicksCrossed { + return fmt.Errorf("max ticks moved cannot be 0 or greater than %d", MaxTicksCrossed) + } + + return nil +} + +// Validates cosmwasm pool information. +func (c *CosmwasmPoolInfo) Validate() error { + if c == nil { + return fmt.Errorf("cosmwasm pool info cannot be nil") + } + + for _, weightMap := range c.WeightMaps { + address, err := sdk.AccAddressFromBech32(weightMap.ContractAddress) + if err != nil { + return err + } + + if weightMap.Weight == 0 { + return fmt.Errorf("cosmwasm pool weight cannot be 0 for contract address %s", address) + } } return nil From f94e6e1b5252dab6ffb3b96572ab2fce25ff931b Mon Sep 17 00:00:00 2001 From: Nicolas Lara Date: Mon, 7 Aug 2023 21:53:17 +0200 Subject: [PATCH 5/6] Add async support to wasm hooks (#5072) * added initial async ack support for wasm hooks * added initial working implementation of async acks * removed debug prints * cleanner acks * Update x/ibc-hooks/README.md Co-authored-by: Dev Ojha * added use case to readme * clippy * clippy * updated hooks to latest * added changelog * updated deps * added proto fix and todo * added initial implementation of ack errors * make protos * better readme * cleanup rust structs * initial params * fixed test * updated async acks based on feedback. Safer this way * add error ack test * added missing types * updated ibc-hooks * tidy * updated osmoutils * updated osmoutils and ibc-hooks after merge * merge main * regen proto * update osmoutils * update ibc hooks * run go get * rl bytecode --------- Co-authored-by: Dev Ojha Co-authored-by: Adam Tucker --- CHANGELOG.md | 4 + app/keepers/keepers.go | 7 +- app/modules.go | 2 +- .../crosschain-registry/src/execute.rs | 2 +- go.mod | 8 +- go.sum | 32 +- osmoutils/ibc.go | 18 +- proto/osmosis/ibc-hooks/genesis.proto | 10 + proto/osmosis/ibc-hooks/params.proto | 13 + proto/osmosis/ibc-hooks/tx.proto | 25 + tests/cl-genesis-positions/convert.go | 14 +- .../edit_localosmosis_genesis.go | 10 +- tests/cl-go-client/main.go | 14 +- tests/ibc-hooks/async_acks_test.go | 122 +++ tests/ibc-hooks/bytecode/counter.wasm | Bin 161782 -> 168366 bytes tests/ibc-hooks/bytecode/echo.wasm | Bin 125095 -> 167708 bytes tests/ibc-hooks/testutils/Cargo.lock | 156 +++- .../contracts/counter/src/contract.rs | 8 +- .../testutils/contracts/echo/Cargo.toml | 8 +- .../testutils/contracts/echo/src/ibc.rs | 65 ++ .../testutils/contracts/echo/src/lib.rs | 101 ++- .../testutils/contracts/echo/src/state.rs | 6 + x/ibc-hooks/README.md | 118 +++ x/ibc-hooks/keeper/keeper.go | 230 +++++- x/ibc-hooks/keeper/msg_server.go | 40 + x/ibc-hooks/sdkmodule.go | 34 +- x/ibc-hooks/types/codec.go | 38 + x/ibc-hooks/types/errors.go | 16 +- x/ibc-hooks/types/expected_keepers.go | 16 + x/ibc-hooks/types/genesis.go | 16 + x/ibc-hooks/types/genesis.pb.go | 322 ++++++++ x/ibc-hooks/types/keys.go | 15 +- x/ibc-hooks/types/msgs.go | 32 + x/ibc-hooks/types/params.go | 62 ++ x/ibc-hooks/types/params.pb.go | 327 ++++++++ x/ibc-hooks/types/tx.pb.go | 724 ++++++++++++++++++ x/ibc-hooks/types/types.go | 84 ++ x/ibc-hooks/wasm_hook.go | 31 +- x/ibc-rate-limit/bytecode/rate_limiter.wasm | Bin 264325 -> 264433 bytes .../contracts/rate-limiter/src/sudo.rs | 6 +- 40 files changed, 2620 insertions(+), 116 deletions(-) create mode 100644 proto/osmosis/ibc-hooks/genesis.proto create mode 100644 proto/osmosis/ibc-hooks/params.proto create mode 100644 proto/osmosis/ibc-hooks/tx.proto create mode 100644 tests/ibc-hooks/async_acks_test.go create mode 100644 tests/ibc-hooks/testutils/contracts/echo/src/ibc.rs create mode 100644 tests/ibc-hooks/testutils/contracts/echo/src/state.rs create mode 100644 x/ibc-hooks/keeper/msg_server.go create mode 100644 x/ibc-hooks/types/codec.go create mode 100644 x/ibc-hooks/types/expected_keepers.go create mode 100644 x/ibc-hooks/types/genesis.go create mode 100644 x/ibc-hooks/types/genesis.pb.go create mode 100644 x/ibc-hooks/types/msgs.go create mode 100644 x/ibc-hooks/types/params.go create mode 100644 x/ibc-hooks/types/params.pb.go create mode 100644 x/ibc-hooks/types/tx.pb.go create mode 100644 x/ibc-hooks/types/types.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 9315564afc5..b76ad0edcaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Features + +* [#5072](https://github.com/osmosis-labs/osmosis/pull/5072) IBC-hooks: Add support for async acks when processing onRecvPacket + ### State Breaking * [#5532](https://github.com/osmosis-labs/osmosis/pull/5532) fix: Fix x/tokenfactory genesis import denoms reset x/bank existing denom metadata diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index ba63d28cbac..93a45b47ff3 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -246,8 +246,11 @@ func (appKeepers *AppKeepers) InitNormalKeepers( // Configure the hooks keeper hooksKeeper := ibchookskeeper.NewKeeper( appKeepers.keys[ibchookstypes.StoreKey], + appKeepers.GetSubspace(ibchookstypes.ModuleName), + appKeepers.IBCKeeper.ChannelKeeper, + nil, ) - appKeepers.IBCHooksKeeper = &hooksKeeper + appKeepers.IBCHooksKeeper = hooksKeeper appKeepers.WireICS20PreWasmKeeper(appCodec, bApp, appKeepers.IBCHooksKeeper) @@ -477,6 +480,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers( appKeepers.RateLimitingICS4Wrapper.ContractKeeper = appKeepers.ContractKeeper appKeepers.Ics20WasmHooks.ContractKeeper = appKeepers.ContractKeeper appKeepers.CosmwasmPoolKeeper.SetContractKeeper(appKeepers.ContractKeeper) + appKeepers.IBCHooksKeeper.ContractKeeper = appKeepers.ContractKeeper // set token factory contract keeper appKeepers.TokenFactoryKeeper.SetContractKeeper(appKeepers.ContractKeeper) @@ -671,6 +675,7 @@ func (appKeepers *AppKeepers) initParamsKeeper(appCodec codec.BinaryCodec, legac paramsKeeper.Subspace(icqtypes.ModuleName) paramsKeeper.Subspace(packetforwardtypes.ModuleName).WithKeyTable(packetforwardtypes.ParamKeyTable()) paramsKeeper.Subspace(cosmwasmpooltypes.ModuleName) + paramsKeeper.Subspace(ibchookstypes.ModuleName) return paramsKeeper } diff --git a/app/modules.go b/app/modules.go index c163cc18d41..75ccd2de33f 100644 --- a/app/modules.go +++ b/app/modules.go @@ -181,7 +181,7 @@ func appModules( tokenfactory.NewAppModule(*app.TokenFactoryKeeper, app.AccountKeeper, app.BankKeeper), valsetprefmodule.NewAppModule(appCodec, *app.ValidatorSetPreferenceKeeper), ibcratelimitmodule.NewAppModule(*app.RateLimitingICS4Wrapper), - ibc_hooks.NewAppModule(app.AccountKeeper), + ibc_hooks.NewAppModule(app.AccountKeeper, *app.IBCHooksKeeper), icq.NewAppModule(*app.AppKeepers.ICQKeeper), packetforward.NewAppModule(app.PacketForwardKeeper), cwpoolmodule.NewAppModule(appCodec, *app.CosmwasmPoolKeeper), diff --git a/cosmwasm/contracts/crosschain-registry/src/execute.rs b/cosmwasm/contracts/crosschain-registry/src/execute.rs index 84829b9720e..4d2ff7964ce 100644 --- a/cosmwasm/contracts/crosschain-registry/src/execute.rs +++ b/cosmwasm/contracts/crosschain-registry/src/execute.rs @@ -828,7 +828,7 @@ mod tests { let unauthorized_remove_msg = ExecuteMsg::ModifyContractAlias { operations: vec![ContractAliasInput { operation: Operation::Remove, - alias: alias, + alias, address: Some(address), new_alias: None, }], diff --git a/go.mod b/go.mod index 22f594aa2c4..996b22cc319 100644 --- a/go.mod +++ b/go.mod @@ -22,9 +22,9 @@ require ( github.com/ory/dockertest/v3 v3.10.0 github.com/osmosis-labs/go-mutesting v0.0.0-20221208041716-b43bcd97b3b3 github.com/osmosis-labs/osmosis/osmomath v0.0.3-dev.0.20230804142026-a81cfe3ddde7 - github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230804142026-a81cfe3ddde7 + github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230807183608-16c217dedde5 github.com/osmosis-labs/osmosis/x/epochs v0.0.0-20230328024000-175ec88e4304 - github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.6 + github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230807183608-16c217dedde5 github.com/pkg/errors v0.9.1 github.com/rakyll/statik v0.1.7 github.com/spf13/cast v1.5.1 @@ -50,7 +50,6 @@ require ( github.com/Djarvur/go-err113 v0.1.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/benbjohnson/clock v1.3.0 // indirect - github.com/bytedance/sonic v1.9.1 // indirect github.com/cosmos/gogoproto v1.4.6 // indirect github.com/cosmos/iavl v0.19.5 // indirect github.com/creachadair/taskgroup v0.3.2 // indirect @@ -58,14 +57,12 @@ require ( github.com/docker/distribution v2.8.2+incompatible // indirect github.com/felixge/httpsnoop v1.0.2 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/gateway v1.1.0 // indirect github.com/google/btree v1.1.2 // indirect github.com/junk1tm/musttag v0.5.0 // indirect github.com/kkHAIKE/contextcheck v1.1.4 // indirect github.com/maratori/testableexamples v1.0.0 // indirect github.com/nunnatsa/ginkgolinter v0.9.0 // indirect - github.com/nxadm/tail v1.4.8 // indirect github.com/regen-network/cosmos-proto v0.3.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.23.0 // indirect @@ -80,7 +77,6 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/goleak v1.1.12 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/arch v0.3.0 // indirect google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 // indirect ) diff --git a/go.sum b/go.sum index a57c409861e..f63f8c4e5b4 100644 --- a/go.sum +++ b/go.sum @@ -189,9 +189,6 @@ github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46f github.com/butuzov/ireturn v0.1.1 h1:QvrO2QF2+/Cx1WA/vETCIYBKtRjc30vesdoPUNo1EbY= github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= @@ -212,9 +209,6 @@ github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoG github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 h1:W9o46d2kbNL06lq7UNDPV0zYLzkrde/bjIqO02eoll0= github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8/go.mod h1:gakxgyXaaPkxvLw1XQxNGK4I37ys9iBRzNUx/B7pUCo= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -383,7 +377,7 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= @@ -450,7 +444,6 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -750,10 +743,7 @@ github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5 h1:2U0HzY8BJ8hVwDKIzp7y4voR9CX/nvcfymLmg2UiOio= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -907,7 +897,6 @@ github.com/nunnatsa/ginkgolinter v0.9.0 h1:Sm0zX5QfjJzkeCjEp+t6d3Ha0jwvoDjleP9XC github.com/nunnatsa/ginkgolinter v0.9.0/go.mod h1:FHaMLURXP7qImeH6bvxWJUpyH+2tuqe5j4rW1gxJRmI= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -953,12 +942,18 @@ github.com/osmosis-labs/go-mutesting v0.0.0-20221208041716-b43bcd97b3b3 h1:Ylmch github.com/osmosis-labs/go-mutesting v0.0.0-20221208041716-b43bcd97b3b3/go.mod h1:lV6KnqXYD/ayTe7310MHtM3I2q8Z6bBfMAi+bhwPYtI= github.com/osmosis-labs/osmosis/osmomath v0.0.3-dev.0.20230804142026-a81cfe3ddde7 h1:NTR4zfrPMP4pJ5T60zyZumBAnTWmTAQX/JSZLGrM9jI= github.com/osmosis-labs/osmosis/osmomath v0.0.3-dev.0.20230804142026-a81cfe3ddde7/go.mod h1:UlftwozB+QObT3o0YfkuuyL9fsVdgoWt0dm6J7MLYnU= -github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230804142026-a81cfe3ddde7 h1:uwP/LzPE/edqNKf/Tpn8y9L6EO3JHOVwb+zq1gjL1hE= -github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230804142026-a81cfe3ddde7/go.mod h1:Pl8Nzx6O6ow/+aqfMoMSz4hX+zz6RrnDYsooptECGxM= +github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230505120443-447d8f14811f h1:eXazuLCKEjXTlciPaSJqtNdSVE+CCiyhgC32p1b30qs= +github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230505120443-447d8f14811f/go.mod h1:QeqmptMwAREnUtUTL6KbPRY+zjejIZrj3rRz4RScfyM= +github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230510161551-8bf252f26bae h1:I1Cy+GpTPWbVi0lBw9+bS1w42YfQjvXNK9bW4YbHhcs= +github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230510161551-8bf252f26bae/go.mod h1:hk/o9/kmTSZmZqwXcSrPuwj/gpRMCqbE/d3vj6teL2A= +github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230807183608-16c217dedde5 h1:j4ifxomFROGROHalqWwX7VPDgxOPotMB1GiAWdb03i4= +github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230807183608-16c217dedde5/go.mod h1:Pl8Nzx6O6ow/+aqfMoMSz4hX+zz6RrnDYsooptECGxM= github.com/osmosis-labs/osmosis/x/epochs v0.0.0-20230328024000-175ec88e4304 h1:RIrWLzIiZN5Xd2JOfSOtGZaf6V3qEQYg6EaDTAkMnCo= github.com/osmosis-labs/osmosis/x/epochs v0.0.0-20230328024000-175ec88e4304/go.mod h1:yPWoJTj5RKrXKUChAicp+G/4Ni/uVEpp27mi/FF/L9c= -github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.6 h1:PjfLL5rwwm44CeLnNQssrFgmj4BdeIS5DriKYhGz7IM= -github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.6/go.mod h1:2Aqs0L6JnMfo+P+It8q7hJsP1YB+Is5DJc4nRSiBF/U= +github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230505120443-447d8f14811f h1:GHbnqUSwOiNanFy+4TtOxT1MG+SeYXRn8ilVTlXOUxg= +github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230505120443-447d8f14811f/go.mod h1:bm9OrnwxpsZjScIPvMhhzMU97Fh4JktGH+PtircEVbw= +github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230807183608-16c217dedde5 h1:clEegVniz0zTTBXKfg2oymKa63IYUxcrVXM+LtsvCpA= +github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230807183608-16c217dedde5/go.mod h1:sR0lpev9mcm9/9RY50T1og3UC3WpZAsINh/OmgrmFlg= github.com/osmosis-labs/wasmd v0.31.0-osmo-v16 h1:X747cZYdnqc/+RV48iPVeGprpVb/fUWSaKGsZUWrdbg= github.com/osmosis-labs/wasmd v0.31.0-osmo-v16/go.mod h1:Rf8zW/GgBQyFRRB4s62VQHWA6sTlMFSjoDQQpoq64iI= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= @@ -1222,8 +1217,6 @@ github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+ github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= @@ -1313,9 +1306,6 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= diff --git a/osmoutils/ibc.go b/osmoutils/ibc.go index cc2f30b5d64..310059d7c56 100644 --- a/osmoutils/ibc.go +++ b/osmoutils/ibc.go @@ -14,6 +14,22 @@ const IbcAcknowledgementErrorType = "ibc-acknowledgement-error" // NewEmitErrorAcknowledgement creates a new error acknowledgement after having emitted an event with the // details of the error. func NewEmitErrorAcknowledgement(ctx sdk.Context, err error, errorContexts ...string) channeltypes.Acknowledgement { + EmitIBCErrorEvents(ctx, err, errorContexts) + + return channeltypes.NewErrorAcknowledgement(err) +} + +// NewSuccessAckRepresentingAnError creates a new success acknowledgement that represents an error. +// This is useful for notifying the sender that an error has occurred in a way that does not allow +// the received tokens to be reverted (which means they shouldn't be released by the sender's ics20 escrow) +func NewSuccessAckRepresentingAnError(ctx sdk.Context, err error, errorContent []byte, errorContexts ...string) channeltypes.Acknowledgement { + EmitIBCErrorEvents(ctx, err, errorContexts) + + return channeltypes.NewResultAcknowledgement(errorContent) +} + +// EmitIBCErrorEvents Emit and Log errors +func EmitIBCErrorEvents(ctx sdk.Context, err error, errorContexts []string) { logger := ctx.Logger().With("module", IbcAcknowledgementErrorType) attributes := make([]sdk.Attribute, len(errorContexts)+1) @@ -29,8 +45,6 @@ func NewEmitErrorAcknowledgement(ctx sdk.Context, err error, errorContexts ...st attributes..., ), }) - - return channeltypes.NewErrorAcknowledgement(err) } // MustExtractDenomFromPacketOnRecv takes a packet with a valid ICS20 token data in the Data field and returns the diff --git a/proto/osmosis/ibc-hooks/genesis.proto b/proto/osmosis/ibc-hooks/genesis.proto new file mode 100644 index 00000000000..435af285cac --- /dev/null +++ b/proto/osmosis/ibc-hooks/genesis.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; +package osmosis.ibchooks; + +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "osmosis/ibc-hooks/params.proto"; + +option go_package = "github.com/osmosis-labs/osmosis/v17/x/ibc-hooks/types"; + +message GenesisState { Params params = 1 [ (gogoproto.nullable) = false ]; } diff --git a/proto/osmosis/ibc-hooks/params.proto b/proto/osmosis/ibc-hooks/params.proto new file mode 100644 index 00000000000..6ca3974c304 --- /dev/null +++ b/proto/osmosis/ibc-hooks/params.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; +package osmosis.ibchooks; + +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "google/protobuf/duration.proto"; + +option go_package = "github.com/osmosis-labs/osmosis/v17/x/ibc-hooks/types"; + +message Params { + repeated string allowed_async_ack_contracts = 1 + [ (gogoproto.moretags) = "yaml:\"allowed_async_ack_contracts\"" ]; +} diff --git a/proto/osmosis/ibc-hooks/tx.proto b/proto/osmosis/ibc-hooks/tx.proto new file mode 100644 index 00000000000..0ec7c398587 --- /dev/null +++ b/proto/osmosis/ibc-hooks/tx.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; +package osmosis.ibchooks; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/osmosis-labs/osmosis/v17/x/ibc-hooks/types"; + +// Msg defines the Msg service. +service Msg { + // EmitIBCAck checks the sender can emit the ack and writes the IBC + // acknowledgement + rpc EmitIBCAck(MsgEmitIBCAck) returns (MsgEmitIBCAckResponse); +} + +message MsgEmitIBCAck { + string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; + uint64 packet_sequence = 2 + [ (gogoproto.moretags) = "yaml:\"packet_sequence\"" ]; + string channel = 3 [ (gogoproto.moretags) = "yaml:\"channel\"" ]; +} +message MsgEmitIBCAckResponse { + string contract_result = 1 + [ (gogoproto.moretags) = "yaml:\"contract_result\"" ]; + string ibc_ack = 2 [ (gogoproto.moretags) = "yaml:\"ibc_ack\"" ]; +} diff --git a/tests/cl-genesis-positions/convert.go b/tests/cl-genesis-positions/convert.go index 96e1fc047ea..ecf0b8311e2 100644 --- a/tests/cl-genesis-positions/convert.go +++ b/tests/cl-genesis-positions/convert.go @@ -13,13 +13,13 @@ import ( "github.com/osmosis-labs/osmosis/osmomath" "github.com/osmosis-labs/osmosis/osmoutils/accum" - "github.com/osmosis-labs/osmosis/v16/app" - "github.com/osmosis-labs/osmosis/v16/app/apptesting" - cl "github.com/osmosis-labs/osmosis/v16/x/concentrated-liquidity" - "github.com/osmosis-labs/osmosis/v16/x/concentrated-liquidity/math" - "github.com/osmosis-labs/osmosis/v16/x/concentrated-liquidity/model" - cltypes "github.com/osmosis-labs/osmosis/v16/x/concentrated-liquidity/types" - clgenesis "github.com/osmosis-labs/osmosis/v16/x/concentrated-liquidity/types/genesis" + "github.com/osmosis-labs/osmosis/v17/app" + "github.com/osmosis-labs/osmosis/v17/app/apptesting" + cl "github.com/osmosis-labs/osmosis/v17/x/concentrated-liquidity" + "github.com/osmosis-labs/osmosis/v17/x/concentrated-liquidity/math" + "github.com/osmosis-labs/osmosis/v17/x/concentrated-liquidity/model" + cltypes "github.com/osmosis-labs/osmosis/v17/x/concentrated-liquidity/types" + clgenesis "github.com/osmosis-labs/osmosis/v17/x/concentrated-liquidity/types/genesis" ) type BigBangPositions struct { diff --git a/tests/cl-genesis-positions/edit_localosmosis_genesis.go b/tests/cl-genesis-positions/edit_localosmosis_genesis.go index 42d6d732121..1705afb1546 100644 --- a/tests/cl-genesis-positions/edit_localosmosis_genesis.go +++ b/tests/cl-genesis-positions/edit_localosmosis_genesis.go @@ -16,12 +16,12 @@ import ( banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - osmosisApp "github.com/osmosis-labs/osmosis/v16/app" - "github.com/osmosis-labs/osmosis/v16/x/concentrated-liquidity/model" + osmosisApp "github.com/osmosis-labs/osmosis/v17/app" + "github.com/osmosis-labs/osmosis/v17/x/concentrated-liquidity/model" - cltypes "github.com/osmosis-labs/osmosis/v16/x/concentrated-liquidity/types" - clgenesis "github.com/osmosis-labs/osmosis/v16/x/concentrated-liquidity/types/genesis" - poolmanagertypes "github.com/osmosis-labs/osmosis/v16/x/poolmanager/types" + cltypes "github.com/osmosis-labs/osmosis/v17/x/concentrated-liquidity/types" + clgenesis "github.com/osmosis-labs/osmosis/v17/x/concentrated-liquidity/types/genesis" + poolmanagertypes "github.com/osmosis-labs/osmosis/v17/x/poolmanager/types" ) func EditLocalOsmosisGenesis(updatedCLGenesis *clgenesis.GenesisState, updatedBankGenesis *banktypes.GenesisState) { diff --git a/tests/cl-go-client/main.go b/tests/cl-go-client/main.go index 29b32552132..5e2e309c914 100644 --- a/tests/cl-go-client/main.go +++ b/tests/cl-go-client/main.go @@ -18,13 +18,13 @@ import ( "github.com/ignite/cli/ignite/pkg/cosmosclient" "github.com/osmosis-labs/osmosis/osmoutils" - clqueryproto "github.com/osmosis-labs/osmosis/v16/x/concentrated-liquidity/client/queryproto" - "github.com/osmosis-labs/osmosis/v16/x/concentrated-liquidity/model" - cltypes "github.com/osmosis-labs/osmosis/v16/x/concentrated-liquidity/types" - incentivestypes "github.com/osmosis-labs/osmosis/v16/x/incentives/types" - lockuptypes "github.com/osmosis-labs/osmosis/v16/x/lockup/types" - poolmanagerqueryproto "github.com/osmosis-labs/osmosis/v16/x/poolmanager/client/queryproto" - poolmanagertypes "github.com/osmosis-labs/osmosis/v16/x/poolmanager/types" + clqueryproto "github.com/osmosis-labs/osmosis/v17/x/concentrated-liquidity/client/queryproto" + "github.com/osmosis-labs/osmosis/v17/x/concentrated-liquidity/model" + cltypes "github.com/osmosis-labs/osmosis/v17/x/concentrated-liquidity/types" + incentivestypes "github.com/osmosis-labs/osmosis/v17/x/incentives/types" + lockuptypes "github.com/osmosis-labs/osmosis/v17/x/lockup/types" + poolmanagerqueryproto "github.com/osmosis-labs/osmosis/v17/x/poolmanager/client/queryproto" + poolmanagertypes "github.com/osmosis-labs/osmosis/v17/x/poolmanager/types" epochstypes "github.com/osmosis-labs/osmosis/x/epochs/types" ) diff --git a/tests/ibc-hooks/async_acks_test.go b/tests/ibc-hooks/async_acks_test.go new file mode 100644 index 00000000000..935ae6f0c1a --- /dev/null +++ b/tests/ibc-hooks/async_acks_test.go @@ -0,0 +1,122 @@ +package ibc_hooks_test + +import ( + "encoding/base64" + "encoding/json" + "fmt" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + sdk "github.com/cosmos/cosmos-sdk/types" + channeltypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v4/testing" + "github.com/tidwall/gjson" + + "github.com/osmosis-labs/osmosis/osmoutils" + "github.com/osmosis-labs/osmosis/v17/app" + "github.com/osmosis-labs/osmosis/x/ibc-hooks/types" +) + +func (suite *HooksTestSuite) forceContractToEmitAckForPacket(osmosisApp *app.OsmosisApp, ctx sdk.Context, contractAddr sdk.AccAddress, packet channeltypes.Packet, success bool) ([]byte, error) { + packetJson, err := json.Marshal(packet) + suite.Require().NoError(err) + + msg := fmt.Sprintf(`{"force_emit_ibc_ack": {"packet": %s, "channel": "channel-0", "success": %v }}`, packetJson, success) + contractKeeper := wasmkeeper.NewDefaultPermissionKeeper(osmosisApp.WasmKeeper) + return contractKeeper.Execute(ctx, contractAddr, suite.chainA.SenderAccount.GetAddress(), []byte(msg), sdk.NewCoins()) + +} + +func (suite *HooksTestSuite) TestWasmHooksAsyncAcks() { + sender := suite.chainB.SenderAccount.GetAddress() + osmosisApp := suite.chainA.GetOsmosisApp() + + // Instantiate a contract that knows how to send async Acks + suite.chainA.StoreContractCode(&suite.Suite, "./bytecode/echo.wasm") + contractAddr := suite.chainA.InstantiateContract(&suite.Suite, "{}", 1) + + // Calls that don't specify async acks work as expected + memo := fmt.Sprintf(`{"wasm": {"contract": "%s", "msg": {"async": {"use_async": false}}}}`, contractAddr) + suite.fundAccount(suite.chainB, sender) + transferMsg := NewMsgTransfer(sdk.NewCoin("token0", sdk.NewInt(2000)), sender.String(), contractAddr.String(), "channel-0", memo) + sendResult, receiveResult, ack, err := suite.FullSend(transferMsg, BtoA) + suite.Require().NoError(err) + suite.Require().NotNil(sendResult) + suite.Require().NotNil(receiveResult) + suite.Require().NotNil(ack) + + // the ack has been written + allAcks := osmosisApp.IBCKeeper.ChannelKeeper.GetAllPacketAcks(suite.chainA.GetContext()) + suite.Require().Equal(1, len(allAcks)) + + // Try to emit an ack for a packet that already has been acked. This should fail + + // we extract the packet that has been acked here to test later that our contract can't emit an ack for it + alreadyAckedPacket, err := ibctesting.ParsePacketFromEvents(sendResult.GetEvents()) + suite.Require().NoError(err) + + _, err = suite.forceContractToEmitAckForPacket(osmosisApp, suite.chainA.GetContext(), contractAddr, alreadyAckedPacket, true) + suite.Require().Error(err) + suite.Require().Contains(err.Error(), "no ack actor set for channel channel-0 packet 1") + + params := types.DefaultParams() + params.AllowedAsyncAckContracts = []string{contractAddr.String()} + osmosisApp.IBCHooksKeeper.SetParams(suite.chainA.GetContext(), params) + + totalExpectedAcks := 1 + testCases := []struct { + success bool + }{ + {true}, + {false}, + } + for _, tc := range testCases { + // Calls that specify async Acks work and no Acks are sent + memo = fmt.Sprintf(`{"wasm": {"contract": "%s", "msg": {"async": {"use_async": true}}}}`, contractAddr) + suite.fundAccount(suite.chainB, sender) + transferMsg = NewMsgTransfer(sdk.NewCoin("token0", sdk.NewInt(2000)), sender.String(), contractAddr.String(), "channel-0", memo) + + sendResult, err = suite.chainB.SendMsgsNoCheck(transferMsg) + suite.Require().NoError(err) + + packet, err := ibctesting.ParsePacketFromEvents(sendResult.GetEvents()) + suite.Require().NoError(err) + + receiveResult = suite.RelayPacketNoAck(packet, BtoA) + newAck, err := ibctesting.ParseAckFromEvents(receiveResult.GetEvents()) + suite.Require().Error(err) // No ack! + suite.Require().Nil(newAck) + + // No new ack has been written + allAcks = osmosisApp.IBCKeeper.ChannelKeeper.GetAllPacketAcks(suite.chainA.GetContext()) + suite.Require().Equal(totalExpectedAcks, len(allAcks)) + + // Store a second contract and ask that one to emit an ack for the packet that the first contract sent + contractAddr2 := suite.chainA.InstantiateContract(&suite.Suite, "{}", 1) + _, err = suite.forceContractToEmitAckForPacket(osmosisApp, suite.chainA.GetContext(), contractAddr2, packet, tc.success) + // This should fail because the new contract is not authorized to emit acks for that packet + suite.Require().Error(err) + suite.Require().Contains(err.Error(), "is not allowed to send an ack for channel channel-0 packet") + + // only the contract that sent the packet can send an ack for that packet sequence + ctx := suite.chainA.GetContext() + _, err = suite.forceContractToEmitAckForPacket(osmosisApp, ctx, contractAddr, packet, tc.success) + totalExpectedAcks++ + suite.Require().NoError(err) + writtenAck, err := ibctesting.ParseAckFromEvents(ctx.EventManager().Events()) + suite.Require().NoError(err) + + allAcks = osmosisApp.IBCKeeper.ChannelKeeper.GetAllPacketAcks(suite.chainA.GetContext()) + suite.Require().Equal(totalExpectedAcks, len(allAcks)) + suite.Require().False(osmoutils.IsAckError(writtenAck)) + ackBase64 := gjson.ParseBytes(writtenAck).Get("result").String() + // decode base64 + ackBytes, err := base64.StdEncoding.DecodeString(ackBase64) + suite.Require().NoError(err) + if tc.success { + suite.Require().Equal("YWNr", gjson.ParseBytes(ackBytes).Get("ibc_ack").String()) + } else { + suite.Require().Equal("forced error", gjson.ParseBytes(ackBytes).Get("error").String()) + } + + } +} diff --git a/tests/ibc-hooks/bytecode/counter.wasm b/tests/ibc-hooks/bytecode/counter.wasm index 938171ef84867121a5cd52ae935f2628d3620ddd..eb667380ce20310c6a7e6365386f6e5b0ef55f43 100644 GIT binary patch literal 168366 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�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 literal 161782 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 diff --git a/tests/ibc-hooks/bytecode/echo.wasm b/tests/ibc-hooks/bytecode/echo.wasm index 4feb4554e7cc0a767abc60c28704dc458d016efd..93e7814b9091fa17723dc16080f0dbdfbbe981cc 100644 GIT binary patch literal 167708 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

R88ApBjy<+zbDv9y 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

Y0IdB`=OrLXvq!3Y4I$#VP_M4j5 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 ZOS+ow(Y1lOuYxpMY3~eyi>o?73og9 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 G>Hh$Rp~(pV diff --git a/tests/ibc-hooks/testutils/Cargo.lock b/tests/ibc-hooks/testutils/Cargo.lock index 52e7e6ca1ef..96450055624 100644 --- a/tests/ibc-hooks/testutils/Cargo.lock +++ b/tests/ibc-hooks/testutils/Cargo.lock @@ -19,6 +19,12 @@ version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "base16ct" version = "0.1.1" @@ -31,6 +37,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "base64ct" version = "1.5.3" @@ -73,6 +85,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "const-oid" version = "0.9.1" @@ -81,9 +103,9 @@ checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" [[package]] name = "cosmwasm-crypto" -version = "1.1.5" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28376836c7677e1ea6d6656a754582e88b91e544ce22fae42956d5fe5549a958" +checksum = "7fecd74d3a0041114110d1260f77fcb644c5d2403549b37096c44f0e643a5177" dependencies = [ "digest 0.10.5", "ed25519-zebra", @@ -94,9 +116,9 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.1.5" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb69f4f7a8a4bce68c8fbd3646238fede1e77056e4ea31c5b6bfc37b709eec3" +checksum = "1c9f7f0e51bfc7295f7b2664fe8513c966428642aa765dad8a74acdab5e0c773" dependencies = [ "syn", ] @@ -127,11 +149,11 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.1.5" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bf9157d060abbc55152aeadcace799d03dc630575daa66604079a1206cb060" +checksum = "5034c772c1369b160731aa00bb81f93733ab2884928edd8d588733d607ac5af4" dependencies = [ - "base64", + "base64 0.13.1", "cosmwasm-crypto", "cosmwasm-derive", "derivative", @@ -140,6 +162,7 @@ dependencies = [ "schemars", "serde", "serde-json-wasm", + "sha2 0.10.6", "thiserror", "uint", ] @@ -162,7 +185,7 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", - "cw-storage-plus", + "cw-storage-plus 0.16.0", "cw2", "schemars", "serde", @@ -227,11 +250,11 @@ checksum = "7192aec80d0c01a0e5941392eea7e2b7e212ee74ca7f430bfdc899420c055ef6" dependencies = [ "anyhow", "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 0.16.0", "cw-utils", "derivative", "itertools", - "prost", + "prost 0.9.0", "schemars", "serde", "thiserror", @@ -248,6 +271,17 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-storage-plus" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053a5083c258acd68386734f428a5a171b29f7d733151ae83090c6fcc9417ffa" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "cw-utils" version = "0.16.0" @@ -271,7 +305,7 @@ checksum = "91398113b806f4d2a8d5f8d05684704a20ffd5968bf87e3473e1973710b884ad" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 0.16.0", "schemars", "serde", ] @@ -339,11 +373,15 @@ dependencies = [ name = "echo" version = "0.1.0" dependencies = [ + "base64 0.21.0", "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", - "cw-storage-plus", + "cw-storage-plus 1.0.1", + "osmosis-std", + "osmosis-std-derive", + "prost 0.11.9", "schemars", "serde", "thiserror", @@ -495,6 +533,25 @@ version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.16.0" @@ -507,6 +564,34 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "osmosis-std" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a10a056b61279b3529aeaa623b0a4b37b39b4b7061e833ff963fb4bc614d5e13" +dependencies = [ + "chrono", + "cosmwasm-std", + "osmosis-std-derive", + "prost 0.11.9", + "prost-types", + "schemars", + "serde", + "serde-cw-value", +] + +[[package]] +name = "osmosis-std-derive" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd08ecb36b79fd53fb6c94b3444566deb185ad6b47bbeeb6cfff6b54e3d37d4" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pkcs8" version = "0.9.0" @@ -533,7 +618,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.9.0", +] + +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive 0.11.9", ] [[package]] @@ -549,6 +644,28 @@ dependencies = [ "syn", ] +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost 0.11.9", +] + [[package]] name = "quote" version = "1.0.21" @@ -643,11 +760,20 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-cw-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75d32da6b8ed758b7d850b6c3c08f1d7df51a4df3cb201296e63e34a78e99d4" +dependencies = [ + "serde", +] + [[package]] name = "serde-json-wasm" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" +checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" dependencies = [ "serde", ] diff --git a/tests/ibc-hooks/testutils/contracts/counter/src/contract.rs b/tests/ibc-hooks/testutils/contracts/counter/src/contract.rs index 259d3634924..e54e1506298 100644 --- a/tests/ibc-hooks/testutils/contracts/counter/src/contract.rs +++ b/tests/ibc-hooks/testutils/contracts/counter/src/contract.rs @@ -366,12 +366,12 @@ mod tests { // No acks query(deps.as_ref(), env.clone(), get_msg.clone()).unwrap_err(); - let msg = SudoMsg::ReceiveAck { + let msg = SudoMsg::IBCLifecycleComplete(IBCLifecycleComplete::IBCAck { channel: format!("channel-0"), sequence: 1, ack: String::new(), success: true, - }; + }); let _res = sudo(deps.as_mut(), env.clone(), msg).unwrap(); // should increase counter by 1 @@ -379,12 +379,12 @@ mod tests { let value: GetCountResponse = from_binary(&res).unwrap(); assert_eq!(1, value.count); - let msg = SudoMsg::ReceiveAck { + let msg = SudoMsg::IBCLifecycleComplete(IBCLifecycleComplete::IBCAck { channel: format!("channel-0"), sequence: 1, ack: String::new(), success: true, - }; + }); let _res = sudo(deps.as_mut(), env.clone(), msg).unwrap(); // should increase counter by 1 diff --git a/tests/ibc-hooks/testutils/contracts/echo/Cargo.toml b/tests/ibc-hooks/testutils/contracts/echo/Cargo.toml index 6980ce8cb24..62553b0fd40 100644 --- a/tests/ibc-hooks/testutils/contracts/echo/Cargo.toml +++ b/tests/ibc-hooks/testutils/contracts/echo/Cargo.toml @@ -30,10 +30,14 @@ optimize = """docker run --rm -v "$(pwd)":/code \ """ [dependencies] +base64 = "0.21.0" cosmwasm-schema = "1.1.3" -cosmwasm-std = "1.1.3" +cosmwasm-std = {version = "1.2", features = ["stargate"]} cosmwasm-storage = "1.1.3" -cw-storage-plus = "0.16.0" +cw-storage-plus = "1.0.1" +osmosis-std = "0.15.2" +osmosis-std-derive = "0.15.2" +prost = {version = "0.11.2", default-features = false, features = ["prost-derive"]} schemars = "0.8.10" serde = { version = "1.0.145", default-features = false, features = ["derive"] } thiserror = { version = "1.0.31" } diff --git a/tests/ibc-hooks/testutils/contracts/echo/src/ibc.rs b/tests/ibc-hooks/testutils/contracts/echo/src/ibc.rs new file mode 100644 index 00000000000..8bda147ed80 --- /dev/null +++ b/tests/ibc-hooks/testutils/contracts/echo/src/ibc.rs @@ -0,0 +1,65 @@ +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub struct Height { + /// Previously known as "epoch" + #[serde(skip_serializing_if = "Option::is_none")] + revision_number: Option, + + /// The height of a block + #[serde(skip_serializing_if = "Option::is_none")] + revision_height: Option, +} + +// An IBC packet +#[cw_serde] +pub struct Packet { + pub sequence: u64, + pub source_port: String, + pub source_channel: String, + pub destination_port: String, + pub destination_channel: String, + pub data: String, // FungibleTokenData + pub timeout_height: Height, + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout_timestamp: Option, +} + +// The following are part of the wasm_hooks interface +#[cw_serde] +pub enum IBCAsyncOptions { + #[serde(rename = "request_ack")] + RequestAck { + /// The source channel (osmosis side) of the IBC packet + source_channel: String, + /// The sequence number that the packet was sent with + packet_sequence: u64, + }, +} + +#[cw_serde] +pub struct OnRecvPacketAsyncResponse { + pub is_async_ack: bool, +} + +#[cw_serde] +pub struct ContractAck { + pub contract_result: String, + pub ibc_ack: String, +} + + +#[cw_serde] +#[serde(tag = "type", content = "content")] +pub enum IBCAck { + AckResponse{ + packet: Packet, + contract_ack: ContractAck, + }, + AckError { + packet: Packet, + error_description: String, + error_response: String, + } +} + diff --git a/tests/ibc-hooks/testutils/contracts/echo/src/lib.rs b/tests/ibc-hooks/testutils/contracts/echo/src/lib.rs index ab1d16a3aef..b34c18752f9 100644 --- a/tests/ibc-hooks/testutils/contracts/echo/src/lib.rs +++ b/tests/ibc-hooks/testutils/contracts/echo/src/lib.rs @@ -1,7 +1,12 @@ +pub mod ibc; +pub mod state; +use cosmwasm_schema::cw_serde; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{DepsMut, Env, MessageInfo, Response, StdError}; -use cosmwasm_schema::{cw_serde}; +use cosmwasm_std::{to_binary, DepsMut, Env, MessageInfo, Response, StdError}; +use ibc::{ContractAck, IBCAck, IBCAsyncOptions, OnRecvPacketAsyncResponse, Packet}; +use osmosis_std_derive::CosmwasmExt; +use state::INFLIGHT_PACKETS; // Messages #[cw_serde] @@ -9,7 +14,25 @@ pub struct InstantiateMsg {} #[cw_serde] pub enum ExecuteMsg { - Echo { msg: String }, + Echo { + msg: String, + }, + Async { + use_async: bool, + }, + #[serde(rename = "force_emit_ibc_ack")] + ForceEmitIBCAck { + packet: Packet, + channel: String, + success: bool, + }, +} + +/// Message type for `sudo` entry_point +#[cw_serde] +pub enum SudoMsg { + #[serde(rename = "ibc_async")] + IBCAsync(IBCAsyncOptions), } // Instantiate @@ -29,14 +52,82 @@ fn simple_response(msg: String) -> Response { .add_attribute("echo", msg) .set_data(b"this should echo") } + +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.ibchooks.MsgEmitIBCAck")] +pub struct MsgEmitIBCAck { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(uint64, tag = "2")] + pub packet_sequence: u64, + #[prost(string, tag = "3")] + pub channel: ::prost::alloc::string::String, +} + #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( - _deps: DepsMut, - _env: Env, + deps: DepsMut, + env: Env, _info: MessageInfo, msg: ExecuteMsg, ) -> Result { match msg { ExecuteMsg::Echo { msg } => Ok(simple_response(msg)), + ExecuteMsg::Async { use_async } => { + if use_async { + let response_data = OnRecvPacketAsyncResponse { is_async_ack: true }; + Ok(Response::new().set_data(to_binary(&response_data)?)) + } else { + Ok(Response::default()) + } + } + ExecuteMsg::ForceEmitIBCAck { packet, channel, success } => { + INFLIGHT_PACKETS.save( + deps.storage, + (&packet.destination_channel, packet.sequence), + &(packet.clone(), success), + )?; + let msg = MsgEmitIBCAck { + sender: env.contract.address.to_string(), + packet_sequence: packet.sequence, + channel, + }; + Ok(Response::new().add_message(msg)) + } + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn sudo(deps: DepsMut, _env: Env, msg: SudoMsg) -> Result { + match msg { + SudoMsg::IBCAsync(IBCAsyncOptions::RequestAck { + source_channel, + packet_sequence, + }) => { + let (packet, success) = INFLIGHT_PACKETS.load(deps.storage, (&source_channel, packet_sequence))?; + let data = match success { + true => to_binary(&IBCAck::AckResponse { + packet, + contract_ack: ContractAck { + contract_result: base64::encode("success"), + ibc_ack: base64::encode("ack"), + }, + })?, + false => to_binary(&IBCAck::AckError { + packet, + error_description: "forced error".to_string(), + error_response: r#"{"error": "forced error"}"#.to_string(), + })? + }; + Ok(Response::new().set_data(data))}, } } diff --git a/tests/ibc-hooks/testutils/contracts/echo/src/state.rs b/tests/ibc-hooks/testutils/contracts/echo/src/state.rs new file mode 100644 index 00000000000..5bdb309a683 --- /dev/null +++ b/tests/ibc-hooks/testutils/contracts/echo/src/state.rs @@ -0,0 +1,6 @@ +use cw_storage_plus::Map; + +use crate::ibc; + +// (channel, sequence) -> packet +pub const INFLIGHT_PACKETS: Map<(&str, u64), (ibc::Packet, bool)> = Map::new("inflight"); diff --git a/x/ibc-hooks/README.md b/x/ibc-hooks/README.md index ed18cfd1459..71d4f45e3c5 100644 --- a/x/ibc-hooks/README.md +++ b/x/ibc-hooks/README.md @@ -182,6 +182,124 @@ pub enum SudoMsg { } ``` +### Async Acks + +IBC supports the ability to send an ack back to the sender of the packet asynchronously. This is useful for +cases where the packet is received, but the ack is not immediately known. For example, if the packet is being +forwarded to another chain, the ack may not be known until the packet is received on the other chain. + +Note this ACK does not imply full revertability. It is possible that unrevertable actions have occurred +even if there is an Ack Error. (This is distinct from the behavior of ICS-20 transfers). If you want to ensure +revertability, your contract should be implemented in a way that actions are not finalized until a success ack +is received. + +#### Use case + +Async acks are useful in cases where the contract needs to wait for a response from another chain before +returning a result to the caller. + +For example, if you want to send tokens to another chain after the contract is executed you need to +add a new ibc packet and wait for its ack. + +In the synchronous acks case, the caller will receive an ack from the contract before the second packet +has been processed. This means that the caller will have to wait (and potentially track) if the second +packet has been processed successfully or not. + +With async acks, you contract can take this responsibility and only send an ack to the caller once the +second packet has been processed + +#### Making contract Acks async + +To support this, we allow contracts to return an `IBCAsync` response from the function being executed when the +packet is received. That response specifies that the ack should be handled asynchronously. + +Concretely the contract should return: + +```rust +#[cw_serde] +pub struct OnRecvPacketAsyncResponse { + pub is_async_ack: bool, +} +``` + +if `is_async_ack` is set to true, `OnRecvPacket` will return `nil` and the ack will not be written. Instead, the +contract wil be stored as the "ack actor" for the packet so that only that contract is allowed to send an ack +for it. + +It is up to the contract developers to decide which conditions will trigger the ack to be sent. + +#### Sending an async ack + +To send the async ack, the contract needs to send the MsgEmitIBCAck message to the chain. This message will +then make a sudo call to the contract requesting the ack and write the ack to state. + +That message can be specified in the contract as: + +```rust +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.ibchooks.MsgEmitIBCAck")] +pub struct MsgEmitIBCAck { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(uint64, tag = "2")] + pub packet_sequence: u64, + #[prost(string, tag = "3")] + pub channel: ::prost::alloc::string::String, +} +``` + +The contract is expected to implement the following sudo message handler: + +```rust +#[cw_serde] +pub enum IBCAsyncOptions { + #[serde(rename = "request_ack")] + RequestAck { + /// The source channel (osmosis side) of the IBC packet + source_channel: String, + /// The sequence number that the packet was sent with + packet_sequence: u64, + }, +} + +#[cw_serde] +pub enum SudoMsg { + #[serde(rename = "ibc_async")] + IBCAsync(IBCAsyncOptions), +} +``` + +and that sudo call should return an `IBCAckResponse`: + +```rust +#[cw_serde] +#[serde(tag = "type", content = "content")] +pub enum IBCAck { + AckResponse{ + packet: Packet, + contract_ack: ContractAck, + }, + AckError { + packet: Packet, + error_description: String, + error_response: String, + } +} +``` + +Note: the sudo call is required to potentially allow anyone to send the MsgEmitIBCAck message. For now, however, +this is artificially limited so that the message can only be send by the same contract. This could be expanded in +the future if needed. + # Testing strategy See go tests. \ No newline at end of file diff --git a/x/ibc-hooks/keeper/keeper.go b/x/ibc-hooks/keeper/keeper.go index 9d14337bc4d..b4bc23cf269 100644 --- a/x/ibc-hooks/keeper/keeper.go +++ b/x/ibc-hooks/keeper/keeper.go @@ -1,11 +1,18 @@ package keeper import ( + "encoding/hex" + "encoding/json" "fmt" - + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" "github.com/cosmos/cosmos-sdk/types/address" - + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + channeltypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types" + "github.com/osmosis-labs/osmosis/osmoutils" + "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/log" + "strings" "github.com/osmosis-labs/osmosis/x/ibc-hooks/types" @@ -14,16 +21,29 @@ import ( type ( Keeper struct { - storeKey sdk.StoreKey + storeKey sdk.StoreKey + paramSpace paramtypes.Subspace + + channelKeeper types.ChannelKeeper + ContractKeeper *wasmkeeper.PermissionedKeeper } ) // NewKeeper returns a new instance of the x/ibchooks keeper func NewKeeper( storeKey sdk.StoreKey, -) Keeper { - return Keeper{ - storeKey: storeKey, + paramSpace paramtypes.Subspace, + channelKeeper types.ChannelKeeper, + contractKeeper *wasmkeeper.PermissionedKeeper, +) *Keeper { + if !paramSpace.HasKeyTable() { + paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) + } + return &Keeper{ + storeKey: storeKey, + paramSpace: paramSpace, + channelKeeper: channelKeeper, + ContractKeeper: contractKeeper, } } @@ -32,31 +52,221 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) } -func GetPacketKey(channel string, packetSequence uint64) []byte { +// GetParams returns the total set of the module's parameters. +func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { + k.paramSpace.GetParamSet(ctx, ¶ms) + return params +} + +// SetParams sets the module's parameters with the provided parameters. +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + k.paramSpace.SetParamSet(ctx, ¶ms) +} + +func (k Keeper) InitGenesis(ctx sdk.Context, genState types.GenesisState) { + k.SetParams(ctx, genState.Params) +} + +func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { + return &types.GenesisState{ + Params: k.GetParams(ctx), + } +} + +func GetPacketCallbackKey(channel string, packetSequence uint64) []byte { return []byte(fmt.Sprintf("%s::%d", channel, packetSequence)) } +func GetPacketAckKey(channel string, packetSequence uint64) []byte { + return []byte(fmt.Sprintf("%s::%d::ack", channel, packetSequence)) +} + +func GeneratePacketAckValue(packet channeltypes.Packet, contract string) ([]byte, error) { + if _, err := sdk.AccAddressFromBech32(contract); err != nil { + return nil, sdkerrors.Wrap(types.ErrInvalidContractAddr, contract) + } + + packetHash, err := hashPacket(packet) + if err != nil { + return nil, sdkerrors.Wrap(err, "could not hash packet") + } + + return []byte(fmt.Sprintf("%s::%s", contract, packetHash)), nil +} + // StorePacketCallback stores which contract will be listening for the ack or timeout of a packet func (k Keeper) StorePacketCallback(ctx sdk.Context, channel string, packetSequence uint64, contract string) { store := ctx.KVStore(k.storeKey) - store.Set(GetPacketKey(channel, packetSequence), []byte(contract)) + store.Set(GetPacketCallbackKey(channel, packetSequence), []byte(contract)) } // GetPacketCallback returns the bech32 addr of the contract that is expecting a callback from a packet func (k Keeper) GetPacketCallback(ctx sdk.Context, channel string, packetSequence uint64) string { store := ctx.KVStore(k.storeKey) - return string(store.Get(GetPacketKey(channel, packetSequence))) + return string(store.Get(GetPacketCallbackKey(channel, packetSequence))) +} + +// IsInAllowList checks the params to see if the contract is in the KeyAsyncAckAllowList param +func (k Keeper) IsInAllowList(ctx sdk.Context, contract string) bool { + var allowList []string + k.paramSpace.GetIfExists(ctx, types.KeyAsyncAckAllowList, &allowList) + for _, addr := range allowList { + if addr == contract { + return true + } + } + return false } // DeletePacketCallback deletes the callback from storage once it has been processed func (k Keeper) DeletePacketCallback(ctx sdk.Context, channel string, packetSequence uint64) { store := ctx.KVStore(k.storeKey) - store.Delete(GetPacketKey(channel, packetSequence)) + store.Delete(GetPacketCallbackKey(channel, packetSequence)) } +// StorePacketAckActor stores which contract is allowed to send an ack for the packet +func (k Keeper) StorePacketAckActor(ctx sdk.Context, packet channeltypes.Packet, contract string) { + store := ctx.KVStore(k.storeKey) + channel := packet.GetSourceChannel() + packetSequence := packet.GetSequence() + + val, err := GeneratePacketAckValue(packet, contract) + if err != nil { + panic(err) + } + store.Set(GetPacketAckKey(channel, packetSequence), val) +} + +// GetPacketAckActor returns the bech32 addr of the contract that is allowed to send an ack for the packet and the packet hash +func (k Keeper) GetPacketAckActor(ctx sdk.Context, channel string, packetSequence uint64) (string, string) { + store := ctx.KVStore(k.storeKey) + rawData := store.Get(GetPacketAckKey(channel, packetSequence)) + if rawData == nil { + return "", "" + } + data := strings.Split(string(rawData), "::") + if len(data) != 2 { + return "", "" + } + // validate that the contract is a valid bech32 addr + if _, err := sdk.AccAddressFromBech32(data[0]); err != nil { + return "", "" + } + // validate that the hash is a valid sha256sum hash + if _, err := hex.DecodeString(data[1]); err != nil { + return "", "" + } + + return data[0], data[1] +} + +// DeletePacketAckActor deletes the ack actor from storage once it has been used +func (k Keeper) DeletePacketAckActor(ctx sdk.Context, channel string, packetSequence uint64) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetPacketAckKey(channel, packetSequence)) +} + +// DeriveIntermediateSender derives the sender address to be used when calling wasm hooks func DeriveIntermediateSender(channel, originalSender, bech32Prefix string) (string, error) { senderStr := fmt.Sprintf("%s/%s", channel, originalSender) senderHash32 := address.Hash(types.SenderPrefix, []byte(senderStr)) sender := sdk.AccAddress(senderHash32[:]) return sdk.Bech32ifyAddressBytes(bech32Prefix, sender) } + +// EmitIBCAck emits an event that the IBC packet has been acknowledged +func (k Keeper) EmitIBCAck(ctx sdk.Context, sender, channel string, packetSequence uint64) ([]byte, error) { + contract, packetHash := k.GetPacketAckActor(ctx, channel, packetSequence) + if contract == "" { + return nil, fmt.Errorf("no ack actor set for channel %s packet %d", channel, packetSequence) + } + // Only the contract itself can request for the ack to be emitted. This will generally happen as a callback + // when the result of other IBC actions has finished, but it could be exposed directly by the contract if the + // proper checks are made + if sender != contract { + return nil, fmt.Errorf("sender %s is not allowed to send an ack for channel %s packet %d", sender, channel, packetSequence) + } + + // Write the acknowledgement + _, cap, err := k.channelKeeper.LookupModuleByChannel(ctx, "transfer", channel) + if err != nil { + return nil, sdkerrors.Wrap(err, "could not retrieve module from port-id") + } + + // Calling the contract. This could be made generic by using an interface if we want + // to support other types of AckActors, but keeping it here for now for simplicity. + contractAddr, err := sdk.AccAddressFromBech32(contract) + if err != nil { + return nil, sdkerrors.Wrap(err, "could not parse contract address") + } + + msg := types.IBCAsync{ + RequestAck: types.RequestAck{RequestAckI: types.RequestAckI{ + PacketSequence: packetSequence, + SourceChannel: channel, + }}, + } + msgBytes, err := json.Marshal(msg) + if err != nil { + return nil, sdkerrors.Wrap(err, "could not marshal message") + } + bz, err := k.ContractKeeper.Sudo(ctx, contractAddr, msgBytes) + if err != nil { + return nil, sdkerrors.Wrap(err, "could not execute contract") + } + + ack, err := types.UnmarshalIBCAck(bz) + if err != nil { + return nil, sdkerrors.Wrap(err, "could not unmarshal into IBCAckResponse or IBCAckError") + + } + var newAck channeltypes.Acknowledgement + var packet channeltypes.Packet + + switch ack.Type { + case "ack_response": + jsonAck, err := json.Marshal(ack.AckResponse.ContractAck) + if err != nil { + return nil, sdkerrors.Wrap(err, "could not marshal acknowledgement") + } + packet = ack.AckResponse.Packet + newAck = channeltypes.NewResultAcknowledgement(jsonAck) + case "ack_error": + packet = ack.AckError.Packet + newAck = osmoutils.NewSuccessAckRepresentingAnError(ctx, types.ErrAckFromContract, []byte(ack.AckError.ErrorResponse), ack.AckError.ErrorDescription) + default: + return nil, sdkerrors.Wrap(err, "could not unmarshal into IBCAckResponse or IBCAckError") + } + + // Validate that the packet returned by the contract matches the one we stored when sending + receivedPacketHash, err := hashPacket(packet) + if err != nil { + return nil, sdkerrors.Wrap(err, "could not hash packet") + } + if receivedPacketHash != packetHash { + return nil, sdkerrors.Wrap(types.ErrAckPacketMismatch, fmt.Sprintf("packet hash mismatch. Expected %s, got %s", packetHash, receivedPacketHash)) + } + + // Now we can write the acknowledgement + err = k.channelKeeper.WriteAcknowledgement(ctx, cap, packet, newAck) + if err != nil { + return nil, sdkerrors.Wrap(err, "could not write acknowledgement") + } + + response, err := json.Marshal(newAck) + if err != nil { + return nil, sdkerrors.Wrap(err, "could not marshal acknowledgement") + } + return response, nil +} + +func hashPacket(packet channeltypes.Packet) (string, error) { + // ignore the data here. We only care about the channel information + packet.Data = nil + bz, err := json.Marshal(packet) + if err != nil { + return "", sdkerrors.Wrap(err, "could not marshal packet") + } + packetHash := tmhash.Sum(bz) + return hex.EncodeToString(packetHash), nil +} diff --git a/x/ibc-hooks/keeper/msg_server.go b/x/ibc-hooks/keeper/msg_server.go new file mode 100644 index 00000000000..fbb7ed5b133 --- /dev/null +++ b/x/ibc-hooks/keeper/msg_server.go @@ -0,0 +1,40 @@ +package keeper + +import ( + "context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/osmosis-labs/osmosis/x/ibc-hooks/types" + "strconv" +) + +type msgServer struct { + Keeper +} + +// NewMsgServerImpl returns an implementation of the MsgServer interface +// for the provided Keeper. +func NewMsgServerImpl(keeper Keeper) types.MsgServer { + return &msgServer{Keeper: keeper} +} + +var _ types.MsgServer = msgServer{} + +func (m msgServer) EmitIBCAck(goCtx context.Context, msg *types.MsgEmitIBCAck) (*types.MsgEmitIBCAckResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.MsgEmitAckKey, + sdk.NewAttribute(types.AttributeSender, msg.Sender), + sdk.NewAttribute(types.AttributeChannel, msg.Channel), + sdk.NewAttribute(types.AttributePacketSequence, strconv.FormatUint(msg.PacketSequence, 10)), + ), + ) + + ack, err := m.Keeper.EmitIBCAck(ctx, msg.Sender, msg.Channel, msg.PacketSequence) + if err != nil { + return nil, err + } + + return &types.MsgEmitIBCAckResponse{ContractResult: string(ack), IbcAck: string(ack)}, nil +} diff --git a/x/ibc-hooks/sdkmodule.go b/x/ibc-hooks/sdkmodule.go index 160e5945808..1f4cccda6aa 100644 --- a/x/ibc-hooks/sdkmodule.go +++ b/x/ibc-hooks/sdkmodule.go @@ -3,6 +3,7 @@ package ibc_hooks import ( "encoding/json" "fmt" + "github.com/osmosis-labs/osmosis/x/ibc-hooks/keeper" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" @@ -38,21 +39,28 @@ func (AppModuleBasic) Name() string { } // RegisterLegacyAminoCodec registers the ibc-hooks module's types on the given LegacyAmino codec. -func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {} +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterCodec(cdc) +} -// RegisterInterfaces registers the module's interface types. -func (b AppModuleBasic) RegisterInterfaces(_ cdctypes.InterfaceRegistry) {} +// RegisterInterfaces registers the module's interface types +func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) { + types.RegisterInterfaces(reg) +} // DefaultGenesis returns default genesis state as raw bytes for the // module. func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { - emptyString := "{}" - return []byte(emptyString) + return cdc.MustMarshalJSON(types.DefaultGenesis()) } // ValidateGenesis performs genesis state validation for the ibc-hooks module. func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { - return nil + var genState types.GenesisState + if err := cdc.UnmarshalJSON(bz, &genState); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + return genState.Validate() } // RegisterRESTRoutes registers the REST routes for the ibc-hooks module. @@ -76,13 +84,15 @@ type AppModule struct { AppModuleBasic authKeeper osmoutils.AccountKeeper + keeper keeper.Keeper } // NewAppModule creates a new AppModule object. -func NewAppModule(ak osmoutils.AccountKeeper) AppModule { +func NewAppModule(ak osmoutils.AccountKeeper, keeper keeper.Keeper) AppModule { return AppModule{ AppModuleBasic: AppModuleBasic{}, authKeeper: ak, + keeper: keeper, } } @@ -112,16 +122,24 @@ func (am AppModule) LegacyQuerierHandler(legacyQuerierCdc *codec.LegacyAmino) sd // RegisterServices registers a gRPC query service to respond to the // module-specific gRPC queries. func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) } // InitGenesis performs genesis initialization for the ibc-hooks module. It returns // no validator updates. func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { + var genState types.GenesisState + // Initialize global index to index in genesis state + cdc.MustUnmarshalJSON(data, &genState) + + am.keeper.InitGenesis(ctx, genState) + return []abci.ValidatorUpdate{} } func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { - return json.RawMessage([]byte("{}")) + genState := am.keeper.ExportGenesis(ctx) + return cdc.MustMarshalJSON(genState) } // BeginBlock returns the begin blocker for the ibc-hooks module. diff --git a/x/ibc-hooks/types/codec.go b/x/ibc-hooks/types/codec.go new file mode 100644 index 00000000000..e4b1dcd78bc --- /dev/null +++ b/x/ibc-hooks/types/codec.go @@ -0,0 +1,38 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authzcodec "github.com/cosmos/cosmos-sdk/x/authz/codec" + + // this line is used by starport scaffolding # 1 + "github.com/cosmos/cosmos-sdk/types/msgservice" +) + +func RegisterCodec(cdc *codec.LegacyAmino) { + cdc.RegisterConcrete(&MsgEmitIBCAck{}, "osmosis/ibc-hooks/emit-ibc-ack", nil) +} + +func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgEmitIBCAck{}, + ) + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} + +var ( + amino = codec.NewLegacyAmino() + ModuleCdc = codec.NewProtoCodec(cdctypes.NewInterfaceRegistry()) +) + +func init() { + RegisterCodec(amino) + // Register all Amino interfaces and concrete types on the authz Amino codec so that this can later be + // used to properly serialize MsgGrant and MsgExec instances + sdk.RegisterLegacyAminoCodec(amino) + RegisterCodec(authzcodec.Amino) + + amino.Seal() +} diff --git a/x/ibc-hooks/types/errors.go b/x/ibc-hooks/types/errors.go index 717e33bddd4..31722619aaf 100644 --- a/x/ibc-hooks/types/errors.go +++ b/x/ibc-hooks/types/errors.go @@ -8,10 +8,14 @@ var ( ErrBadMetadataFormatMsg = "wasm metadata not properly formatted for: '%v'. %s" ErrBadExecutionMsg = "cannot execute contract: %v" - ErrMsgValidation = errorsmod.Register("wasm-hooks", 2, "error in wasmhook message validation") - ErrMarshaling = errorsmod.Register("wasm-hooks", 3, "cannot marshal the ICS20 packet") - ErrInvalidPacket = errorsmod.Register("wasm-hooks", 4, "invalid packet data") - ErrBadResponse = errorsmod.Register("wasm-hooks", 5, "cannot create response") - ErrWasmError = errorsmod.Register("wasm-hooks", 6, "wasm error") - ErrBadSender = errorsmod.Register("wasm-hooks", 7, "bad sender") + ErrMsgValidation = errorsmod.Register("wasm-hooks", 2, "error in wasmhook message validation") + ErrMarshaling = errorsmod.Register("wasm-hooks", 3, "cannot marshal the ICS20 packet") + ErrInvalidPacket = errorsmod.Register("wasm-hooks", 4, "invalid packet data") + ErrBadResponse = errorsmod.Register("wasm-hooks", 5, "cannot create response") + ErrWasmError = errorsmod.Register("wasm-hooks", 6, "wasm error") + ErrBadSender = errorsmod.Register("wasm-hooks", 7, "bad sender") + ErrAckFromContract = errorsmod.Register("wasm-hooks", 8, "contract returned error ack") + ErrAsyncAckNotAllowed = errorsmod.Register("wasm-hooks", 9, "contract not allowed to send async acks") + ErrAckPacketMismatch = errorsmod.Register("wasm-hooks", 10, "packet does not match the expected packet") + ErrInvalidContractAddr = errorsmod.Register("wasm-hooks", 11, "invalid contract address") ) diff --git a/x/ibc-hooks/types/expected_keepers.go b/x/ibc-hooks/types/expected_keepers.go new file mode 100644 index 00000000000..0110dcd9c8e --- /dev/null +++ b/x/ibc-hooks/types/expected_keepers.go @@ -0,0 +1,16 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + channeltypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v4/modules/core/exported" +) + +type ChannelKeeper interface { + GetChannel(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) + GetPacketCommitment(ctx sdk.Context, portID, channelID string, sequence uint64) []byte + GetNextSequenceSend(ctx sdk.Context, portID, channelID string) (uint64, bool) + LookupModuleByChannel(ctx sdk.Context, portID, channelID string) (string, *capabilitytypes.Capability, error) + WriteAcknowledgement(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.PacketI, acknowledgement exported.Acknowledgement) error +} diff --git a/x/ibc-hooks/types/genesis.go b/x/ibc-hooks/types/genesis.go new file mode 100644 index 00000000000..dd111be6489 --- /dev/null +++ b/x/ibc-hooks/types/genesis.go @@ -0,0 +1,16 @@ +package types + +// DefaultGenesis returns the default GenesisState for the concentrated-liquidity module. +func DefaultGenesis() *GenesisState { + return &GenesisState{ + Params: DefaultParams(), + } +} + +// Validate performs basic genesis state validation returning an error upon any failure. +func (gs GenesisState) Validate() error { + if err := gs.Params.Validate(); err != nil { + return err + } + return nil +} diff --git a/x/ibc-hooks/types/genesis.pb.go b/x/ibc-hooks/types/genesis.pb.go new file mode 100644 index 00000000000..51b10276c82 --- /dev/null +++ b/x/ibc-hooks/types/genesis.pb.go @@ -0,0 +1,322 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: osmosis/ibc-hooks/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type GenesisState struct { + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_a335b3a5deaed556, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.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 *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "osmosis.ibchooks.GenesisState") +} + +func init() { proto.RegisterFile("osmosis/ibc-hooks/genesis.proto", fileDescriptor_a335b3a5deaed556) } + +var fileDescriptor_a335b3a5deaed556 = []byte{ + // 212 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xcf, 0x2f, 0xce, 0xcd, + 0x2f, 0xce, 0x2c, 0xd6, 0xcf, 0x4c, 0x4a, 0xd6, 0xcd, 0xc8, 0xcf, 0xcf, 0x2e, 0xd6, 0x4f, 0x4f, + 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x80, 0x2a, 0xd0, + 0xcb, 0x4c, 0x4a, 0x06, 0xcb, 0x4b, 0x89, 0xa4, 0xe7, 0xa7, 0xe7, 0x83, 0x25, 0xf5, 0x41, 0x2c, + 0x88, 0x3a, 0x29, 0xc9, 0x64, 0xb0, 0xc2, 0x78, 0x88, 0x04, 0x84, 0x03, 0x95, 0x92, 0xc3, 0xb4, + 0xa3, 0x20, 0xb1, 0x28, 0x31, 0x17, 0x2a, 0xaf, 0xe4, 0xc6, 0xc5, 0xe3, 0x0e, 0xb1, 0x33, 0xb8, + 0x24, 0xb1, 0x24, 0x55, 0xc8, 0x8c, 0x8b, 0x0d, 0x22, 0x2f, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x6d, + 0x24, 0xa1, 0x87, 0xee, 0x06, 0xbd, 0x00, 0xb0, 0xbc, 0x13, 0xcb, 0x89, 0x7b, 0xf2, 0x0c, 0x41, + 0x50, 0xd5, 0x4e, 0xfe, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, + 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0x65, 0x9a, + 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x0f, 0x35, 0x4b, 0x37, 0x27, 0x31, + 0xa9, 0x18, 0xc6, 0xd1, 0x2f, 0x33, 0x34, 0xd7, 0xaf, 0x40, 0x72, 0x5f, 0x49, 0x65, 0x41, 0x6a, + 0x71, 0x12, 0x1b, 0xd8, 0x7d, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x94, 0x67, 0x2a, 0x86, + 0x25, 0x01, 0x00, 0x00, +} + +func (m *GenesisState) 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 *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) 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 ErrIntOverflowGenesis + } + 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: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/ibc-hooks/types/keys.go b/x/ibc-hooks/types/keys.go index 9a3e7ded1bc..db7527bd1fb 100644 --- a/x/ibc-hooks/types/keys.go +++ b/x/ibc-hooks/types/keys.go @@ -1,8 +1,17 @@ package types const ( - ModuleName = "ibchooks" - StoreKey = "hooks-for-ibc" // not using the module name because of collisions with key "ibc" + ModuleName = "ibchooks" + RouterKey = ModuleName + StoreKey = "hooks-for-ibc" // not using the module name because of collisions with key "ibc" + IBCCallbackKey = "ibc_callback" - SenderPrefix = "ibc-wasm-hook-intermediary" + IBCAsyncAckKey = "ibc_async_ack" + + MsgEmitAckKey = "emit_ack" + AttributeSender = "sender" + AttributeChannel = "channel" + AttributePacketSequence = "sequence" + + SenderPrefix = "ibc-wasm-hook-intermediary" ) diff --git a/x/ibc-hooks/types/msgs.go b/x/ibc-hooks/types/msgs.go new file mode 100644 index 00000000000..85ccb3b8cb3 --- /dev/null +++ b/x/ibc-hooks/types/msgs.go @@ -0,0 +1,32 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// constants. +const ( + TypeMsgEmitIBCAck = "emit-ibc-ack" +) + +var _ sdk.Msg = &MsgEmitIBCAck{} + +func (m MsgEmitIBCAck) Route() string { return RouterKey } +func (m MsgEmitIBCAck) Type() string { return TypeMsgEmitIBCAck } +func (m MsgEmitIBCAck) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(m.Sender) + if err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) + } + return nil +} + +func (m MsgEmitIBCAck) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m)) +} + +func (m MsgEmitIBCAck) GetSigners() []sdk.AccAddress { + sender, _ := sdk.AccAddressFromBech32(m.Sender) + return []sdk.AccAddress{sender} +} diff --git a/x/ibc-hooks/types/params.go b/x/ibc-hooks/types/params.go new file mode 100644 index 00000000000..091fbb14683 --- /dev/null +++ b/x/ibc-hooks/types/params.go @@ -0,0 +1,62 @@ +package types + +import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +// Parameter store keys. +var ( + KeyAsyncAckAllowList = []byte("AsyncAckAllowList") + + _ paramtypes.ParamSet = &Params{} +) + +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) +} + +func NewParams(allowedAsyncAckContracts []string) Params { + return Params{ + AllowedAsyncAckContracts: allowedAsyncAckContracts, + } +} + +// DefaultParams returns default concentrated-liquidity module parameters. +func DefaultParams() Params { + return Params{ + AllowedAsyncAckContracts: []string{}, + } +} + +// ParamSetPairs implements params.ParamSet. +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair(KeyAsyncAckAllowList, &p.AllowedAsyncAckContracts, validateAsyncAckAllowList), + } +} + +// Validate params. +func (p Params) Validate() error { + if err := validateAsyncAckAllowList(p.AllowedAsyncAckContracts); err != nil { + return err + } + return nil +} + +func validateAsyncAckAllowList(i interface{}) error { + allowedContracts, ok := i.([]string) + + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + for _, contract := range allowedContracts { + if _, err := sdk.AccAddressFromBech32(contract); err != nil { + return err + } + } + + return nil +} diff --git a/x/ibc-hooks/types/params.pb.go b/x/ibc-hooks/types/params.pb.go new file mode 100644 index 00000000000..6ab409300d2 --- /dev/null +++ b/x/ibc-hooks/types/params.pb.go @@ -0,0 +1,327 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: osmosis/ibc-hooks/params.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + _ "github.com/gogo/protobuf/types" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type Params struct { + AllowedAsyncAckContracts []string `protobuf:"bytes,1,rep,name=allowed_async_ack_contracts,json=allowedAsyncAckContracts,proto3" json:"allowed_async_ack_contracts,omitempty" yaml:"allowed_async_ack_contracts"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_a8a3c4779e5e4552, []int{0} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.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 *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetAllowedAsyncAckContracts() []string { + if m != nil { + return m.AllowedAsyncAckContracts + } + return nil +} + +func init() { + proto.RegisterType((*Params)(nil), "osmosis.ibchooks.Params") +} + +func init() { proto.RegisterFile("osmosis/ibc-hooks/params.proto", fileDescriptor_a8a3c4779e5e4552) } + +var fileDescriptor_a8a3c4779e5e4552 = []byte{ + // 254 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xcb, 0x2f, 0xce, 0xcd, + 0x2f, 0xce, 0x2c, 0xd6, 0xcf, 0x4c, 0x4a, 0xd6, 0xcd, 0xc8, 0xcf, 0xcf, 0x2e, 0xd6, 0x2f, 0x48, + 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x80, 0xca, 0xeb, 0x65, + 0x26, 0x25, 0x83, 0xa5, 0xa5, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0x92, 0xfa, 0x20, 0x16, 0x44, + 0x9d, 0x94, 0x64, 0x32, 0x58, 0x61, 0x3c, 0x44, 0x02, 0xc2, 0x81, 0x4a, 0xc9, 0xa5, 0xe7, 0xe7, + 0xa7, 0xe7, 0xa4, 0xea, 0x83, 0x79, 0x49, 0xa5, 0x69, 0xfa, 0x29, 0xa5, 0x45, 0x89, 0x25, 0x99, + 0xf9, 0x79, 0x10, 0x79, 0xa5, 0x7c, 0x2e, 0xb6, 0x00, 0xb0, 0x95, 0x42, 0xa9, 0x5c, 0xd2, 0x89, + 0x39, 0x39, 0xf9, 0xe5, 0xa9, 0x29, 0xf1, 0x89, 0xc5, 0x95, 0x79, 0xc9, 0xf1, 0x89, 0xc9, 0xd9, + 0xf1, 0xc9, 0xf9, 0x79, 0x25, 0x45, 0x89, 0xc9, 0x25, 0xc5, 0x12, 0x8c, 0x0a, 0xcc, 0x1a, 0x9c, + 0x4e, 0x6a, 0x9f, 0xee, 0xc9, 0x2b, 0x55, 0x26, 0xe6, 0xe6, 0x58, 0x29, 0xe1, 0x51, 0xac, 0x14, + 0x24, 0x01, 0x95, 0x75, 0x04, 0x49, 0x3a, 0x26, 0x67, 0x3b, 0xc3, 0xa4, 0x9c, 0xfc, 0x4f, 0x3c, + 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, + 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x34, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, + 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0xea, 0x71, 0xdd, 0x9c, 0xc4, 0xa4, 0x62, 0x18, 0x47, 0xbf, 0xcc, + 0xd0, 0x5c, 0xbf, 0x02, 0x29, 0xac, 0x4a, 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, 0xc0, 0x1e, 0x31, + 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x54, 0xb5, 0xc2, 0xdb, 0x4d, 0x01, 0x00, 0x00, +} + +func (m *Params) 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 *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.AllowedAsyncAckContracts) > 0 { + for iNdEx := len(m.AllowedAsyncAckContracts) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.AllowedAsyncAckContracts[iNdEx]) + copy(dAtA[i:], m.AllowedAsyncAckContracts[iNdEx]) + i = encodeVarintParams(dAtA, i, uint64(len(m.AllowedAsyncAckContracts[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintParams(dAtA []byte, offset int, v uint64) int { + offset -= sovParams(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.AllowedAsyncAckContracts) > 0 { + for _, s := range m.AllowedAsyncAckContracts { + l = len(s) + n += 1 + l + sovParams(uint64(l)) + } + } + return n +} + +func sovParams(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozParams(x uint64) (n int) { + return sovParams(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Params) 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 ErrIntOverflowParams + } + 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: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowedAsyncAckContracts", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + 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 ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AllowedAsyncAckContracts = append(m.AllowedAsyncAckContracts, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipParams(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthParams + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipParams(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthParams + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupParams + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthParams + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthParams = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowParams = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupParams = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/ibc-hooks/types/tx.pb.go b/x/ibc-hooks/types/tx.pb.go new file mode 100644 index 00000000000..ba689b967dc --- /dev/null +++ b/x/ibc-hooks/types/tx.pb.go @@ -0,0 +1,724 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: osmosis/ibc-hooks/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" + grpc1 "github.com/gogo/protobuf/grpc" + proto "github.com/gogo/protobuf/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type MsgEmitIBCAck struct { + Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` + PacketSequence uint64 `protobuf:"varint,2,opt,name=packet_sequence,json=packetSequence,proto3" json:"packet_sequence,omitempty" yaml:"packet_sequence"` + Channel string `protobuf:"bytes,3,opt,name=channel,proto3" json:"channel,omitempty" yaml:"channel"` +} + +func (m *MsgEmitIBCAck) Reset() { *m = MsgEmitIBCAck{} } +func (m *MsgEmitIBCAck) String() string { return proto.CompactTextString(m) } +func (*MsgEmitIBCAck) ProtoMessage() {} +func (*MsgEmitIBCAck) Descriptor() ([]byte, []int) { + return fileDescriptor_93268c51ed820a58, []int{0} +} +func (m *MsgEmitIBCAck) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgEmitIBCAck) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgEmitIBCAck.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 *MsgEmitIBCAck) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgEmitIBCAck.Merge(m, src) +} +func (m *MsgEmitIBCAck) XXX_Size() int { + return m.Size() +} +func (m *MsgEmitIBCAck) XXX_DiscardUnknown() { + xxx_messageInfo_MsgEmitIBCAck.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgEmitIBCAck proto.InternalMessageInfo + +func (m *MsgEmitIBCAck) GetSender() string { + if m != nil { + return m.Sender + } + return "" +} + +func (m *MsgEmitIBCAck) GetPacketSequence() uint64 { + if m != nil { + return m.PacketSequence + } + return 0 +} + +func (m *MsgEmitIBCAck) GetChannel() string { + if m != nil { + return m.Channel + } + return "" +} + +type MsgEmitIBCAckResponse struct { + ContractResult string `protobuf:"bytes,1,opt,name=contract_result,json=contractResult,proto3" json:"contract_result,omitempty" yaml:"contract_result"` + IbcAck string `protobuf:"bytes,2,opt,name=ibc_ack,json=ibcAck,proto3" json:"ibc_ack,omitempty" yaml:"ibc_ack"` +} + +func (m *MsgEmitIBCAckResponse) Reset() { *m = MsgEmitIBCAckResponse{} } +func (m *MsgEmitIBCAckResponse) String() string { return proto.CompactTextString(m) } +func (*MsgEmitIBCAckResponse) ProtoMessage() {} +func (*MsgEmitIBCAckResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_93268c51ed820a58, []int{1} +} +func (m *MsgEmitIBCAckResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgEmitIBCAckResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgEmitIBCAckResponse.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 *MsgEmitIBCAckResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgEmitIBCAckResponse.Merge(m, src) +} +func (m *MsgEmitIBCAckResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgEmitIBCAckResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgEmitIBCAckResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgEmitIBCAckResponse proto.InternalMessageInfo + +func (m *MsgEmitIBCAckResponse) GetContractResult() string { + if m != nil { + return m.ContractResult + } + return "" +} + +func (m *MsgEmitIBCAckResponse) GetIbcAck() string { + if m != nil { + return m.IbcAck + } + return "" +} + +func init() { + proto.RegisterType((*MsgEmitIBCAck)(nil), "osmosis.ibchooks.MsgEmitIBCAck") + proto.RegisterType((*MsgEmitIBCAckResponse)(nil), "osmosis.ibchooks.MsgEmitIBCAckResponse") +} + +func init() { proto.RegisterFile("osmosis/ibc-hooks/tx.proto", fileDescriptor_93268c51ed820a58) } + +var fileDescriptor_93268c51ed820a58 = []byte{ + // 367 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x92, 0xcf, 0x4a, 0x2b, 0x31, + 0x14, 0xc6, 0x9b, 0xdb, 0x4b, 0xcb, 0x0d, 0xb4, 0xf7, 0xde, 0x41, 0xa5, 0xcc, 0x62, 0xa6, 0x64, + 0x63, 0x45, 0x3b, 0x83, 0x8a, 0x08, 0xee, 0x3a, 0xc5, 0x85, 0x8b, 0x22, 0x44, 0x70, 0x21, 0x48, + 0x99, 0xc4, 0x30, 0x0d, 0xf3, 0x27, 0xe3, 0x24, 0x95, 0xf6, 0x11, 0xdc, 0xf9, 0x22, 0xbe, 0x87, + 0xcb, 0x2e, 0x5d, 0x15, 0x69, 0xdf, 0xa0, 0x4f, 0x20, 0x9d, 0xc9, 0x40, 0xdb, 0x8d, 0xbb, 0xe4, + 0xfb, 0x7e, 0x27, 0xf9, 0xce, 0x49, 0xa0, 0x29, 0x64, 0x2c, 0x24, 0x97, 0x2e, 0x27, 0xb4, 0x3b, + 0x12, 0x22, 0x94, 0xae, 0x9a, 0x38, 0x69, 0x26, 0x94, 0x30, 0xfe, 0x69, 0xcf, 0xe1, 0x84, 0xe6, + 0x96, 0xb9, 0x17, 0x88, 0x40, 0xe4, 0xa6, 0xbb, 0x5e, 0x15, 0x1c, 0x7a, 0x07, 0xb0, 0x31, 0x90, + 0xc1, 0x75, 0xcc, 0xd5, 0x8d, 0xd7, 0xef, 0xd1, 0xd0, 0x38, 0x82, 0x35, 0xc9, 0x92, 0x27, 0x96, + 0xb5, 0x40, 0x1b, 0x74, 0xfe, 0x78, 0xff, 0x57, 0x73, 0xbb, 0x31, 0xf5, 0xe3, 0xe8, 0x0a, 0x15, + 0x3a, 0xc2, 0x1a, 0x30, 0xfa, 0xf0, 0x6f, 0xea, 0xd3, 0x90, 0xa9, 0xa1, 0x64, 0xcf, 0x63, 0x96, + 0x50, 0xd6, 0xfa, 0xd5, 0x06, 0x9d, 0xdf, 0x9e, 0xb9, 0x9a, 0xdb, 0x07, 0x45, 0xcd, 0x0e, 0x80, + 0x70, 0xb3, 0x50, 0xee, 0xb4, 0x60, 0x9c, 0xc0, 0x3a, 0x1d, 0xf9, 0x49, 0xc2, 0xa2, 0x56, 0x35, + 0xbf, 0xd0, 0x58, 0xcd, 0xed, 0x66, 0x51, 0xac, 0x0d, 0x84, 0x4b, 0x04, 0xbd, 0x02, 0xb8, 0xbf, + 0x95, 0x17, 0x33, 0x99, 0x8a, 0x44, 0xb2, 0x75, 0x18, 0x2a, 0x12, 0x95, 0xf9, 0x54, 0x0d, 0x33, + 0x26, 0xc7, 0x91, 0xd2, 0x0d, 0x6c, 0x84, 0xd9, 0x01, 0x10, 0x6e, 0x96, 0x0a, 0xce, 0x05, 0xe3, + 0x18, 0xd6, 0x39, 0xa1, 0x43, 0x9f, 0x86, 0x79, 0x27, 0x5b, 0x61, 0xb4, 0x81, 0x70, 0x8d, 0x13, + 0xda, 0xa3, 0xe1, 0xd9, 0x23, 0xac, 0x0e, 0x64, 0x60, 0xdc, 0x43, 0xb8, 0x31, 0x3e, 0xdb, 0xd9, + 0x9d, 0xbc, 0xb3, 0x95, 0xd7, 0x3c, 0xfc, 0x01, 0x28, 0x1b, 0xf2, 0x6e, 0x3f, 0x16, 0x16, 0x98, + 0x2d, 0x2c, 0xf0, 0xb5, 0xb0, 0xc0, 0xdb, 0xd2, 0xaa, 0xcc, 0x96, 0x56, 0xe5, 0x73, 0x69, 0x55, + 0x1e, 0x2e, 0x02, 0xae, 0x46, 0x63, 0xe2, 0x50, 0x11, 0xbb, 0xfa, 0xb0, 0x6e, 0xe4, 0x13, 0x59, + 0x6e, 0xdc, 0x97, 0xd3, 0x4b, 0x77, 0xb2, 0xf9, 0x2d, 0xa6, 0x29, 0x93, 0xa4, 0x96, 0x3f, 0xf9, + 0xf9, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6c, 0xeb, 0x3e, 0x0e, 0x38, 0x02, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // EmitIBCAck checks the sender can emit the ack and writes the IBC + // acknowledgement + EmitIBCAck(ctx context.Context, in *MsgEmitIBCAck, opts ...grpc.CallOption) (*MsgEmitIBCAckResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) EmitIBCAck(ctx context.Context, in *MsgEmitIBCAck, opts ...grpc.CallOption) (*MsgEmitIBCAckResponse, error) { + out := new(MsgEmitIBCAckResponse) + err := c.cc.Invoke(ctx, "/osmosis.ibchooks.Msg/EmitIBCAck", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // EmitIBCAck checks the sender can emit the ack and writes the IBC + // acknowledgement + EmitIBCAck(context.Context, *MsgEmitIBCAck) (*MsgEmitIBCAckResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) EmitIBCAck(ctx context.Context, req *MsgEmitIBCAck) (*MsgEmitIBCAckResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method EmitIBCAck not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_EmitIBCAck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgEmitIBCAck) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).EmitIBCAck(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/osmosis.ibchooks.Msg/EmitIBCAck", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).EmitIBCAck(ctx, req.(*MsgEmitIBCAck)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "osmosis.ibchooks.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "EmitIBCAck", + Handler: _Msg_EmitIBCAck_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "osmosis/ibc-hooks/tx.proto", +} + +func (m *MsgEmitIBCAck) 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 *MsgEmitIBCAck) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgEmitIBCAck) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Channel) > 0 { + i -= len(m.Channel) + copy(dAtA[i:], m.Channel) + i = encodeVarintTx(dAtA, i, uint64(len(m.Channel))) + i-- + dAtA[i] = 0x1a + } + if m.PacketSequence != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.PacketSequence)) + 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 *MsgEmitIBCAckResponse) 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 *MsgEmitIBCAckResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgEmitIBCAckResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.IbcAck) > 0 { + i -= len(m.IbcAck) + copy(dAtA[i:], m.IbcAck) + i = encodeVarintTx(dAtA, i, uint64(len(m.IbcAck))) + i-- + dAtA[i] = 0x12 + } + if len(m.ContractResult) > 0 { + i -= len(m.ContractResult) + copy(dAtA[i:], m.ContractResult) + i = encodeVarintTx(dAtA, i, uint64(len(m.ContractResult))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgEmitIBCAck) 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.PacketSequence != 0 { + n += 1 + sovTx(uint64(m.PacketSequence)) + } + l = len(m.Channel) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgEmitIBCAckResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ContractResult) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.IbcAck) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgEmitIBCAck) 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: MsgEmitIBCAck: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgEmitIBCAck: 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 PacketSequence", wireType) + } + m.PacketSequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PacketSequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Channel", 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.Channel = string(dAtA[iNdEx:postIndex]) + 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 *MsgEmitIBCAckResponse) 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: MsgEmitIBCAckResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgEmitIBCAckResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ContractResult", 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.ContractResult = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IbcAck", 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.IbcAck = string(dAtA[iNdEx:postIndex]) + 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 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/ibc-hooks/types/types.go b/x/ibc-hooks/types/types.go new file mode 100644 index 00000000000..c2afc5e3e89 --- /dev/null +++ b/x/ibc-hooks/types/types.go @@ -0,0 +1,84 @@ +package types + +import ( + "encoding/json" + channeltypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types" +) + +// Async: The following types represent the response sent by a contract on OnRecvPacket when it wants the ack to be async + +// OnRecvPacketAsyncAckResponse the response a contract sends to instruct the module to make the ack async +type OnRecvPacketAsyncAckResponse struct { + IsAsyncAck bool `json:"is_async_ack"` +} + +// Async The following types are used to ask a contract that has sent a packet to generate an ack for it + +// RequestAckI internals of IBCAsync +type RequestAckI struct { + PacketSequence uint64 `json:"packet_sequence"` + SourceChannel string `json:"source_channel"` +} + +// RequestAck internals of IBCAsync +type RequestAck struct { + RequestAckI `json:"request_ack"` +} + +// IBCAsync is the sudo message to be sent to the contract for it to generate an ack for a sent packet +type IBCAsync struct { + RequestAck `json:"ibc_async"` +} + +// General + +// ContractAck is the response to be stored when a wasm hook is executed +type ContractAck struct { + ContractResult []byte `json:"contract_result"` + IbcAck []byte `json:"ibc_ack"` +} + +// IBCAckResponse is the response that a contract returns from the sudo() call on OnRecvPacket or RequestAck +type IBCAckResponse struct { + Packet channeltypes.Packet `json:"packet"` + ContractAck ContractAck `json:"contract_ack"` +} + +// IBCAckError is the error that a contract returns from the sudo() call on RequestAck +type IBCAckError struct { + Packet channeltypes.Packet `json:"packet"` + ErrorDescription string `json:"error_description"` + ErrorResponse string `json:"error_response"` +} + +type IBCAck struct { + Type string `json:"type"` + Content json.RawMessage `json:"content"` + // Note: These two fields have to be pointers so that they can be null + // If they are not pointers, they will be empty structs when null, + // which will cause issues with json.Unmarshal. + AckResponse *IBCAckResponse `json:"response,omitempty"` + AckError *IBCAckError `json:"error,omitempty"` +} + +func UnmarshalIBCAck(bz []byte) (*IBCAck, error) { + var ack IBCAck + if err := json.Unmarshal(bz, &ack); err != nil { + return nil, err + } + + switch ack.Type { + case "ack_response": + ack.AckResponse = &IBCAckResponse{} + if err := json.Unmarshal(ack.Content, ack.AckResponse); err != nil { + return nil, err + } + case "ack_error": + ack.AckError = &IBCAckError{} + if err := json.Unmarshal(ack.Content, ack.AckError); err != nil { + return nil, err + } + } + + return &ack, nil +} diff --git a/x/ibc-hooks/wasm_hook.go b/x/ibc-hooks/wasm_hook.go index 75ed8e5ef5b..8c169649b31 100644 --- a/x/ibc-hooks/wasm_hook.go +++ b/x/ibc-hooks/wasm_hook.go @@ -3,18 +3,17 @@ package ibc_hooks import ( "encoding/json" "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" errorsmod "cosmossdk.io/errors" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" "github.com/osmosis-labs/osmosis/x/ibc-hooks/keeper" - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - "github.com/osmosis-labs/osmosis/osmoutils" - sdk "github.com/cosmos/cosmos-sdk/types" transfertypes "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types" channeltypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types" ibcexported "github.com/cosmos/ibc-go/v4/modules/core/exported" @@ -22,11 +21,6 @@ import ( "github.com/osmosis-labs/osmosis/x/ibc-hooks/types" ) -type ContractAck struct { - ContractResult []byte `json:"contract_result"` - IbcAck []byte `json:"ibc_ack"` -} - type WasmHooks struct { ContractKeeper *wasmkeeper.PermissionedKeeper ibcHooksKeeper *keeper.Keeper @@ -117,7 +111,24 @@ func (h WasmHooks) OnRecvPacketOverride(im IBCMiddleware, ctx sdk.Context, packe return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrWasmError, err.Error()) } - fullAck := ContractAck{ContractResult: response.Data, IbcAck: ack.Acknowledgement()} + // Check if the contract is requesting for the ack to be async. + var asyncAckRequest types.OnRecvPacketAsyncAckResponse + err = json.Unmarshal(response.Data, &asyncAckRequest) + if err == nil { + // If unmarshalling succeeds, the contract is requesting for the ack to be async. + if asyncAckRequest.IsAsyncAck { // in which case IsAsyncAck is expected to be set to true + if !h.ibcHooksKeeper.IsInAllowList(ctx, contractAddr.String()) { + // Only allowed contracts can send async acks + return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrAsyncAckNotAllowed) + } + // Store the contract as the packet's ack actor and return nil + h.ibcHooksKeeper.StorePacketAckActor(ctx, packet, contractAddr.String()) + return nil + } + } + + // If the ack is not async, we continue generating the ack and return it + fullAck := types.ContractAck{ContractResult: response.Data, IbcAck: ack.Acknowledgement()} bz, err = json.Marshal(fullAck) if err != nil { return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrBadResponse, err.Error()) diff --git a/x/ibc-rate-limit/bytecode/rate_limiter.wasm b/x/ibc-rate-limit/bytecode/rate_limiter.wasm index 9dee6eabb6f09a9907e2851a23b2a29075b4be06..30b545e0612d86ee2751dbcfa11595954e0575e5 100644 GIT binary patch delta 4886 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 delta 4926 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 EC%DR;TL1t6 diff --git a/x/ibc-rate-limit/contracts/rate-limiter/src/sudo.rs b/x/ibc-rate-limit/contracts/rate-limiter/src/sudo.rs index 80e6985d578..c42b5ea7e2d 100644 --- a/x/ibc-rate-limit/contracts/rate-limiter/src/sudo.rs +++ b/x/ibc-rate-limit/contracts/rate-limiter/src/sudo.rs @@ -21,7 +21,8 @@ pub fn process_packet( #[cfg(test)] channel_value_mock: Option, ) -> Result { let (channel_id, denom) = packet.path_data(&direction); - let path = &Path::new(channel_id, denom); + #[allow(clippy::needless_borrow)] + let path = &Path::new(&channel_id, &denom); let funds = packet.get_funds(); #[cfg(test)] @@ -144,7 +145,8 @@ fn add_rate_limit_attributes(response: Response, result: &RateLimit) -> Response pub fn undo_send(deps: DepsMut, packet: Packet) -> Result { // Sudo call. Only go modules should be allowed to access this let (channel_id, denom) = packet.path_data(&FlowType::Out); // Sends have direction out. - let path = &Path::new(channel_id, &denom); + #[allow(clippy::needless_borrow)] + let path = &Path::new(&channel_id, &denom); let any_path = Path::new("any", &denom); let funds = packet.get_funds(); From 03d558b3d13744e70b2b7012302ed45d8df91fd7 Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Mon, 7 Aug 2023 15:00:47 -0500 Subject: [PATCH 6/6] chore[e2e]: parse from result (#5965) * parse from result e2e * no err check for os.Remove * default node * more logging * fix * add mutex for IBC sends * clean up * add node level mutex * add wallet address across all validators * use any available node (chainA or chainB) * properly release nodes * mutex for addr rather than entire chain * faster epoch * remove use of public address * potential revert, remove node mutex * full remove node level mutex --- tests/e2e/configurer/chain/chain.go | 35 +- tests/e2e/configurer/chain/commands.go | 338 +++++++++------- tests/e2e/configurer/chain/queries.go | 12 + tests/e2e/configurer/config/constants.go | 12 +- tests/e2e/configurer/upgrade.go | 123 ++++-- tests/e2e/containers/config.go | 4 +- tests/e2e/e2e_test.go | 489 ++++++++++++----------- tests/e2e/helpers_e2e_test.go | 45 ++- tests/e2e/initialization/config.go | 8 +- 9 files changed, 618 insertions(+), 448 deletions(-) diff --git a/tests/e2e/configurer/chain/chain.go b/tests/e2e/configurer/chain/chain.go index 268f6674b8e..7f35df9dcca 100644 --- a/tests/e2e/configurer/chain/chain.go +++ b/tests/e2e/configurer/chain/chain.go @@ -26,11 +26,10 @@ type Config struct { VotingPeriod float32 ExpeditedVotingPeriod float32 // upgrade proposal height for chain. - UpgradePropHeight int64 - LatestProposalNumber int - LatestLockNumber int - NodeConfigs []*NodeConfig - NodeTempConfigs []*NodeConfig + UpgradePropHeight int64 + + NodeConfigs []*NodeConfig + NodeTempConfigs []*NodeConfig LatestCodeId int @@ -227,6 +226,12 @@ func (c *Config) SendIBC(dstChain *Config, recipient string, token sdk.Coin) { c.t.Log("successfully sent IBC tokens") } +func (c *Config) GetAllChainNodes() []*NodeConfig { + nodeConfigs := make([]*NodeConfig, len(c.NodeConfigs)) + copy(nodeConfigs, c.NodeConfigs) + return nodeConfigs +} + // GetDefaultNode returns the default node of the chain. // The default node is the first one created. Returns error if no // ndoes created. @@ -251,21 +256,15 @@ func (c *Config) GetNodeAtIndex(nodeIndex int) (*NodeConfig, error) { func (c *Config) getNodeAtIndex(nodeIndex int) (*NodeConfig, error) { if nodeIndex > len(c.NodeConfigs) { - return nil, fmt.Errorf("node index (%d) is greter than the number of nodes available (%d)", nodeIndex, len(c.NodeConfigs)) + return nil, fmt.Errorf("node index (%d) is greater than the number of nodes available (%d)", nodeIndex, len(c.NodeConfigs)) } return c.NodeConfigs[nodeIndex], nil } -func (c *Config) SubmitCreateConcentratedPoolProposal() (uint64, error) { - node, err := c.GetDefaultNode() - if err != nil { - return 0, err - } - - propNumber := node.SubmitCreateConcentratedPoolProposal(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(config.InitialMinDeposit))) - c.LatestProposalNumber += 1 +func (c *Config) SubmitCreateConcentratedPoolProposal(chainANode *NodeConfig) (uint64, error) { + propNumber := chainANode.SubmitCreateConcentratedPoolProposal(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(config.InitialMinDeposit))) - node.DepositProposal(propNumber, false) + chainANode.DepositProposal(propNumber, false) var wg sync.WaitGroup @@ -280,12 +279,12 @@ func (c *Config) SubmitCreateConcentratedPoolProposal() (uint64, error) { wg.Wait() require.Eventually(c.t, func() bool { - status, err := node.QueryPropStatus(propNumber) + status, err := chainANode.QueryPropStatus(propNumber) if err != nil { return false } return status == proposalStatusPassed - }, time.Second*30, time.Millisecond*500) - poolId := node.QueryNumPools() + }, time.Second*30, 10*time.Millisecond) + poolId := chainANode.QueryNumPools() return poolId, nil } diff --git a/tests/e2e/configurer/chain/commands.go b/tests/e2e/configurer/chain/commands.go index 419cde40ada..1bc862fe8c5 100644 --- a/tests/e2e/configurer/chain/commands.go +++ b/tests/e2e/configurer/chain/commands.go @@ -2,6 +2,7 @@ package chain import ( "encoding/json" + "errors" "fmt" "os" "path/filepath" @@ -11,6 +12,7 @@ import ( "sync" "time" + transfertypes "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types" "github.com/tendermint/tendermint/libs/bytes" appparams "github.com/osmosis-labs/osmosis/v17/app/params" @@ -46,15 +48,8 @@ func (n *NodeConfig) CreateBalancerPool(poolFile, from string) uint64 { resp, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) - // TODO: create a helper function for parsing pool ID and prop ID from the response - startIndex := strings.Index(resp.String(), `{"key":"pool_id","value":"`) + len(`{"key":"pool_id","value":"`) - endIndex := strings.Index(resp.String()[startIndex:], `"`) - - // Extract the proposal ID substring - codeIdStr := resp.String()[startIndex : startIndex+endIndex] - - // Convert the proposal ID from string to int - poolID, _ := strconv.ParseUint(codeIdStr, 10, 64) + poolID, err := extractPoolIdFromResponse(resp.String()) + require.NoError(n.t, err) n.LogActionF("successfully created balancer pool %d", poolID) return poolID @@ -66,14 +61,8 @@ func (n *NodeConfig) CreateStableswapPool(poolFile, from string) uint64 { resp, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) - startIndex := strings.Index(resp.String(), `{"key":"pool_id","value":"`) + len(`{"key":"pool_id","value":"`) - endIndex := strings.Index(resp.String()[startIndex:], `"`) - - // Extract the proposal ID substring - codeIdStr := resp.String()[startIndex : startIndex+endIndex] - - // Convert the proposal ID from string to int - poolID, _ := strconv.ParseUint(codeIdStr, 10, 64) + poolID, err := extractPoolIdFromResponse(resp.String()) + require.NoError(n.t, err) n.LogActionF("successfully created stableswap pool with ID %d", poolID) return poolID @@ -91,26 +80,18 @@ func (n *NodeConfig) CollectSpreadRewards(from, positionIds string) { // CreateConcentratedPool creates a concentrated pool. // Returns pool id of newly created pool on success -func (n *NodeConfig) CreateConcentratedPool(from, denom1, denom2 string, tickSpacing uint64, spreadFactor string) (uint64, error) { +func (n *NodeConfig) CreateConcentratedPool(from, denom1, denom2 string, tickSpacing uint64, spreadFactor string) uint64 { n.LogActionF("creating concentrated pool") cmd := []string{"osmosisd", "tx", "concentratedliquidity", "create-pool", denom1, denom2, fmt.Sprintf("%d", tickSpacing), spreadFactor, fmt.Sprintf("--from=%s", from)} resp, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) - if err != nil { - return 0, err - } - - startIndex := strings.Index(resp.String(), `{"key":"pool_id","value":"`) + len(`{"key":"pool_id","value":"`) - endIndex := strings.Index(resp.String()[startIndex:], `"`) - - // Extract the proposal ID substring - codeIdStr := resp.String()[startIndex : startIndex+endIndex] + require.NoError(n.t, err) - // Convert the proposal ID from string to int - poolID, _ := strconv.ParseUint(codeIdStr, 10, 64) + poolID, err := extractPoolIdFromResponse(resp.String()) + require.NoError(n.t, err) n.LogActionF("successfully created concentrated pool with ID %d", poolID) - return poolID, nil + return poolID } // CreateConcentratedPosition creates a concentrated position from [lowerTick; upperTick] in pool with id of poolId @@ -120,17 +101,10 @@ func (n *NodeConfig) CreateConcentratedPosition(from, lowerTick, upperTick strin // gas = 50,000 because e2e default to 40,000, we hardcoded extra 10k gas to initialize tick // fees = 1250 (because 50,000 * 0.0025 = 1250) cmd := []string{"osmosisd", "tx", "concentratedliquidity", "create-position", fmt.Sprint(poolId), lowerTick, upperTick, tokens, fmt.Sprintf("%d", token0MinAmt), fmt.Sprintf("%d", token1MinAmt), fmt.Sprintf("--from=%s", from), "--gas=500000", "--fees=1250uosmo", "-o json"} - outJson, _, err := n.containerManager.ExecTxCmdWithSuccessString(n.t, n.chainId, n.Name, cmd, "code\":0") - require.NoError(n.t, err) - - var txResponse map[string]interface{} - err = json.Unmarshal(outJson.Bytes(), &txResponse) + resp, _, err := n.containerManager.ExecTxCmdWithSuccessString(n.t, n.chainId, n.Name, cmd, "code\":0") require.NoError(n.t, err) - positionIDString, err := GetPositionID(txResponse) - require.NoError(n.t, err) - - positionID, err := strconv.ParseUint(positionIDString, 10, 64) + positionID, err := extractPositionIdFromResponse(resp.Bytes()) require.NoError(n.t, err) n.LogActionF("successfully created concentrated position from %s to %s", lowerTick, upperTick) @@ -143,14 +117,10 @@ func (n *NodeConfig) StoreWasmCode(wasmFile, from string) int { cmd := []string{"osmosisd", "tx", "wasm", "store", wasmFile, fmt.Sprintf("--from=%s", from), "--gas=auto", "--gas-prices=0.1uosmo", "--gas-adjustment=1.3"} resp, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) - startIndex := strings.Index(resp.String(), `{"key":"code_id","value":"`) + len(`{"key":"code_id","value":"`) - endIndex := strings.Index(resp.String()[startIndex:], `"`) - // Extract the proposal ID substring - codeIdStr := resp.String()[startIndex : startIndex+endIndex] + codeId, err := extractCodeIdFromResponse(resp.String()) + require.NoError(n.t, err) - // Convert the proposal ID from string to int - codeId, _ := strconv.Atoi(codeIdStr) n.LogActionF("successfully stored") return codeId } @@ -235,17 +205,10 @@ func (n *NodeConfig) SubmitParamChangeProposal(proposalJson, from string) int { resp, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) - err = os.Remove(localProposalFile) - require.NoError(n.t, err) - - startIndex := strings.Index(resp.String(), `[{"key":"proposal_id","value":"`) + len(`[{"key":"proposal_id","value":"`) - endIndex := strings.Index(resp.String()[startIndex:], `"`) + os.Remove(localProposalFile) - // Extract the proposal ID substring - proposalIDStr := resp.String()[startIndex : startIndex+endIndex] - - // Convert the proposal ID from string to int - proposalID, _ := strconv.Atoi(proposalIDStr) + proposalID, err := extractProposalIdFromResponse(resp.String()) + require.NoError(n.t, err) n.LogActionF("successfully submitted param change proposal") @@ -255,8 +218,8 @@ func (n *NodeConfig) SubmitParamChangeProposal(proposalJson, from string) int { func (n *NodeConfig) SendIBCTransfer(dstChain *Config, from, recipient, memo string, token sdk.Coin) { n.LogActionF("IBC sending %s from %s to %s. memo: %s", token.Amount.String(), from, recipient, memo) - cmd := []string{"hermes", "tx", "ft-transfer", "--dst-chain", dstChain.Id, "--src-chain", n.chainId, "--src-port", "transfer", "--src-channel", "channel-0", "--amount", token.Amount.String(), fmt.Sprintf("--denom=%s", token.Denom), fmt.Sprintf("--receiver=%s", recipient), "--timeout-height-offset=1000", "--memo", memo} - _, _, err := n.containerManager.ExecHermesCmd(n.t, cmd, "SUCCESS") + cmd := []string{"osmosisd", "tx", "ibc-transfer", "transfer", "transfer", "channel-0", recipient, token.String(), fmt.Sprintf("--from=%s", from), "--memo", memo} + _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) cmd = []string{"hermes", "clear", "packets", "--chain", dstChain.Id, "--port", "transfer", "--channel", "channel-0"} @@ -274,7 +237,6 @@ func (n *NodeConfig) FailIBCTransfer(from, recipient, amount string) { n.LogActionF("IBC sending %s from %s to %s", amount, from, recipient) cmd := []string{"osmosisd", "tx", "ibc-transfer", "transfer", "transfer", "channel-0", recipient, amount, fmt.Sprintf("--from=%s", from)} - _, _, err := n.containerManager.ExecTxCmdWithSuccessString(n.t, n.chainId, n.Name, cmd, "rate limit exceeded") require.NoError(n.t, err) @@ -310,12 +272,19 @@ func (n *NodeConfig) ExitPool(from, minAmountsOut string, poolId uint64, shareAm n.LogActionF("successfully exited pool %d, minAmountsOut %s, shareAmountIn %s", poolId, minAmountsOut, shareAmountIn) } -func (n *NodeConfig) SubmitUpgradeProposal(upgradeVersion string, upgradeHeight int64, initialDeposit sdk.Coin) { +func (n *NodeConfig) SubmitUpgradeProposal(upgradeVersion string, upgradeHeight int64, initialDeposit sdk.Coin) int { n.LogActionF("submitting upgrade proposal %s for height %d", upgradeVersion, upgradeHeight) cmd := []string{"osmosisd", "tx", "gov", "submit-proposal", "software-upgrade", upgradeVersion, fmt.Sprintf("--title=\"%s upgrade\"", upgradeVersion), "--description=\"upgrade proposal submission\"", fmt.Sprintf("--upgrade-height=%d", upgradeHeight), "--upgrade-info=\"\"", "--from=val", fmt.Sprintf("--deposit=%s", initialDeposit)} - _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + resp, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + require.NoError(n.t, err) + + proposalID, err := extractProposalIdFromResponse(resp.String()) + require.NoError(n.t, err) + require.NoError(n.t, err) n.LogActionF("successfully submitted upgrade proposal") + + return proposalID } func (n *NodeConfig) SubmitSuperfluidProposal(asset string, initialDeposit sdk.Coin) int { @@ -324,15 +293,8 @@ func (n *NodeConfig) SubmitSuperfluidProposal(asset string, initialDeposit sdk.C resp, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) - // Extract the proposal ID from the response - startIndex := strings.Index(resp.String(), `[{"key":"proposal_id","value":"`) + len(`[{"key":"proposal_id","value":"`) - endIndex := strings.Index(resp.String()[startIndex:], `"`) - - // Extract the proposal ID substring - proposalIDStr := resp.String()[startIndex : startIndex+endIndex] - - // Convert the proposal ID from string to int - proposalID, _ := strconv.Atoi(proposalIDStr) + proposalID, err := extractProposalIdFromResponse(resp.String()) + require.NoError(n.t, err) n.LogActionF("successfully submitted superfluid proposal for asset %s", asset) @@ -344,15 +306,9 @@ func (n *NodeConfig) SubmitCreateConcentratedPoolProposal(initialDeposit sdk.Coi cmd := []string{"osmosisd", "tx", "gov", "submit-proposal", "create-concentratedliquidity-pool-proposal", "--pool-records=stake,uosmo,100,0.001", "--title=\"create concentrated pool\"", "--description=\"create concentrated pool", "--from=val", fmt.Sprintf("--deposit=%s", initialDeposit)} resp, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) - // Extract the proposal ID from the response - startIndex := strings.Index(resp.String(), `[{"key":"proposal_id","value":"`) + len(`[{"key":"proposal_id","value":"`) - endIndex := strings.Index(resp.String()[startIndex:], `"`) - // Extract the proposal ID substring - proposalIDStr := resp.String()[startIndex : startIndex+endIndex] - - // Convert the proposal ID from string to int - proposalID, _ := strconv.Atoi(proposalIDStr) + proposalID, err := extractProposalIdFromResponse(resp.String()) + require.NoError(n.t, err) n.LogActionF("successfully created a create concentrated liquidity pool proposal") @@ -368,30 +324,29 @@ func (n *NodeConfig) SubmitTextProposal(text string, initialDeposit sdk.Coin, is resp, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) - // Extract the proposal ID from the response - startIndex := strings.Index(resp.String(), `[{"key":"proposal_id","value":"`) + len(`[{"key":"proposal_id","value":"`) - endIndex := strings.Index(resp.String()[startIndex:], `"`) - - // Extract the proposal ID substring - proposalIDStr := resp.String()[startIndex : startIndex+endIndex] - - // Convert the proposal ID from string to int - proposalID, _ := strconv.Atoi(proposalIDStr) + proposalID, err := extractProposalIdFromResponse(resp.String()) + require.NoError(n.t, err) n.LogActionF("successfully submitted text gov proposal") return proposalID } -func (n *NodeConfig) SubmitTickSpacingReductionProposal(poolTickSpacingRecords string, initialDeposit sdk.Coin, isExpedited bool) { +func (n *NodeConfig) SubmitTickSpacingReductionProposal(poolTickSpacingRecords string, initialDeposit sdk.Coin, isExpedited bool) int { n.LogActionF("submitting tick spacing reduction gov proposal") cmd := []string{"osmosisd", "tx", "gov", "submit-proposal", "tick-spacing-decrease-proposal", "--title=\"test tick spacing reduction proposal title\"", "--description=\"test tick spacing reduction proposal\"", "--from=val", fmt.Sprintf("--deposit=%s", initialDeposit), fmt.Sprintf("--pool-tick-spacing-records=%s", poolTickSpacingRecords)} if isExpedited { cmd = append(cmd, "--is-expedited=true") } - _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + resp, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) + + proposalID, err := extractProposalIdFromResponse(resp.String()) + require.NoError(n.t, err) + n.LogActionF("successfully submitted tick spacing reduction gov proposal") + + return proposalID } func (n *NodeConfig) DepositProposal(proposalNumber int, isExpedited bool) { @@ -498,28 +453,49 @@ func (n *NodeConfig) FundCommunityPool(sendAddress string, funds string) { // 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 { +func (n *NodeConfig) CreateWallet(walletName string, chain *Config) string { n.LogActionF("creating wallet %s", walletName) cmd := []string{"osmosisd", "keys", "add", walletName, "--keyring-backend=test"} - outBuf, _, err := n.containerManager.ExecCmd(n.t, n.Name, cmd, "") + outBuf, errBuf, err := n.containerManager.ExecCmd(n.t, n.Name, cmd, "") require.NoError(n.t, err) re := regexp.MustCompile("osmo1(.{38})") walletAddr := fmt.Sprintf("%s\n", re.FindString(outBuf.String())) walletAddr = strings.TrimSuffix(walletAddr, "\n") + + mnemonic, err := pullMnemonicFromResponse(errBuf.String()) + require.NoError(n.t, err) + + chainNodes := chain.GetAllChainNodes() + for _, node := range chainNodes { + if node.Name == n.Name { + continue + } + node.AddExistingWallet(walletName, mnemonic) + } + 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 { - return n.CreateWalletAndFundFrom(walletName, initialization.ValidatorWalletName, tokensToFund) +func (n *NodeConfig) AddExistingWallet(walletName, mnemonic string) { + n.LogActionF("adding existing wallet %s", walletName) + cmd := []string{"sh", "-c", fmt.Sprintf("echo '%s' | osmosisd keys add %s --keyring-backend=test --recover", mnemonic, walletName)} + _, _, err := n.containerManager.ExecCmd(n.t, n.Name, cmd, "") + require.NoError(n.t, err) + + n.LogActionF("added existing wallet %s", walletName) } -func (n *NodeConfig) CreateWalletAndFundFrom(newWalletName string, fundingWalletName string, tokensToFund []string) string { +func (n *NodeConfig) CreateWalletAndFund(walletName string, tokensToFund []string, chain *Config) string { + return n.CreateWalletAndFundFrom(walletName, initialization.ValidatorWalletName, tokensToFund, chain) +} + +func (n *NodeConfig) CreateWalletAndFundFrom(newWalletName string, fundingWalletName string, tokensToFund []string, chain *Config) string { n.LogActionF("Sending tokens to %s", newWalletName) - walletAddr := n.CreateWallet(newWalletName) + walletAddr := n.CreateWallet(newWalletName, chain) for _, tokenToFund := range tokensToFund { n.BankSend(tokenToFund, fundingWalletName, walletAddr) } @@ -642,54 +618,51 @@ func ParseEvent(responseJson map[string]interface{}, field string) (string, erro return "", fmt.Errorf("%s field not found in response", field) } -func (n *NodeConfig) SendIBC(dstChain *Config, recipient string, token sdk.Coin) { +var addrMutexMap = make(map[string]*sync.Mutex) + +func (n *NodeConfig) SendIBC(srcChain, dstChain *Config, recipient string, token sdk.Coin) { n.t.Logf("IBC sending %s from %s to %s (%s)", token, n.chainId, dstChain.Id, recipient) + // We add a mutex here since we don't want multiple IBC transfers to happen at the same time + // Otherwise, when we check if the receiving end has the correct balance, we might get the balance + // of a previous transfer. + sender := n.GetWallet(initialization.ValidatorWalletName) + + // Create or get the mutex for the specific sender and recipient + func() { + if _, exists := addrMutexMap[recipient]; !exists { + addrMutexMap[recipient] = &sync.Mutex{} + } + if _, exists := addrMutexMap[sender]; !exists { + addrMutexMap[sender] = &sync.Mutex{} + } + }() + + // Lock the mutex for the specific sender and recipient + addrMutexMap[recipient].Lock() + defer addrMutexMap[recipient].Unlock() + addrMutexMap[sender].Lock() + defer addrMutexMap[sender].Unlock() dstNode, err := dstChain.GetDefaultNode() require.NoError(n.t, err) - // removes the fee token from balances for calculating the difference in other tokens - // before and after the IBC send. Since we run tests in parallel now, some tests may - // send uosmo between accounts while this test is running. Since we don't care about - // non ibc denoms, its safe to filter uosmo out. - removeFeeTokenFromBalance := func(balance sdk.Coins) sdk.Coins { - filteredCoinDenoms := []string{} - for _, coin := range balance { - if !strings.HasPrefix(coin.Denom, "ibc/") { - filteredCoinDenoms = append(filteredCoinDenoms, coin.Denom) - } - } - feeRewardTokenBalance := balance.FilterDenoms(filteredCoinDenoms) - return balance.Sub(feeRewardTokenBalance) - } + denomTrace := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", "channel-0", token.Denom)) + ibcDenom := denomTrace.IBCDenom() - balancesDstPreWithTxFeeBalance, err := dstNode.QueryBalances(recipient) - require.NoError(n.t, err) - fmt.Println("balancesDstPre with no fee removed: ", balancesDstPreWithTxFeeBalance) - balancesDstPre := removeFeeTokenFromBalance(balancesDstPreWithTxFeeBalance) - cmd := []string{"hermes", "tx", "ft-transfer", "--dst-chain", dstChain.Id, "--src-chain", n.chainId, "--src-port", "transfer", "--src-channel", "channel-0", "--amount", token.Amount.String(), fmt.Sprintf("--denom=%s", token.Denom), fmt.Sprintf("--receiver=%s", recipient), "--timeout-height-offset=1000"} - _, _, err = n.containerManager.ExecHermesCmd(n.t, cmd, "SUCCESS") + balancePre, err := dstNode.QueryBalance(recipient, ibcDenom) require.NoError(n.t, err) + n.SendIBCTransfer(dstChain, sender, recipient, "", token) + require.Eventually( n.t, func() bool { - balancesDstPostWithTxFeeBalance, err := dstNode.QueryBalances(recipient) + balancePost, err := dstNode.QueryBalance(recipient, ibcDenom) require.NoError(n.t, err) - balancesDstPost := removeFeeTokenFromBalance(balancesDstPostWithTxFeeBalance) - - ibcCoin := balancesDstPost.Sub(balancesDstPre) - if ibcCoin.Len() == 1 { - tokenPre := balancesDstPre.AmountOfNoDenomValidation(ibcCoin[0].Denom) - tokenPost := balancesDstPost.AmountOfNoDenomValidation(ibcCoin[0].Denom) - resPre := token.Amount - resPost := tokenPost.Sub(tokenPre) - return resPost.Uint64() == resPre.Uint64() - } else { - return false - } + + return balancePost.Amount.Equal(balancePre.Amount.Add(token.Amount)) }, - time.Minute, + 2*time.Minute, 10*time.Millisecond, "tx not received on destination chain", ) @@ -699,7 +672,6 @@ func (n *NodeConfig) SendIBC(dstChain *Config, recipient string, token sdk.Coin) func (n *NodeConfig) EnableSuperfluidAsset(srcChain *Config, denom string) { propNumber := n.SubmitSuperfluidProposal(denom, sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(config.InitialMinDeposit))) - srcChain.LatestProposalNumber += 1 n.DepositProposal(propNumber, false) var wg sync.WaitGroup @@ -718,15 +690,13 @@ func (n *NodeConfig) EnableSuperfluidAsset(srcChain *Config, denom string) { func (n *NodeConfig) LockAndAddToExistingLock(srcChain *Config, amount sdk.Int, denom, lockupWalletAddr, lockupWalletSuperfluidAddr string) { // lock tokens lockID := n.LockTokens(fmt.Sprintf("%v%s", amount, denom), "240s", lockupWalletAddr) - srcChain.LatestLockNumber += 1 - fmt.Println("lock number: ", lockID) + // add to existing lock n.AddToExistingLock(amount, denom, "240s", lockupWalletAddr, lockID) // superfluid lock tokens lockID = n.LockTokens(fmt.Sprintf("%v%s", amount, denom), "240s", lockupWalletSuperfluidAddr) - srcChain.LatestLockNumber += 1 - fmt.Println("lock number: ", lockID) + n.SuperfluidDelegate(lockID, srcChain.NodeConfigs[1].OperatorAddress, lockupWalletSuperfluidAddr) // add to existing lock n.AddToExistingLock(amount, denom, "240s", lockupWalletSuperfluidAddr, lockID) @@ -804,12 +774,7 @@ func (n *NodeConfig) ParamChangeProposal(subspace, key string, value []byte, cha return err } - node, err := chain.GetDefaultNode() - if err != nil { - return err - } - propNumber := node.SubmitParamChangeProposal(string(proposalJson), initialization.ValidatorWalletName) - chain.LatestProposalNumber += 1 + propNumber := n.SubmitParamChangeProposal(string(proposalJson), initialization.ValidatorWalletName) var wg sync.WaitGroup @@ -824,7 +789,7 @@ func (n *NodeConfig) ParamChangeProposal(subspace, key string, value []byte, cha wg.Wait() require.Eventually(n.t, func() bool { - status, err := node.QueryPropStatus(propNumber) + status, err := n.QueryPropStatus(propNumber) if err != nil { return false } @@ -832,3 +797,94 @@ func (n *NodeConfig) ParamChangeProposal(subspace, key string, value []byte, cha }, time.Minute, 10*time.Millisecond) return nil } + +func extractProposalIdFromResponse(response string) (int, error) { + // Extract the proposal ID from the response + startIndex := strings.Index(response, `[{"key":"proposal_id","value":"`) + len(`[{"key":"proposal_id","value":"`) + endIndex := strings.Index(response[startIndex:], `"`) + + // Extract the proposal ID substring + proposalIDStr := response[startIndex : startIndex+endIndex] + + // Convert the proposal ID from string to int + proposalID, err := strconv.Atoi(proposalIDStr) + if err != nil { + return 0, err + } + + return proposalID, nil +} + +func extractPoolIdFromResponse(response string) (uint64, error) { + // Extract the pool ID from the response + startIndex := strings.Index(response, `{"key":"pool_id","value":"`) + len(`{"key":"pool_id","value":"`) + endIndex := strings.Index(response[startIndex:], `"`) + + // Extract the pool ID substring + codeIdStr := response[startIndex : startIndex+endIndex] + + // Convert the pool ID from string to int + poolID, err := strconv.ParseUint(codeIdStr, 10, 64) + if err != nil { + return 0, err + } + + return poolID, nil +} + +func extractPositionIdFromResponse(responseBytes []byte) (uint64, error) { + var txResponse map[string]interface{} + err := json.Unmarshal(responseBytes, &txResponse) + if err != nil { + return 0, err + } + + positionIDString, err := GetPositionID(txResponse) + if err != nil { + return 0, err + } + + positionID, err := strconv.ParseUint(positionIDString, 10, 64) + if err != nil { + return 0, err + } + + return positionID, nil +} + +func extractCodeIdFromResponse(response string) (int, error) { + startIndex := strings.Index(response, `{"key":"code_id","value":"`) + len(`{"key":"code_id","value":"`) + endIndex := strings.Index(response[startIndex:], `"`) + + // Extract the proposal ID substring + codeIdStr := response[startIndex : startIndex+endIndex] + + // Convert the proposal ID from string to int + codeId, err := strconv.Atoi(codeIdStr) + if err != nil { + return 0, err + } + + return codeId, nil +} + +func pullMnemonicFromResponse(response string) (string, error) { + // Using regex to get mnemonic + r := regexp.MustCompile(`(?m)(?i)^(\w+\s){23}\w+$`) + mnemonicMatch := r.FindString(response) + + // Check if we found the mnemonic + if mnemonicMatch == "" { + return "", errors.New("mnemonic not found") + } + + // Split the mnemonicMatch on spaces to get individual words + mnemonicWords := strings.Split(mnemonicMatch, " ") + + // Check if we got 24 words + if len(mnemonicWords) != 24 { + return "", errors.New("mnemonic does not contain 24 words") + } + + return mnemonicMatch, nil +} diff --git a/tests/e2e/configurer/chain/queries.go b/tests/e2e/configurer/chain/queries.go index 67483c74c08..436db9a9075 100644 --- a/tests/e2e/configurer/chain/queries.go +++ b/tests/e2e/configurer/chain/queries.go @@ -339,6 +339,18 @@ func (n *NodeConfig) QueryBalances(address string) (sdk.Coins, error) { return balancesResp.GetBalances(), nil } +func (n *NodeConfig) QueryBalance(address, denom string) (sdk.Coin, error) { + path := fmt.Sprintf("cosmos/bank/v1beta1/balances/%s/by_denom?denom=%s", address, denom) + bz, err := n.QueryGRPCGateway(path) + require.NoError(n.t, err) + + var balancesResp banktypes.QueryBalanceResponse + if err := util.Cdc.UnmarshalJSON(bz, &balancesResp); err != nil { + return sdk.Coin{}, err + } + return *balancesResp.GetBalance(), nil +} + func (n *NodeConfig) QuerySupplyOf(denom string) (sdk.Int, error) { path := fmt.Sprintf("cosmos/bank/v1beta1/supply/%s", denom) bz, err := n.QueryGRPCGateway(path) diff --git a/tests/e2e/configurer/config/constants.go b/tests/e2e/configurer/config/constants.go index de1c38af4ff..28713db9edd 100644 --- a/tests/e2e/configurer/config/constants.go +++ b/tests/e2e/configurer/config/constants.go @@ -33,15 +33,15 @@ var ( DaiOsmoPoolIdv16 uint64 // A pool created via CLI before starting an // upgrade. - PreUpgradePoolId uint64 + PreUpgradePoolId = []uint64{} - PreUpgradeStableSwapPoolId uint64 + PreUpgradeStableSwapPoolId = []uint64{} - StrideMigrateWallet = "stride-migration" + StrideMigrateWallet = []string{"stride-migration", "stride-migration"} - LockupWallet = "lockup-wallet" + LockupWallet = []string{"lockup-wallet", "lockup-wallet"} - LockupWalletSuperfluid = "lockup-wallet-superfluid" + LockupWalletSuperfluid = []string{"lockup-wallet-superfluid", "lockup-wallet-superfluid"} - StableswapWallet = "stableswap-wallet" + StableswapWallet = []string{"stableswap-wallet", "stableswap-wallet"} ) diff --git a/tests/e2e/configurer/upgrade.go b/tests/e2e/configurer/upgrade.go index bdddce8015d..547fc750c38 100644 --- a/tests/e2e/configurer/upgrade.go +++ b/tests/e2e/configurer/upgrade.go @@ -126,14 +126,14 @@ func (uc *UpgradeConfigurer) CreatePreUpgradeState() error { go func() { defer wg.Done() - chainA.SendIBC(chainB, chainB.NodeConfigs[0].PublicAddress, initialization.OsmoToken) - chainA.SendIBC(chainB, chainB.NodeConfigs[0].PublicAddress, initialization.StakeToken) + chainA.SendIBC(chainB, chainBNode.PublicAddress, initialization.OsmoToken) + chainA.SendIBC(chainB, chainBNode.PublicAddress, initialization.StakeToken) }() go func() { defer wg.Done() - chainB.SendIBC(chainA, chainA.NodeConfigs[0].PublicAddress, initialization.OsmoToken) - chainB.SendIBC(chainA, chainA.NodeConfigs[0].PublicAddress, initialization.StakeToken) + chainB.SendIBC(chainA, chainANode.PublicAddress, initialization.OsmoToken) + chainB.SendIBC(chainA, chainANode.PublicAddress, initialization.StakeToken) }() // Wait for all goroutines to complete @@ -145,6 +145,8 @@ func (uc *UpgradeConfigurer) CreatePreUpgradeState() error { wg.Add(4) + // Chain A + go func() { defer wg.Done() chainANode.FundCommunityPool(initialization.ValidatorWalletName, strAllUpgradeBaseDenoms()) @@ -152,12 +154,14 @@ func (uc *UpgradeConfigurer) CreatePreUpgradeState() error { go func() { defer wg.Done() - chainBNode.FundCommunityPool(initialization.ValidatorWalletName, strAllUpgradeBaseDenoms()) + chainANode.EnableSuperfluidAsset(chainA, v17SuperfluidAssets) }() + // Chain B + go func() { defer wg.Done() - chainANode.EnableSuperfluidAsset(chainA, v17SuperfluidAssets) + chainBNode.FundCommunityPool(initialization.ValidatorWalletName, strAllUpgradeBaseDenoms()) }() go func() { @@ -170,34 +174,40 @@ func (uc *UpgradeConfigurer) CreatePreUpgradeState() error { // END: CAN REMOVE POST v17 UPGRADE var ( - poolShareDenom string - preUpgradePoolId uint64 - preUpgradeStableSwapPoolId uint64 + poolShareDenom = make([]string, 2) + preUpgradePoolId = make([]uint64, 2) + preUpgradeStableSwapPoolId = make([]uint64, 2) ) // Increment the WaitGroup counter for each goroutine wg.Add(4) + // Chain A + go func() { defer wg.Done() - preUpgradePoolId = chainANode.CreateBalancerPool("pool1A.json", initialization.ValidatorWalletName) - poolShareDenom = fmt.Sprintf("gamm/pool/%d", preUpgradePoolId) - chainANode.EnableSuperfluidAsset(chainA, poolShareDenom) + preUpgradePoolId[0] = chainANode.CreateBalancerPool("pool1A.json", initialization.ValidatorWalletName) + poolShareDenom[0] = fmt.Sprintf("gamm/pool/%d", preUpgradePoolId[0]) + chainANode.EnableSuperfluidAsset(chainA, poolShareDenom[0]) }() go func() { defer wg.Done() - chainBNode.CreateBalancerPool("pool1B.json", initialization.ValidatorWalletName) + preUpgradeStableSwapPoolId[0] = chainANode.CreateStableswapPool("stablePool.json", initialization.ValidatorWalletName) }() + // Chain B + go func() { defer wg.Done() - preUpgradeStableSwapPoolId = chainANode.CreateStableswapPool("stablePool.json", initialization.ValidatorWalletName) + preUpgradePoolId[1] = chainBNode.CreateBalancerPool("pool1B.json", initialization.ValidatorWalletName) + poolShareDenom[1] = fmt.Sprintf("gamm/pool/%d", preUpgradePoolId[1]) + chainBNode.EnableSuperfluidAsset(chainB, poolShareDenom[1]) }() go func() { defer wg.Done() - chainBNode.CreateStableswapPool("stablePool.json", initialization.ValidatorWalletName) + preUpgradeStableSwapPoolId[1] = chainBNode.CreateStableswapPool("stablePool.json", initialization.ValidatorWalletName) }() // Wait for all goroutines to complete @@ -207,53 +217,77 @@ func (uc *UpgradeConfigurer) CreatePreUpgradeState() error { config.PreUpgradeStableSwapPoolId = preUpgradeStableSwapPoolId var ( - lockupWallet string - lockupWalletSuperfluid string - stableswapWallet string + lockupWallet = make([]string, 2) + lockupWalletSuperfluid = make([]string, 2) + stableswapWallet = make([]string, 2) ) - wg.Add(3) + wg.Add(6) + // Chain A go func() { defer wg.Done() // Setup wallets and send tokens to wallets (only chainA) - lockupWallet = chainANode.CreateWalletAndFund(config.LockupWallet, []string{ - "10000000000000000000" + poolShareDenom, - }) + lockupWallet[0] = chainANode.CreateWalletAndFund(config.LockupWallet[0], []string{ + "10000000000000000000" + poolShareDenom[0], + }, chainA) }() go func() { defer wg.Done() - lockupWalletSuperfluid = chainANode.CreateWalletAndFund(config.LockupWalletSuperfluid, []string{ - "10000000000000000000" + poolShareDenom, - }) + lockupWalletSuperfluid[0] = chainANode.CreateWalletAndFund(config.LockupWalletSuperfluid[0], []string{ + "10000000000000000000" + poolShareDenom[0], + }, chainA) }() go func() { defer wg.Done() - stableswapWallet = chainANode.CreateWalletAndFund(config.StableswapWallet, []string{ + stableswapWallet[0] = chainANode.CreateWalletAndFund(config.StableswapWallet[0], []string{ "100000stake", - }) + }, chainA) + }() + + // Chain B + go func() { + defer wg.Done() + // Setup wallets and send tokens to wallets (only chainA) + lockupWallet[1] = chainBNode.CreateWalletAndFund(config.LockupWallet[1], []string{ + "10000000000000000000" + poolShareDenom[1], + }, chainB) + }() + + go func() { + defer wg.Done() + lockupWalletSuperfluid[1] = chainBNode.CreateWalletAndFund(config.LockupWalletSuperfluid[1], []string{ + "10000000000000000000" + poolShareDenom[1], + }, chainB) + }() + + go func() { + defer wg.Done() + stableswapWallet[1] = chainBNode.CreateWalletAndFund(config.StableswapWallet[1], []string{ + "100000stake", + }, chainB) }() - // Wait for all goroutines to complete wg.Wait() config.LockupWallet = lockupWallet config.LockupWalletSuperfluid = lockupWalletSuperfluid config.StableswapWallet = stableswapWallet - wg.Add(4) + wg.Add(6) var errCh = make(chan error, 2) + // Chain A + go func() { defer wg.Done() - // test swap exact amount in for stable swap pool (only chainA)A - chainANode.SwapExactAmountIn("2000stake", "1", fmt.Sprintf("%d", config.PreUpgradeStableSwapPoolId), "uosmo", config.StableswapWallet) + // test swap exact amount in for stable swap pool + chainANode.SwapExactAmountIn("2000stake", "1", fmt.Sprintf("%d", config.PreUpgradeStableSwapPoolId[0]), "uosmo", config.StableswapWallet[0]) }() - // Upload the rate limiting contract to both chains (as they both will be updated) go func() { defer wg.Done() uc.t.Logf("Uploading rate limiting contract to chainA") @@ -261,6 +295,20 @@ func (uc *UpgradeConfigurer) CreatePreUpgradeState() error { errCh <- err }() + go func() { + defer wg.Done() + uc.t.Logf("Lock and add to existing lock for both regular and superfluid lockups on chainA") + chainANode.LockAndAddToExistingLock(chainA, sdk.NewInt(1000000000000000000), poolShareDenom[0], config.LockupWallet[0], config.LockupWalletSuperfluid[0]) + }() + + // Chain B + + go func() { + defer wg.Done() + // test swap exact amount in for stable swap pool + chainBNode.SwapExactAmountIn("2000stake", "1", fmt.Sprintf("%d", config.PreUpgradeStableSwapPoolId[1]), "uosmo", config.StableswapWallet[1]) + }() + go func() { defer wg.Done() uc.t.Logf("Uploading rate limiting contract to chainB") @@ -270,8 +318,8 @@ func (uc *UpgradeConfigurer) CreatePreUpgradeState() error { go func() { defer wg.Done() - uc.t.Logf("Lock and add to existing lock for both regular and superfluid lockups on chainA") - chainANode.LockAndAddToExistingLock(chainA, sdk.NewInt(1000000000000000000), poolShareDenom, config.LockupWallet, config.LockupWalletSuperfluid) + uc.t.Logf("Lock and add to existing lock for both regular and superfluid lockups on chainB") + chainBNode.LockAndAddToExistingLock(chainB, sdk.NewInt(1000000000000000000), poolShareDenom[1], config.LockupWallet[1], config.LockupWalletSuperfluid[1]) }() wg.Wait() @@ -332,10 +380,9 @@ func (uc *UpgradeConfigurer) runProposalUpgrade() error { return err } chainConfig.UpgradePropHeight = currentHeight + int64(chainConfig.VotingPeriod) + int64(config.PropSubmitBlocks) + int64(config.PropBufferBlocks) - node.SubmitUpgradeProposal(uc.upgradeVersion, chainConfig.UpgradePropHeight, sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(config.InitialMinDeposit))) - chainConfig.LatestProposalNumber += 1 - node.DepositProposal(chainConfig.LatestProposalNumber, false) - propNumber := chainConfig.LatestProposalNumber + propNumber := node.SubmitUpgradeProposal(uc.upgradeVersion, chainConfig.UpgradePropHeight, sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(config.InitialMinDeposit))) + + node.DepositProposal(propNumber, false) var wg sync.WaitGroup diff --git a/tests/e2e/containers/config.go b/tests/e2e/containers/config.go index 8746ddd5946..d6f24d3b01f 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 = "16.0" + previousVersionOsmoTag = "16.0-alpine" // Pre-upgrade repo/tag for osmosis initialization (this should be one version below upgradeVersion) previousVersionInitRepository = "osmolabs/osmosis-e2e-init-chain" - previousVersionInitTag = "v16.x-f34d07f8-1690568248" + previousVersionInitTag = "v16-faster-epoch" // Hermes repo/version for relayer relayerRepository = "informalsystems/hermes" relayerTag = "1.5.1" diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 18fc4cd6a6b..79a2bca1e96 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -43,9 +43,6 @@ var ( // TODO: Find more scalable way to do this func (s *IntegrationTestSuite) TestAllE2E() { - // There appears to be an E2E quirk that requires a sleep here - time.Sleep(3 * time.Second) - // Zero Dependent Tests s.T().Run("CreateConcentratedLiquidityPoolVoting_And_TWAP", func(t *testing.T) { t.Parallel() @@ -201,7 +198,11 @@ func (s *IntegrationTestSuite) ProtoRev() { epochIdentifier = "day" ) - chainA, chainANode := s.getChainACfgs() + // NOTE: Uses chainA since IBC denoms are hard coded. + chainA, chainANode, err := s.getChainACfgs() + s.Require().NoError(err) + + sender := chainANode.GetWallet(initialization.ValidatorWalletName) // --------------- Module init checks ---------------- // // The module should be enabled by default. @@ -256,9 +257,9 @@ func (s *IntegrationTestSuite) ProtoRev() { chainA.WaitForNumEpochs(1, epochIdentifier) // Create a wallet to use for the swap txs. - swapWalletAddr := chainANode.CreateWallet(walletName) + swapWalletAddr := chainANode.CreateWallet(walletName, chainA) coinIn := fmt.Sprintf("%s%s", amount, denomIn) - chainANode.BankSend(coinIn, chainA.NodeConfigs[0].PublicAddress, swapWalletAddr) + chainANode.BankSend(coinIn, sender, swapWalletAddr) // Check supplies before swap. supplyBefore, err := chainANode.QuerySupply() @@ -303,7 +304,6 @@ func (s *IntegrationTestSuite) ProtoRev() { } func (s *IntegrationTestSuite) ConcentratedLiquidity() { - chainA, chainANode := s.getChainACfgs() var ( denom0 = "uion" denom1 = "uosmo" @@ -312,27 +312,29 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { spreadFactorDec = sdk.MustNewDecFromStr("0.001") ) + chainAB, chainABNode, err := s.getChainCfgs() + s.Require().NoError(err) + // Get the permisionless pool creation parameter. - isPermisionlessCreationEnabledStr := chainANode.QueryParams(cltypes.ModuleName, string(cltypes.KeyIsPermisionlessPoolCreationEnabled)) + isPermisionlessCreationEnabledStr := chainABNode.QueryParams(cltypes.ModuleName, string(cltypes.KeyIsPermisionlessPoolCreationEnabled)) if !strings.EqualFold(isPermisionlessCreationEnabledStr, "false") { s.T().Fatal("concentrated liquidity pool creation is enabled when should not have been") } // Change the parameter to enable permisionless pool creation. - err := chainANode.ParamChangeProposal("concentratedliquidity", string(cltypes.KeyIsPermisionlessPoolCreationEnabled), []byte("true"), chainA) + err = chainABNode.ParamChangeProposal("concentratedliquidity", string(cltypes.KeyIsPermisionlessPoolCreationEnabled), []byte("true"), chainAB) s.Require().NoError(err) // Confirm that the parameter has been changed. - isPermisionlessCreationEnabledStr = chainANode.QueryParams(cltypes.ModuleName, string(cltypes.KeyIsPermisionlessPoolCreationEnabled)) + isPermisionlessCreationEnabledStr = chainABNode.QueryParams(cltypes.ModuleName, string(cltypes.KeyIsPermisionlessPoolCreationEnabled)) if !strings.EqualFold(isPermisionlessCreationEnabledStr, "true") { s.T().Fatal("concentrated liquidity pool creation is not enabled") } // Create concentrated liquidity pool when permisionless pool creation is enabled. - poolID, err := chainANode.CreateConcentratedPool(initialization.ValidatorWalletName, denom0, denom1, tickSpacing, spreadFactor) - s.Require().NoError(err) + poolID := chainABNode.CreateConcentratedPool(initialization.ValidatorWalletName, denom0, denom1, tickSpacing, spreadFactor) - concentratedPool := s.updatedConcentratedPool(chainANode, poolID) + concentratedPool := s.updatedConcentratedPool(chainABNode, poolID) // Sanity check that pool initialized with valid parameters (the ones that we haven't explicitly specified) s.Require().Equal(concentratedPool.GetCurrentTick(), int64(0)) @@ -350,27 +352,27 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { fundTokens := []string{"100000000uosmo", "100000000uion", "100000000stake"} // Get 3 addresses to create positions - address1 := chainANode.CreateWalletAndFund("addr1", fundTokens) - address2 := chainANode.CreateWalletAndFund("addr2", fundTokens) - address3 := chainANode.CreateWalletAndFund("addr3", fundTokens) + address1 := chainABNode.CreateWalletAndFund("addr1", fundTokens, chainAB) + address2 := chainABNode.CreateWalletAndFund("addr2", fundTokens, chainAB) + address3 := chainABNode.CreateWalletAndFund("addr3", fundTokens, chainAB) // Create 2 positions for address1: overlap together, overlap with 2 address3 positions - chainANode.CreateConcentratedPosition(address1, "[-120000]", "40000", fmt.Sprintf("10000000%s,10000000%s", denom0, denom1), 0, 0, poolID) - chainANode.CreateConcentratedPosition(address1, "[-40000]", "120000", fmt.Sprintf("10000000%s,10000000%s", denom0, denom1), 0, 0, poolID) + chainABNode.CreateConcentratedPosition(address1, "[-120000]", "40000", fmt.Sprintf("10000000%s,10000000%s", denom0, denom1), 0, 0, poolID) + chainABNode.CreateConcentratedPosition(address1, "[-40000]", "120000", fmt.Sprintf("10000000%s,10000000%s", denom0, denom1), 0, 0, poolID) // Create 1 position for address2: does not overlap with anything, ends at maximum - chainANode.CreateConcentratedPosition(address2, "220000", fmt.Sprintf("%d", cltypes.MaxTick), fmt.Sprintf("10000000%s,10000000%s", denom0, denom1), 0, 0, poolID) + chainABNode.CreateConcentratedPosition(address2, "220000", fmt.Sprintf("%d", cltypes.MaxTick), fmt.Sprintf("10000000%s,10000000%s", denom0, denom1), 0, 0, poolID) // Create 2 positions for address3: overlap together, overlap with 2 address1 positions, one position starts from minimum - chainANode.CreateConcentratedPosition(address3, "[-160000]", "[-20000]", fmt.Sprintf("10000000%s,10000000%s", denom0, denom1), 0, 0, poolID) - chainANode.CreateConcentratedPosition(address3, fmt.Sprintf("[%d]", cltypes.MinInitializedTick), "140000", fmt.Sprintf("10000000%s,10000000%s", denom0, denom1), 0, 0, poolID) + chainABNode.CreateConcentratedPosition(address3, "[-160000]", "[-20000]", fmt.Sprintf("10000000%s,10000000%s", denom0, denom1), 0, 0, poolID) + chainABNode.CreateConcentratedPosition(address3, fmt.Sprintf("[%d]", cltypes.MinInitializedTick), "140000", fmt.Sprintf("10000000%s,10000000%s", denom0, denom1), 0, 0, poolID) // Get newly created positions - positionsAddress1 := chainANode.QueryConcentratedPositions(address1) - positionsAddress2 := chainANode.QueryConcentratedPositions(address2) - positionsAddress3 := chainANode.QueryConcentratedPositions(address3) + positionsAddress1 := chainABNode.QueryConcentratedPositions(address1) + positionsAddress2 := chainABNode.QueryConcentratedPositions(address2) + positionsAddress3 := chainABNode.QueryConcentratedPositions(address3) - concentratedPool = s.updatedConcentratedPool(chainANode, poolID) + concentratedPool = s.updatedConcentratedPool(chainABNode, poolID) // Assert number of positions per address s.Require().Equal(len(positionsAddress1), 2) @@ -416,7 +418,7 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { uosmoIn_Swap1 = fmt.Sprintf("%suosmo", uosmoInDec_Swap1.SDKDec().String()) ) // Perform swap (not crossing initialized ticks) - chainANode.SwapExactAmountIn(uosmoIn_Swap1, outMinAmt, fmt.Sprintf("%d", poolID), denom0, initialization.ValidatorWalletName) + chainABNode.SwapExactAmountIn(uosmoIn_Swap1, outMinAmt, fmt.Sprintf("%d", poolID), denom0, initialization.ValidatorWalletName) // Calculate and track global spread reward growth for swap 1 spreadRewardGrowthGlobal.AddMut(calculateSpreadRewardGrowthGlobal(uosmoInDec_Swap1.SDKDec(), spreadFactorDec, concentratedPool.GetLiquidity())) @@ -424,7 +426,7 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { liquidityBeforeSwap := concentratedPool.GetLiquidity() sqrtPriceBeforeSwap := concentratedPool.GetCurrentSqrtPrice() - concentratedPool = s.updatedConcentratedPool(chainANode, poolID) + concentratedPool = s.updatedConcentratedPool(chainABNode, poolID) liquidityAfterSwap := concentratedPool.GetLiquidity() sqrtPriceAfterSwap := concentratedPool.GetCurrentSqrtPrice() @@ -442,9 +444,9 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { // Collect SpreadRewards: Swap 1 // Track balances for address1 position1 - addr1BalancesBefore := s.addrBalance(chainANode, address1) - chainANode.CollectSpreadRewards(address1, fmt.Sprint(positionsAddress1[0].Position.PositionId)) - addr1BalancesAfter := s.addrBalance(chainANode, address1) + addr1BalancesBefore := s.addrBalance(chainABNode, address1) + chainABNode.CollectSpreadRewards(address1, fmt.Sprint(positionsAddress1[0].Position.PositionId)) + addr1BalancesAfter := s.addrBalance(chainABNode, address1) // Assert that the balance changed only for tokenIn (uosmo) s.assertBalancesInvariants(addr1BalancesBefore, addr1BalancesAfter, false, true) @@ -516,13 +518,13 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { spreadRewardGrowthGlobal_Swap1 = spreadRewardGrowthGlobal.Clone() ) // Perform a swap - chainANode.SwapExactAmountIn(uosmoIn_Swap2, outMinAmt, fmt.Sprintf("%d", poolID), denom0, initialization.ValidatorWalletName) + chainABNode.SwapExactAmountIn(uosmoIn_Swap2, outMinAmt, fmt.Sprintf("%d", poolID), denom0, initialization.ValidatorWalletName) // Calculate the amount of liquidity of the position that was kicked out during swap (address1 position1) liquidityOfKickedOutPosition := positionsAddress1[0].Position.Liquidity // Update pool and track pool's liquidity - concentratedPool = s.updatedConcentratedPool(chainANode, poolID) + concentratedPool = s.updatedConcentratedPool(chainABNode, poolID) liquidityAfterSwap = concentratedPool.GetLiquidity() @@ -555,9 +557,9 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { // Assert that address1 position1 earned spread rewards only from first swap step // Track balances for address1 position1 - addr1BalancesBefore = s.addrBalance(chainANode, address1) - chainANode.CollectSpreadRewards(address1, fmt.Sprint(positionsAddress1[0].Position.PositionId)) - addr1BalancesAfter = s.addrBalance(chainANode, address1) + addr1BalancesBefore = s.addrBalance(chainABNode, address1) + chainABNode.CollectSpreadRewards(address1, fmt.Sprint(positionsAddress1[0].Position.PositionId)) + addr1BalancesAfter = s.addrBalance(chainABNode, address1) // Assert that the balance changed only for tokenIn (uosmo) s.assertBalancesInvariants(addr1BalancesBefore, addr1BalancesAfter, false, true) @@ -580,9 +582,9 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { // Assert that address3 position2 earned rewards from first and second swaps // Track balance off address3 position2: check that position that has not been kicked out earned full rewards - addr3BalancesBefore := s.addrBalance(chainANode, address3) - chainANode.CollectSpreadRewards(address3, fmt.Sprint(positionsAddress3[1].Position.PositionId)) - addr3BalancesAfter := s.addrBalance(chainANode, address3) + addr3BalancesBefore := s.addrBalance(chainABNode, address3) + chainABNode.CollectSpreadRewards(address3, fmt.Sprint(positionsAddress3[1].Position.PositionId)) + addr3BalancesAfter := s.addrBalance(chainABNode, address3) // Calculate uncollected spread rewards for address3 position2 earned from Swap 1 spreadRewardsUncollectedAddress3Position2_Swap1 := calculateUncollectedSpreadRewards( @@ -661,21 +663,21 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { spreadRewardGrowthInsideAddress1Position1Last = spreadRewardGrowthGlobal_Swap1.Add(spreadRewardCharge_Swap2_Step1) ) // Collect spread rewards for address1 position1 to avoid overhead computations (swap2 already asserted spread rewards are aggregated correctly from multiple swaps) - chainANode.CollectSpreadRewards(address1, fmt.Sprint(positionsAddress1[0].Position.PositionId)) + chainABNode.CollectSpreadRewards(address1, fmt.Sprint(positionsAddress1[0].Position.PositionId)) // Perform a swap - chainANode.SwapExactAmountIn(uionIn_Swap3, outMinAmt, fmt.Sprintf("%d", poolID), denom1, initialization.ValidatorWalletName) + chainABNode.SwapExactAmountIn(uionIn_Swap3, outMinAmt, fmt.Sprintf("%d", poolID), denom1, initialization.ValidatorWalletName) // Assert liquidity of kicked in position was successfully added to the pool - concentratedPool = s.updatedConcentratedPool(chainANode, poolID) + concentratedPool = s.updatedConcentratedPool(chainABNode, poolID) liquidityAfterSwap = concentratedPool.GetLiquidity() s.Require().Equal(liquidityBeforeSwap.Add(positionsAddress1[0].Position.Liquidity), liquidityAfterSwap) // Track balance of address1 - addr1BalancesBefore = s.addrBalance(chainANode, address1) - chainANode.CollectSpreadRewards(address1, fmt.Sprint(positionsAddress1[0].Position.PositionId)) - addr1BalancesAfter = s.addrBalance(chainANode, address1) + addr1BalancesBefore = s.addrBalance(chainABNode, address1) + chainABNode.CollectSpreadRewards(address1, fmt.Sprint(positionsAddress1[0].Position.PositionId)) + addr1BalancesAfter = s.addrBalance(chainABNode, address1) // Assert that the balance changed only for tokenIn (uion) s.assertBalancesInvariants(addr1BalancesBefore, addr1BalancesAfter, true, false) @@ -719,9 +721,9 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { // Assert position that was active throughout the whole swap: // Track balance of address3 - addr3BalancesBefore = s.addrBalance(chainANode, address3) - chainANode.CollectSpreadRewards(address3, fmt.Sprint(positionsAddress3[1].Position.PositionId)) - addr3BalancesAfter = s.addrBalance(chainANode, address3) + addr3BalancesBefore = s.addrBalance(chainABNode, address3) + chainABNode.CollectSpreadRewards(address3, fmt.Sprint(positionsAddress3[1].Position.PositionId)) + addr3BalancesAfter = s.addrBalance(chainABNode, address3) // Assert that the balance changed only for tokenIn (uion) s.assertBalancesInvariants(addr3BalancesBefore, addr3BalancesAfter, true, false) @@ -760,17 +762,17 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { // Assert that positions, which were not included in swaps, were not affected // Address3 Position1: [-160000; -20000] - addr3BalancesBefore = s.addrBalance(chainANode, address3) - chainANode.CollectSpreadRewards(address3, fmt.Sprint(positionsAddress3[0].Position.PositionId)) - addr3BalancesAfter = s.addrBalance(chainANode, address3) + addr3BalancesBefore = s.addrBalance(chainABNode, address3) + chainABNode.CollectSpreadRewards(address3, fmt.Sprint(positionsAddress3[0].Position.PositionId)) + addr3BalancesAfter = s.addrBalance(chainABNode, address3) // Assert that balances did not change for any token s.assertBalancesInvariants(addr3BalancesBefore, addr3BalancesAfter, true, true) // Address2's only position: [220000; 342000] - addr2BalancesBefore := s.addrBalance(chainANode, address2) - chainANode.CollectSpreadRewards(address2, fmt.Sprint(positionsAddress2[0].Position.PositionId)) - addr2BalancesAfter := s.addrBalance(chainANode, address2) + addr2BalancesBefore := s.addrBalance(chainABNode, address2) + chainABNode.CollectSpreadRewards(address2, fmt.Sprint(positionsAddress2[0].Position.PositionId)) + addr2BalancesAfter := s.addrBalance(chainABNode, address2) // Assert the balances did not change for every token s.assertBalancesInvariants(addr2BalancesBefore, addr2BalancesAfter, true, true) @@ -780,41 +782,41 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { // Withdraw Position parameters defaultLiquidityRemoval := "1000" - chainA.WaitForNumHeights(2) + chainAB.WaitForNumHeights(2) // Assert removing some liquidity // address1: check removing some amount of liquidity address1position1liquidityBefore := positionsAddress1[0].Position.Liquidity - chainANode.WithdrawPosition(address1, defaultLiquidityRemoval, positionsAddress1[0].Position.PositionId) + chainABNode.WithdrawPosition(address1, defaultLiquidityRemoval, positionsAddress1[0].Position.PositionId) // assert - positionsAddress1 = chainANode.QueryConcentratedPositions(address1) + positionsAddress1 = chainABNode.QueryConcentratedPositions(address1) s.Require().Equal(address1position1liquidityBefore, positionsAddress1[0].Position.Liquidity.Add(sdk.MustNewDecFromStr(defaultLiquidityRemoval))) // address2: check removing some amount of liquidity address2position1liquidityBefore := positionsAddress2[0].Position.Liquidity - chainANode.WithdrawPosition(address2, defaultLiquidityRemoval, positionsAddress2[0].Position.PositionId) + chainABNode.WithdrawPosition(address2, defaultLiquidityRemoval, positionsAddress2[0].Position.PositionId) // assert - positionsAddress2 = chainANode.QueryConcentratedPositions(address2) + positionsAddress2 = chainABNode.QueryConcentratedPositions(address2) s.Require().Equal(address2position1liquidityBefore, positionsAddress2[0].Position.Liquidity.Add(sdk.MustNewDecFromStr(defaultLiquidityRemoval))) // address3: check removing some amount of liquidity address3position1liquidityBefore := positionsAddress3[0].Position.Liquidity - chainANode.WithdrawPosition(address3, defaultLiquidityRemoval, positionsAddress3[0].Position.PositionId) + chainABNode.WithdrawPosition(address3, defaultLiquidityRemoval, positionsAddress3[0].Position.PositionId) // assert - positionsAddress3 = chainANode.QueryConcentratedPositions(address3) + positionsAddress3 = chainABNode.QueryConcentratedPositions(address3) s.Require().Equal(address3position1liquidityBefore, positionsAddress3[0].Position.Liquidity.Add(sdk.MustNewDecFromStr(defaultLiquidityRemoval))) // Assert removing all liquidity // address2: no more positions left allLiquidityAddress2Position1 := positionsAddress2[0].Position.Liquidity - chainANode.WithdrawPosition(address2, allLiquidityAddress2Position1.String(), positionsAddress2[0].Position.PositionId) - positionsAddress2 = chainANode.QueryConcentratedPositions(address2) + chainABNode.WithdrawPosition(address2, allLiquidityAddress2Position1.String(), positionsAddress2[0].Position.PositionId) + positionsAddress2 = chainABNode.QueryConcentratedPositions(address2) s.Require().Empty(positionsAddress2) // address1: one position left allLiquidityAddress1Position1 := positionsAddress1[0].Position.Liquidity - chainANode.WithdrawPosition(address1, allLiquidityAddress1Position1.String(), positionsAddress1[0].Position.PositionId) - positionsAddress1 = chainANode.QueryConcentratedPositions(address1) + chainABNode.WithdrawPosition(address1, allLiquidityAddress1Position1.String(), positionsAddress1[0].Position.PositionId) + positionsAddress1 = chainABNode.QueryConcentratedPositions(address1) s.Require().Equal(len(positionsAddress1), 1) // Test tick spacing reduction proposal @@ -835,22 +837,19 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { newTickSpacing := cltypes.AuthorizedTickSpacing[indexOfCurrentTickSpacing-1] // Run the tick spacing reduction proposal - chainANode.SubmitTickSpacingReductionProposal(fmt.Sprintf("%d,%d", poolID, newTickSpacing), sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(config.InitialMinExpeditedDeposit)), true) - // TODO: We should remove every instance of prop number inc and just parse from tx response - chainA.LatestProposalNumber += 1 - latestPropNumber := chainA.LatestProposalNumber + propNumber := chainABNode.SubmitTickSpacingReductionProposal(fmt.Sprintf("%d,%d", poolID, newTickSpacing), sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(config.InitialMinExpeditedDeposit)), true) - chainANode.DepositProposal(latestPropNumber, true) + chainABNode.DepositProposal(propNumber, true) totalTimeChan := make(chan time.Duration, 1) - go chainANode.QueryPropStatusTimed(latestPropNumber, "PROPOSAL_STATUS_PASSED", totalTimeChan) + go chainABNode.QueryPropStatusTimed(propNumber, "PROPOSAL_STATUS_PASSED", totalTimeChan) var wg sync.WaitGroup // TODO: create a helper function for all these go routine yes vote calls. - for _, n := range chainA.NodeConfigs { + for _, n := range chainAB.NodeConfigs { wg.Add(1) go func(nodeConfig *chain.NodeConfig) { defer wg.Done() - nodeConfig.VoteYesProposal(initialization.ValidatorWalletName, latestPropNumber) + nodeConfig.VoteYesProposal(initialization.ValidatorWalletName, propNumber) }(n) } @@ -867,7 +866,7 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { } // Check that the tick spacing was reduced to the expected new tick spacing - concentratedPool = s.updatedConcentratedPool(chainANode, poolID) + concentratedPool = s.updatedConcentratedPool(chainABNode, poolID) s.Require().Equal(newTickSpacing, concentratedPool.GetTickSpacing()) } @@ -876,7 +875,12 @@ func (s *IntegrationTestSuite) StableSwapPostUpgrade() { s.T().Skip("Skipping StableSwapPostUpgrade test") } - chainA, chainANode := s.getChainACfgs() + chainAB, chainABNode, err := s.getChainCfgs() + s.Require().NoError(err) + + index := s.getChainIndex(chainAB) + + sender := chainABNode.GetWallet(initialization.ValidatorWalletName) const ( denomA = "stake" @@ -887,13 +891,13 @@ func (s *IntegrationTestSuite) StableSwapPostUpgrade() { coinAIn, coinBIn := fmt.Sprintf("20000%s", denomA), fmt.Sprintf("1%s", denomB) - chainANode.BankSend(initialization.WalletFeeTokens.String(), chainA.NodeConfigs[0].PublicAddress, config.StableswapWallet) - chainANode.BankSend(coinAIn, chainA.NodeConfigs[0].PublicAddress, config.StableswapWallet) - chainANode.BankSend(coinBIn, chainA.NodeConfigs[0].PublicAddress, config.StableswapWallet) + chainABNode.BankSend(initialization.WalletFeeTokens.String(), sender, config.StableswapWallet[index]) + chainABNode.BankSend(coinAIn, sender, config.StableswapWallet[index]) + chainABNode.BankSend(coinBIn, sender, config.StableswapWallet[index]) s.T().Log("performing swaps") - chainANode.SwapExactAmountIn(coinAIn, minAmountOut, fmt.Sprintf("%d", config.PreUpgradeStableSwapPoolId), denomB, config.StableswapWallet) - chainANode.SwapExactAmountIn(coinBIn, minAmountOut, fmt.Sprintf("%d", config.PreUpgradeStableSwapPoolId), denomA, config.StableswapWallet) + chainABNode.SwapExactAmountIn(coinAIn, minAmountOut, fmt.Sprintf("%d", config.PreUpgradeStableSwapPoolId[index]), denomB, config.StableswapWallet[index]) + chainABNode.SwapExactAmountIn(coinBIn, minAmountOut, fmt.Sprintf("%d", config.PreUpgradeStableSwapPoolId[index]), denomA, config.StableswapWallet[index]) } // TestGeometricTwapMigration tests that the geometric twap record @@ -909,24 +913,29 @@ func (s *IntegrationTestSuite) GeometricTwapMigration() { s.T().Skip("Skipping upgrade tests") } - const ( + var ( // Configurations for tests/e2e/scripts/pool1A.json // This pool gets initialized pre-upgrade. minAmountOut = "1" - otherDenom = "ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518" + otherDenom = []string{"ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518", "ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B77878"} migrationWallet = "migration" ) - chainA, chainANode := s.getChainACfgs() + chainAB, chainABNode, err := s.getChainCfgs() + s.Require().NoError(err) + + index := s.getChainIndex(chainAB) + + sender := chainABNode.GetWallet(initialization.ValidatorWalletName) uosmoIn := fmt.Sprintf("1000000%s", "uosmo") - swapWalletAddr := chainANode.CreateWallet(migrationWallet) + swapWalletAddr := chainABNode.CreateWallet(migrationWallet, chainAB) - chainANode.BankSend(uosmoIn, chainA.NodeConfigs[0].PublicAddress, swapWalletAddr) + chainABNode.BankSend(uosmoIn, sender, swapWalletAddr) // Swap to create new twap records on the pool that was created pre-upgrade. - chainANode.SwapExactAmountIn(uosmoIn, minAmountOut, fmt.Sprintf("%d", config.PreUpgradePoolId), otherDenom, swapWalletAddr) + chainABNode.SwapExactAmountIn(uosmoIn, minAmountOut, fmt.Sprintf("%d", config.PreUpgradePoolId[index]), otherDenom[index], swapWalletAddr) } // TestIBCTokenTransfer tests that IBC token transfers work as expected. @@ -935,17 +944,15 @@ func (s *IntegrationTestSuite) IBCTokenTransferAndCreatePool() { if s.skipIBC { s.T().Skip("Skipping IBC tests") } - chainA := s.configurer.GetChainConfig(0) - chainANode, err := chainA.GetNodeAtIndex(1) + chainA, chainANode, err := s.getChainACfgs() s.Require().NoError(err) - chainB := s.configurer.GetChainConfig(1) - chainBNode, err := chainB.GetNodeAtIndex(1) + chainB, chainBNode, err := s.getChainBCfgs() s.Require().NoError(err) - chainANode.SendIBC(chainB, chainB.NodeConfigs[1].PublicAddress, initialization.OsmoToken) - chainBNode.SendIBC(chainA, chainA.NodeConfigs[1].PublicAddress, initialization.OsmoToken) - chainANode.SendIBC(chainB, chainB.NodeConfigs[1].PublicAddress, initialization.StakeToken) - chainBNode.SendIBC(chainA, chainA.NodeConfigs[1].PublicAddress, initialization.StakeToken) + chainANode.SendIBC(chainA, chainB, chainBNode.PublicAddress, initialization.OsmoToken) + chainBNode.SendIBC(chainB, chainA, chainANode.PublicAddress, initialization.OsmoToken) + chainANode.SendIBC(chainA, chainB, chainBNode.PublicAddress, initialization.StakeToken) + chainBNode.SendIBC(chainB, chainA, chainANode.PublicAddress, initialization.StakeToken) chainANode.CreateBalancerPool("ibcDenomPool.json", initialization.ValidatorWalletName) } @@ -958,46 +965,42 @@ func (s *IntegrationTestSuite) IBCTokenTransferAndCreatePool() { // - voting no on the proposal from the delegator wallet // - ensuring that delegator's wallet overwrites the validator's vote func (s *IntegrationTestSuite) SuperfluidVoting() { - chainA := s.configurer.GetChainConfig(0) - chainANode, err := chainA.GetNodeAtIndex(2) + chainAB, chainABNode, err := s.getChainCfgs() s.Require().NoError(err) - poolId := chainANode.CreateBalancerPool("nativeDenomPool.json", chainA.NodeConfigs[2].PublicAddress) + poolId := chainABNode.CreateBalancerPool("nativeDenomPool.json", initialization.ValidatorWalletName) // enable superfluid assets - chainANode.EnableSuperfluidAsset(chainA, fmt.Sprintf("gamm/pool/%d", poolId)) + chainABNode.EnableSuperfluidAsset(chainAB, fmt.Sprintf("gamm/pool/%d", poolId)) // setup wallets and send gamm tokens to these wallets (both chains) - superfluidVotingWallet := chainANode.CreateWallet("TestSuperfluidVoting") - chainANode.BankSend(fmt.Sprintf("10000000000000000000gamm/pool/%d", poolId), chainA.NodeConfigs[2].PublicAddress, superfluidVotingWallet) - lockId := chainANode.LockTokens(fmt.Sprintf("%v%s", sdk.NewInt(1000000000000000000), fmt.Sprintf("gamm/pool/%d", poolId)), "240s", superfluidVotingWallet) - chainA.LatestLockNumber += 1 - chainANode.SuperfluidDelegate(lockId, chainA.NodeConfigs[2].OperatorAddress, superfluidVotingWallet) + superfluidVotingWallet := chainABNode.CreateWallet("TestSuperfluidVoting", chainAB) + chainABNode.BankSend(fmt.Sprintf("10000000000000000000gamm/pool/%d", poolId), initialization.ValidatorWalletName, superfluidVotingWallet) + lockId := chainABNode.LockTokens(fmt.Sprintf("%v%s", sdk.NewInt(1000000000000000000), fmt.Sprintf("gamm/pool/%d", poolId)), "240s", superfluidVotingWallet) + chainABNode.SuperfluidDelegate(lockId, chainABNode.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) - chainA.LatestProposalNumber += 1 + propNumber := chainABNode.SubmitTextProposal("superfluid vote overwrite test", sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(config.InitialMinDeposit)), false) + chainABNode.DepositProposal(propNumber, false) - chainANode.DepositProposal(chainA.LatestProposalNumber, false) - latestPropNumber := chainA.LatestProposalNumber var wg sync.WaitGroup - for _, n := range chainA.NodeConfigs { + for _, n := range chainAB.NodeConfigs { wg.Add(1) go func(nodeConfig *chain.NodeConfig) { defer wg.Done() - nodeConfig.VoteYesProposal(initialization.ValidatorWalletName, latestPropNumber) + nodeConfig.VoteYesProposal(initialization.ValidatorWalletName, propNumber) }(n) } wg.Wait() // set delegator vote to no - chainANode.VoteNoProposal(superfluidVotingWallet, latestPropNumber) + chainABNode.VoteNoProposal(superfluidVotingWallet, propNumber) s.Eventually( func() bool { - noTotal, yesTotal, noWithVetoTotal, abstainTotal, err := chainANode.QueryPropTally(latestPropNumber) + noTotal, yesTotal, noWithVetoTotal, abstainTotal, err := chainABNode.QueryPropTally(propNumber) if err != nil { return false } @@ -1010,13 +1013,13 @@ func (s *IntegrationTestSuite) SuperfluidVoting() { 10*time.Millisecond, "Osmosis node failed to retrieve prop tally", ) - noTotal, _, _, _, _ := chainANode.QueryPropTally(latestPropNumber) + noTotal, _, _, _, _ := chainABNode.QueryPropTally(propNumber) noTotalFinal, err := strconv.Atoi(noTotal.String()) s.NoError(err) s.Eventually( func() bool { - intAccountBalance, err := chainANode.QueryIntermediaryAccount(fmt.Sprintf("gamm/pool/%d", poolId), chainA.NodeConfigs[2].OperatorAddress) + intAccountBalance, err := chainABNode.QueryIntermediaryAccount(fmt.Sprintf("gamm/pool/%d", poolId), chainABNode.OperatorAddress) s.Require().NoError(err) if err != nil { return false @@ -1034,9 +1037,10 @@ func (s *IntegrationTestSuite) SuperfluidVoting() { } func (s *IntegrationTestSuite) CreateConcentratedLiquidityPoolVoting_And_TWAP() { - chainA, chainANode := s.getChainACfgs() + chainAB, chainABNode, err := s.getChainCfgs() + s.Require().NoError(err) - poolId, err := chainA.SubmitCreateConcentratedPoolProposal() + poolId, err := chainAB.SubmitCreateConcentratedPoolProposal(chainABNode) s.NoError(err) fmt.Println("poolId", poolId) @@ -1050,7 +1054,7 @@ func (s *IntegrationTestSuite) CreateConcentratedLiquidityPoolVoting_And_TWAP() var concentratedPool cltypes.ConcentratedPoolExtension s.Eventually( func() bool { - concentratedPool = s.updatedConcentratedPool(chainANode, poolId) + concentratedPool = s.updatedConcentratedPool(chainABNode, poolId) s.Require().Equal(poolmanagertypes.Concentrated, concentratedPool.GetType()) s.Require().Equal(expectedDenom0, concentratedPool.GetToken0()) s.Require().Equal(expectedDenom1, concentratedPool.GetToken1()) @@ -1067,45 +1071,45 @@ func (s *IntegrationTestSuite) CreateConcentratedLiquidityPoolVoting_And_TWAP() fundTokens := []string{"100000000stake", "100000000uosmo"} // Get address to create positions - address1 := chainANode.CreateWalletAndFund("address1", fundTokens) + address1 := chainABNode.CreateWalletAndFund("address1", fundTokens, chainAB) // 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. - timeBeforePositionCreationBeforeSwap := chainANode.QueryLatestBlockTime().Add(5 * time.Millisecond) + timeBeforePositionCreationBeforeSwap := chainABNode.QueryLatestBlockTime().Add(5 * time.Millisecond) s.T().Log("geometric twap, start time ", timeBeforePositionCreationBeforeSwap.Unix()) // Wait for the next height so that the requested twap // start time (timeBeforePositionCreationBeforeSwap) is not equal to the block time. - chainA.WaitForNumHeights(1) + chainAB.WaitForNumHeights(1) // Check initial TWAP // We expect this to error since there is no spot price yet. s.T().Log("initial twap check") - initialTwapBOverA, err := chainANode.QueryGeometricTwapToNow(concentratedPool.GetId(), concentratedPool.GetToken1(), concentratedPool.GetToken0(), timeBeforePositionCreationBeforeSwap) + initialTwapBOverA, err := chainABNode.QueryGeometricTwapToNow(concentratedPool.GetId(), concentratedPool.GetToken1(), concentratedPool.GetToken0(), timeBeforePositionCreationBeforeSwap) s.Require().Error(err) s.Require().Equal(sdk.Dec{}, initialTwapBOverA) // Create a position and check that TWAP now returns a value. s.T().Log("creating first position") - chainANode.CreateConcentratedPosition(address1, "[-120000]", "40000", fmt.Sprintf("10000000%s,20000000%s", concentratedPool.GetToken0(), concentratedPool.GetToken1()), 0, 0, concentratedPool.GetId()) - timeAfterPositionCreationBeforeSwap := chainANode.QueryLatestBlockTime() - chainA.WaitForNumHeights(2) - firstPositionTwapBOverA, err := chainANode.QueryGeometricTwapToNow(concentratedPool.GetId(), concentratedPool.GetToken1(), concentratedPool.GetToken0(), timeAfterPositionCreationBeforeSwap) + chainABNode.CreateConcentratedPosition(address1, "[-120000]", "40000", fmt.Sprintf("10000000%s,20000000%s", concentratedPool.GetToken0(), concentratedPool.GetToken1()), 0, 0, concentratedPool.GetId()) + timeAfterPositionCreationBeforeSwap := chainABNode.QueryLatestBlockTime() + chainAB.WaitForNumHeights(2) + firstPositionTwapBOverA, err := chainABNode.QueryGeometricTwapToNow(concentratedPool.GetId(), concentratedPool.GetToken1(), concentratedPool.GetToken0(), timeAfterPositionCreationBeforeSwap) s.Require().NoError(err) s.Require().Equal(sdk.MustNewDecFromStr("0.5"), firstPositionTwapBOverA) // Run a swap and check that the TWAP updates. s.T().Log("run swap") coinAIn := fmt.Sprintf("1000000%s", concentratedPool.GetToken0()) - chainANode.SwapExactAmountIn(coinAIn, "1", fmt.Sprintf("%d", concentratedPool.GetId()), concentratedPool.GetToken1(), address1) + chainABNode.SwapExactAmountIn(coinAIn, "1", fmt.Sprintf("%d", concentratedPool.GetId()), concentratedPool.GetToken1(), address1) - timeAfterSwap := chainANode.QueryLatestBlockTime() - chainA.WaitForNumHeights(1) - timeAfterSwapPlus1Height := chainANode.QueryLatestBlockTime() + timeAfterSwap := chainABNode.QueryLatestBlockTime() + chainAB.WaitForNumHeights(1) + timeAfterSwapPlus1Height := chainABNode.QueryLatestBlockTime() s.T().Log("querying for the TWAP after swap") - afterSwapTwapBOverA, err := chainANode.QueryGeometricTwap(concentratedPool.GetId(), concentratedPool.GetToken1(), concentratedPool.GetToken0(), timeAfterSwap, timeAfterSwapPlus1Height) + afterSwapTwapBOverA, err := chainABNode.QueryGeometricTwap(concentratedPool.GetId(), concentratedPool.GetToken1(), concentratedPool.GetToken0(), timeAfterSwap, timeAfterSwapPlus1Height) s.Require().NoError(err) // We swap stake so uosmo's supply will decrease and stake will increase. @@ -1114,22 +1118,22 @@ func (s *IntegrationTestSuite) CreateConcentratedLiquidityPoolVoting_And_TWAP() // Remove the position and check that TWAP returns an error. s.T().Log("removing first position (pool is drained)") - positions := chainANode.QueryConcentratedPositions(address1) - chainANode.WithdrawPosition(address1, positions[0].Position.Liquidity.String(), positions[0].Position.PositionId) - chainA.WaitForNumHeights(1) + positions := chainABNode.QueryConcentratedPositions(address1) + chainABNode.WithdrawPosition(address1, positions[0].Position.Liquidity.String(), positions[0].Position.PositionId) + chainAB.WaitForNumHeights(1) s.T().Log("querying for the TWAP from after pool drained") - afterRemoveTwapBOverA, err := chainANode.QueryGeometricTwapToNow(concentratedPool.GetId(), concentratedPool.GetToken1(), concentratedPool.GetToken0(), timeAfterSwapPlus1Height) + afterRemoveTwapBOverA, err := chainABNode.QueryGeometricTwapToNow(concentratedPool.GetId(), concentratedPool.GetToken1(), concentratedPool.GetToken0(), timeAfterSwapPlus1Height) s.Require().Error(err) s.Require().Equal(sdk.Dec{}, afterRemoveTwapBOverA) // Create a position and check that TWAP now returns a value. // Should be equal to 1 since the position contains equal amounts of both tokens. s.T().Log("creating position") - chainANode.CreateConcentratedPosition(address1, "[-120000]", "40000", fmt.Sprintf("10000000%s,10000000%s", concentratedPool.GetToken0(), concentratedPool.GetToken1()), 0, 0, concentratedPool.GetId()) - chainA.WaitForNumHeights(1) - timeAfterSwapRemoveAndCreatePlus1Height := chainANode.QueryLatestBlockTime() - secondTwapBOverA, err := chainANode.QueryGeometricTwapToNow(concentratedPool.GetId(), concentratedPool.GetToken1(), concentratedPool.GetToken0(), timeAfterSwapRemoveAndCreatePlus1Height) + chainABNode.CreateConcentratedPosition(address1, "[-120000]", "40000", fmt.Sprintf("10000000%s,10000000%s", concentratedPool.GetToken0(), concentratedPool.GetToken1()), 0, 0, concentratedPool.GetId()) + chainAB.WaitForNumHeights(1) + timeAfterSwapRemoveAndCreatePlus1Height := chainABNode.QueryLatestBlockTime() + secondTwapBOverA, err := chainABNode.QueryGeometricTwapToNow(concentratedPool.GetId(), concentratedPool.GetToken1(), concentratedPool.GetToken0(), timeAfterSwapRemoveAndCreatePlus1Height) s.Require().NoError(err) s.Require().Equal(sdk.NewDec(1), secondTwapBOverA) } @@ -1138,12 +1142,13 @@ func (s *IntegrationTestSuite) IBCTokenTransferRateLimiting() { if s.skipIBC { s.T().Skip("Skipping IBC tests") } - chainA := s.configurer.GetChainConfig(0) - chainB := s.configurer.GetChainConfig(1) - - chainANode, err := chainA.GetNodeAtIndex(1) + chainA, chainANode, err := s.getChainACfgs() + s.Require().NoError(err) + chainB, chainBNode, err := s.getChainBCfgs() s.Require().NoError(err) + receiver := chainBNode.GetWallet(initialization.ValidatorWalletName) + // If the RL param is already set. Remember it to set it back at the end param := chainANode.QueryParams(ibcratelimittypes.ModuleName, string(ibcratelimittypes.KeyContractAddress)) fmt.Println("param", param) @@ -1160,9 +1165,9 @@ func (s *IntegrationTestSuite) IBCTokenTransferRateLimiting() { // Sending >1% fmt.Println("Sending >1%") - chainANode.SendIBC(chainB, chainB.NodeConfigs[0].PublicAddress, sdk.NewInt64Coin(initialization.OsmoDenom, int64(over))) + chainANode.SendIBC(chainA, chainB, receiver, sdk.NewInt64Coin(initialization.OsmoDenom, int64(over))) - contract, err := chainANode.SetupRateLimiting(paths, chainA.NodeConfigs[1].PublicAddress, chainA) + contract, err := chainANode.SetupRateLimiting(paths, chainANode.PublicAddress, chainA) s.Require().NoError(err) s.Eventually( @@ -1177,10 +1182,10 @@ func (s *IntegrationTestSuite) IBCTokenTransferRateLimiting() { // Sending <1%. Should work fmt.Println("Sending <1%. Should work") - chainANode.SendIBC(chainB, chainB.NodeConfigs[0].PublicAddress, sdk.NewInt64Coin(initialization.OsmoDenom, 1)) + chainANode.SendIBC(chainA, chainB, receiver, sdk.NewInt64Coin(initialization.OsmoDenom, 1)) // Sending >1%. Should fail fmt.Println("Sending >1%. Should fail") - chainANode.FailIBCTransfer(initialization.ValidatorWalletName, chainB.NodeConfigs[1].PublicAddress, fmt.Sprintf("%duosmo", int(over))) + chainANode.FailIBCTransfer(initialization.ValidatorWalletName, receiver, fmt.Sprintf("%duosmo", int(over))) // Removing the rate limit so it doesn't affect other tests chainANode.WasmExecute(contract, `{"remove_path": {"channel_id": "channel-0", "denom": "uosmo"}}`, initialization.ValidatorWalletName) @@ -1196,7 +1201,7 @@ func (s *IntegrationTestSuite) IBCTokenTransferRateLimiting() { s.Eventually(func() bool { val := chainANode.QueryParams(ibcratelimittypes.ModuleName, string(ibcratelimittypes.KeyContractAddress)) return strings.Contains(val, param) - }, time.Second*30, time.Second) + }, time.Second*30, 10*time.Millisecond) } } @@ -1212,9 +1217,9 @@ func (s *IntegrationTestSuite) IBCWasmHooks() { if s.skipIBC { s.T().Skip("Skipping IBC tests") } - chainA, chainANode := s.getChainACfgs() - chainB := s.configurer.GetChainConfig(1) - chainBNode, err := chainB.GetDefaultNode() + chainA, chainANode, err := s.getChainACfgs() + s.Require().NoError(err) + _, chainBNode, err := s.getChainBCfgs() s.Require().NoError(err) contractAddr := s.UploadAndInstantiateCounter(chainA) @@ -1276,12 +1281,12 @@ func (s *IntegrationTestSuite) PacketForwarding() { if s.skipIBC { s.T().Skip("Skipping IBC tests") } - chainA, chainANode := s.getChainACfgs() + chainA, chainANode, err := s.getChainACfgs() + s.Require().NoError(err) chainB := s.configurer.GetChainConfig(1) // Instantiate the counter contract on chain A contractAddr := s.UploadAndInstantiateCounter(chainA) - fmt.Println("contractAddr PacketForwarding", contractAddr) transferAmount := int64(10) validatorAddr := chainANode.GetWallet(initialization.ValidatorWalletName) @@ -1337,35 +1342,39 @@ func (s *IntegrationTestSuite) AddToExistingLockPostUpgrade() { if s.skipUpgrade { s.T().Skip("Skipping AddToExistingLockPostUpgrade test") } - _, chainANode := s.getChainACfgs() + + chainAB, chainABNode, err := s.getChainCfgs() + s.Require().NoError(err) + index := s.getChainIndex(chainAB) + // 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) + preUpgradePoolShareDenom := fmt.Sprintf("gamm/pool/%d", config.PreUpgradePoolId[index]) - lockupWalletAddr, lockupWalletSuperfluidAddr := chainANode.GetWallet("lockup-wallet"), chainANode.GetWallet("lockup-wallet-superfluid") - chainANode.AddToExistingLock(sdk.NewInt(1000000000000000000), preUpgradePoolShareDenom, "240s", lockupWalletAddr, 1) - chainANode.AddToExistingLock(sdk.NewInt(1000000000000000000), preUpgradePoolShareDenom, "240s", lockupWalletSuperfluidAddr, 2) + lockupWalletAddr, lockupWalletSuperfluidAddr := chainABNode.GetWallet("lockup-wallet"), chainABNode.GetWallet("lockup-wallet-superfluid") + chainABNode.AddToExistingLock(sdk.NewInt(1000000000000000000), preUpgradePoolShareDenom, "240s", lockupWalletAddr, 1) + chainABNode.AddToExistingLock(sdk.NewInt(1000000000000000000), preUpgradePoolShareDenom, "240s", lockupWalletSuperfluidAddr, 2) } // TestAddToExistingLock tests lockups to both regular and superfluid locks. func (s *IntegrationTestSuite) AddToExistingLock() { - chainA := s.configurer.GetChainConfig(0) - chainANode, err := chainA.GetNodeAtIndex(2) + chainAB, chainABNode, err := s.getChainCfgs() s.Require().NoError(err) - funder := chainANode.PublicAddress + + funder := chainABNode.GetWallet(initialization.ValidatorWalletName) // ensure we can add to new locks and superfluid locks // create pool and enable superfluid assets - poolId := chainANode.CreateBalancerPool("nativeDenomPool.json", funder) - chainANode.EnableSuperfluidAsset(chainA, fmt.Sprintf("gamm/pool/%d", poolId)) + poolId := chainABNode.CreateBalancerPool("nativeDenomPool.json", funder) + chainABNode.EnableSuperfluidAsset(chainAB, fmt.Sprintf("gamm/pool/%d", poolId)) // setup wallets and send gamm tokens to these wallets on chainA 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) + lockupWalletAddr := chainABNode.CreateWalletAndFundFrom("TestAddToExistingLock", funder, fundTokens, chainAB) + lockupWalletSuperfluidAddr := chainABNode.CreateWalletAndFundFrom("TestAddToExistingLockSuperfluid", funder, fundTokens, chainAB) // ensure we can add to new locks and superfluid locks on chainA - chainANode.LockAndAddToExistingLock(chainA, sdk.NewInt(1000000000000000000), fmt.Sprintf("gamm/pool/%d", poolId), lockupWalletAddr, lockupWalletSuperfluidAddr) + chainABNode.LockAndAddToExistingLock(chainAB, sdk.NewInt(1000000000000000000), fmt.Sprintf("gamm/pool/%d", poolId), lockupWalletAddr, lockupWalletSuperfluidAddr) } // TestArithmeticTWAP tests TWAP by creating a pool, performing a swap. @@ -1392,35 +1401,38 @@ func (s *IntegrationTestSuite) ArithmeticTWAP() { coinAIn, coinBIn, coinCIn := fmt.Sprintf("2000000%s", denomA), fmt.Sprintf("2000000%s", denomB), fmt.Sprintf("2000000%s", denomC) - chainA, chainANode := s.getChainACfgs() + chainAB, chainABNode, err := s.getChainCfgs() + s.Require().NoError(err) + + sender := chainABNode.GetWallet(initialization.ValidatorWalletName) // Triggers the creation of TWAP records. - poolId := chainANode.CreateBalancerPool(poolFile, initialization.ValidatorWalletName) - swapWalletAddr := chainANode.CreateWalletAndFund(walletName, []string{initialization.WalletFeeTokens.String()}) + poolId := chainABNode.CreateBalancerPool(poolFile, initialization.ValidatorWalletName) + swapWalletAddr := chainABNode.CreateWalletAndFund(walletName, []string{initialization.WalletFeeTokens.String()}, chainAB) - timeBeforeSwap := chainANode.QueryLatestBlockTime() + timeBeforeSwap := chainABNode.QueryLatestBlockTime() // Wait for the next height so that the requested twap // start time (timeBeforeSwap) is not equal to the block time. - chainA.WaitForNumHeights(2) + chainAB.WaitForNumHeights(2) s.T().Log("querying for the first TWAP to now before swap") - twapFromBeforeSwapToBeforeSwapOneAB, err := chainANode.QueryArithmeticTwapToNow(poolId, denomA, denomB, timeBeforeSwap) + twapFromBeforeSwapToBeforeSwapOneAB, err := chainABNode.QueryArithmeticTwapToNow(poolId, denomA, denomB, timeBeforeSwap) s.Require().NoError(err) - twapFromBeforeSwapToBeforeSwapOneBC, err := chainANode.QueryArithmeticTwapToNow(poolId, denomB, denomC, timeBeforeSwap) + twapFromBeforeSwapToBeforeSwapOneBC, err := chainABNode.QueryArithmeticTwapToNow(poolId, denomB, denomC, timeBeforeSwap) s.Require().NoError(err) - twapFromBeforeSwapToBeforeSwapOneCA, err := chainANode.QueryArithmeticTwapToNow(poolId, denomC, denomA, timeBeforeSwap) + twapFromBeforeSwapToBeforeSwapOneCA, err := chainABNode.QueryArithmeticTwapToNow(poolId, denomC, denomA, timeBeforeSwap) s.Require().NoError(err) - chainANode.BankSend(coinAIn, chainA.NodeConfigs[0].PublicAddress, swapWalletAddr) - chainANode.BankSend(coinBIn, chainA.NodeConfigs[0].PublicAddress, swapWalletAddr) - chainANode.BankSend(coinCIn, chainA.NodeConfigs[0].PublicAddress, swapWalletAddr) + chainABNode.BankSend(coinAIn, sender, swapWalletAddr) + chainABNode.BankSend(coinBIn, sender, swapWalletAddr) + chainABNode.BankSend(coinCIn, sender, 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 := chainABNode.QueryArithmeticTwapToNow(poolId, denomA, denomB, timeBeforeSwap.Add(50*time.Millisecond)) s.Require().NoError(err) - twapFromBeforeSwapToBeforeSwapTwoBC, err := chainANode.QueryArithmeticTwapToNow(poolId, denomB, denomC, timeBeforeSwap.Add(50*time.Millisecond)) + twapFromBeforeSwapToBeforeSwapTwoBC, err := chainABNode.QueryArithmeticTwapToNow(poolId, denomB, denomC, timeBeforeSwap.Add(50*time.Millisecond)) s.Require().NoError(err) - twapFromBeforeSwapToBeforeSwapTwoCA, err := chainANode.QueryArithmeticTwapToNow(poolId, denomC, denomA, timeBeforeSwap.Add(50*time.Millisecond)) + twapFromBeforeSwapToBeforeSwapTwoCA, err := chainABNode.QueryArithmeticTwapToNow(poolId, denomC, denomA, timeBeforeSwap.Add(50*time.Millisecond)) s.Require().NoError(err) // Since there were no swaps between the two queries, the TWAPs should be the same. @@ -1429,30 +1441,30 @@ func (s *IntegrationTestSuite) ArithmeticTWAP() { osmoassert.DecApproxEq(s.T(), twapFromBeforeSwapToBeforeSwapOneCA, twapFromBeforeSwapToBeforeSwapTwoCA, sdk.NewDecWithPrec(1, 3)) s.T().Log("performing swaps") - chainANode.SwapExactAmountIn(coinAIn, minAmountOut, fmt.Sprintf("%d", poolId), denomB, swapWalletAddr) - chainANode.SwapExactAmountIn(coinBIn, minAmountOut, fmt.Sprintf("%d", poolId), denomC, swapWalletAddr) - chainANode.SwapExactAmountIn(coinCIn, minAmountOut, fmt.Sprintf("%d", poolId), denomA, swapWalletAddr) + chainABNode.SwapExactAmountIn(coinAIn, minAmountOut, fmt.Sprintf("%d", poolId), denomB, swapWalletAddr) + chainABNode.SwapExactAmountIn(coinBIn, minAmountOut, fmt.Sprintf("%d", poolId), denomC, swapWalletAddr) + chainABNode.SwapExactAmountIn(coinCIn, minAmountOut, fmt.Sprintf("%d", poolId), denomA, swapWalletAddr) keepPeriodCountDown := time.NewTimer(initialization.TWAPPruningKeepPeriod) // Make sure that we are still producing blocks and move far enough for the swap TWAP record to be created // so that we can measure start time post-swap (timeAfterSwap). - chainA.WaitForNumHeights(2) + chainAB.WaitForNumHeights(2) // Measure time after swap and wait for a few blocks to be produced. // This is needed to ensure that start time is before the block time // when we query for TWAP. - timeAfterSwap := chainANode.QueryLatestBlockTime() - chainA.WaitForNumHeights(2) + timeAfterSwap := chainABNode.QueryLatestBlockTime() + chainAB.WaitForNumHeights(2) // TWAP "from before to after swap" should be different from "from before to before swap" // because swap introduces a new record with a different spot price. s.T().Log("querying for the TWAP from before swap to now after swap") - twapFromBeforeSwapToAfterSwapAB, err := chainANode.QueryArithmeticTwapToNow(poolId, denomA, denomB, timeBeforeSwap) + twapFromBeforeSwapToAfterSwapAB, err := chainABNode.QueryArithmeticTwapToNow(poolId, denomA, denomB, timeBeforeSwap) s.Require().NoError(err) - twapFromBeforeSwapToAfterSwapBC, err := chainANode.QueryArithmeticTwapToNow(poolId, denomB, denomC, timeBeforeSwap) + twapFromBeforeSwapToAfterSwapBC, err := chainABNode.QueryArithmeticTwapToNow(poolId, denomB, denomC, timeBeforeSwap) s.Require().NoError(err) - twapFromBeforeSwapToAfterSwapCA, err := chainANode.QueryArithmeticTwapToNow(poolId, denomC, denomA, timeBeforeSwap) + twapFromBeforeSwapToAfterSwapCA, err := chainABNode.QueryArithmeticTwapToNow(poolId, denomC, denomA, timeBeforeSwap) s.Require().NoError(err) // We had a swap of 2000000stake for some amount of uion, // 2000000uion for some amount of uosmo, and @@ -1465,11 +1477,11 @@ func (s *IntegrationTestSuite) ArithmeticTWAP() { s.Require().True(twapFromBeforeSwapToAfterSwapCA.GT(twapFromBeforeSwapToBeforeSwapOneCA)) s.T().Log("querying for the TWAP from after swap to now") - twapFromAfterToNowAB, err := chainANode.QueryArithmeticTwapToNow(poolId, denomA, denomB, timeAfterSwap) + twapFromAfterToNowAB, err := chainABNode.QueryArithmeticTwapToNow(poolId, denomA, denomB, timeAfterSwap) s.Require().NoError(err) - twapFromAfterToNowBC, err := chainANode.QueryArithmeticTwapToNow(poolId, denomB, denomC, timeAfterSwap) + twapFromAfterToNowBC, err := chainABNode.QueryArithmeticTwapToNow(poolId, denomB, denomC, timeAfterSwap) s.Require().NoError(err) - twapFromAfterToNowCA, err := chainANode.QueryArithmeticTwapToNow(poolId, denomC, denomA, timeAfterSwap) + twapFromAfterToNowCA, err := chainABNode.QueryArithmeticTwapToNow(poolId, denomC, denomA, timeAfterSwap) s.Require().NoError(err) // Because twapFromAfterToNow has a higher time weight for the after swap period, // we expect the results to be flipped from the previous comparison to twapFromBeforeSwapToBeforeSwapOne @@ -1478,11 +1490,11 @@ func (s *IntegrationTestSuite) ArithmeticTWAP() { s.Require().True(twapFromBeforeSwapToAfterSwapCA.LT(twapFromAfterToNowCA)) s.T().Log("querying for the TWAP from after swap to after swap + 10ms") - twapAfterSwapBeforePruning10MsAB, err := chainANode.QueryArithmeticTwap(poolId, denomA, denomB, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond)) + twapAfterSwapBeforePruning10MsAB, err := chainABNode.QueryArithmeticTwap(poolId, denomA, denomB, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond)) s.Require().NoError(err) - twapAfterSwapBeforePruning10MsBC, err := chainANode.QueryArithmeticTwap(poolId, denomB, denomC, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond)) + twapAfterSwapBeforePruning10MsBC, err := chainABNode.QueryArithmeticTwap(poolId, denomB, denomC, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond)) s.Require().NoError(err) - twapAfterSwapBeforePruning10MsCA, err := chainANode.QueryArithmeticTwap(poolId, denomC, denomA, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond)) + twapAfterSwapBeforePruning10MsCA, err := chainABNode.QueryArithmeticTwap(poolId, denomC, denomA, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond)) s.Require().NoError(err) // Again, because twapAfterSwapBeforePruning10Ms has a higher time weight for the after swap period between the two, // we expect no change in the inequality @@ -1502,24 +1514,24 @@ func (s *IntegrationTestSuite) ArithmeticTWAP() { // Epoch end triggers the prunning of TWAP records. // Records before swap should be pruned. - chainA.WaitForNumEpochs(1, epochIdentifier) + chainAB.WaitForNumEpochs(1, epochIdentifier) // We should not be able to get TWAP before swap since it should have been pruned. s.T().Log("pruning is now complete, querying TWAP for period that should be pruned") - _, err = chainANode.QueryArithmeticTwapToNow(poolId, denomA, denomB, timeBeforeSwap) + _, err = chainABNode.QueryArithmeticTwapToNow(poolId, denomA, denomB, timeBeforeSwap) s.Require().ErrorContains(err, "too old") - _, err = chainANode.QueryArithmeticTwapToNow(poolId, denomB, denomC, timeBeforeSwap) + _, err = chainABNode.QueryArithmeticTwapToNow(poolId, denomB, denomC, timeBeforeSwap) s.Require().ErrorContains(err, "too old") - _, err = chainANode.QueryArithmeticTwapToNow(poolId, denomC, denomA, timeBeforeSwap) + _, err = chainABNode.QueryArithmeticTwapToNow(poolId, denomC, denomA, timeBeforeSwap) s.Require().ErrorContains(err, "too old") // TWAPs for the same time range should be the same when we query for them before and after pruning. s.T().Log("querying for TWAP for period before pruning took place but should not have been pruned") - twapAfterPruning10msAB, err := chainANode.QueryArithmeticTwap(poolId, denomA, denomB, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond)) + twapAfterPruning10msAB, err := chainABNode.QueryArithmeticTwap(poolId, denomA, denomB, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond)) s.Require().NoError(err) - twapAfterPruning10msBC, err := chainANode.QueryArithmeticTwap(poolId, denomB, denomC, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond)) + twapAfterPruning10msBC, err := chainABNode.QueryArithmeticTwap(poolId, denomB, denomC, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond)) s.Require().NoError(err) - twapAfterPruning10msCA, err := chainANode.QueryArithmeticTwap(poolId, denomC, denomA, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond)) + twapAfterPruning10msCA, err := chainABNode.QueryArithmeticTwap(poolId, denomC, denomA, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond)) s.Require().NoError(err) s.Require().Equal(twapAfterSwapBeforePruning10MsAB, twapAfterPruning10msAB) s.Require().Equal(twapAfterSwapBeforePruning10MsBC, twapAfterPruning10msBC) @@ -1527,13 +1539,13 @@ func (s *IntegrationTestSuite) ArithmeticTWAP() { // TWAP "from after to after swap" should equal to "from after swap to after pruning" // These must be equal because they are calculated over time ranges with the stable and equal spot price. - timeAfterPruning := chainANode.QueryLatestBlockTime() + timeAfterPruning := chainABNode.QueryLatestBlockTime() s.T().Log("querying for TWAP from after swap to after pruning") - twapToNowPostPruningAB, err := chainANode.QueryArithmeticTwap(poolId, denomA, denomB, timeAfterSwap, timeAfterPruning) + twapToNowPostPruningAB, err := chainABNode.QueryArithmeticTwap(poolId, denomA, denomB, timeAfterSwap, timeAfterPruning) s.Require().NoError(err) - twapToNowPostPruningBC, err := chainANode.QueryArithmeticTwap(poolId, denomB, denomC, timeAfterSwap, timeAfterPruning) + twapToNowPostPruningBC, err := chainABNode.QueryArithmeticTwap(poolId, denomB, denomC, timeAfterSwap, timeAfterPruning) s.Require().NoError(err) - twapToNowPostPruningCA, err := chainANode.QueryArithmeticTwap(poolId, denomC, denomA, timeAfterSwap, timeAfterPruning) + twapToNowPostPruningCA, err := chainABNode.QueryArithmeticTwap(poolId, denomC, denomA, timeAfterSwap, timeAfterPruning) s.Require().NoError(err) // There are potential rounding errors requiring us to approximate the comparison. osmoassert.DecApproxEq(s.T(), twapToNowPostPruningAB, twapAfterSwapBeforePruning10MsAB, sdk.NewDecWithPrec(1, 3)) @@ -1546,7 +1558,11 @@ func (s *IntegrationTestSuite) StateSync() { s.T().Skip() } - chainA, chainANode := s.getChainACfgs() + // This test benefits from the use of chainA's default node, since it has + // the shortest snapshot interval. + chainA := s.configurer.GetChainConfig(0) + chainANode, err := chainA.GetDefaultNode() + s.Require().NoError(err) persistentPeers := chainA.GetPersistentPeers() @@ -1626,7 +1642,7 @@ func (s *IntegrationTestSuite) StateSync() { return stateSyncNodeHeight == runningNodeHeight }, 1*time.Minute, - time.Second, + 10*time.Millisecond, ) // stop the state synching node. @@ -1635,22 +1651,22 @@ func (s *IntegrationTestSuite) StateSync() { } func (s *IntegrationTestSuite) ExpeditedProposals() { - chainA, chainANode := s.getChainACfgs() + chainAB, chainABNode, err := s.getChainCfgs() + s.Require().NoError(err) - latestPropNumber := chainANode.SubmitTextProposal("expedited text proposal", sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(config.InitialMinExpeditedDeposit)), true) - chainA.LatestProposalNumber += 1 + propNumber := chainABNode.SubmitTextProposal("expedited text proposal", sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(config.InitialMinExpeditedDeposit)), true) - chainANode.DepositProposal(latestPropNumber, true) + chainABNode.DepositProposal(propNumber, true) totalTimeChan := make(chan time.Duration, 1) - go chainANode.QueryPropStatusTimed(latestPropNumber, "PROPOSAL_STATUS_PASSED", totalTimeChan) + go chainABNode.QueryPropStatusTimed(propNumber, "PROPOSAL_STATUS_PASSED", totalTimeChan) var wg sync.WaitGroup - for _, n := range chainA.NodeConfigs { + for _, n := range chainAB.NodeConfigs { wg.Add(1) go func(nodeConfig *chain.NodeConfig) { defer wg.Done() - nodeConfig.VoteYesProposal(initialization.ValidatorWalletName, latestPropNumber) + nodeConfig.VoteYesProposal(initialization.ValidatorWalletName, propNumber) }(n) } @@ -1667,7 +1683,7 @@ func (s *IntegrationTestSuite) ExpeditedProposals() { } // compare the time it took to reach pass status to expected expedited voting period - expeditedVotingPeriodDuration := time.Duration(chainA.ExpeditedVotingPeriod * float32(time.Second)) + expeditedVotingPeriodDuration := time.Duration(chainAB.ExpeditedVotingPeriod * float32(time.Second)) timeDelta := elapsed - expeditedVotingPeriodDuration // ensure delta is within two seconds of expected time s.Require().Less(timeDelta, 2*time.Second) @@ -1695,43 +1711,45 @@ func (s *IntegrationTestSuite) GeometricTWAP() { minAmountOut = "1" ) - chainA, chainANode := s.getChainACfgs() + chainAB, chainABNode, err := s.getChainCfgs() + s.Require().NoError(err) + + sender := chainABNode.GetWallet(initialization.ValidatorWalletName) // Triggers the creation of TWAP records. - poolId := chainANode.CreateBalancerPool(poolFile, initialization.ValidatorWalletName) - fmt.Println("poolId", poolId) - swapWalletAddr := chainANode.CreateWalletAndFund(walletName, []string{initialization.WalletFeeTokens.String()}) + poolId := chainABNode.CreateBalancerPool(poolFile, initialization.ValidatorWalletName) + swapWalletAddr := chainABNode.CreateWalletAndFund(walletName, []string{initialization.WalletFeeTokens.String()}, chainAB) // 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) + timeBeforeSwapPlus5ms := chainABNode.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(2) + chainAB.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() + curBlockTime := chainABNode.QueryLatestBlockTime().Unix() s.T().Log("geometric twap, end time ", curBlockTime) - initialTwapBOverA, err := chainANode.QueryGeometricTwapToNow(poolId, denomA, denomB, timeBeforeSwapPlus5ms) + initialTwapBOverA, err := chainABNode.QueryGeometricTwapToNow(poolId, denomA, denomB, timeBeforeSwapPlus5ms) s.Require().NoError(err) s.Require().Equal(sdk.NewDec(2), initialTwapBOverA) // Assume base = stake, quote = uosmo // At pool creation time, the twap should be: // quote assset supply / base asset supply = 1_000_000 / 2_000_000 = 0.5 - initialTwapAOverB, err := chainANode.QueryGeometricTwapToNow(poolId, denomB, denomA, timeBeforeSwapPlus5ms) + initialTwapAOverB, err := chainABNode.QueryGeometricTwapToNow(poolId, denomB, denomA, timeBeforeSwapPlus5ms) s.Require().NoError(err) s.Require().Equal(sdk.NewDecWithPrec(5, 1), initialTwapAOverB) coinAIn := fmt.Sprintf("1000000%s", denomA) - chainANode.BankSend(coinAIn, chainA.NodeConfigs[0].PublicAddress, swapWalletAddr) + chainABNode.BankSend(coinAIn, sender, swapWalletAddr) s.T().Logf("performing swap of %s for %s", coinAIn, denomB) @@ -1739,18 +1757,18 @@ func (s *IntegrationTestSuite) GeometricTWAP() { // = 2_000_000 * (1 - (1_000_000 / 2_000_000)^1) // = 2_000_000 * 0.5 // = 1_000_000 - chainANode.SwapExactAmountIn(coinAIn, minAmountOut, fmt.Sprintf("%d", poolId), denomB, swapWalletAddr) + chainABNode.SwapExactAmountIn(coinAIn, minAmountOut, fmt.Sprintf("%d", poolId), denomB, swapWalletAddr) // New supply post swap: // stake = 2_000_000 - 1_000_000 - 1_000_000 // uosmo = 1_000_000 + 1_000_000 = 2_000_000 - timeAfterSwap := chainANode.QueryLatestBlockTime() - chainA.WaitForNumHeights(1) - timeAfterSwapPlus1Height := chainANode.QueryLatestBlockTime() + timeAfterSwap := chainABNode.QueryLatestBlockTime() + chainAB.WaitForNumHeights(1) + timeAfterSwapPlus1Height := chainABNode.QueryLatestBlockTime() s.T().Log("querying for the TWAP from after swap to now") - afterSwapTwapBOverA, err := chainANode.QueryGeometricTwap(poolId, denomA, denomB, timeAfterSwap, timeAfterSwapPlus1Height) + afterSwapTwapBOverA, err := chainABNode.QueryGeometricTwap(poolId, denomA, denomB, timeAfterSwap, timeAfterSwapPlus1Height) s.Require().NoError(err) // We swap uosmo so uosmo's supply will increase and stake will decrease. @@ -1776,12 +1794,13 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity_CanonicalPools() { s.T().Skip("Skipping v17 canonical pools creation test because upgrade is not enabled") } - _, chainANode := s.getChainACfgs() + _, chainABNode, err := s.getChainCfgs() + s.Require().NoError(err) for _, assetPair := range v17.AssetPairsForTestsOnly { expectedSpreadFactor := assetPair.SpreadFactor - concentratedPoolId := chainANode.QueryConcentratedPooIdLinkFromCFMM(assetPair.LinkedClassicPool) - concentratedPool := s.updatedConcentratedPool(chainANode, concentratedPoolId) + concentratedPoolId := chainABNode.QueryConcentratedPooIdLinkFromCFMM(assetPair.LinkedClassicPool) + concentratedPool := s.updatedConcentratedPool(chainABNode, concentratedPoolId) s.Require().Equal(poolmanagertypes.Concentrated, concentratedPool.GetType()) s.Require().Equal(assetPair.BaseAsset, concentratedPool.GetToken0()) @@ -1789,7 +1808,7 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity_CanonicalPools() { s.Require().Equal(uint64(v17.TickSpacing), concentratedPool.GetTickSpacing()) s.Require().Equal(expectedSpreadFactor.String(), concentratedPool.GetSpreadFactor(sdk.Context{}).String()) - superfluidAssets := chainANode.QueryAllSuperfluidAssets() + superfluidAssets := chainABNode.QueryAllSuperfluidAssets() found := false for _, superfluidAsset := range superfluidAssets { @@ -1806,7 +1825,7 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity_CanonicalPools() { } // This spot price is taken from the balancer pool that was initiated pre upgrade. - balancerPool := s.updatedCFMMPool(chainANode, assetPair.LinkedClassicPool) + balancerPool := s.updatedCFMMPool(chainABNode, assetPair.LinkedClassicPool) expectedSpotPrice, err := balancerPool.SpotPrice(sdk.Context{}, v17.QuoteAsset, assetPair.BaseAsset) s.Require().NoError(err) @@ -1819,8 +1838,8 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity_CanonicalPools() { } // Check that the community pool module account possesses positions for all the canonical pools. - communityPoolAddress := chainANode.QueryCommunityPoolModuleAccount() - positions := chainANode.QueryConcentratedPositions(communityPoolAddress) + communityPoolAddress := chainABNode.QueryCommunityPoolModuleAccount() + positions := chainABNode.QueryConcentratedPositions(communityPoolAddress) s.Require().Len(positions, len(v17.AssetPairsForTestsOnly)) } diff --git a/tests/e2e/helpers_e2e_test.go b/tests/e2e/helpers_e2e_test.go index 728b0223036..95eab2f110b 100644 --- a/tests/e2e/helpers_e2e_test.go +++ b/tests/e2e/helpers_e2e_test.go @@ -62,11 +62,40 @@ func (s *IntegrationTestSuite) addrBalance(node *chain.NodeConfig, address strin return addrBalances } -func (s *IntegrationTestSuite) getChainACfgs() (*chain.Config, *chain.NodeConfig) { +var currentNodeIndexA int + +func (s *IntegrationTestSuite) getChainACfgs() (*chain.Config, *chain.NodeConfig, error) { chainA := s.configurer.GetChainConfig(0) - chainANode, err := chainA.GetDefaultNode() - s.Require().NoError(err) - return chainA, chainANode + + chainANodes := chainA.GetAllChainNodes() + + chosenNode := chainANodes[currentNodeIndexA] + currentNodeIndexA = (currentNodeIndexA + 1) % len(chainANodes) + return chainA, chosenNode, nil +} + +var currentNodeIndexB int + +func (s *IntegrationTestSuite) getChainBCfgs() (*chain.Config, *chain.NodeConfig, error) { + chainB := s.configurer.GetChainConfig(1) + + chainBNodes := chainB.GetAllChainNodes() + + chosenNode := chainBNodes[currentNodeIndexB] + currentNodeIndexB = (currentNodeIndexB + 1) % len(chainBNodes) + return chainB, chosenNode, nil +} + +var useChainA bool + +func (s *IntegrationTestSuite) getChainCfgs() (*chain.Config, *chain.NodeConfig, error) { + if useChainA { + useChainA = false + return s.getChainACfgs() + } else { + useChainA = true + return s.getChainBCfgs() + } } // Helper function for calculating uncollected spread rewards since the time that spreadRewardGrowthInsideLast corresponds to @@ -160,3 +189,11 @@ func (s *IntegrationTestSuite) UploadAndInstantiateCounter(chain *chain.Config) contractAddr := contracts[0] return contractAddr } + +func (s *IntegrationTestSuite) getChainIndex(chain *chain.Config) int { + if chain.Id == "osmo-test-a" { + return 0 + } else { + return 1 + } +} diff --git a/tests/e2e/initialization/config.go b/tests/e2e/initialization/config.go index e0327aacc1f..35345b85eb7 100644 --- a/tests/e2e/initialization/config.go +++ b/tests/e2e/initialization/config.go @@ -438,10 +438,10 @@ func updatePoolManagerGenesis(appGenState map[string]json.RawMessage) func(*pool func updateEpochGenesis(epochGenState *epochtypes.GenesisState) { epochGenState.Epochs = []epochtypes.EpochInfo{ - // override week epochs which are in default integrations, to be 2min - epochtypes.NewGenesisEpochInfo("week", time.Second*120), - // override day epochs which are in default integrations, to be 1min - epochtypes.NewGenesisEpochInfo("day", time.Second*60), + // override week epochs which are in default integrations, to be 60 seconds + epochtypes.NewGenesisEpochInfo("week", time.Second*60), + // override day epochs which are in default integrations, to be 5 seconds + epochtypes.NewGenesisEpochInfo("day", time.Second*5), } }